OmniRoute 仪表盘导航重构实战:Monitoring 与 Costs 分区结构与源码实现解析
Monitoring & Costs(监控与成本)是 OmniRoute 网关仪表盘中最常被使用、也最容易因条目膨胀而变得难以导航的两类功能。本文基于 MONITORING_SECTIONS.md 记录的分区设计(对应 Group B / plan 16),系统梳理新版侧边栏的顶层分区顺序、独立出来的 Costs 一级分区、重组后的 Monitoring 三分组结构,并从仓库源码层面验证 Activity 与 Audit Log 的数据边界、HIGH_LEVEL_ACTIONS 白名单机制、旧路径重定向与 i18n 回退策略。读完你既能按新导航快速定位成本与监控功能,也能理解这套侧边栏定义的实现骨架,便于后续扩展自己的可见性/成本类模块。
高层面导航:分区顺序与侧边栏骨架
Group B 改造后,Dashboard 侧边栏的顶层分区(Section)按固定顺序排列,其定义全部收敛在 sections.ts 的 SIDEBAR_SECTIONS 中:
Home
Providers (OmniProxy 分区内,含 Endpoints、API Keys、Combos、Quota 等)
Analytics
Costs ← 新增一级分区(Group B, plan 16)
Monitoring ← 重组(Group B, plan 16)
Dev Tools
Agentic Features
Other Features
Configuration
Help
从源码结构看,每个分区对应 types.ts 中的 SidebarSectionId 联合类型(home | omni-proxy | analytics | costs | monitoring | devtools | agentic-features | other-features | configuration | help),分区可携带 visibility: "debug" 标记(如 Dev Tools)用于调试环境,也可声明 featureFlagKey(如 Radar 条目用 RADAR_ENABLED 做特性开关门控)。
顶层分区顺序是可通过设置调整的——sidebarVisibility.ts 提供了 applySectionOrder() / applyItemOrder() 与 sidebarSectionOrder / sidebarItemOrder 持久化键;同时内置 5 个预设(all / essentials / minimal / developer / admin),对应 SIDEBAR_PRESETS,例如 admin 预设会额外展开 costs-pricing、costs-budget、costs-quota-share、activity、audit、audit-mcp、audit-a2a 等运维条目,而 essentials 只保留核心路径。
Costs:独立的一级分区
成本类功能从原先嵌套在 Monitoring > Costs Parameters 的位置提升为独立一级分区,路由前缀统一为 /dashboard/costs/:
| 条目 | URL | 说明 |
|---|---|---|
| Overview(概览) | /dashboard/costs |
聚合成本看板(从 Analytics 迁入) |
| Pricing(定价) | /dashboard/costs/pricing |
按模型区分的价格表 |
| Budget(预算) | /dashboard/costs/budget |
预算阈值与告警 |
| Quota Sharing(配额共享) | /dashboard/costs/quota-share |
Quota Share 池与用量 |
| Plan Config(计划配置) | /dashboard/costs/quota-share/plans |
按 provider 的计划覆盖配置 |
Rationale(设计动机):Pricing、Budget、Quota Sharing 原本藏在
Monitoring > Costs Parameters,导致用户必须先进入可观测性工具才能触达成本管理。把它们提到一级分区,能让成本治理在不动用监控链路的前提下被直接发现。
在 sections.ts 中,成本类条目进一步细化为 COSTS_ITEMS(含 costs、costs-pricing、costs-budget、costs-free-tiers、free-provider-rankings 以及带 RADAR_ENABLED 特性的 radar),其中 costs-quota-share(指向 /dashboard/costs/quota-share)同时出现在 OmniProxy 分区,便于在配额管理语境中直达配额池。对应页面源码位于 costs/page.tsx/dashboard/costs/page.tsx)、costs/budget/page.tsx/dashboard/costs/budget/page.tsx)、costs/pricing/page.tsx/dashboard/costs/pricing/page.tsx) 与 costs/quota-share/page.tsx/dashboard/costs/quota-share/page.tsx)。
Monitoring:重组为「Activity + 三个分组」
重组后的 Monitoring 分区把 Activity 提到分组最顶部,其后跟随 3 个分组(Group):
Monitoring
├── Activity ← 时间线信息流(顶层条目)
├── Logs group
│ ├── Logs (all)
│ ├── Proxy Logs
│ └── Console Logs
├── Audit group
│ ├── Audit Log
│ ├── MCP Audit
│ └── A2A Audit
└── System group
├── Health
└── Runtime
在 sections.ts 中可看到该结构的精确表达:MONITORING_ITEMS 先放入 activity,随后依次展开 LOGS_GROUP、AUDIT_GROUP、SYSTEM_GROUP 三个 SidebarItemGroup(注意 getSectionItems() 会把 group 内的 items 摊平,用于隐藏项计算)。侧边栏图标强调色映射见 sidebarVisibility.ts 中的 SIDEBAR_ICON_ACCENTS(如 logs-activity: #60A5FA、costs: #FB923C、audit: #F43F5E)。
相对旧结构的变化对照
| 改造前 | 改造后 |
|---|---|
| Activity 是 Logs 内的一个 tab,渲染的就是 Audit Log | Activity 成为独立信息流(/dashboard/activity) |
| Monitoring 中带 Costs Parameters 分组 | 已迁至独立 Costs 分区 |
| 扁平列表:Logs、Activity(logs)、Audit、Health、Runtime、Pricing、Budget、Quota | 结构化 3 分组 + 独立 Costs 分区 |
Activity 与 Audit Log:两种语义、一个数据底座
改造后两者彻底区分。区分点可用下表快速记忆:
| 维度 | Activity(/dashboard/activity) |
Audit Log(/dashboard/audit) |
|---|---|---|
| 用途 | 面向用户的事件信息流(“最近发生了什么”) | 合规 / 安全审计日志 |
| 数据来源 | GET /api/compliance/audit-log?level=high |
GET /api/compliance/audit-log?level=all |
| 展示形式 | 时间线、按天分组、人类可读动词 + 图标 | 密集分页表格,50 条/页 |
| 筛选 | 事件类型分类 | 动作、严重级别、操作者、时间范围 |
| 导出 | 不支持 | JSON 导出 |
| 操作者筛选 | 不适用 | 支持按 actor 过滤 |
| 展示事件 | 仅高层级动作(白名单) | 全部审计事件 |
值得强调的是两者共享同一数据底座——Audit Log API audit-log/route.ts 通过 level 查询参数区分返回集合,Activity 只消费 level=high 子集,Audit Log 消费 level=all 全量。换句话说:Activity 是白名单过滤后的“人话时间线”,Audit Log 才是完整的事实记录,二者天然互补而非替代。
HIGH_LEVEL_ACTIONS:Activity 的「顶层动作白名单」
哪些事件会进入 Activity 时间线,由 highLevelActions.ts 中的 HIGH_LEVEL_ACTIONS 白名单唯一决定。白名单覆盖了以下类别(Action 字符串与仓库内 logAuditEvent() 实际发射名严格对齐,源码注释明确要求“不要用自造的干净名字”):
- Provider 增删改查/测试类:
provider.credentials.*(created / applied / updated / revoked / batch_revoked / batch_updated / bulk_created / bulk_imported / imported)以及provider.validation.ssrf_blocked - API Key 生命周期:同步令牌
sync.token.created/sync.token.revoked - 认证登录登出:
auth.login.success/auth.login.failed/auth.login.locked/auth.logout.success等 - 设置变更:
settings.update/settings.update_failed - 敏感操作:
service.reveal_api_key - Quota 池与计划变更(Group B 新增):
quota.pool.created/quota.pool.updated/quota.pool.deleted/quota.plan.updated/quota.store.driver_changed
名单之外的事件(例如细粒度的内部操作)只会出现在 Audit Log,不会污染 Activity 时间线。判定函数 isHighLevelAction(action) 底层用 Set 做常量级查找:
const SET: ReadonlySet<string> = new Set<string>(HIGH_LEVEL_ACTIONS);
export function isHighLevelAction(action: string): boolean {
return SET.has(action);
}
新增一个高层级动作的正确姿势
若要让某类事件进入 Activity 时间线,需要按以下步骤走代码路径(白名单是代码而非数据库配置,因此必须走 PR 流程):
- 打开 highLevelActions.ts,先全局
grep logAuditEvent确认上游事件发射名是否已存在; - 把真实 action 字符串追加进
HIGH_LEVEL_ACTIONS数组(严禁使用“美化过”的别名,否则列表与实际事件永远对不上); - 同步在 activityIcons.ts 的
ACTIVITY_ICONS中为该 action 注册图标与动词 i18n key,如:"quota.pool.created": { icon: "pie_chart", i18nKeyVerb: "quotaPoolCreated" },getActivityIcon()对未注册的动作会回退到通用info图标与genericEvent动词,保证渲染不崩溃; - 若上游发射名将来变化,须同时原子化更新发射点与白名单(源码注释明确强调这一点)。
Activity 页面的前端实现由 activity/page.tsx/dashboard/activity/page.tsx)(强制 dynamic 渲染)与其客户端组件 ActivityFeedClient 承担,时间线聚合逻辑在 src/lib/audit/timeline.ts。
兼容旧路径:/dashboard/logs/activity 永久重定向
为避免破坏既有书签与集成,旧路径 /dashboard/logs/activity 通过 logs/activity/page.tsx/dashboard/logs/activity/page.tsx) 内的 permanentRedirect() 以 HTTP 308 永久重定向至 /dashboard/activity:
import { permanentRedirect } from "next/navigation";
export default function LogsActivityRedirect() {
permanentRedirect("/dashboard/activity");
}
同时,旧侧边栏 ID logs-activity 被保留在 HIDEABLE_SIDEBAR_ITEM_IDS(见 types.ts)但移出 SIDEBAR_DEFINITIONS(sections.ts)。这样做的目的是兼容用户侧已持久化的隐藏项/排序配置(hiddenSidebarItems、sidebarItemOrder 等设置键,常量定义见 sidebarVisibility.ts 的 HIDDEN_SIDEBAR_ITEMS_SETTING_KEY 等),避免用户旧预设中引用的旧 ID 在规范化时被当作非法项丢弃——normalizeHiddenSidebarItems() 只会保留白名单内 ID,若从 HIDEABLE_SIDEBAR_ITEM_IDS 中删掉 logs-activity,老配置就会被静默清掉。
国际化:命名空间与多语言回退
Group B 新增的所有 UI 文案都走 next-intl,新增命名空间如下:
| 命名空间 key | 覆盖内容 |
|---|---|
sidebar.costsSection |
Costs 分区标签 |
sidebar.activity |
Activity 侧边栏条目 |
sidebar.logsGroup |
Logs 分组标签 |
sidebar.systemGroup |
System 分组标签 |
sidebar.costsOverview |
Costs 概览条目 |
activity.* |
Activity 页面全部文案(标题、动词、筛选、空状态) |
i18n 的真实性语言(source-of-truth locales)为 pt-BR 与 en,其余 41 个语言通过 next-intl 的 fallback 机制回退到英文(配置位于 src/i18n/config.ts)。前端组件侧的 fallback 也有一层兜底:sections.ts 中每条 SidebarItemDefinition 均带 labelFallback / subtitleFallback,用于翻译缺失时直接展示字面量,避免出现空标签。
关键实现文件速查
- MONITORING_SECTIONS.md —— 本文依据的分区设计文档(Group B / plan 16)
- src/shared/constants/sidebarVisibility/sections.ts ——
SIDEBAR_SECTIONS分区/分组/条目定义,侧边栏结构的唯一真源 - src/shared/constants/sidebarVisibility/types.ts ——
SidebarItemId、SidebarSectionId、HIDEABLE_SIDEBAR_ITEM_IDS等类型契约 - src/shared/constants/sidebarVisibility.ts —— 图标强调色、预设集、顺序/隐藏设置与运行时项解析(
resolveRuntimeSidebarSections) - src/lib/audit/highLevelActions.ts —— Activity 顶层动作白名单
- src/lib/audit/activityIcons.ts —— 动作图标与人类可读动词映射
- src/app/api/compliance/audit-log/route.ts —— Activity 与 Audit Log 的共同数据 API(
level=high/level=all) - src/app/(dashboard)/dashboard/logs/activity/page.tsx/dashboard/logs/activity/page.tsx) —— 旧路径 308 重定向入口
小结
本次重构的本质,是把「成本治理」与「可观测性」两类心智模型解耦:Costs 独立成区让预算与配额管理前置可及,Monitoring 以 Activity 时间线打头并收敛为 Logs / Audit / System 三组,使安全审计、日志排障、健康观测各归其位。Activity 与 Audit Log 共用一套审计数据底座却呈现两种视图,靠 HIGH_LEVEL_ACTIONS 白名单与 level 参数完成取舍,兼顾“易读”与“完整”。对开发者而言,若要扩展监控或成本模块,只需沿着侧边栏定义、白名单常量、图标映射与 i18n 命名空间四条代码链路做增量修改,即可让新功能无缝接入这套导航体系。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00