Skip to content

feat(ops): show service version and uptime on the operator overview - #174

Merged
argszero merged 1 commit into
mainfrom
feat/ops-version-uptime-cards
Sep 12, 2026
Merged

argszero merged 1 commit into
mainfrom
feat/ops-version-uptime-cards

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

运营视图「运行概览」此前完全读不到当前部署的版本号(grep -rn version ui/ 为空),确认线上跑的到底是哪个 build 只能手工 curl /healthz。本 PR 补上原型 docs/prototype/aitokenpool-console.html:1386 的服务版本与运行时长两张卡,值全部来自后端真实数据。

Related Issue

无关联 issue。本改动来自对原型与实现的功能面差异扫描(docs/prototype/aitokenpool-console.html vs ui/):该扫描的结论是运营概览是唯一存在真实功能缺口的视图,其余维度(视图标题 9/9、卡片标题 29/29、列头 38/38、按钮 54/57——其余 3 处是改名)均已对齐。

Changes

  • GET /api/ops/runtime 新增 version 与 uptime_secs / uptime_days / uptime_hours / uptime_minutes / uptime_secs_rest;version 取自 env!("CARGO_PKG_VERSION"),与 /healthz(src/routes/mod.rs:121)同源。
  • AppState 新增 started_at: std::time::Instant(单调时钟,不受系统时间调整影响);进位在后端一处完成(split_uptime),不放到两个语言包里各写一遍。
  • 运营概览渲染两张新卡(ui/js/app.js renderOps)——版本号取自响应,绝不写死。原型那张卡里的 v0.7.20 是写下当天就已过期的字面量,这正是要避免的做法。
  • 新增双语键 ops.stats.version{,.sub} / ops.stats.uptime{,.sub} / ops.uptime.{days,hours,minutes,seconds}(zh/en 同步,键集相等)。
  • ui/README.md 运营视图条目更新为四张卡;cache-bust ?v=20260911-15 → ?v=20260911-16(5 处)。
  • 涉及配置/数据结构的改动已同步示例文件 —— 不适用:本次未新增配置项,config/config.example.toml 无变化。

刻意不做的部分:原型第三张卡错误率未实现。后端不存在任何 5xx / 超时计数器,实现它只能凭空造数,与本仓「零 mock」约定冲突。等真实埋点落地再补。

Tests

  • cargo test 全部通过 —— 163 passed / 0 failed(原 161 + 新增 2)
  • cargo fmt --check 通过
  • cargo clippy --all-targets -- -D warnings 0 warning
  • 新增/更新了单元测试(如适用)

新增测试与证据:

  1. split_uptime 进位单测(src/routes/ops.rs,2 个):已知真值表(0 / 59 / 60 / 3599 / 3600 / 86399 / 86400 / 86460 / 「6 天 4 小时」/ 400 天)+ 区间无损归一性(0..100_000 每 7 秒)。已用负向对照验证有牙齿:把 secs % 60 改成 secs % 3600 ⇒ 两个测试各自变红并点名错值((0,0,1,60)、拼不回原值: 63);文件用 cp + md5 逐字节还原(绝不 git checkout/stash)。

    注:只断言「能拼回去」是不够的——(总秒数,0,0,0) 这种错法同样能拼回去,所以真值表是必需的。

  2. 路由测试扩展(src/routes/mod.rs ops_runtime_credits_users):version 与 /healthz 同源、四位分解能拼回 uptime_secs、各位已归一、且刚构造的 AppState 运行时长 < 60s(起点取自构造时而非更早的静态时刻)。

  3. A/B 运行时证明(同一生成器取 git show HEAD:ui/js/app.js 与工作树):用真实 renderOps 的 #ops-stats 表达式 + 真实 fmtUptime + 虚构响应 version: "9.9.9" 求值 —— BEFORE 6 张卡(无版本/时长卡),AFTER 8 张卡且新卡紧随「运行状态」(与原型顺序一致),渲染出 9.9.9(证明值来自响应而非硬编码)、6 天 4 小时;阴性对照:渲染结果里不含 v0.7.20。

    ⚠️ 该探针第一版自己假零:语言包经 process.env 传递却在模块加载后才赋值 ⇒ T() 拿到空包、全部报 !!MISSING。已加阳性对照(断言包解析出 783 键且 ops.stats.version == "服务版本")后才采信结果。

Checklist

  • 分支命名符合约定(feat/ /fix/ /docs/ ...)—— feat/ops-version-uptime-cards
  • Commit message 使用 Conventional Commits 格式 —— feat(ops): ...
  • 单一职责,改动最小化 —— 一张卡一件事:让运营视图能读到版本与运行时长

i18n 门禁计数是有意更新的:src/i18n_pack.rs 的阳性对照真值随本次改动从 775→783 键、520→531 调用点(去重 418→426)。这是该门禁设计的维护方式(注释里已写明);它先以 panic 拦下我一次——我最初把调用点增量估成 +6,实际是 +11,是门禁报出的实际值纠正了我。

The operator runtime overview had no way to read the deployed build's version,
so confirming which build is live required curling /healthz by hand.

- GET /api/ops/runtime now returns `version` (env!("CARGO_PKG_VERSION"), the
  same source /healthz uses) plus uptime_secs / uptime_days / uptime_hours /
  uptime_minutes / uptime_secs_rest. Carrying is done in one place, backend
  side (split_uptime), instead of in both language packs.
- AppState gains `started_at` (std::time::Instant) to measure uptime on a
  monotonic clock.
- The operator overview renders the two new stat cards, matching the prototype
  (docs/prototype/aitokenpool-console.html:1386). The version is read from the
  response, never hardcoded: the prototype's own "v0.7.20" was a literal that
  was already stale the day it was written.
- New bilingual keys ops.stats.version{,.sub}, ops.stats.uptime{,.sub} and
  ops.uptime.{days,hours,minutes,seconds}; the i18n_pack gate's maintained
  counts move 775 -> 783 keys and 520 -> 531 call sites.

The prototype's third card (错误率) is deliberately not built: the backend has
no 5xx/timeout counter, so it could only be fabricated.
@argszero
argszero merged commit 7d9b56e into main Sep 12, 2026
1 check passed
@argszero
argszero deleted the feat/ops-version-uptime-cards branch September 12, 2026 02:27
argszero added a commit that referenced this pull request Sep 28, 2026
`ui/README.md`'s "设置 / 管理 / 运营布局约定(v1.22)" section still calls the
operator overview's key card "上游 key 健康" and lists its three pills as
"健康 / N 个异常 / 全部失败". That was true when `7d9b56e` (PR #174) wrote the
line; C2158 (#269, `e5ee178`) then renamed the card and rewrote the pills,
because the data has no health signal at all — `/api/ops/runtime` only returns
`total` / `on` / `off`. #269 updated only the section it added itself further
down this same file (`:1080`, which says the opposite), leaving this line
behind, and its edit sheet (4 files / 7 edits) never listed it.

Correct the wording to what the code ships: the title is `ops.keys.title`
("上游 key 状态") and the pills are `ops.keys.allOn` / `someOff` / `allOff`
("全部启用 / N 个停用 / 全部停用"). No gate: this is prose restating an
implementation name, and the repo's standing decision for that axis is to fix
the data, not to guard the prose (see PRs #309, #310, #312–#315).

Verified on the branch tree: `cargo test` 417 passed / 0 failed (unchanged
baseline), `cargo fmt --check` and `cargo clippy --all-targets -D warnings`
both clean. The other claims on the same line were re-checked and are still
true (`version` ← `env!("CARGO_PKG_VERSION")`, the five `uptime_*` fields,
`split_uptime`, `fmtUptime`'s top-two-nonzero units, `ops.uptime.*`,
`today_hours` zero-filled 0–23). The one remaining occurrence of the old
wording (`:1084`) is the C2158 section quoting it as history and is left
untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant