// 系统管理分区面板(重设计方案 阶段②):普通用户界面不再暴露的系统能力 // 全部收进这里,由 AdminZone 以分区注册表呈现。数据入口与原弹窗完全一致 //(api.* 不变),只换呈现位置;插件面板/声明工作区经 UIRenderer 内联渲染。 import { useEffect, useState } from 'react' import { api } from '../api' import { store, useStore } from '../store' import type { AgentProfile } from '../contracts' import { Btn, EmptyState, Icon, SettingRow, SettingsGroup, UIRenderer, type DocBridge } from '@ui' import { SettingInput, flatten, unflatten } from './SettingsModal' import type { SettingDesc } from '../contracts' import { docBridge } from './Workbench' import { randomPassword } from '../passwords' // ---- 用户管理(原 AdminUsersModal 主体,R2 管理员发放制 + 2026-10-13 恢复 Key)---- interface AdminUserRow { owner: string; name: string; isAdmin: boolean; mustChangePassword: boolean; hasPassword: boolean; passwordSetAt?: string; createdAt: string } interface LoginKeyRow { id: string; owner: string; purpose: string; status: string; expiresAt: string; createdAt: string; createdBy?: string } /** Key 状态徽标:复用既有 chip 体系,不新增颜色。 */ function keyStatusChip(status: string) { if (status === 'unused') return 待使用 if (status === 'consumed') return 已使用 if (status === 'expired') return 已过期 return 已撤销 } export function UsersPanel() { const me = useStore(s => s.me) const [users, setUsers] = useState(null) const [err, setErr] = useState('') const [notice, setNotice] = useState('') const [owner, setOwner] = useState('') const [name, setName] = useState('') const [pw, setPw] = useState('') const [asAdmin, setAsAdmin] = useState(false) const [busy, setBusy] = useState(false) // 登录 Key 面板:keyFor 打开指定用户的签发/状态视图;issued 明文仅签发响应持有一次。 const [keyFor, setKeyFor] = useState(null) const [issued, setIssued] = useState<{ key: string; expiresAt: string } | null>(null) const [keys, setKeys] = useState(null) const refresh = async () => { try { setUsers(await api.listAdminUsers()) } catch (e) { setErr(String(e)) } } const refreshKeys = async (owner: string) => { try { setKeys(await api.adminListLoginKeys(owner)) } catch (e) { setErr(String(e)) } } useEffect(() => { refresh() }, []) const openKeyPanel = async (owner: string) => { setErr(''); setNotice(''); setIssued(null); setKeyFor(owner); setKeys(null) await refreshKeys(owner) } const create = async () => { setErr(''); setNotice('') if (pw.length < 8) { setErr('初始密码至少 8 位'); return } setBusy(true) try { await api.createAdminUser(owner.trim(), name.trim(), pw, asAdmin) setNotice(`已创建 ${owner.trim()}:首次登录须改密`) setOwner(''); setName(''); setPw('') await refresh() } catch (e) { setErr(String(e).includes('409') ? '用户名已存在' : String(e)) } finally { setBusy(false) } } const issue = async () => { if (!keyFor) return setErr(''); setNotice(''); setBusy(true) try { const out = await api.adminIssueLoginKey(keyFor) setIssued({ key: out.key, expiresAt: out.expiresAt }) await refreshKeys(keyFor) } catch (e) { setErr(String(e)) } finally { setBusy(false) } } const copyKey = async () => { if (!issued) return try { await navigator.clipboard.writeText(issued.key); setNotice('已复制到剪贴板,请安全交付给该用户') } catch { setErr('复制失败,请手动选择复制') } } const revoke = async (id: string) => { if (!keyFor) return setErr(''); setBusy(true) try { await api.adminRevokeLoginKey(keyFor, id) await refreshKeys(keyFor) } catch (e) { setErr(String(e)) } finally { setBusy(false) } } return (
{me?.isAdmin && (
setOwner(e.target.value)} /> setName(e.target.value)} />
setPw(e.target.value)} />

创建后该用户首次登录必须修改此初始密码;忘记密码时为其生成一次性登录 Key。

)} {notice &&
{notice}
} {err &&
{err}
} {keyFor ? (
{keyFor} 的一次性登录 Key(忘记密码恢复)
{issued && (
e.currentTarget.select()} />
)} {issued &&

明文仅此一次显示,关闭后不可再取;单次有效,{new Date(issued.expiresAt).toLocaleString()} 过期。请经安全渠道交付本人。

} {keys !== null && keys.length > 0 && ( {keys.map(k => ( ))}
签发时间用途状态过期时间
{new Date(k.createdAt).toLocaleString()} {k.purpose === 'recover' ? '恢复' : '激活'} {keyStatusChip(k.status)} {new Date(k.expiresAt).toLocaleString()} {k.status === 'unused' && }
)}
) : ( {(users ?? []).map(u => ( ))} {users !== null && users.length === 0 && }
用户名显示名角色状态密码设置于
{u.owner} {u.name || '—'} {u.isAdmin ? ADMIN : USER} {u.mustChangePassword ? 待改密 : u.hasPassword ? '正常' : '未设密码'} {u.passwordSetAt ? new Date(u.passwordSetAt).toLocaleString() : '—'} {me?.isAdmin && ( )}
暂无账号
)}
) } // ---- 智能体画像(原 SettingsModal ProfilesSection:数据化导入导出 + 编辑)---- export function ProfilesSection({ profiles }: { profiles: AgentProfile[] }) { const [editing, setEditing] = useState(null) const [text, setText] = useState('') return (
{ setEditing(newProfile()); setText('') }}>新建画像
{profiles.map(p => ( {p.name} {p.id}{p.builtin ? ' · 内置' : ''}} desc={p.description}>
{ const blob = new Blob([JSON.stringify(p, null, 2)], { type: 'application/json' }) const a = document.createElement('a') a.href = URL.createObjectURL(blob); a.download = `${p.id}.json`; a.click() }}>导出 {!p.builtin && { setEditing(p); setText(p.systemPrompt) }}>编辑} {!p.builtin && { await api.deleteProfile(p.id); store.refreshProfiles() }}>删除}
))} {editing && (