chore: merge upstream/main into admin-frontend-domain preserving custom domain and branding features

This commit is contained in:
Bot
2026-08-31 02:56:31 +08:00
114 changed files with 8616 additions and 5337 deletions
+97 -43
View File
@@ -7,6 +7,7 @@ import { Combobox } from "@/components/ui/combobox"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -25,13 +26,13 @@ import { Textarea } from "@/components/ui/textarea"
import { useAuth } from "@/hooks/useAuth"
import { useNotification } from "@/hooks/useNotfication"
import useSetting from "@/hooks/useSetting"
import { asOptionalField } from "@/lib/utils"
import { asOptionalField, safeExternalHref } from "@/lib/utils"
import { nezhaLang, settingCoverageTypes } from "@/types"
import { zodResolver } from "@hookform/resolvers/zod"
import { useEffect } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { Navigate } from "react-router-dom"
import { toast } from "sonner"
import { z } from "zod"
@@ -44,6 +45,8 @@ const settingFormSchema = z.object({
language: z.string().min(2),
user_template: z.string().min(1),
install_host: asOptionalField(z.string()),
dashboard_host: asOptionalField(z.string()),
reserved_hosts: asOptionalField(z.string()),
custom_code: asOptionalField(z.string()),
custom_code_dashboard: asOptionalField(z.string()),
web_real_ip_header: asOptionalField(z.string()),
@@ -53,6 +56,7 @@ const settingFormSchema = z.object({
enable_ip_change_notification: asOptionalField(z.boolean()),
enable_plain_ip_in_notification: asOptionalField(z.boolean()),
custom_logo: asOptionalField(z.string()),
custom_description: asOptionalField(z.string()),
custom_links: asOptionalField(z.string()),
background_image_day: asOptionalField(z.string()),
@@ -66,13 +70,14 @@ const settingFormSchema = z.object({
domain_expiry_notification_days: asOptionalField(z.string()),
server_expiry_notification_days: asOptionalField(z.string()),
expiry_notification_group_id: z.coerce.number().int().min(0),
enable_mcp: asOptionalField(z.boolean()),
})
export default function SettingsPage() {
const { t, i18n } = useTranslation()
const { data: config, mutate } = useSetting()
const { profile } = useAuth()
const navigate = useNavigate()
const { profile, loading: authLoading } = useAuth()
const { notifierGroup } = useNotification()
const ngroupList = notifierGroup?.map((ng) => ({
@@ -82,12 +87,10 @@ export default function SettingsPage() {
const isAdmin = profile?.role === 0
if (!isAdmin) {
navigate("/dashboard/settings/online-user")
}
const form = useForm({
// 所有 hooks 必须在条件 return 之前调用,否则违反 rules-of-hooks。
const form = useForm<z.infer<typeof settingFormSchema>>({
resolver: zodResolver(settingFormSchema) as any,
defaultValues: config
? {
...config.config,
@@ -117,6 +120,13 @@ export default function SettingsPage() {
}
}, [config?.config, form])
if (authLoading) {
return null
}
if (!isAdmin) {
return <Navigate to="/dashboard/settings/api-tokens" replace />
}
const onSubmit = async (values: any) => {
try {
await updateSettings(values)
@@ -129,19 +139,19 @@ export default function SettingsPage() {
}),
})
return
} finally {
if (values.language != i18n.language) {
i18n.changeLanguage(values.language)
}
toast(t("Success"))
}
if (values.language != i18n.language) {
i18n.changeLanguage(values.language)
}
toast(t("Success"))
}
return (
<div className="px-3">
<SettingsTab className="mt-6 mb-4 w-full" />
<div>
<Form {...form}>
<Form {...(form as any)}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2 my-2">
<FormField
control={form.control}
@@ -390,14 +400,24 @@ export default function SettingsPage() {
</div>
</SelectItem>
<div className="px-8 py-1">
<a
href={template.repository}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-blue-600 hover:text-blue-800 hover:underline"
>
{template.repository}
</a>
{safeExternalHref(
template.repository,
) ? (
<a
href={safeExternalHref(
template.repository,
)}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-blue-600 hover:text-blue-800 hover:underline"
>
{template.repository}
</a>
) : (
<span className="text-sm text-muted-foreground">
{template.repository}
</span>
)}
</div>
</div>
))}
@@ -459,6 +479,34 @@ export default function SettingsPage() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="dashboard_host"
render={({ field }) => (
<FormItem>
<FormLabel>{t("DashboardHost")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>{t("DashboardHostHint")}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="reserved_hosts"
render={({ field }) => (
<FormItem>
<FormLabel>{t("ReservedHosts")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>{t("ReservedHostsHint")}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="tls"
@@ -512,16 +560,10 @@ export default function SettingsPage() {
checked={field.value == "NZ::Use-Peer-IP"}
className="ml-2"
onCheckedChange={(checked) => {
if (checked) {
field.disabled = true
form.setValue(
"web_real_ip_header",
"NZ::Use-Peer-IP",
)
} else {
field.disabled = false
form.setValue("web_real_ip_header", "")
}
form.setValue(
"web_real_ip_header",
checked ? "NZ::Use-Peer-IP" : "",
)
}}
/>
<FormLabel className="font-normal ml-2">
@@ -551,16 +593,10 @@ export default function SettingsPage() {
checked={field.value == "NZ::Use-Peer-IP"}
className="ml-2"
onCheckedChange={(checked) => {
if (checked) {
field.disabled = true
form.setValue(
"agent_real_ip_header",
"NZ::Use-Peer-IP",
)
} else {
field.disabled = false
form.setValue("agent_real_ip_header", "")
}
form.setValue(
"agent_real_ip_header",
checked ? "NZ::Use-Peer-IP" : "",
)
}}
/>
<FormLabel className="font-normal ml-2">
@@ -749,6 +785,24 @@ export default function SettingsPage() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="enable_mcp"
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<FormControl>
<div className="flex items-center gap-2">
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label className="text-sm">{t("EnableMCP")}</Label>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">{t("Confirm")}</Button>
</form>
</Form>