设置界面优化

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 Link from "next/link"
import {Card, CardContent, CardDescription, CardTitle} from "@/components/ui/card" import {Card, CardContent, CardDescription, CardTitle} from "@/components/ui/card"
import {Bell, Key, Loader2, Palette, Shield, UserRound} from "lucide-react" import {Bell, Key, Palette, UserRound} from "lucide-react"
import {useAuth} from "@/components/providers/auth-provider"
/* 设置项 */ /* 设置项 */
const settingsItems = [ const settingsItems = [
@@ -28,13 +27,6 @@ const settingsItems = [
href: "/settings/notifications", href: "/settings/notifications",
category: "账户设置", category: "账户设置",
}, },
{
title: "安全设置",
description: "管理您的账户安全",
icon: Shield,
href: "/settings/security",
category: "账户设置",
},
{ {
title: "外观设置", title: "外观设置",
description: "自定义界面主题 and 显示", description: "自定义界面主题 and 显示",
@@ -45,25 +37,7 @@ const settingsItems = [
] ]
export default function SettingsPage() { export default function SettingsPage() {
const { user, loading } = useAuth() const groupedSettings = settingsItems.reduce((acc, item) => {
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) => {
if (!acc[item.category]) { if (!acc[item.category]) {
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() { 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 Link from "next/link"
import {toast} from "sonner" import {toast} from "sonner"
import {format} from "date-fns" 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 {AdminService, TaskExecution, TaskExecutionStatus, TaskMeta} from "@/lib/services"
import {ErrorInline} from "@/components/layout/error" import {ErrorInline} from "@/components/layout/error"
@@ -160,8 +160,7 @@ export function TaskExecutionsManager() {
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Button variant="outline" size="sm" asChild> <Button variant="outline" size="sm" asChild>
<Link href="/admin/tasks"> <Link href="/admin/tasks">
<Layers className="size-4" /> 返回
任务管理
</Link> </Link>
</Button> </Button>
<Select value={executionStatus} onValueChange={(value) => setExecutionStatus(value as TaskExecutionStatus | "all")}> <Select value={executionStatus} onValueChange={(value) => setExecutionStatus(value as TaskExecutionStatus | "all")}>
@@ -2,7 +2,6 @@
import {useCallback, useEffect, useState} from "react" import {useCallback, useEffect, useState} from "react"
import Link from "next/link" import Link from "next/link"
import {useRouter} from "next/navigation"
import {toast} from "sonner" import {toast} from "sonner"
import {Button} from "@/components/ui/button" import {Button} from "@/components/ui/button"
import {Input} from "@/components/ui/input" import {Input} from "@/components/ui/input"
@@ -130,7 +129,6 @@ function DatePickerWithTime({ date, setDate }: { date: Date | undefined, setDate
} }
export function TaskManager() { export function TaskManager() {
const router = useRouter()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null) const [error, setError] = useState<Error | null>(null)
const [taskTypes, setTaskTypes] = useState<TaskMeta[]>([]) const [taskTypes, setTaskTypes] = useState<TaskMeta[]>([])
@@ -191,7 +189,6 @@ export function TaskManager() {
setStartTime(undefined) setStartTime(undefined)
setEndTime(undefined) setEndTime(undefined)
setUserId("") setUserId("")
router.push("/admin/tasks/executions")
} catch (err) { } catch (err) {
toast.error('任务下发失败', { toast.error('任务下发失败', {
description: err instanceof Error ? err.message : '未知错误' description: err instanceof Error ? err.message : '未知错误'
+14 -18
View File
@@ -1,24 +1,11 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { motion } from "motion/react" import {motion} from "motion/react"
import { useUser } from "@/contexts/user-context" import {useUser} from "@/contexts/user-context"
import { formatDateTime } from "@/lib/utils" import {Card, CardHeader, CardTitle} from "@/components/ui/card"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import {Button} from "@/components/ui/button"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import {ArrowRight, ExternalLink, FileText, HelpCircle, Layers, Shield, ShieldCheck, User} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
User,
Key,
ShieldCheck,
FileText,
Layers,
ArrowRight,
Shield,
HelpCircle,
Clock,
ExternalLink
} from "lucide-react"
import Link from "next/link" import Link from "next/link"
export function HomeMain() { export function HomeMain() {
@@ -57,6 +44,15 @@ export function HomeMain() {
] ]
const adminLinks = [ 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: "全局系统配置", title: "全局系统配置",
description: "动态管理平台运行时核心配置参数", description: "动态管理平台运行时核心配置参数",
+1 -30
View File
@@ -1,7 +1,6 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import Link from "next/link"
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query" import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"
import {AnimatePresence, motion} from "motion/react" import {AnimatePresence, motion} from "motion/react"
import { import {
@@ -11,7 +10,6 @@ import {
FileImage, FileImage,
FileText, FileText,
FileVideo, FileVideo,
Folder,
Loader2, Loader2,
Search, Search,
Trash2, Trash2,
@@ -21,14 +19,6 @@ import {
import {toast} from "sonner" import {toast} from "sonner"
import {Button} from "@/components/ui/button" import {Button} from "@/components/ui/button"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {Input} from "@/components/ui/input" import {Input} from "@/components/ui/input"
import {Badge} from "@/components/ui/badge" import {Badge} from "@/components/ui/badge"
import { import {
@@ -148,32 +138,13 @@ export function FilesMain() {
transition={{ duration: 0.35, ease: "easeOut" }} transition={{ duration: 0.35, ease: "easeOut" }}
className="py-6 space-y-6 max-w-6xl mx-auto" 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 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 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> <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 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> </h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
管理您上传的所有文件,支持下载和批量操作 管理您上传的所有文件,支持下载和批量操作
+175 -186
View File
@@ -2,22 +2,14 @@
import {useEffect, useMemo, useState} from "react" import {useEffect, useMemo, useState} from "react"
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query" import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"
import {Fingerprint, Globe, Loader2, Lock, Pencil, Plus, Settings, ShieldCheck, Trash2, UserPlus} from "lucide-react" import {Fingerprint, Globe, Loader2, Lock, Pencil, Plus, Settings, Trash2, UserPlus} from "lucide-react"
import Link from "next/link"
import {useRouter} from "next/navigation" import {useRouter} from "next/navigation"
import {motion} from "motion/react" import {motion} from "motion/react"
import {Button} from "@/components/ui/button" import {Button} from "@/components/ui/button"
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card" import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"
import {Switch} from "@/components/ui/switch" import {Switch} from "@/components/ui/switch"
import { import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator
} from "@/components/ui/breadcrumb"
import {useAuth} from "@/components/providers/auth-provider" import {useAuth} from "@/components/providers/auth-provider"
import {AuthSourceModal} from "@/components/common/settings/auth-source-modal" import {AuthSourceModal} from "@/components/common/settings/auth-source-modal"
import {AdminService} from "@/lib/services" import {AdminService} from "@/lib/services"
@@ -163,196 +155,194 @@ export function SecurityMain() {
transition={{ duration: 0.35, ease: "easeOut" }} transition={{ duration: 0.35, ease: "easeOut" }}
className="py-6 space-y-6 max-w-4xl mx-auto px-4" className="py-6 space-y-6 max-w-4xl mx-auto px-4"
> >
<div className="font-semibold"> <Tabs defaultValue="security" className="w-full">
<Breadcrumb> <TabsList className="w-full overflow-x-auto">
<BreadcrumbList> <TabsTrigger value="security" className="px-0 pb-2 text-xs font-semibold">
<BreadcrumbItem> 系统安全设置
<BreadcrumbLink asChild> </TabsTrigger>
<Link href="/settings" className="text-base text-primary">设置</Link> <TabsTrigger value="operation" className="px-0 pb-2 text-xs font-semibold">
</BreadcrumbLink> 运营设置
</BreadcrumbItem> </TabsTrigger>
<BreadcrumbSeparator /> <TabsTrigger value="system" className="px-0 pb-2 text-xs font-semibold">
<BreadcrumbItem> 系统设置
<BreadcrumbPage className="text-base font-semibold">安全设置</BreadcrumbPage> </TabsTrigger>
</BreadcrumbItem> <TabsTrigger value="other" className="px-0 pb-2 text-xs font-semibold">
</BreadcrumbList> 其他设置
</Breadcrumb> </TabsTrigger>
</div> <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"> <TabsContent value="security" className="pt-4">
<div className="flex items-center gap-4"> <div className="space-y-6">
<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" /> <Card className="border border-dashed shadow-sm">
</div> <CardHeader className="border-b border-dashed pb-4">
<div> <div className="flex items-center gap-2">
<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> <div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500">
<p className="text-sm text-muted-foreground">管理系统安全控制及身份验证源</p> <Settings className="size-4" />
</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)}
/>
</div> </div>
) <div>
})} <CardTitle className="text-base font-semibold">系统安全与注册控制</CardTitle>
</div> <CardDescription className="text-xs">配置系统的登录限制与用户自主注册权限</CardDescription>
</CardContent> </div>
</Card> </div>
</CardHeader>
{/* 认证源配置管理 */} <CardContent className="pt-6">
<Card className="border border-dashed shadow-sm"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<CardHeader className="border-b border-dashed pb-4 flex flex-row items-center justify-between gap-4"> {SECURITY_KEYS.map((item) => {
<div className="flex items-center gap-2"> const config = configs[item.key]
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500"> const checked = config ? config.value === "true" : false
<Globe className="size-4" /> const Icon = item.icon
</div> return (
<div> <div
<CardTitle className="text-base font-semibold">认证源管理</CardTitle> key={item.key}
<CardDescription className="text-xs">添加、修改并启用系统自定义的 OIDC 认证源</CardDescription> 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> >
</div> <div className="space-y-1">
<Button <div className="flex items-center gap-2">
type="button" {Icon && <Icon className="size-4 text-indigo-500" />}
size="sm" <span className="font-medium text-sm text-foreground">{item.title}</span>
onClick={() => { </div>
setSelectedSource(null) <p className="text-xs text-muted-foreground leading-relaxed pr-2">{item.description}</p>
setAuthSourceModalOpen(true) </div>
}} <Switch
variant="secondary" checked={checked}
> disabled={updateConfigMutation.isPending}
<Plus className="mr-1.5 size-3.5" /> onCheckedChange={(value) => handleToggle(item.key, value)}
新增认证源 />
</Button> </div>
</CardHeader> )
<CardContent className="pt-6 space-y-3"> })}
{authSourcesQuery.isPending ? ( </div>
<div className="flex items-center justify-center p-8"> </CardContent>
<Loader2 className="size-6 animate-spin text-muted-foreground/50" /> </Card>
</div>
) : (authSourcesQuery.data ?? []).length > 0 ? ( {/* 认证源配置管理 */}
(authSourcesQuery.data ?? []).map((source) => ( <Card className="border border-dashed shadow-sm">
<div <CardHeader className="border-b border-dashed pb-4 flex flex-row items-center justify-between gap-4">
key={source.id} <div className="flex items-center gap-2">
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="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-500">
> <Globe className="size-4" />
<div className="space-y-1.5"> </div>
<div className="flex items-center gap-2"> <div>
<span className="font-semibold text-sm text-foreground">{source.display_name || source.name}</span> <CardTitle className="text-base font-semibold">认证源管理</CardTitle>
<span className={`text-[10px] px-2 py-0.5 rounded-full border font-medium ${ <CardDescription className="text-xs">添加、修改并启用系统自定义的 OIDC 认证源</CardDescription>
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> </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 <Button
type="button" type="button"
size="sm" size="sm"
variant="outline"
onClick={() => { onClick={() => {
setSelectedSource(null) setSelectedSource(null)
setAuthSourceModalOpen(true) setAuthSourceModalOpen(true)
}} }}
className="border-dashed" variant="secondary"
> >
<Plus className="mr-1.5 size-3.5" /> <Plus className="mr-1.5 size-3.5" />
新增认证源 新增认证源
</Button> </Button>
</div> </CardHeader>
)} <CardContent className="pt-6 space-y-3">
</CardContent> {authSourcesQuery.isPending ? (
</Card> <div className="flex items-center justify-center p-8">
</div> <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 <AuthSourceModal
isOpen={authSourceModalOpen} isOpen={authSourceModalOpen}
@@ -367,4 +357,3 @@ export function SecurityMain() {
</motion.div> </motion.div>
) )
} }
+3 -3
View File
@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { cn } from "@/lib/utils"; import {cn} from "@/lib/utils";
import Link from "next/link"; import Link from "next/link";
import { Github, LucideIcon } from "lucide-react"; import {Github, LucideIcon} from "lucide-react";
export interface FooterSectionProps { export interface FooterSectionProps {
className?: string; className?: string;
@@ -36,7 +36,7 @@ export const FooterSection = React.memo(function FooterSection({ className }: Fo
<h3 className="font-semibold text-foreground mb-6">产品</h3> <h3 className="font-semibold text-foreground mb-6">产品</h3>
<ul className="space-y-4 text-sm text-muted-foreground"> <ul className="space-y-4 text-sm text-muted-foreground">
<li><FooterLink href="/home">仪表盘</FooterLink></li> <li><FooterLink href="/home">仪表盘</FooterLink></li>
<li><FooterLink href="/settings">系统设置</FooterLink></li> <li><FooterLink href="/settings">个人设置</FooterLink></li>
</ul> </ul>
</div> </div>
+2 -30
View File
@@ -67,10 +67,8 @@ const data = {
{ title: "首页", url: "/home", icon: Home }, { title: "首页", url: "/home", icon: Home },
{ title: "文件管理", url: "/settings/files", icon: FolderOpen }, { title: "文件管理", url: "/settings/files", icon: FolderOpen },
], ],
systemSettings: [
{ title: "系统设置", url: "/settings/security", icon: Settings },
],
admin: [ admin: [
{ title: "系统设置", url: "/admin/settings", icon: Settings },
{ title: "系统配置", url: "/admin/system", icon: ShieldCheck }, { title: "系统配置", url: "/admin/system", icon: ShieldCheck },
{ title: "用户管理", url: "/admin/users", icon: UserRound }, { title: "用户管理", url: "/admin/users", icon: UserRound },
{ title: "任务管理", url: "/admin/tasks", icon: Layers }, { title: "任务管理", url: "/admin/tasks", icon: Layers },
@@ -283,32 +281,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup> </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 && ( {user?.is_admin && (
<SidebarGroup className="py-0 pt-4"> <SidebarGroup className="py-0 pt-4">
<SidebarGroupLabel className="text-xs font-normal text-muted-foreground"> <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}> <SidebarMenuItem key={item.title}>
<SidebarMenuButton <SidebarMenuButton
tooltip={item.title} tooltip={item.title}
isActive={pathname === item.url} isActive={pathname === item.url || pathname.startsWith(`${item.url}/`)}
asChild asChild
> >
<Link href={item.url} onClick={handleCloseSidebar}> <Link href={item.url} onClick={handleCloseSidebar}>
+8 -9
View File
@@ -98,16 +98,15 @@ export const searchData: SearchItem[] = [
category: 'setting', category: 'setting',
keywords: ['appearance', '外观', '主题', 'theme', 'dark', 'light'], 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', id: 'admin-system',
title: '系统配置', title: '系统配置',