feat(admin): 接上可观测性下行帧,概览页实时刷新指标 / trace / 日志
契约 v0.55 §3.12 的三帧(`observability_metrics` / `observability_trace` / `observability_log`)
此前在本端没有 handler,落到未知帧被静默丢弃,概览页只能「进页面 / 手动刷新各拉一次 REST」。
- 解析(data/ws):三个事件类 + `dispatchObservability` 分支,载荷按契约逐字对齐
(metrics = 扁平本体、trace = `{spans}`、log = `{entries}`),帧壳 `sessionId` 有就透传。
载荷缺席时**整帧安全忽略**——metrics 回落成默认值就是一份全零快照,会把界面上的真实指标
擦成 0;未知帧、坏帧仍安静丢弃(回归用例钉住,绝不抛)。
- 消费(ui/admin):AdminViewModel 自持一条 socket(照 SessionsViewModel 的做法:登录且
角色够档时连、登出 / 角色降档 / onCleared 关),把三帧合并进概览状态。REST 仍是兜底真源:
进页面与手动刷新照旧各拉一次全量;每次「连上」(含首次连接与断线重连)后再重读一次——
断线窗口里的帧永久丢失,而服务端只为「有在线 owner/admin 连接」的凭据保留订阅,
重读同时也重建订阅(契约 §3.12:读取即订阅握手)。
- 角色门在客户端也判(domain/admin 新增纯函数 `canReceiveObservabilityFrames`):这三帧服务端
fail-closed 只发 owner + admin,与概览页自身的 Monitor 档是两条门,viewer 既不开连接也不合帧。
- 日志只有推送这一个来源(本端没有 logs REST 端点):一帧都没到过时整段不渲染——不写
「暂无日志」,那等于把「没有推送」说成「没有数据」;等级过滤按契约在客户端本地做。
- 合并与过滤是 ui/admin 下的纯函数(新帧在前、按业务键去重、窗口 100/200),单测覆盖。
测试 485 → 514(+29),0 失败 0 错误;`assembleDebug` 通过。
This commit is contained in:
1 parent
a6b4bae2a0
commit
b5e6f78910
9 files changed
+831
-11
No files matched your search
@@ -68,4 +68,26 @@ class AdminAccessTest {
|
||||
assertTrue(AdminRole.Viewer.rank < AdminRole.Admin.rank)
|
||||
assertTrue(AdminRole.Admin.rank < AdminRole.Owner.rank)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only owner and admin receive observability frames`() {
|
||||
// 契约 §3.12:这三个帧不在「状态机信号」白名单里 ⇒ 后端 fail-closed 只发 owner + admin。
|
||||
assertTrue(canReceiveObservabilityFrames(AdminRole.Owner))
|
||||
assertTrue(canReceiveObservabilityFrames(AdminRole.Admin))
|
||||
assertFalse(canReceiveObservabilityFrames(AdminRole.Viewer))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `being able to open the overview page is not enough for the frames`() {
|
||||
// 概览页是 Monitor 档(viewer 也能进、也能读那两个 REST),但它收不到推送——
|
||||
// 两条门必须分开判,否则 viewer 会开着连接干等一份永远不来的帧。
|
||||
assertTrue(canAccess(AdminRole.Viewer, AdminPage.Overview))
|
||||
assertFalse(canReceiveObservabilityFrames(AdminRole.Viewer))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown role is treated as a viewer for the frames too`() {
|
||||
assertFalse(canReceiveObservabilityFrames(AdminRole.of("superowner")))
|
||||
assertFalse(canReceiveObservabilityFrames(AdminRole.of(null)))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user