本文说明 1flowbase 后台 console 路由以后应该怎么注册。结论先说清楚:
- 导航和可见性由后端
Console Route Registry / Surface Registry统一决定。 - 前端不再维护一份本地导航菜单,也不根据权限自行推导菜单。
- API 权限仍由后端真实 route、service 或 action 保护;console registry 只决定“当前用户应该看到哪些入口”。
RouteDefinition、NavigationItem、PermissionBinding是三件事,不要合并成一个对象。
三个对象
RouteDefinition
描述一个 console surface 的技术路由。
| 字段 | 含义 |
|---|---|
route_id |
全局唯一 route id。系统内置可用短名;HostExtension 必须使用扩展拥有的命名空间。 |
surface_key |
surface 业务 key。settings 页面会从 /settings/<segment> 推导 section key,推导失败时才使用它。 |
path |
前端可访问路径,例如 /settings/model-providers。 |
surface_kind |
system、dynamic_page 或 host_extension。 |
NavigationItem
描述这个 route 放到哪个导航槽位、用什么文案、排序如何。
| 字段 | 含义 |
|---|---|
item_id |
全局唯一 navigation item id。 |
route_id |
指向一个 RouteDefinition.route_id。 |
parent_item_id |
父导航项。settings 子项当前使用 settings。顶层 primary/secondary 为 null。 |
label_key |
i18n key,例如 auto.model_providers。 |
navigation_slot |
primary、secondary 或 settings。 |
order |
同一 slot 下排序,数值越小越靠前。 |
PermissionBinding
描述当前用户是否能看到这个 route。
| 字段 | 含义 |
|---|---|
binding_id |
全局唯一 binding id。常用 <route_id>.access。 |
route_id |
指向一个 RouteDefinition.route_id。 |
requirement |
authenticated 或 any_permission。 |
permission_codes |
any_permission 时必须非空;authenticated 时必须为空。 |
裁剪规则:
authenticated:登录用户可见。any_permission:用户拥有permission_codes中任一权限即可见。- 子项只有在自身 route 可见且父项可见时才返回。
GET /api/console/navigation只返回当前用户可访问的 registry 结果。
注册入口怎么选
1. 系统内置 console 入口
适用于 1flowbase 自带页面,例如工作台、模板、设置里的模型供应商、用户管理等。
后端入口:
api/crates/access-control/src/navigation.rs
新增或调整 SYSTEM_CONSOLE_ROUTES 中的 ConsoleRouteSpec。
示例:
ConsoleRouteSpec {
route_id: "model-providers",
surface_key: "model-providers",
path: "/settings/model-providers",
label_key: "auto.model_providers",
navigation_slot: ConsoleNavigationSlot::Settings,
parent_item_id: Some("settings"),
order: 900,
permission_codes: STATE_MODEL_PERMISSIONS,
requirement: ConsolePermissionRequirement::AnyPermission,
}
如果这是一个新的 settings 内置页面,还要补前端页面内容:
web/app/src/features/settings/lib/settings-sections.tsx- 增加
SettingsSectionKey。 - 增加 section 定义,用于已知内置 section 的本地页面 body。
- 增加
web/app/src/features/settings/pages/settings-page/SettingsSectionBody.tsx- 把新的 section key 映射到真实 React 页面或组件。
- i18n
- 补
label_key对应的 settings/appShell 文案。
- 补
注意:前端这里是页面内容映射,不是导航真值。导航能不能显示仍以后端 /api/console/navigation 为准。
2. HostExtension 设置入口
适用于可信 HostExtension 想在后台 settings 下贡献一个受控入口。
manifest 入口:
console_surfaces:
route_definitions:
- route_id: file-security.settings
surface_key: file-security.settings
path: /settings/file-security
surface_kind: host_extension
navigation_items:
- item_id: file-security.settings
route_id: file-security.settings
parent_item_id: settings
label_key: auto.file_security
navigation_slot: settings
order: 1300
permission_bindings:
- binding_id: file-security.settings.access
route_id: file-security.settings
requirement: any_permission
permission_codes:
- plugin_config.view.all
当前 validator 的硬规则:
route_id、item_id、binding_id必须属于 extension 命名空间:- 等于
extension_id,或 - 以
<extension_id>.开头。
- 等于
path必须以/settings/开头。surface_kind必须是host_extension。navigation_slot必须是settings。parent_item_id必须是settings,或属于 extension 自己的命名空间。- 同一个 manifest 内的
route_id、path、item_id、binding_id不能重复。 navigation_items[].route_id和permission_bindings[].route_id必须引用同一个 manifest 里的route_definitions[].route_id。requirement: any_permission时permission_codes必须非空。requirement: authenticated时permission_codes必须为空。
启动注册规则:
- HostExtension manifest 解析和 validator 先执行。
- Console surface 注册进入
ConsoleSurfaceRegistry。 - 如果与内置或已注册贡献发生
route_id、path、item_id、binding_id冲突,注册失败。 - loader 会把该 HostExtension 标记为
LoadFailed,不会静默忽略。
当前限制:
- 这只注册导航入口和 route surface。
- 远程前端 bundle 加载不在本阶段。
- 如果没有宿主侧页面 renderer,未知
/settings/<sectionKey>可以出现在导航里,但页面 body 不会自动变成插件前端。
3. 用户动态页面
surface_kind: dynamic_page 已经是 registry contract 的一部分,但当前还没有动态路由 CRUD 和持久化 records。
后续实现动态页面时,应遵守同一模型:
- 动态页面 records 落到后端,后端生成
RouteDefinition、NavigationItem、PermissionBinding。 - 前端仍只消费
/api/console/navigation。 - 不在前端本地拼菜单、补权限或做字段别名兼容。
如果实现需要数据库 migration、历史数据迁移或页面内容 runtime,需要先回到 issue/design gate。
API 和前端消费链
后端接口:
GET /api/console/navigation
返回结构:
{
"route_definitions": [],
"navigation_items": [],
"permission_bindings": []
}
前端消费链:
web/packages/api-client/src/console-navigation.ts
-> web/app/src/features/settings/api/console-navigation.ts
-> app-shell Navigation / SettingsChromeMenu / SettingsPage
当前前端状态机:
loading:registry 还没返回,显示加载状态。error:registry 请求失败,显示错误状态。ready:registry 返回后,才按后端裁剪结果渲染导航。
只有 ready 后返回的空数组,才能表示“后端裁剪后当前用户没有可见入口”。不要把 loading/error 映射成空菜单或空页面。
APP_ROUTES 还要不要改
web/app/src/routes/route-config.ts 现在不是导航菜单真值层。它保留的是:
- shell route 的选中态 matcher。
RouteGuard所需的 route id。- 登录态 guard。
一般规则:
- 新增 settings 子入口时,优先只改后端 registry。
- settings 已有
/settings/$sectionKey壳路由,很多 settings 子入口不需要新增 TanStack route。 - 新增一个真正的顶层 shell 页面、详情页或需要特殊 selected matcher 的路径时,才改
APP_ROUTES和 router。 - 即使改了
APP_ROUTES,导航是否展示仍以后端 registry 为准。
选择 order 的建议
系统内置 settings 当前大致按 100 递增:
100 docs
200 api-key-authentication
300 auth-center
400 system-runtime
500 host-infrastructure
600 memory-observation
700 files
800 data-models
900 model-providers
1000 mcp-management
1100 members
1200 roles
HostExtension 建议从 1300 之后选择自己的区间,并为同一个 extension 留出间隔。
注册后必须补的测试
后端:
cargo test -p access-control navigation_tests
cargo test -p plugin-framework host_extension_contribution_tests
cargo test -p api-server console_navigation_route
cargo test -p api-server host_extension_loader_tests
前端:
pnpm --dir web/app exec ../../scripts/node/cli/exec-with-real-node.sh ../../scripts/node/cli/run-frontend-vitest.js run \
src/features/settings/api/_tests/console-navigation-api.test.ts \
src/app-shell/_tests/navigation.test.tsx \
src/app-shell/_tests/settings-chrome-menu.test.tsx \
src/features/settings/_tests/settings-page.test.tsx \
src/routes/_tests/section-shell-routing.test.tsx
如果改了 app shell、settings 页面或样式边界:
pnpm --dir web/app build
node scripts/node/check-style-boundary/cli.js page page.settings
node scripts/node/tooling.js i18n-hygiene --max-findings 30
常见错误
- 把
RouteDefinition、NavigationItem、PermissionBinding合成一个对象。 - 前端根据
me.permissions自己推导导航。 - registry 请求失败时回退到本地静态菜单。
- registry loading/error 时显示普通空态。
- 为展示文案新增接口字段别名。
- HostExtension 使用不属于自己的 id。
any_permission没有 permission codes。- 只注册了 navigation item,没有对应 route definition 或 permission binding。
- 以为注册
console_surfaces就自动获得远程前端 bundle。
最小 checklist
新增入口前先确认:
- 我是在注册系统内置、HostExtension,还是未来动态页面?
route_id、item_id、binding_id是否全局唯一?path是否已有壳路由或动态壳路由承接?label_key是否有 i18n 文案?PermissionBinding是否表达了后端真实权限语义?- 页面 API 本身是否仍有后端权限保护?
- registry loading/error 是否有显式 UI?
- 定向后端和前端测试是否覆盖了裁剪、排序、错误态和不回退本地静态菜单?