diff --git a/src/api.ts b/src/api.ts index f53c683..4b323bb 100644 --- a/src/api.ts +++ b/src/api.ts @@ -9,6 +9,11 @@ import type { const BASE = '' +/** 管理区声明分区(WP3-I3/KTD4,backend 专属端点类型):编译插件经 + * Registry.AddAdminZone 声明;入口不做前端权限合并,区内数据能力由组件 + * 自身命令面 fail-closed。 */ +export interface AdminZoneSection { id: string; title: string; icon?: string; desc?: string; order?: number } + async function json(res: Response): Promise { if (!res.ok) { const text = await res.text() @@ -113,6 +118,7 @@ export const api = { listTools: () => auth.fetch(`${BASE}/api/tools`).then(r => r.json()), listPlugins: () => auth.fetch(`${BASE}/api/plugins`).then(r => r.json()), + adminZoneSections: () => auth.fetch(`${BASE}/api/admin-zone/sections`).then(r => r.json()) as Promise, reloadPlugins: () => auth.fetch(`${BASE}/api/plugins/reload`, { method: 'POST' }).then(r => r.json()), listCommands: () => auth.fetch(`${BASE}/api/commands`).then(r => r.json()) as Promise<{ name?: string; id?: string; title?: string; description?: string; danger?: string; enabled?: boolean }[]>, setSessionNote: (id: string, sessionNote: string) => auth.fetch(`${BASE}/api/conversations/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionNote }) }).then(r => { if (!r.ok) throw new Error(`${r.status}`) }), diff --git a/src/components/AdminZone.tsx b/src/components/AdminZone.tsx index bc83811..a5f50a4 100644 --- a/src/components/AdminZone.tsx +++ b/src/components/AdminZone.tsx @@ -1,10 +1,15 @@ -// 系统管理(重设计方案 阶段②):管理员专属的全屏独立视图(带返回,非弹窗)。 +// 系统管理(重设计方案 阶段②):全屏独立视图(带返回,非弹窗)。 // 用户决策:系统管理 = 全屏独立视图;普通用户界面不暴露任何系统能力。 // 分区注册表:新增管理页只改 SECTIONS,不改 AdminZone 本体(声明式注册)。 +// WP3-I3/KTD4:编译插件可经 Registry.AddAdminZone 声明展示分区(服务端 +// /api/admin-zone/sections 下发);声明分区对全部认证主体开放,区内数据 +// 能力由分区组件自身的命令面 fail-closed——前端只消费、不合并角色。 import { useState } from 'react' import { store, useStore } from '../store' -import { Icon, type IconName } from '@ui' +import { EmptyState, Icon, type IconName } from '@ui' import { UsersPanel, ProfilesSection, PluginsSection, PluginWorkspacesPanel } from './adminSections' +import { RdmAuditWorkspace } from './RdmAuditWorkspace' +import type { AdminZoneSection } from '../api' interface AdminSectionDef { key: string @@ -27,12 +32,47 @@ const SECTIONS: AdminSectionDef[] = [ { key: 'workspaces', label: '插件工作区', icon: 'grid', desc: '插件声明面板与服务端声明工作区', render: () => }, ] +/** 声明分区 → 内置分区组件(按声明 ID 映射;AdminZone 不硬编码领域逻辑)。 */ +const DECLARED_COMPONENTS: Record React.ReactNode> = { + 'rdm.audit': () => , +} + +/** icon 走基座 IconName 词表;声明携带未知图标时回退通用图标(不渲染空白)。 */ +const ICONS: IconName[] = ['plus', 'search', 'panel-left', 'inbox', 'settings', 'sun', 'moon', 'pin', 'archive', 'undo', + 'chevron-right', 'chevron-down', 'folder', 'message', 'package', 'bot', 'puzzle', 'grid', 'arrow-up', 'paperclip', + 'more', 'logout', 'key', 'user', 'users', 'shield', 'x', 'check', 'alert', 'send', 'sparkles', 'clock', 'list', + 'file', 'image', 'download', 'trash', 'eye', 'maximize', 'minimize'] +function declaredIcon(name?: string): IconName { + return (ICONS as string[]).includes(name ?? '') ? (name as IconName) : 'grid' +} + +function DeclaredSection({ section }: { section: AdminZoneSection }) { + const render = DECLARED_COMPONENTS[section.id] + if (!render) { + return 分区「{section.title}」由内置插件声明,但当前前端版本未携带其组件
(部署前端与二进制版本不匹配时会出现;升级前端即可)
+ } + return <>{render()} +} + export function AdminZone() { const me = useStore(s => s.me) + const declared = useStore(s => s.adminSections) const [sectionKey, setSectionKey] = useState('users') - // 双保险:入口已在菜单层门控(me?.isAdmin),此处再拦一次(纵深防御)。 - if (!me?.isAdmin) return null - const section = SECTIONS.find(s => s.key === sectionKey) ?? SECTIONS[0] + const isAdmin = !!me?.isAdmin + // 门控:基座系统分区仅 isAdmin(纵深防御与菜单层一致);声明分区对全部 + // 认证主体开放(区内能力由组件命令面判定)。两者皆无时不渲染。 + if (!isAdmin && declared.length === 0) return null + const sections: AdminSectionDef[] = [ + ...(isAdmin ? SECTIONS : []), + ...declared.map(s => ({ + key: `decl:${s.id}`, label: s.title, icon: declaredIcon(s.icon), + desc: s.desc || '插件声明的管理分区(区内数据能力由插件命令面判定)', + render: () => , + })), + ] + const fallback = sections[0]?.key ?? '' + const section = sections.find(s => s.key === sectionKey) ?? sections[0] + if (!section) return null return (
@@ -40,14 +80,14 @@ export function AdminZone() { 返回工作台 系统管理 - 仅管理员可见 · 变更即时生效 + {isAdmin ? '仅管理员可见' : '按插件声明开放'} · 变更即时生效 - {me.owner} + {me?.owner ?? ''}