feat: standardize cms data views
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Activity, AlertTriangle, ShieldCheck, Users } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import type { AuditLog, PublicAccount, RoleDefinition } from "@/modules/core/types";
|
||||
import { ROLE_LABELS } from "@/modules/core/types";
|
||||
|
||||
@@ -14,6 +16,20 @@ export function SettingsOverview({
|
||||
roles,
|
||||
auditLogs,
|
||||
}: SettingsOverviewProps): React.ReactElement {
|
||||
const activeAccountCount = accounts.filter((account) => account.status === "active").length;
|
||||
const disabledAccountCount = accounts.length - activeAccountCount;
|
||||
const deniedAuditCount = auditLogs.filter((log) => log.result === "denied").length;
|
||||
const failureAuditCount = auditLogs.filter((log) => log.result === "failure").length;
|
||||
const successAuditCount = auditLogs.filter((log) => log.result === "success").length;
|
||||
const totalPermissionCount = roles.reduce((sum, role) => sum + role.permissions.length, 0);
|
||||
const actionCounts = auditLogs.reduce<Record<string, number>>((counts, log) => {
|
||||
counts[log.action] = (counts[log.action] ?? 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
const topActions = Object.entries(actionCounts)
|
||||
.sort((left, right) => right[1] - left[1])
|
||||
.slice(0, 4);
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-5">
|
||||
<section className="border-b pb-5">
|
||||
@@ -22,32 +38,98 @@ export function SettingsOverview({
|
||||
<p className="mt-2 text-sm text-muted-foreground">内置角色、账号状态和关键操作审计。</p>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>系统账号</CardTitle>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
||||
<div>
|
||||
<CardDescription>系统账号</CardDescription>
|
||||
<CardTitle className="mt-2 text-3xl">{accounts.length}</CardTitle>
|
||||
</div>
|
||||
<Users className="size-5 text-primary" aria-hidden="true" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-semibold">{accounts.length}</p>
|
||||
<p className="mt-2 text-sm text-muted-foreground">服务端持久化账号</p>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm text-muted-foreground">启用 {activeAccountCount} 个,停用 {disabledAccountCount} 个</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>角色数量</CardTitle>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
||||
<div>
|
||||
<CardDescription>角色数量</CardDescription>
|
||||
<CardTitle className="mt-2 text-3xl">{roles.length}</CardTitle>
|
||||
</div>
|
||||
<ShieldCheck className="size-5 text-primary" aria-hidden="true" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-semibold">{roles.length}</p>
|
||||
<p className="mt-2 text-sm text-muted-foreground">内置权限组</p>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm text-muted-foreground">累计覆盖 {totalPermissionCount} 项权限</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-red-200 bg-red-50/70">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
||||
<div>
|
||||
<CardDescription className="text-red-700">红色审计</CardDescription>
|
||||
<CardTitle className="mt-2 text-3xl text-red-700">{deniedAuditCount + failureAuditCount}</CardTitle>
|
||||
</div>
|
||||
<AlertTriangle className="size-5 text-red-600" aria-hidden="true" />
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm text-red-700">拒绝 {deniedAuditCount} 条,失败 {failureAuditCount} 条</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>审计日志</CardTitle>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
||||
<div>
|
||||
<CardDescription>审计日志</CardDescription>
|
||||
<CardTitle className="mt-2 text-3xl">{auditLogs.length}</CardTitle>
|
||||
</div>
|
||||
<Activity className="size-5 text-primary" aria-hidden="true" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-semibold">{auditLogs.length}</p>
|
||||
<p className="mt-2 text-sm text-muted-foreground">最近 100 条</p>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm text-muted-foreground">成功 {successAuditCount} 条,最近 100 条</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-[0.8fr_1.2fr]">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>风险标记</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3">
|
||||
<div className="flex items-center justify-between rounded-md border border-red-200 bg-red-50 p-3">
|
||||
<span className="text-sm text-red-700">红色:拒绝/失败审计</span>
|
||||
<Badge variant="danger">{deniedAuditCount + failureAuditCount}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md border border-amber-200 bg-amber-50 p-3">
|
||||
<span className="text-sm text-amber-700">黄色:停用账号</span>
|
||||
<Badge variant="warning">{disabledAccountCount}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md border border-emerald-200 bg-emerald-50 p-3">
|
||||
<span className="text-sm text-emerald-700">绿色:成功审计</span>
|
||||
<Badge variant="success">{successAuditCount}</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>高频动作</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3">
|
||||
{topActions.map(([action, count]) => (
|
||||
<div key={action} className="grid gap-2 rounded-md border p-3">
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
<span className="font-medium">{action}</span>
|
||||
<span className="text-muted-foreground">{count} 次</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-muted">
|
||||
<div
|
||||
className="h-2 rounded-full bg-primary"
|
||||
style={{ width: `${Math.max(10, Math.min(100, count * 18))}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{topActions.length === 0 ? <p className="text-sm text-muted-foreground">暂无审计动作统计</p> : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
@@ -173,4 +255,3 @@ export function SettingsOverview({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user