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

@@ -0,0 +1,79 @@
import { useState } from "react";
import { useAsyncData } from "../../lib/useAsyncData";
import { adminListSessions } from "../../lib/api";
import type { SessionRecord } from "../../lib/types";
import { useAutoReload } from "../hooks";
import { useAuditStore } from "../audit";
import { AdminTable, Notice, PageHeader, type AdminColumn } from "../components";
import { Button, Surface, Tag } from "../../components/primitives";
const columns: AdminColumn<SessionRecord>[] = [
{ key: "platform", title: "渠道", render: (s) => <Tag tone="accent">{s.platform}</Tag> },
{ key: "actor", title: "actor 元数据", render: (s) => <Tag tone="neutral">{s.actorRole}</Tag> },
{ key: "flags", title: "标记", render: (s) => (
<span className="flex flex-wrap gap-1">
{s.owner ? <Tag tone="ok">主人</Tag> : null}
{s.isGroup ? <Tag tone="warn">群聊</Tag> : null}
<Tag tone={s.memWritable ? "ok" : "neutral"}>{s.memWritable ? "记忆可写" : "记忆只读"}</Tag>
</span>
) },
{ key: "channel", title: "channelId", render: (s) => <span className="font-mono text-xs text-[var(--mu-text-lo)]">{s.channelId}</span> },
{ key: "session", title: "sessionId", render: (s) => <span className="font-mono text-xs">{s.sessionId}</span> },
];
export function SessionsModule(): React.ReactNode {
const { data, loading, error, reload } = useAsyncData(adminListSessions, []);
useAutoReload(reload, 10000);
const [query, setQuery] = useState("");
const log = useAuditStore((s) => s.log);
const filtered = (data ?? []).filter((s) => {
if (!query.trim()) return true;
const q = query.toLowerCase();
return (
s.platform.toLowerCase().includes(q) ||
s.sessionId.toLowerCase().includes(q) ||
s.channelId.toLowerCase().includes(q) ||
s.actorRole.toLowerCase().includes(q)
);
});
return (
<div className="space-y-5">
<PageHeader
title="会话管理"
subtitle="GET /api/v1/admin/sessions · 渠道/模式/角色/分页 · 身份识别靠 channel 层 actor 元数据"
actions={
<Button
variant="soft"
onClick={() => {
log("session.export", "ok");
alert("导出会话(占位):后端 M10+ 提供导出。");
}}
>
导出
</Button>
}
/>
<Surface className="space-y-3">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="按渠道 / sessionId / channelId / actor 过滤…"
className="w-full rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
/>
<AdminTable
columns={columns}
rows={loading ? null : filtered}
loading={loading}
error={error}
emptyTitle="无会话数据"
rowKey={(s) => s.sessionId}
/>
</Surface>
<Notice>会话详情 / 上下文编辑 / 删除为后端 M10+ 提供,当前仅列表只读。身份判定永远由 actor 元数据 + 白名单 + 策略引擎完成,不依赖提示词。</Notice>
</div>
);
}