import { useState } from "react"; import { useAsyncData } from "../../lib/useAsyncData"; import { adminListUsers, adminCreateUser, adminIssueApiKey, adminRevokeApiKey, getStoredRole, } from "../../lib/api"; import type { AdminUserRecord, OwnerProfile } from "../../lib/types"; import { useAuditStore } from "../audit"; import { AdminTable, Notice, PageHeader, type AdminColumn } from "../components"; import { Button, Surface, Tag } from "../../components/primitives"; const roleTone: Record = { owner: "accent", admin: "ok", viewer: "neutral", }; const initialProfile: OwnerProfile = { id: "owner", displayName: "叶酱", defaultPersona: "cyrene", identityFile: "configs/users/owner_identity.md", notes: "主人。关系:陪伴对象(昔涟称「开拓者」)。群聊/陌生人使用隔离上下文、记忆只读不写、不泄露主人敏感信息。", accounts: [], }; export function UsersModule(): React.ReactNode { const { data, error, loading, reload } = useAsyncData(adminListUsers, []); const isOwner = getStoredRole() === "owner"; const log = useAuditStore((s) => s.log); const [profile, setProfile] = useState(initialProfile); const [issuedKey, setIssuedKey] = useState(null); const [role, setRole] = useState("owner"); const [busy, setBusy] = useState(false); const [fail, setFail] = useState(null); const saveProfile = () => { log("owner.profile.edit", "ok"); setProfile({ ...profile }); }; const createUser = () => { setBusy(true); setFail(null); adminCreateUser({ username: "new-user", role: "viewer" }) .then(() => { log("user.create", "ok"); reload(); }) .catch((err: unknown) => { log("user.create", "error"); setFail(err instanceof Error ? err.message : "新增用户失败"); }) .finally(() => setBusy(false)); }; const issueKey = () => { setBusy(true); setFail(null); adminIssueApiKey(role) .then((rg) => { log("apikey.generate", "ok"); setIssuedKey(rg.key); reload(); }) .catch((err: unknown) => { log("apikey.generate", "error"); setFail(err instanceof Error ? err.message : "签发密钥失败"); }) .finally(() => setBusy(false)); }; const revokeKey = (id: string) => { setBusy(true); setFail(null); adminRevokeApiKey(id) .then(() => { log("apikey.revoke", "ok"); reload(); }) .catch((err: unknown) => { log("apikey.revoke", "error"); setFail(err instanceof Error ? err.message : "撤销密钥失败"); }) .finally(() => setBusy(false)); }; const columns: AdminColumn[] = [ { key: "username", title: "用户", render: (u) => {u.username} }, { key: "role", title: "角色", render: (u) => {u.role} }, { key: "created", title: "创建时间", render: (u) => {u.createdAt ?? "—"} }, { key: "id", title: "ID", render: (u) => {u.id} }, { key: "actions", title: "操作", render: (u) => u.username === "apikey" && isOwner ? ( ) : ( — ), }, ]; return (
新增用户 } /> {fail ? {fail} : null}

用户列表 · 角色(RBAC)

{error && !data ? (
后端未提供 /api/v1/admin/users,以下为示例壳(M10 提供真实数据)。{error} u.id} />
) : ( u.id} /> )}

主人档案编辑({isOwner ? "可编辑" : "只读 · 需 owner"})

对应 configs/users/owner.yaml:各渠道确认的主人账号白名单(供策略引擎比对 actor 元数据)+ 关系备注。身份说明书(给 LLM 的人格知识)见 identityFile。

setProfile({ ...profile, id: v })} disabled={!isOwner} /> setProfile({ ...profile, displayName: v })} disabled={!isOwner} /> setProfile({ ...profile, defaultPersona: v })} disabled={!isOwner} /> setProfile({ ...profile, identityFile: v })} disabled={!isOwner} />
关系备注