mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
[frontend-next] Refactored install and 404 pages
This commit is contained in:
parent
070c53b0b2
commit
9be594f9dc
8 changed files with 199 additions and 183 deletions
12
frontend/src/api/index.ts
Normal file
12
frontend/src/api/index.ts
Normal 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
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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: '& " ? < > # {} % ~ / \\'}
|
||||
),
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
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: '& " ? < > # {} % ~ / \\'}
|
||||
)}] },
|
||||
]"
|
||||
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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue