feat(frontend/admin): M10 后台管理面板(/admin hash 路由 + 15 大模块 + RBAC(owner/admin/viewer) + 审计 + Miuix);Docs/api-client.md v0.3 契约补全;tsc+vite build 通过

This commit is contained in:
Aska committed 2026-09-06 07:55:04 +08:00
1 parent 4c48e94659
commit f43eb17030
26 files changed
+2253 -4

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
import { useState } from "react";
import { getStoredRole } from "../../lib/api";
import { useAuditStore } from "../audit";
import { KeyValueRow, Notice, PageHeader } from "../components";
import { Button, Surface, Tag } from "../../components/primitives";
export function SystemModule(): React.ReactNode {
const isOwner = getStoredRole() === "owner";
const log = useAuditStore((s) => s.log);
const [logLevel, setLogLevel] = useState("info");
const sensitive = (what: string, note: string) => {
if (!isOwner) {
alert(`仅 owner 可执行:${what}。当前角色 ${getStoredRole()}。`);
return;
}
log(`system.${what}`, "ok");
alert(`${note}(占位):后端 M10+ 提供真实执行。`);
};
return (
<div className="space-y-5">
<PageHeader title="系统设置" subtitle="环境变量 + 安全(密钥/JWT) + 存储 + 备份/恢复 + 重启/更新 + 日志级别" />
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<Surface className="space-y-2">
<SectionTitle icon="🔐" title="安全 & 密钥" ownerOnly />
<KeyValueRow label="JWT 密钥" value="••••••••" mono />
<KeyValueRow label="鉴权" value="JWT + RBAC + API key" />
<KeyValueRow label="审计" value="who/what/when/result" />
<div className="flex gap-2 pt-2">
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("rotateJwt", "轮换 JWT 密钥")}>轮换 JWT 密钥</Button>
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("revokeAllKeys", "吊销全部 API key")}>吊销全部 API key</Button>
</div>
</Surface>
<Surface className="space-y-2">
<SectionTitle icon="🗃" title="存储" />
<KeyValueRow label="默认" value="SQLite" />
<KeyValueRow label="可选增强" value="PG / pgvector(语义/向量路径)" />
<KeyValueRow label="单二进制 / 单端口" value=":8080 · 零跨进程 IPC" />
<div className="flex gap-2 pt-2">
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("backup", "备份数据")}>备份</Button>
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("restore", "恢复数据")}>恢复</Button>
</div>
</Surface>
<Surface className="space-y-2">
<SectionTitle icon="📦" title="环境变量" />
<KeyValueRow label="SERVER_HOST" value="0.0.0.0" mono />
<KeyValueRow label="SERVER_PORT" value="8080" mono />
<KeyValueRow label="AUTH_ADMIN_USER/PASS" value="••••••••" mono />
<div className="pt-2">
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("editEnv", "编辑环境变量")}>编辑环境变量</Button>
</div>
</Surface>
<Surface className="space-y-2">
<SectionTitle icon="🔄" title="运行时" />
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-[var(--mu-text-lo)]">日志级别</span>
<span className="flex gap-1.5">
{["debug", "info", "warn", "error"].map((lvl) => (
<button
key={lvl}
onClick={() => setLogLevel(lvl)}
className={`rounded-[var(--mu-radius)] px-2 py-1 text-xs ${
logLevel === lvl ? "bg-[var(--mu-accent)]/20 text-[var(--mu-accent-hi)]" : "bg-[var(--mu-surface-4)] text-[var(--mu-text-mid)]"
}`}
>
{lvl}
</button>
))}
</span>
</div>
<div className="flex flex-wrap gap-2 pt-2">
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("restart", "重启服务")}>重启服务</Button>
<Button size="sm" variant="outline" disabled={!isOwner} onClick={() => sensitive("update", "检查/安装更新")}>检查更新</Button>
</div>
</Surface>
</div>
{!isOwner ? <Notice tone="warn">安全 / 密钥 / 系统设置类敏感操作仅 owner 可执行;当前角色 {getStoredRole()}。</Notice> : null}
</div>
);
}
function SectionTitle({ icon, title, ownerOnly = false }: { icon: string; title: string; ownerOnly?: boolean }): React.ReactNode {
return (
<h3 className="flex items-center gap-2 text-sm font-semibold">
<span>{icon}</span>
<span>{title}</span>
{ownerOnly ? <Tag tone="warn">owner</Tag> : null}
</h3>
);
}