[frontend-next] Refactored install and 404 pages

This commit is contained in:
0xJacky 2022-08-02 14:03:35 +08:00
parent 070c53b0b2
commit 9be594f9dc
8 changed files with 199 additions and 183 deletions

12
frontend/src/api/index.ts Normal file
View file

@ -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

View file

@ -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"
/>

View file

@ -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)

View file

@ -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

View file

@ -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 {

View file

@ -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;

View file

@ -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>

View file

@ -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",
})
}