// 系统管理分区面板(重设计方案 阶段②):普通用户界面不再暴露的系统能力 // 全部收进这里,由 AdminZone 以分区注册表呈现。数据入口与原弹窗完全一致 //(api.* 不变),只换呈现位置;插件面板/声明工作区经 UIRenderer 内联渲染。 import { useEffect, useState } from 'react' import { api, type RecoveryProposal } 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 { recoveryProposalErrorMessage, recoveryProposalHint, recoveryStatusLabel } from '../recovery' // ---- 用户管理(原 AdminUsersModal 主体,R2 管理员发放制):建号仅创建待初始化 // 账号(HTTP 契约不含密码与管理员位),初始凭证由服务器本地 CLI 发放激活 Key; // 恢复走属主确认制提案:管理员提案 → 用户在「设置 → 账号」本人确认 → 一次性 Key // 只在属主会话内出现(直接签发 Key 的 HTTP 入口已被 410 替换)。---- interface AdminUserRow { userId: string; owner: string; name: string; isAdmin: boolean; mustChangePassword: boolean; hasPassword: boolean; passwordSetAt?: string; createdAt: string; stateVersion: number } 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 [busy, setBusy] = useState(false) // 恢复提案面板:propFor 打开指定用户的提案视图(创建 / 状态 / 撤销)。 const [propFor, setPropFor] = useState(null) const [proposals, setProposals] = useState(null) const refresh = async () => { try { setUsers(await api.listAdminUsers()) } catch (e) { setErr(String(e)) } } const refreshProposals = async (userId: string) => { try { setProposals(await api.listAdminRecoveryProposals(userId)) } catch (e) { setErr(String(e)) } } useEffect(() => { refresh() }, []) const create = async () => { setErr(''); setNotice('') setBusy(true) try { const out = await api.createAdminUser(owner.trim(), name.trim()) setNotice(`已创建 ${out.owner}(待初始化):请在服务器执行 user initialize-account 发放激活 Key`) setOwner(''); setName('') await refresh() } catch (e) { setErr(String(e).includes('409') ? '用户名已存在' : String(e)) } finally { setBusy(false) } } const openProposals = async (u: AdminUserRow) => { setErr(''); setNotice(''); setPropFor(u); setProposals(null) await refreshProposals(u.userId) } const propose = async () => { if (!propFor) return setErr(''); setNotice(''); setBusy(true) try { await api.createAdminRecoveryProposal(propFor.userId, propFor.stateVersion, crypto.randomUUID()) setNotice(`已为 ${propFor.owner} 建立恢复提案:请该用户在「设置 → 账号」由本人确认;确认后一次性 Key 只在其会话内出现`) await refreshProposals(propFor.userId) } catch (e) { setErr(recoveryProposalErrorMessage(String(e))) } finally { setBusy(false) } } const revokeProposal = async (p: RecoveryProposal) => { if (!propFor) return setErr(''); setBusy(true) try { await api.adminRevokeRecoveryProposal(propFor.userId, p.id, p.version) await refreshProposals(propFor.userId) } catch (e) { setErr(String(e)) } finally { setBusy(false) } } return (
{me?.isAdmin && (
setOwner(e.target.value)} /> setName(e.target.value)} />

账号创建后处于待初始化状态:在服务器执行 user initialize-account 发放一次性激活 Key,用户经兑换入口设置密码后登录

)} {notice &&
{notice}
} {err &&
{err}
} {propFor ? (
{propFor.owner} 的恢复提案(属主确认制:管理员提案,本人确认后获一次性 Key)

前提:该用户当前在任一设备保持完整密码登录(会话证明)。已锁死且无会话的账号无法在线恢复,请走服务器本地处置。

{proposals !== null && proposals.length > 0 && ( {proposals.map(p => ( ))}
提案状态到期时间发起人
{p.id} {recoveryStatusLabel[p.status]}{recoveryProposalHint(p.status) && · {recoveryProposalHint(p.status)}} {new Date(p.expiresAt).toLocaleString()} {p.createdBy} {(p.status === 'pending' || p.status === 'confirmed') && ( )}
)} {proposals !== null && proposals.length === 0 &&

该用户当前没有恢复提案。

}
) : ( {(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 && (