mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
设置界面优化
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import {SecurityMain} from "@/components/common/settings/security"
|
||||
|
||||
/* 系统设置页面 */
|
||||
export default function AdminSettingsPage() {
|
||||
return <SecurityMain />
|
||||
}
|
||||
@@ -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 : '未知错误'
|
||||
|
||||
@@ -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,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">
|
||||
管理您上传的所有文件,支持下载和批量操作
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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: '系统配置',
|
||||
|
||||
Reference in New Issue
Block a user