瀏覽代碼

[frontend-next] Refactored install and 404 pages

0xJacky 2 年之前
父節點
當前提交
9be594f9dc

+ 12 - 0
frontend/src/api/index.ts

@@ -0,0 +1,12 @@
+import http from '@/lib/http'
+
+const install = {
+    get_lock() {
+        return http.get('/install')
+    },
+    install_nginx_ui(data: any) {
+        return http.post('/install', data)
+    }
+}
+
+export default install

+ 0 - 1
frontend/src/components/StdDataDisplay/StdPagination.vue

@@ -17,7 +17,6 @@ function changePage(num: number) {
             :pageSize="pagination.per_page"
             :size="size"
             :total="pagination.total"
-            :show-total="(total, range) => `当前显示${range[0]}-${range[1]}条数据,共${total}条数据`"
             class="pagination"
             @change="changePage"
         />

+ 4 - 6
frontend/src/lib/http/index.ts

@@ -2,15 +2,12 @@ import axios, {AxiosRequestConfig} from 'axios'
 import {useUserStore} from '@/pinia'
 import {storeToRefs} from 'pinia'
 
+import router from '@/routes'
+
 const user = useUserStore()
 
 const {token} = storeToRefs(user)
 
-declare module 'axios' {
-    export interface AxiosResponse<T = any> extends Promise<T> {
-    }
-}
-
 let instance = axios.create({
     baseURL: import.meta.env.VITE_API_ROOT,
     timeout: 50000,
@@ -38,7 +35,6 @@ instance.interceptors.request.use(
     }
 )
 
-
 instance.interceptors.response.use(
     response => {
         return Promise.resolve(response.data)
@@ -47,6 +43,8 @@ instance.interceptors.response.use(
         switch (error.response.status) {
             case 401:
             case 403:
+                user.logout()
+                await router.push('/login')
                 break
         }
         return Promise.reject(error.response.data)

+ 5 - 28
frontend/src/routes/index.ts

@@ -100,7 +100,7 @@ export const routes = [
     {
         path: '/install',
         name: () => $gettext('Install'),
-        // component: () => import('@/views/other/Install.vue'),
+        component: () => import('@/views/other/Install.vue'),
         meta: {noAuth: true}
     },
     {
@@ -110,16 +110,10 @@ export const routes = [
         meta: {noAuth: true}
     },
     {
-        path: '/404',
-        name: () => $gettext('404 Not Found'),
-        component: () => import('@/views/other/Error.vue'),
-        meta: {noAuth: true, status_code: 404, error: 'Not Found'}
-    },
-    {
-        path: '/*',
+        path: '/:pathMatch(.*)*',
         name: () => $gettext('Not Found'),
-        redirect: '/404',
-        meta: {noAuth: true}
+        component: () => import('@/views/other/Error.vue'),
+        meta: {noAuth: true, status_code: 404, error: () => $gettext('Not Found')}
     }
 ]
 
@@ -130,25 +124,8 @@ const router = createRouter({
 })
 
 router.beforeEach((to, from, next) => {
-
     // @ts-ignore
-    document.title = to.name() + ' | Nginx UI'
-
-    if (import.meta.env.MODE === 'production') {
-        // axios.get('/version.json?' + Date.now()).then(r => {
-        //     if (!(process.env.VUE_APP_VERSION === r.data.version
-        //         && Number(process.env.VUE_APP_BUILD_ID) === r.data.build_id)) {
-        //         Vue.prototype.$info({
-        //             title: $gettext('System message'),
-        //             content: $gettext('Detected version update, this page will refresh.'),
-        //             onOk() {
-        //                 location.reload()
-        //             },
-        //             okText: $gettext('OK')
-        //         })
-        //     }
-        // })
-    }
+    document.title = to.name?.() + ' | Nginx UI'
 
     const user = useUserStore()
     const {is_login} = user

+ 11 - 9
frontend/src/views/other/Error.vue

@@ -1,16 +1,17 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+
+const {$gettext} = useGettext()
+</script>
+
 <template>
     <div class="wrapper">
-        <h1 class="title">{{ $route.meta.status_code ? $route.meta.status_code : 404 }}</h1>
-        <p>{{ $route.meta.error ? $route.meta.error : $gettext('File Not Found') }}</p>
+        <h1 class="title">{{ $route.meta.status_code || 404 }}</h1>
+        <p>{{ $route.meta.error?.() ?? $gettext('File Not Found') }}</p>
+        <a-button type="primary" v-translate @click="$router.push('/')">Back Home</a-button>
     </div>
 </template>
 
-<script>
-export default {
-    name: 'Error'
-}
-</script>
-
 <style lang="less" scoped>
 body, div, h1, html {
     padding: 0;
@@ -27,7 +28,8 @@ body, html {
 
 h1 {
     font-size: 8em;
-    font-weight: 100
+    font-weight: 100;
+    margin: 10px 0;
 }
 
 a {

+ 102 - 84
frontend/src/views/other/Install.vue

@@ -1,72 +1,130 @@
+<script setup lang="ts">
+import {Form, message} from 'ant-design-vue'
+import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
+import {reactive, ref} from 'vue'
+import gettext from '@/gettext'
+import install from '@/api'
+import {useRoute, useRouter} from 'vue-router'
+import {MailOutlined, UserOutlined, LockOutlined, DatabaseOutlined} from '@ant-design/icons-vue'
+
+const {$gettext, interpolate} = gettext
+
+const thisYear = new Date().getFullYear()
+const loading = ref(false)
+
+const route = useRoute()
+const router = useRouter()
+
+install.get_lock().then(async (r: { lock: boolean }) => {
+    if (r.lock) {
+        await router.push('/login')
+    }
+})
+
+const modelRef = reactive({
+    email: '',
+    username: '',
+    password: '',
+    database: ''
+})
+
+const rulesRef = reactive({
+    email: [
+        {
+            required: true,
+            type: 'email',
+            message: () => $gettext('Please input your E-mail!'),
+        }
+    ],
+    username: [
+        {
+            required: true,
+            message: () => $gettext('Please input your username!'),
+        }
+    ],
+    password: [
+        {
+            required: true,
+            message: () => $gettext('Please input your password!'),
+        }
+    ],
+    database: [
+        {
+            message: () => interpolate(
+                $gettext('The filename cannot contain the following characters: %{c}'),
+                {c: '& &quot; ? < > # {} % ~ / \\'}
+            ),
+        }
+    ],
+})
+
+const {validate, validateInfos} = Form.useForm(modelRef, rulesRef)
+
+const onSubmit = () => {
+    validate().then(() => {
+        // modelRef
+        loading.value = true
+        install.install_nginx_ui(modelRef).then(async () => {
+            message.success($gettext('Install successfully'))
+            await router.push('/login')
+        }).catch(e => {
+            message.error(e.message ?? $gettext('Server error'))
+        }).finally(() => {
+            loading.value = false
+        })
+    })
+}
+</script>
+
 <template>
     <div class="login-form">
         <div class="project-title">
             <h1>Nginx UI</h1>
         </div>
-        <a-form
-            id="components-form-install"
-            :form="form"
-            class="login-form"
-            @submit="handleSubmit"
-        >
-            <a-form-item>
+        <a-form id="components-form-install" class="login-form">
+            <a-form-item v-bind="validateInfos.email">
                 <a-input
-                    v-decorator="[
-          'email',
-          { rules: [{
-                type: 'email',
-                message: $gettext('Invalid E-mail!'),
-              },
-              {
-                required: true,
-                message: $gettext('Please input your E-mail!'),
-              },] },
-        ]"
+                    v-model:value="modelRef.email"
                     :placeholder="$gettext('Email (*)')"
                 >
-                    <a-icon slot="prefix" type="mail" style="color: rgba(0,0,0,.25)"/>
+                    <template #prefix>
+                        <MailOutlined/>
+                    </template>
                 </a-input>
             </a-form-item>
-            <a-form-item>
+            <a-form-item v-bind="validateInfos.username">
                 <a-input
-                    v-decorator="[
-          'username',
-          { rules: [{ required: true, message: $gettext('Please input your username!') }] },
-        ]"
+                    v-model:value="modelRef.username"
                     :placeholder="$gettext('Username (*)')"
                 >
-                    <a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)"/>
+                    <template #prefix>
+                        <UserOutlined/>
+                    </template>
                 </a-input>
             </a-form-item>
-            <a-form-item>
-                <a-input
-                    v-decorator="[
-          'password',
-          { rules: [{ required: true, message: $gettext('Please input your password!') }] },
-        ]"
-                    type="password"
+            <a-form-item v-bind="validateInfos.password">
+                <a-input-password
+                    v-model:value="modelRef.password"
                     :placeholder="$gettext('Password (*)')"
                 >
-                    <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
-                </a-input>
+                    <template #prefix>
+                        <LockOutlined/>
+                    </template>
+                </a-input-password>
             </a-form-item>
             <a-form-item>
                 <a-input
-                    v-decorator="[
-          'database',
-          { rules: [{ pattern: /^[^\\/:*?\x22<>|]{1,120}$/,
-          message: $gettextInterpolate(
-              $gettext('The filename cannot contain the following characters: %{c}'),
-              {c: '& &quot; ? < > # {} % ~ / \\'}
-          )}] },
-        ]"
+                    v-bind="validateInfos.database"
+                    v-model:value="modelRef.database"
                     :placeholder="$gettext('Database (Optional, default: database)')"
                 >
-                    <a-icon slot="prefix" type="database" style="color: rgba(0,0,0,.25)"/>
+                    <template #prefix>
+                        <DatabaseOutlined/>
+                    </template>
                 </a-input>
             </a-form-item>
             <a-form-item>
-                <a-button type="primary" :block="true" html-type="submit" :loading="loading">
+                <a-button type="primary" :block="true" @click="onSubmit" html-type="submit" :loading="loading">
                     <translate>Install</translate>
                 </a-button>
             </a-form-item>
@@ -79,46 +137,6 @@
 
 </template>
 
-<script>
-import SetLanguage from '@/components/SetLanguage/SetLanguage'
-
-export default {
-    name: 'Login',
-    components: {SetLanguage},
-    data() {
-        return {
-            form: {},
-            lock: true,
-            thisYear: new Date().getFullYear(),
-            loading: false
-        }
-    },
-    created() {
-        this.form = this.$form.createForm(this)
-    },
-    mounted() {
-        this.$api.install.get_lock().then(r => {
-            if (r.lock) {
-                this.$router.push('/login')
-            }
-        })
-    },
-    methods: {
-        handleSubmit: async function (e) {
-            e.preventDefault()
-            this.loading = true
-            await this.form.validateFields(async (err, values) => {
-                if (!err) {
-                    this.$api.install.install_nginx_ui(values).then(() => {
-                        this.$router.push('/login')
-                    })
-                }
-                this.loading = false
-            })
-        },
-    },
-}
-</script>
 <style lang="less">
 .project-title {
     margin: 50px;

+ 12 - 3
frontend/src/views/other/Login.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import {useUserStore} from '@/pinia'
+
 const thisYear = new Date().getFullYear()
 
 import {LockOutlined, UserOutlined} from '@ant-design/icons-vue'
@@ -23,13 +25,13 @@ const rulesRef = reactive({
     username: [
         {
             required: true,
-            message: $gettext('Please input your username!'),
+            message: () => $gettext('Please input your username!'),
         }
     ],
     password: [
         {
             required: true,
-            message: $gettext('Please input your password!'),
+            message: () => $gettext('Please input your password!'),
         }
     ]
 })
@@ -44,11 +46,18 @@ const onSubmit = () => {
             const next = (route.query?.next || '').toString() || '/'
             await router.push(next)
         }).catch(e => {
-            message.error(e.message)
+            message.error(e.message ?? $gettext('Server error'))
         })
     })
 }
 
+const user = useUserStore()
+
+if (user.is_login) {
+    const next = (route.query?.next || '').toString() || '/dashboard'
+    router.push(next)
+}
+
 </script>
 
 <template>

+ 53 - 52
server/api/install.go

@@ -1,73 +1,74 @@
 package api
 
 import (
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/gin-gonic/gin"
-    "github.com/google/uuid"
-    "golang.org/x/crypto/bcrypt"
-    "net/http"
+	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/gin-gonic/gin"
+	"github.com/google/uuid"
+	"golang.org/x/crypto/bcrypt"
+	"net/http"
 )
 
 func installLockStatus() bool {
-    return "" != settings.ServerSettings.JwtSecret
+	return "" != settings.ServerSettings.JwtSecret
 }
 
 func InstallLockCheck(c *gin.Context) {
-    c.JSON(http.StatusOK, gin.H{
-        "lock": installLockStatus(),
-    })
+	c.JSON(http.StatusOK, gin.H{
+		"lock": installLockStatus(),
+	})
 }
 
 type InstallJson struct {
-    Email    string `json:"email" binding:"required,email"`
-    Username string `json:"username" binding:"required,max=255"`
-    Password string `json:"password" binding:"required,max=255"`
-    Database string `json:"database"`
+	Email    string `json:"email" binding:"required,email"`
+	Username string `json:"username" binding:"required,max=255"`
+	Password string `json:"password" binding:"required,max=255"`
+	Database string `json:"database"`
 }
 
 func InstallNginxUI(c *gin.Context) {
-    // 安装过就别访问了
-    if installLockStatus() {
-        c.JSON(http.StatusForbidden, gin.H{
-            "message": "installed",
-        })
-        return
-    }
-    var json InstallJson
-    ok := BindAndValid(c, &json)
-    if !ok {
-        return
-    }
+	// Visit this api after installed is forbidden
+	if installLockStatus() {
+		c.JSON(http.StatusForbidden, gin.H{
+			"error": "installed",
+		})
+		return
+	}
+	var json InstallJson
+	ok := BindAndValid(c, &json)
+	if !ok {
+		return
+	}
 
-    settings.ServerSettings.JwtSecret = uuid.New().String()
-    settings.ServerSettings.Email = json.Email
-    if "" != json.Database {
-        settings.ServerSettings.Database = json.Database
-    }
-    settings.ReflectFrom()
+	settings.ServerSettings.JwtSecret = uuid.New().String()
+	settings.ServerSettings.Email = json.Email
+	if "" != json.Database {
+		settings.ServerSettings.Database = json.Database
+	}
+	settings.ReflectFrom()
 
-    err := settings.Save()
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	err := settings.Save()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    // Init model
-    model.Init()
+	// Init model
+	model.Init()
 
-    curd := model.NewCurd(&model.Auth{})
-    pwd, _ := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
-    err = curd.Add(&model.Auth{
-        Name:     json.Username,
-        Password: string(pwd),
-    })
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
+	curd := model.NewCurd(&model.Auth{})
+	pwd, _ := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
+	err = curd.Add(&model.Auth{
+		Name:     json.Username,
+		Password: string(pwd),
+	})
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
 
 }