Why: Approved redesign phase 2 (IA): normal users run projects entirely through conversation; system capabilities (user management, agent profiles, plugin management, plugin-declared workspaces) belong behind a dedicated admin entry, not in the user-facing sidebar or settings. The sidebar workspace strip is removed; artifacts stay reachable via the right-rail tab and conversation chips. Evidence: npx tsc -b clean; npm test -> 38 passed (design-guard ratchet still green). Full combined run recorded in the Dev task card. Impact: new AdminZone full-screen view with a declarative section registry (users/profiles/plugins/plugin-workspaces via UIRenderer); sections moved out of SettingsModal (now personal preferences only) and AdminUsersModal (now password change only); store ui.adminUsersOpen renamed adminZoneOpen; workspaceEntries narrowed by role so plugin entries no longer take Cmd1-9 for regular users; theme toggle moved into the account menu. Refs: Agent-System-Dev task feature/ui-tokens-v3 (phase 2).
67 lines
3.0 KiB
TypeScript
67 lines
3.0 KiB
TypeScript
// 系统管理(重设计方案 阶段②):管理员专属的全屏独立视图(带返回,非弹窗)。
|
|
// 用户决策:系统管理 = 全屏独立视图;普通用户界面不暴露任何系统能力。
|
|
// 分区注册表:新增管理页只改 SECTIONS,不改 AdminZone 本体(声明式注册)。
|
|
import { useState } from 'react'
|
|
import { store, useStore } from '../store'
|
|
import { Icon, type IconName } from '@ui'
|
|
import { UsersPanel, ProfilesSection, PluginsSection, PluginWorkspacesPanel } from './adminSections'
|
|
|
|
interface AdminSectionDef {
|
|
key: string
|
|
label: string
|
|
icon: IconName
|
|
desc: string
|
|
/** 懒渲染:进入分区才挂载(每个面板自带数据加载)。 */
|
|
render: () => React.ReactNode
|
|
}
|
|
|
|
function ProfilesPanelWrapper() {
|
|
const profiles = useStore(s => s.profiles)
|
|
return <ProfilesSection profiles={profiles} />
|
|
}
|
|
|
|
const SECTIONS: AdminSectionDef[] = [
|
|
{ key: 'users', label: '用户管理', icon: 'users', desc: '建号、发放初始密码、重置密码(管理员发放制)', render: () => <UsersPanel /> },
|
|
{ key: 'profiles', label: '智能体画像', icon: 'bot', desc: '系统级画像:系统提示、工具允许/确认/拒绝清单', render: () => <ProfilesPanelWrapper /> },
|
|
{ key: 'plugins', label: '插件管理', icon: 'puzzle', desc: '已装载插件清单与热重载(装卸不影响核心)', render: () => <PluginsSection /> },
|
|
{ key: 'workspaces', label: '插件工作区', icon: 'grid', desc: '插件声明面板与服务端声明工作区', render: () => <PluginWorkspacesPanel /> },
|
|
]
|
|
|
|
export function AdminZone() {
|
|
const me = useStore(s => s.me)
|
|
const [sectionKey, setSectionKey] = useState('users')
|
|
// 双保险:入口已在菜单层门控(me?.isAdmin),此处再拦一次(纵深防御)。
|
|
if (!me?.isAdmin) return null
|
|
const section = SECTIONS.find(s => s.key === sectionKey) ?? SECTIONS[0]
|
|
return (
|
|
<div className="az" role="dialog" aria-label="系统管理">
|
|
<div className="az-head">
|
|
<button className="btn ghost" onClick={() => store.setUI({ adminZoneOpen: false })}>
|
|
<Icon name="chevron-down" size={14} /> 返回工作台
|
|
</button>
|
|
<span className="az-title">系统管理</span>
|
|
<span className="az-sub">仅管理员可见 · 变更即时生效</span>
|
|
<span style={{ flex: 1 }} />
|
|
<span className="az-tenant">{me.owner}</span>
|
|
</div>
|
|
<div className="az-body">
|
|
<nav className="az-side">
|
|
{SECTIONS.map(s => (
|
|
<button key={s.key} className={`az-nav-item ${s.key === sectionKey ? 'on' : ''}`} onClick={() => setSectionKey(s.key)}>
|
|
<Icon name={s.icon} size={15} />
|
|
<span>{s.label}</span>
|
|
</button>
|
|
))}
|
|
</nav>
|
|
<main className="az-main">
|
|
<div className="az-section-head">
|
|
<h3>{section.label}</h3>
|
|
<p>{section.desc}</p>
|
|
</div>
|
|
{section.render()}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|