设置界面优化

This commit is contained in:
ryan
2026-06-07 23:39:25 +08:00
parent 589ae08318
commit 53a4c92176
11 changed files with 217 additions and 312 deletions
@@ -0,0 +1,8 @@
"use client"
import {SecurityMain} from "@/components/common/settings/security"
/* 系统设置页面 */
export default function AdminSettingsPage() {
return <SecurityMain />
}
+2 -28
View File
@@ -2,8 +2,7 @@
import Link from "next/link"
import {Card, CardContent, CardDescription, CardTitle} from "@/components/ui/card"
import {Bell, Key, Loader2, Palette, Shield, UserRound} from "lucide-react"
import {useAuth} from "@/components/providers/auth-provider"
import {Bell, Key, Palette, UserRound} from "lucide-react"
/* 设置项 */
const settingsItems = [
@@ -28,13 +27,6 @@ const settingsItems = [
href: "/settings/notifications",
category: "账户设置",
},
{
title: "安全设置",
description: "管理您的账户安全",
icon: Shield,
href: "/settings/security",
category: "账户设置",
},
{
title: "外观设置",
description: "自定义界面主题 and 显示",
@@ -45,25 +37,7 @@ const settingsItems = [
]
export default function SettingsPage() {
const { user, loading } = useAuth()
if (loading) {
return (
<div className="flex items-center justify-center min-h-[200px]">
<Loader2 className="size-6 animate-spin text-indigo-500" />
</div>
)
}
// 非管理员过滤掉安全设置
const filteredItems = settingsItems.filter((item) => {
if (item.href === "/settings/security") {
return !!user?.is_admin
}
return true
})
const groupedSettings = filteredItems.reduce((acc, item) => {
const groupedSettings = settingsItems.reduce((acc, item) => {
if (!acc[item.category]) {
acc[item.category] = []
}
@@ -1,5 +1,5 @@
import { SecurityMain } from "@/components/common/settings/security"
import {redirect} from "next/navigation"
export default function SecurityPage() {
return <SecurityMain />
redirect("/admin/settings")
}
@@ -4,7 +4,7 @@ import {useCallback, useEffect, useState} from "react"
import Link from "next/link"
import {toast} from "sonner"
import {format} from "date-fns"
import {Activity, ChevronLeft, ChevronRight, Layers, RefreshCw, RotateCcw} from "lucide-react"
import {Activity, ChevronLeft, ChevronRight, RefreshCw, RotateCcw} from "lucide-react"
import {AdminService, TaskExecution, TaskExecutionStatus, TaskMeta} from "@/lib/services"
import {ErrorInline} from "@/components/layout/error"
@@ -160,8 +160,7 @@ export function TaskExecutionsManager() {
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" size="sm" asChild>
<Link href="/admin/tasks">
<Layers className="size-4" />
任务管理
返回
</Link>
</Button>
<Select value={executionStatus} onValueChange={(value) => setExecutionStatus(value as TaskExecutionStatus | "all")}>
@@ -2,7 +2,6 @@
import {useCallback, useEffect, useState} from "react"
import Link from "next/link"
import {useRouter} from "next/navigation"
import {toast} from "sonner"
import {Button} from "@/components/ui/button"
import {Input} from "@/components/ui/input"
@@ -130,7 +129,6 @@ function DatePickerWithTime({ date, setDate }: { date: Date | undefined, setDate
}
export function TaskManager() {
const router = useRouter()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const [taskTypes, setTaskTypes] = useState<TaskMeta[]>([])
@@ -191,7 +189,6 @@ export function TaskManager() {
setStartTime(undefined)
setEndTime(undefined)
setUserId("")
router.push("/admin/tasks/executions")
} catch (err) {
toast.error('任务下发失败', {
description: err instanceof Error ? err.message : '未知错误'
+14 -18
View File
@@ -1,24 +1,11 @@
"use client"
import * as React from "react"
import { motion } from "motion/react"
import { useUser } from "@/contexts/user-context"
import { formatDateTime } from "@/lib/utils"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
User,
Key,
ShieldCheck,
FileText,
Layers,
ArrowRight,
Shield,
HelpCircle,
Clock,
ExternalLink
} from "lucide-react"
import {motion} from "motion/react"
import {useUser} from "@/contexts/user-context"
import {Card, CardHeader, CardTitle} from "@/components/ui/card"
import {Button} from "@/components/ui/button"
import {ArrowRight, ExternalLink, FileText, HelpCircle, Layers, Shield, ShieldCheck, User} from "lucide-react"
import Link from "next/link"
export function HomeMain() {
@@ -57,6 +44,15 @@ export function HomeMain() {
]
const adminLinks = [
{
title: "系统设置",
description: "管理登录注册开关与认证源安全策略",
icon: Shield,
url: "/admin/settings",
color: "text-amber-500",
bgColor: "bg-amber-500/10",
borderColor: "hover:border-amber-500/30",
},
{
title: "全局系统配置",
description: "动态管理平台运行时核心配置参数",
+1 -30
View File
@@ -1,7 +1,6 @@
"use client"
import * as React from "react"
import Link from "next/link"
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"
import {AnimatePresence, motion} from "motion/react"
import {
@@ -11,7 +10,6 @@ import {
FileImage,
FileText,
FileVideo,
Folder,
Loader2,
Search,
Trash2,
@@ -21,14 +19,6 @@ import {
import {toast} from "sonner"
import {Button} from "@/components/ui/button"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {Input} from "@/components/ui/input"
import {Badge} from "@/components/ui/badge"
import {
@@ -148,32 +138,13 @@ export function FilesMain() {
transition={{ duration: 0.35, ease: "easeOut" }}
className="py-6 space-y-6 max-w-6xl mx-auto"
>
{/* Breadcrumb */}
<div className="font-semibold">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/settings" className="text-base text-primary">设置</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage className="text-base font-semibold">文件管理</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
{/* 头部 */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b pb-5">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br from-sky-500 to-indigo-600 text-white shadow-lg shadow-sky-500/20">
<Folder className="size-6" />
</div>
<div>
<h1 className="text-xl font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-muted-foreground bg-clip-text text-transparent">
我的文件
文件管理
</h1>
<p className="text-sm text-muted-foreground">
管理您上传的所有文件,支持下载和批量操作
+175 -186
View File
@@ -2,22 +2,14 @@
import {useEffect, useMemo, useState} from "react"
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"
import {Fingerprint, Globe, Loader2, Lock, Pencil, Plus, Settings, ShieldCheck, Trash2, UserPlus} from "lucide-react"
import Link from "next/link"
import {Fingerprint, Globe, Loader2, Lock, Pencil, Plus, Settings, Trash2, UserPlus} from "lucide-react"
import {useRouter} from "next/navigation"
import {motion} from "motion/react"
import {Button} from "@/components/ui/button"
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"
import {Switch} from "@/components/ui/switch"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator
} from "@/components/ui/breadcrumb"
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"
import {useAuth} from "@/components/providers/auth-provider"
import {AuthSourceModal} from "@/components/common/settings/auth-source-modal"
import {AdminService} from "@/lib/services"
@@ -163,196 +155,194 @@ export function SecurityMain() {
transition={{ duration: 0.35, ease: "easeOut" }}
className="py-6 space-y-6 max-w-4xl mx-auto px-4"
>
<div className="font-semibold">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/settings" className="text-base text-primary">设置</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage className="text-base font-semibold">安全设置</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
<Tabs defaultValue="security" className="w-full">
<TabsList className="w-full overflow-x-auto">
<TabsTrigger value="security" className="px-0 pb-2 text-xs font-semibold">
系统安全设置
</TabsTrigger>
<TabsTrigger value="operation" className="px-0 pb-2 text-xs font-semibold">
运营设置
</TabsTrigger>
<TabsTrigger value="system" className="px-0 pb-2 text-xs font-semibold">
系统设置
</TabsTrigger>
<TabsTrigger value="other" className="px-0 pb-2 text-xs font-semibold">
其他设置
</TabsTrigger>
<TabsTrigger value="info" className="px-0 pb-2 text-xs font-semibold">
系统信息
</TabsTrigger>
</TabsList>
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 border-b pb-5">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-lg shadow-indigo-500/20">
<ShieldCheck className="size-6 animate-pulse" />
</div>
<div>
<h1 className="text-xl font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-muted-foreground bg-clip-text text-transparent">系统安全设置</h1>
<p className="text-sm text-muted-foreground">管理系统安全控制及身份验证源</p>
</div>
</div>
</div>
<div className="space-y-6">
{/* 系统登录与注册控制 */}
<Card className="border border-dashed shadow-sm">
<CardHeader className="border-b border-dashed pb-4">
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500">
<Settings className="size-4" />
</div>
<div>
<CardTitle className="text-base font-semibold">系统安全与注册控制</CardTitle>
<CardDescription className="text-xs">配置系统的登录限制与用户自主注册权限</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{SECURITY_KEYS.map((item) => {
const config = configs[item.key]
const checked = config ? config.value === "true" : false
const Icon = item.icon
return (
<div
key={item.key}
className="flex items-center justify-between gap-4 rounded-xl border border-dashed p-4 bg-card hover:bg-muted/10 hover:border-indigo-500/30 transition-all duration-300 shadow-sm"
>
<div className="space-y-1">
<div className="flex items-center gap-2">
{Icon && <Icon className="size-4 text-indigo-500" />}
<span className="font-medium text-sm text-foreground">{item.title}</span>
</div>
<p className="text-xs text-muted-foreground leading-relaxed pr-2">{item.description}</p>
</div>
<Switch
checked={checked}
disabled={updateConfigMutation.isPending}
onCheckedChange={(value) => handleToggle(item.key, value)}
/>
<TabsContent value="security" className="pt-4">
<div className="space-y-6">
{/* 系统登录与注册控制 */}
<Card className="border border-dashed shadow-sm">
<CardHeader className="border-b border-dashed pb-4">
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500">
<Settings className="size-4" />
</div>
)
})}
</div>
</CardContent>
</Card>
{/* 认证源配置管理 */}
<Card className="border border-dashed shadow-sm">
<CardHeader className="border-b border-dashed pb-4 flex flex-row items-center justify-between gap-4">
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500">
<Globe className="size-4" />
</div>
<div>
<CardTitle className="text-base font-semibold">认证源管理</CardTitle>
<CardDescription className="text-xs">添加、修改并启用系统自定义的 OIDC 认证源</CardDescription>
</div>
</div>
<Button
type="button"
size="sm"
onClick={() => {
setSelectedSource(null)
setAuthSourceModalOpen(true)
}}
variant="secondary"
>
<Plus className="mr-1.5 size-3.5" />
新增认证源
</Button>
</CardHeader>
<CardContent className="pt-6 space-y-3">
{authSourcesQuery.isPending ? (
<div className="flex items-center justify-center p-8">
<Loader2 className="size-6 animate-spin text-muted-foreground/50" />
</div>
) : (authSourcesQuery.data ?? []).length > 0 ? (
(authSourcesQuery.data ?? []).map((source) => (
<div
key={source.id}
className="flex items-center justify-between rounded-xl border border-dashed p-4 bg-card hover:bg-muted/10 transition-all duration-300 shadow-sm"
>
<div className="space-y-1.5">
<div className="flex items-center gap-2">
<span className="font-semibold text-sm text-foreground">{source.display_name || source.name}</span>
<span className={`text-[10px] px-2 py-0.5 rounded-full border font-medium ${
source.is_active
? "bg-emerald-500/10 text-emerald-500 border-emerald-500/20"
: "bg-amber-500/10 text-amber-500 border-amber-500/20"
}`}>
{source.is_active ? "已启用" : "已禁用"}
</span>
</div>
<div className="text-xs text-muted-foreground font-mono">
标识符: {source.name} · 类型: {source.type.toUpperCase()}
</div>
</div>
<div className="flex items-center gap-4">
<span className={`text-xs px-2.5 py-1 rounded-lg border font-medium hidden sm:inline-block ${
source.client_secret_configured
? "bg-indigo-500/5 text-indigo-500 border-indigo-500/10"
: "bg-rose-500/5 text-rose-500 border-rose-500/10"
}`}>
{source.client_secret_configured ? "Secret 已配置" : "Secret 未配置"}
</span>
<div className="flex items-center gap-2">
<Switch
checked={source.is_active}
disabled={toggleSourceMutation.isPending}
className="scale-90 mr-2"
onCheckedChange={() => toggleSourceMutation.mutate(source)}
/>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 text-muted-foreground hover:text-indigo-500 hover:bg-indigo-500/10 rounded-lg transition-colors"
onClick={() => {
setSelectedSource(source)
setAuthSourceModalOpen(true)
}}
>
<Pencil className="size-4" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 text-muted-foreground hover:text-rose-500 hover:bg-rose-500/10 rounded-lg transition-colors"
disabled={deleteSourceMutation.isPending}
onClick={() => {
if (window.confirm(`确定删除认证源「${source.display_name || source.name}」吗?`)) {
deleteSourceMutation.mutate(source.id)
}
}}
>
<Trash2 className="size-4" />
</Button>
</div>
<div>
<CardTitle className="text-base font-semibold">系统安全与注册控制</CardTitle>
<CardDescription className="text-xs">配置系统的登录限制与用户自主注册权限</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{SECURITY_KEYS.map((item) => {
const config = configs[item.key]
const checked = config ? config.value === "true" : false
const Icon = item.icon
return (
<div
key={item.key}
className="flex items-center justify-between gap-4 rounded-xl border border-dashed p-4 bg-card hover:bg-muted/10 hover:border-indigo-500/30 transition-all duration-300 shadow-sm"
>
<div className="space-y-1">
<div className="flex items-center gap-2">
{Icon && <Icon className="size-4 text-indigo-500" />}
<span className="font-medium text-sm text-foreground">{item.title}</span>
</div>
<p className="text-xs text-muted-foreground leading-relaxed pr-2">{item.description}</p>
</div>
<Switch
checked={checked}
disabled={updateConfigMutation.isPending}
onCheckedChange={(value) => handleToggle(item.key, value)}
/>
</div>
)
})}
</div>
</CardContent>
</Card>
{/* 认证源配置管理 */}
<Card className="border border-dashed shadow-sm">
<CardHeader className="border-b border-dashed pb-4 flex flex-row items-center justify-between gap-4">
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500">
<Globe className="size-4" />
</div>
<div>
<CardTitle className="text-base font-semibold">认证源管理</CardTitle>
<CardDescription className="text-xs">添加、修改并启用系统自定义的 OIDC 认证源</CardDescription>
</div>
</div>
))
) : (
<div className="rounded-xl border border-dashed border-border/50 px-4 py-8 text-center text-xs text-muted-foreground bg-muted/5 flex flex-col items-center justify-center gap-3">
<span>暂无配置的认证源,点击上方按钮新增</span>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => {
setSelectedSource(null)
setAuthSourceModalOpen(true)
}}
className="border-dashed"
variant="secondary"
>
<Plus className="mr-1.5 size-3.5" />
新增认证源
</Button>
</div>
)}
</CardContent>
</Card>
</div>
</CardHeader>
<CardContent className="pt-6 space-y-3">
{authSourcesQuery.isPending ? (
<div className="flex items-center justify-center p-8">
<Loader2 className="size-6 animate-spin text-muted-foreground/50" />
</div>
) : (authSourcesQuery.data ?? []).length > 0 ? (
(authSourcesQuery.data ?? []).map((source) => (
<div
key={source.id}
className="flex items-center justify-between rounded-xl border border-dashed p-4 bg-card hover:bg-muted/10 transition-all duration-300 shadow-sm"
>
<div className="space-y-1.5">
<div className="flex items-center gap-2">
<span className="font-semibold text-sm text-foreground">{source.display_name || source.name}</span>
<span className={`text-[10px] px-2 py-0.5 rounded-full border font-medium ${
source.is_active
? "bg-emerald-500/10 text-emerald-500 border-emerald-500/20"
: "bg-amber-500/10 text-amber-500 border-amber-500/20"
}`}>
{source.is_active ? "已启用" : "已禁用"}
</span>
</div>
<div className="text-xs text-muted-foreground font-mono">
标识符: {source.name} · 类型: {source.type.toUpperCase()}
</div>
</div>
<div className="flex items-center gap-4">
<span className={`text-xs px-2.5 py-1 rounded-lg border font-medium hidden sm:inline-block ${
source.client_secret_configured
? "bg-indigo-500/5 text-indigo-500 border-indigo-500/10"
: "bg-rose-500/5 text-rose-500 border-rose-500/10"
}`}>
{source.client_secret_configured ? "Secret 已配置" : "Secret 未配置"}
</span>
<div className="flex items-center gap-2">
<Switch
checked={source.is_active}
disabled={toggleSourceMutation.isPending}
className="scale-90 mr-2"
onCheckedChange={() => toggleSourceMutation.mutate(source)}
/>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 text-muted-foreground hover:text-indigo-500 hover:bg-indigo-500/10 rounded-lg transition-colors"
onClick={() => {
setSelectedSource(source)
setAuthSourceModalOpen(true)
}}
>
<Pencil className="size-4" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 text-muted-foreground hover:text-rose-500 hover:bg-rose-500/10 rounded-lg transition-colors"
disabled={deleteSourceMutation.isPending}
onClick={() => {
if (window.confirm(`确定删除认证源「${source.display_name || source.name}」吗?`)) {
deleteSourceMutation.mutate(source.id)
}
}}
>
<Trash2 className="size-4" />
</Button>
</div>
</div>
</div>
))
) : (
<div className="rounded-xl border border-dashed border-border/50 px-4 py-8 text-center text-xs text-muted-foreground bg-muted/5 flex flex-col items-center justify-center gap-3">
<span>暂无配置的认证源,点击上方按钮新增</span>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => {
setSelectedSource(null)
setAuthSourceModalOpen(true)
}}
className="border-dashed"
>
<Plus className="mr-1.5 size-3.5" />
新增认证源
</Button>
</div>
)}
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="operation" />
<TabsContent value="system" />
<TabsContent value="other" />
<TabsContent value="info" />
</Tabs>
<AuthSourceModal
isOpen={authSourceModalOpen}
@@ -367,4 +357,3 @@ export function SecurityMain() {
</motion.div>
)
}
+3 -3
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import {cn} from "@/lib/utils";
import Link from "next/link";
import { Github, LucideIcon } from "lucide-react";
import {Github, LucideIcon} from "lucide-react";
export interface FooterSectionProps {
className?: string;
@@ -36,7 +36,7 @@ export const FooterSection = React.memo(function FooterSection({ className }: Fo
<h3 className="font-semibold text-foreground mb-6">产品</h3>
<ul className="space-y-4 text-sm text-muted-foreground">
<li><FooterLink href="/home">仪表盘</FooterLink></li>
<li><FooterLink href="/settings">系统设置</FooterLink></li>
<li><FooterLink href="/settings">个人设置</FooterLink></li>
</ul>
</div>
+2 -30
View File
@@ -67,10 +67,8 @@ const data = {
{ title: "首页", url: "/home", icon: Home },
{ title: "文件管理", url: "/settings/files", icon: FolderOpen },
],
systemSettings: [
{ title: "系统设置", url: "/settings/security", icon: Settings },
],
admin: [
{ title: "系统设置", url: "/admin/settings", icon: Settings },
{ title: "系统配置", url: "/admin/system", icon: ShieldCheck },
{ title: "用户管理", url: "/admin/users", icon: UserRound },
{ title: "任务管理", url: "/admin/tasks", icon: Layers },
@@ -283,32 +281,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarGroupContent>
</SidebarGroup>
{user?.is_admin && (
<SidebarGroup className="py-0 pt-4">
<SidebarGroupLabel className="text-xs font-normal text-muted-foreground">
设置
</SidebarGroupLabel>
<SidebarGroupContent className="py-1">
<SidebarMenu className="gap-1">
{data.systemSettings.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={pathname.startsWith(item.url)}
asChild
>
<Link href={item.url} onClick={handleCloseSidebar}>
{item.icon && <item.icon />}
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)}
{user?.is_admin && (
<SidebarGroup className="py-0 pt-4">
<SidebarGroupLabel className="text-xs font-normal text-muted-foreground">
@@ -320,7 +292,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={pathname === item.url}
isActive={pathname === item.url || pathname.startsWith(`${item.url}/`)}
asChild
>
<Link href={item.url} onClick={handleCloseSidebar}>
+8 -9
View File
@@ -98,16 +98,15 @@ export const searchData: SearchItem[] = [
category: 'setting',
keywords: ['appearance', '外观', '主题', 'theme', 'dark', 'light'],
},
{
id: 'settings-security',
title: '安全设置',
description: '管理您的账户安全设置及安全密钥',
url: '/settings',
category: 'setting',
keywords: ['settings', '安全', 'security', 'privacy', 'keys'],
},
// ==================== 管理员 ====================
{
id: 'admin-settings',
title: '系统设置',
description: '管理系统登录注册与认证源配置 (管理员专属)',
url: '/admin/settings',
category: 'admin',
keywords: ['admin', '管理员', '系统设置', '安全', 'security', 'oidc', 'login'],
},
{
id: 'admin-system',
title: '系统配置',