enhance: show the form error details of Preference

This commit is contained in:
0xJacky 2024-01-28 15:01:46 +08:00
parent b0a3989ef4
commit 398eea2159
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
8 changed files with 74 additions and 22 deletions

View file

@ -38,18 +38,18 @@ func BindAndValid(c *gin.Context, target interface{}) bool {
return false return false
} }
t := reflect.TypeOf(target) t := reflect.TypeOf(target).Elem()
errorsMap := make(map[string]interface{}) errorsMap := make(map[string]interface{})
for _, value := range verrs { for _, value := range verrs {
var path []string var path []string
namespace := strings.Split(value.StructNamespace(), ".") namespace := strings.Split(value.StructNamespace(), ".")
logger.Debug(t.Name(), namespace)
if t.Name() == "" && len(namespace) > 1 { if t.Name() != "" && len(namespace) > 1 {
namespace = namespace[1:] namespace = namespace[1:]
} }
getJsonPath(t.Elem(), namespace, &path) getJsonPath(t, namespace, &path)
insertError(errorsMap, path, value.Tag()) insertError(errorsMap, path, value.Tag())
} }

View file

@ -87,9 +87,11 @@ type certJson struct {
func AddCert(c *gin.Context) { func AddCert(c *gin.Context) {
var json certJson var json certJson
if !api.BindAndValid(c, &json) { if !api.BindAndValid(c, &json) {
return return
} }
certModel := &model.Cert{ certModel := &model.Cert{
Name: json.Name, Name: json.Name,
SSLCertificatePath: json.SSLCertificatePath, SSLCertificatePath: json.SSLCertificatePath,

View file

@ -27,7 +27,6 @@ func SaveSettings(c *gin.Context) {
return return
} }
// todo: omit protected fields when binding
fillSettings(&settings.ServerSettings, &json.Server) fillSettings(&settings.ServerSettings, &json.Server)
fillSettings(&settings.NginxSettings, &json.Nginx) fillSettings(&settings.NginxSettings, &json.Nginx)
fillSettings(&settings.OpenAISettings, &json.Openai) fillSettings(&settings.OpenAISettings, &json.Openai)

View file

@ -1,10 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { inject } from 'vue' import { inject } from 'vue'
import type { IData } from '@/views/preference/typedef' import type { Settings } from '@/views/preference/typedef'
const { $gettext } = useGettext() const { $gettext } = useGettext()
const data: IData = inject('data') as IData const data: Settings = inject('data') as Settings
const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
</script> </script>
<template> <template>
@ -30,13 +31,25 @@ const data: IData = inject('data') as IData
<AFormItem :label="$gettext('HTTP Challenge Port')"> <AFormItem :label="$gettext('HTTP Challenge Port')">
<AInputNumber v-model:value="data.server.http_challenge_port" /> <AInputNumber v-model:value="data.server.http_challenge_port" />
</AFormItem> </AFormItem>
<AFormItem :label="$gettext('Github Proxy')"> <AFormItem
:label="$gettext('Github Proxy')"
:validate-status="errors?.server?.github_proxy ? 'error' : ''"
:help="errors?.server?.github_proxy === 'url'
? $gettext('The url is not valid')
: ''"
>
<AInput <AInput
v-model:value="data.server.github_proxy" v-model:value="data.server.github_proxy"
:placeholder="$gettext('Chinese user: https://mirror.ghproxy.com/')" :placeholder="$gettext('For Chinese user: https://mirror.ghproxy.com/')"
/> />
</AFormItem> </AFormItem>
<AFormItem :label="$gettext('CADir')"> <AFormItem
:label="$gettext('CADir')"
:validate-status="errors?.server?.ca_dir ? 'error' : ''"
:help="errors?.server?.ca_dir === 'url'
? $gettext('The url is not valid')
: ''"
>
<AInput v-model:value="data.server.ca_dir" /> <AInput v-model:value="data.server.ca_dir" />
</AFormItem> </AFormItem>
</AForm> </AForm>

View file

@ -1,19 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { inject } from 'vue' import { inject } from 'vue'
import type { IData } from '@/views/preference/typedef' import type { Settings } from '@/views/preference/typedef'
const { $gettext } = useGettext() const { $gettext } = useGettext()
const data: IData = inject('data')! const data: Settings = inject('data')!
const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
</script> </script>
<template> <template>
<AForm layout="vertical"> <AForm layout="vertical">
<AFormItem :label="$gettext('Nginx Access Log Path')"> <AFormItem
:label="$gettext('Nginx Access Log Path')"
:validate-status="errors?.nginx?.access_log_path ? 'error' : ''"
:help="errors?.nginx?.access_log_path === 'file'
? $gettext('File not found')
: ''"
>
<AInput v-model:value="data.nginx.access_log_path" /> <AInput v-model:value="data.nginx.access_log_path" />
</AFormItem> </AFormItem>
<AFormItem :label="$gettext('Nginx Error Log Path')"> <AFormItem
:label="$gettext('Nginx Error Log Path')"
:validate-status="errors?.nginx?.error_log_path ? 'error' : ''"
:help="errors?.nginx?.error_log_path === 'file'
? $gettext('File not found')
: ''"
>
<AInput v-model:value="data.nginx.error_log_path" /> <AInput v-model:value="data.nginx.error_log_path" />
</AFormItem> </AFormItem>
</AForm> </AForm>

View file

@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { inject } from 'vue' import { inject } from 'vue'
import type { IData } from '@/views/preference/typedef' import type { Settings } from '@/views/preference/typedef'
const { $gettext } = useGettext() const { $gettext } = useGettext()
const data: IData = inject('data')! const data: Settings = inject('data')!
const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
</script> </script>
<template> <template>
@ -26,19 +27,37 @@ const data: IData = inject('data')!
</ASelectOption> </ASelectOption>
</ASelect> </ASelect>
</AFormItem> </AFormItem>
<AFormItem :label="$gettext('API Base Url')"> <AFormItem
:label="$gettext('API Base Url')"
:validate-status="errors?.openai?.base_url ? 'error' : ''"
:help="errors?.openai?.base_url === 'url'
? $gettext('The url is not valid')
: ''"
>
<AInput <AInput
v-model:value="data.openai.base_url" v-model:value="data.openai.base_url"
:placeholder="$gettext('Leave blank for the default: https://api.openai.com/')" :placeholder="$gettext('Leave blank for the default: https://api.openai.com/')"
/> />
</AFormItem> </AFormItem>
<AFormItem :label="$gettext('API Proxy')"> <AFormItem
:label="$gettext('API Proxy')"
:validate-status="errors?.openai?.proxy ? 'error' : ''"
:help="errors?.openai?.proxy === 'url'
? $gettext('The url is not valid')
: ''"
>
<AInput <AInput
v-model:value="data.openai.proxy" v-model:value="data.openai.proxy"
placeholder="http://127.0.0.1:1087" placeholder="http://127.0.0.1:1087"
/> />
</AFormItem> </AFormItem>
<AFormItem :label="$gettext('API Token')"> <AFormItem
:label="$gettext('API Token')"
:validate-status="errors?.openai?.token ? 'error' : ''"
:help="errors?.openai?.token === 'alphanumdash'
? $gettext('Token is not valid')
: ''"
>
<AInputPassword v-model:value="data.openai.token" /> <AInputPassword v-model:value="data.openai.token" />
</AFormItem> </AFormItem>
</AForm> </AForm>

View file

@ -1,16 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { Ref } from 'vue'
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue' import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
import settings from '@/api/settings' import settings from '@/api/settings'
import BasicSettings from '@/views/preference/BasicSettings.vue' import BasicSettings from '@/views/preference/BasicSettings.vue'
import OpenAISettings from '@/views/preference/OpenAISettings.vue' import OpenAISettings from '@/views/preference/OpenAISettings.vue'
import NginxSettings from '@/views/preference/NginxSettings.vue' import NginxSettings from '@/views/preference/NginxSettings.vue'
import type { IData } from '@/views/preference/typedef' import type { Settings } from '@/views/preference/typedef'
const { $gettext } = useGettext() const { $gettext } = useGettext()
const data = ref<IData>({ const data = ref<Settings>({
server: { server: {
http_host: '0.0.0.0', http_host: '0.0.0.0',
http_port: '9000', http_port: '9000',
@ -50,18 +51,23 @@ settings.get().then(r => {
data.value = r data.value = r
}) })
const errors = ref({}) as Ref<Record<string, Record<string, string>>>
async function save() { async function save() {
// fix type // fix type
data.value.server.http_challenge_port = data.value.server.http_challenge_port.toString() data.value.server.http_challenge_port = data.value.server.http_challenge_port.toString()
settings.save(data.value).then(r => { settings.save(data.value).then(r => {
data.value = r data.value = r
message.success($gettext('Save successfully')) message.success($gettext('Save successfully'))
errors.value = {}
}).catch(e => { }).catch(e => {
errors.value = e.errors
message.error(e?.message ?? $gettext('Server error')) message.error(e?.message ?? $gettext('Server error'))
}) })
} }
provide('data', data) provide('data', data)
provide('errors', errors)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()

View file

@ -1,4 +1,4 @@
export interface IData { export interface Settings {
server: { server: {
http_host: string http_host: string
http_port: string http_port: string