Skip to content

api-webui — WebUI 服务与声明式页面组件契约

包名: @aalis/api-webui
源码: packages/api-webui/src/index.ts
实现: @aalis/plugin-webui-server(+ @aalis/plugin-webui-client 提供前端)

概述

定义 WebUI 后台服务接口、声明式页面组件 schema、页面注册 helper。插件不需要懂 HTTP/React,只需在 apply(ctx) 中调用 useWebuiService(ctx).registerPage(page),由 webui-server 自动渲染并暴露 REST + WebSocket。

服务接口

ts
interface WebUIService {
  getPort(): number;
  getHost(): string;
  setClientDir?(dir: string): void;      // 允许替换前端
  // 页面注册(一般通过 `useWebuiService(ctx)` helper 间接调用)
  registerPage(page: WebuiPage, contextId: string): () => void;
  getPages(): Array<WebuiPage & { pluginName: string }>;
  unregisterByPlugin(contextId: string): void;
}

PluginModule 注入槽位(declaration merging)

本包向 core 的 PluginModule 合并注入以下字段(core 不读取,仅 webui 消费):

  • subsystem?: string / extends?: ExtendDeclaration —— 纯展示元数据;
  • actions?: Record<string, (ctx, args, caller?: UserIdentity) => Promise<unknown>> —— 插件 RPC 动作表,webui-server 经 POST /api/page-action/:plugin/:method 调起。 第三参 caller 为权限闸放行后的调用者身份(登录账户或单 token 模式的 webui:console),handler 可用它做业务级检查;忽略即向后兼容;
  • actionsMeta?: Record<string, { visibility?: CapabilityVisibility }> —— action 的默认可见性 ('public' / 'restricted')。未声明的 action 默认 restricted(默认拒绝),作者必须 显式标 visibility: 'public' 才放开;闸的 capability 形状为 action:<plugin>:<method>, 由 authority 数字等级闸裁决——authorize 比对用户 level >= minLevel(minLevel 由 risk/visibility 与 authorityOverrides 派生),owner(*)放行一切,deniedCapabilities 全局硬禁压过一切(见 api-authority)。
ts
export const actions: PluginModule['actions'] = {
  async getStats(ctx) { /* ... */ },
};
export const actionsMeta = { getStats: { visibility: 'public' } }; // 显式放开才对所有人开放

页面注册 helper

ts
import { useWebuiService } from '@aalis/api-webui';

export function apply(ctx: Context) {
  const webui = useWebuiService(ctx);
  webui.registerPage({ key: 'shell', label: 'Shell', content: [/* ... */] });
}

helper 内部先 ctx.getService('webui-server') 取服务;未就绪时自动 whenService 延迟。registerPage 返回 disposer,插件 ctx.dispose() 时自动取消注册。

声明式页面组件

ts
type WebuiComponent =
  | WebuiStatComponent       // 数字统计卡
  | WebuiTableComponent      // 表格 + 行内操作
  | WebuiFormComponent       // 配置表单(复用 ConfigSchema)
  | WebuiActionsComponent    // 按钮组
  | WebuiInfoComponent       // 键值面板
  | WebuiMarkdownComponent   // Markdown 内容
  | WebuiTabsComponent;      // 子标签页容器

每种组件都有:

  • source —— 拉数据的 action 方法名(不是 HTTP 路径;前端调 POST /api/page-action/:plugin/:method
  • save / method —— 提交动作的 action 方法名,同上
  • confirm —— 行内/按钮确认提示
  • danger —— 红色样式标记

失败约定:action 的业务失败返回 { ok: false, error: '原因' },HTTP 仍是 200——路由只把 handler 的抛错转成 5xx;前端 form / actions / table 三种组件都据此显示原因,返回其它任何值(含 undefined)视为成功;table 的非 danger / confirm 操作若返回不带 ok 的普通对象,会被当作详情弹窗内容展示,只想刷新表格就返回 undefined{ ok: true }

示例:表格 + 操作

ts
{
  type: 'table',
  label: '后台进程',
  source: 'listProcesses',
  columns: [
    { key: 'pid', label: 'PID' },
    { key: 'cmd', label: '命令' },
    { key: 'startedAt', label: '启动时间', render: 'date' },
  ],
  actions: [
    // 点击时把整行作为 args 传给 killProcess;失败让它返回 { ok: false, error: '进程已退出' }
    { label: '终止', method: 'killProcess', confirm: '确定?', danger: true },
  ],
  refresh: 5,      // 每 5 秒自动刷新(单位:秒)
}

示例:表单复用 ConfigSchema

ts
{
  type: 'form',
  label: '基础配置',
  source: 'getConfig',   // 返回表单初值对象
  save: 'saveConfig',    // 失败返回 { ok: false, error: '...' },前端显示原因
  schema: ctx.configSchema,
}

WebuiPage 完整结构

ts
interface WebuiPage {
  key: string;                       // URL 段
  label: string;
  icon?: string;
  order?: number;                    // 排序权重(越小越靠前,默认 99)
  content?: WebuiComponent[];        // 声明式页面内容(不提供则用客户端内置页面)
}

在插件 apply() 中注册:

ts
import { useWebuiService } from '@aalis/api-webui';

const PAGES: WebuiPage[] = [
  { key: 'shell', label: 'Shell', content: [/* ... */] },
];

export function apply(ctx: Context) {
  const webui = useWebuiService(ctx);
  for (const p of PAGES) webui.registerPage(p);
}

实现者

  • @aalis/plugin-webui-server — 后端服务 + 静态文件托管
  • @aalis/plugin-webui-client — 前端 React 应用(独立发布)

相关

  • ConfigSchema 来自 @aalis/schema-config(本包经 declaration merging 向其 SchemaField 注入 secret / dynamicOptions / allowCustom 等表单属性)
  • 事件 'tool:execute''token:usage' 都被 webui-server 转 WebSocket 推送给前端