docs: 补充环境总览SSE实时出口方案

This commit is contained in:
sunlei 2026-06-18 15:55:08 +08:00
parent 6b8b76b6ff
commit 674d238f6f
2 changed files with 117 additions and 8 deletions

View File

@ -4,7 +4,7 @@
**Goal:** 将 Admin `/dashboard/analytics` 改造成多站点环境状态总览总控面板覆盖本机开发、NAS 线上、腾讯云、r4se 远程环境,并明确展示未接入证据,第一版只做观测与只读自检,不提供高风险写操作。
**Architecture:** 后端在 API `admin/platform-config` 下新增环境面板聚合模块,以 `Site -> Node -> Service -> Signal` 为统一模型HTTP dashboard/self-check 负责状态快照和只读兜底,平台级 EnvironmentEventBus 通过 local/mqtt 模式收口 topic 事件并驱动 recent events 与 cache invalidation内部服务信号从现有 Nest 服务读取,远程信号通过只读适配器读取 Jenkins、Kubernetes、Tencent Cloud、Caddy、WireGuard、OpenClash/Mihomo前端保留 Vben Dashboard 路由,替换为 A 方案布局:顶部状态条、左侧站点栏、中间拓扑、右侧证据/动作抽屉、底部事件流。
**Architecture:** 后端在 API `admin/platform-config` 下新增环境面板聚合模块,以 `Site -> Node -> Service -> Signal` 为统一模型HTTP dashboard/self-check 负责状态快照和只读兜底,平台级 EnvironmentEventBus 通过 local/mqtt 模式收口 topic 事件并驱动 recent events 与 cache invalidationAPI SSE stream 把脱敏后的增量事件推送给 Admin;内部服务信号从现有 Nest 服务读取,远程信号通过只读适配器读取 Jenkins、Kubernetes、Tencent Cloud、Caddy、WireGuard、OpenClash/Mihomo前端保留 Vben Dashboard 路由,替换为 A 方案布局:顶部状态条、左侧站点栏、中间拓扑、右侧证据/动作抽屉、底部事件流。Admin 不跑轮询或定时刷新。
**Tech Stack:** NestJS、TypeScript、TypeORM、axios、mqtt、tencentcloud-sdk-nodejs、Vben Admin、Vue 3、antdv-next、Vitest/Jest、Playwright 轻量页面烟测。
@ -26,6 +26,7 @@
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\application\environment-event.materializer.ts`
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\application\environment-dashboard.service.ts`
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\application\environment-dashboard-self-check.service.ts`
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\application\environment-event-stream.service.ts`
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\infrastructure\environment-dashboard-config.service.ts`
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\infrastructure\environment-dashboard-cache.service.ts`
- `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\environment-dashboard\infrastructure\environment-dashboard-evidence.mapper.ts`
@ -60,6 +61,7 @@
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\dashboard\analytics\components\EnvironmentTopology.vue`
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\dashboard\analytics\components\EnvironmentEvidencePanel.vue`
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\dashboard\analytics\components\EnvironmentEventStream.vue`
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\dashboard\analytics\composables\useEnvironmentDashboardStream.ts`
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\dashboard\analytics\types.ts`
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\dashboard\analytics\environment-dashboard.spec.tsx`
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\system\environment.spec.ts`
@ -115,6 +117,13 @@ export interface EnvironmentEventEnvelope {
summary: string;
evidence?: EnvironmentEvidence[];
}
export type EnvironmentStreamEventType =
| 'environment-event'
| 'environment-signal'
| 'snapshot-required'
| 'heartbeat'
| 'error';
```
Severity aggregation order is fixed:
@ -140,6 +149,7 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
- `unwired` and `unknown` are valid product states. A missing integration must be visible evidence, not a green health badge.
- MQTT is an event layer, not the only source of truth. Retained messages require `observedAt` and `expiresAt`; expired retained messages must become `unknown` or `unwired`.
- Admin must not connect to MQTT directly. Broker credentials and internal topics stay inside API runtime.
- Admin must not poll or use timer-based dashboard refresh. After the first snapshot, fresh state arrives through SSE; only user actions and `snapshot-required` may trigger another dashboard request.
- For interface changes, verify through a real local HTTP request against the Nest app or an already running local service.
---
@ -323,6 +333,25 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
```
Expected output after implementation: topic mapping is deterministic, expired retained signal becomes non-green, broker disconnect creates an event without marking every service down, and QQBot bridge tests pass without importing dashboard code from QQBot-specific topic constants.
## Task 3B: API SSE Stream For Admin Realtime Updates
- [ ] Create `application/environment-event-stream.service.ts`:
- exposes an RxJS Observable stream compatible with Nest `@Sse`
- subscribes to `EnvironmentEventBus` / `environment-event.materializer.ts`
- emits `environment-event`, `environment-signal`, `snapshot-required`, `heartbeat`, and `error`
- supports browser `Last-Event-ID` or `lastEventId` query fallback for replay
- maintains a bounded in-memory replay buffer controlled by `ENV_DASHBOARD_SSE_REPLAY_LIMIT`
- emits `snapshot-required` when the requested event id is older than the replay buffer
- uses `ENV_DASHBOARD_SSE_HEARTBEAT_MS` only for connection keepalive, not for dashboard refresh
- never opens MQTT or remote HTTP connections from the SSE method itself
- [ ] Add RED/GREEN tests:
```powershell
cd D:\MyFiles\KT\Node\kt-template-online-api
pnpm exec jest --runTestsByPath test/modules/admin/environment-dashboard/environment-event-stream.service.spec.ts --runInBand
```
Expected output after implementation: replay by last event id works, stale replay id emits `snapshot-required`, heartbeat does not trigger dashboard service calls, and stream payloads contain no broker credentials.
## Task 4: API Remote Adapters For Jenkins And Kubernetes
- [ ] Create `environment-dashboard-config.service.ts` that reads optional dashboard integration env vars. It returns explicit missing-key lists for each integration instead of throwing.
@ -460,9 +489,11 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
- [ ] Create `presentation/environment-dashboard.controller.ts`:
- `GET /system/environment/dashboard`
- `POST /system/environment/self-check`
- `GET /system/environment/events/stream`
- guards: `JwtAuthGuard`
- response wrapper: existing `vbenSuccess`
- JSDoc on controller methods states Admin route origin and no side effects beyond read-only probes/cache refresh
- `events/stream` uses Nest `@Sse` and returns `EnvironmentStreamEvent`
- JSDoc on controller methods states Admin route origin and no side effects beyond read-only probes/cache refresh/SSE subscription
- [ ] Update `admin-platform-config.module.ts` imports/providers/controllers. Include only required modules.
@ -476,6 +507,8 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
ENV_DASHBOARD_MQTT_USERNAME=
ENV_DASHBOARD_MQTT_PASSWORD=
ENV_DASHBOARD_MQTT_TOPIC_PREFIX=kt/env
ENV_DASHBOARD_SSE_REPLAY_LIMIT=200
ENV_DASHBOARD_SSE_HEARTBEAT_MS=25000
ENV_DASHBOARD_JENKINS_URL=
ENV_DASHBOARD_JENKINS_JOB=KT-Template/KT-Template-API/main
ENV_DASHBOARD_JENKINS_USERNAME=
@ -505,6 +538,13 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
```
Expected output: controller spec passes.
- [ ] Add controller spec coverage for SSE:
```powershell
cd D:\MyFiles\KT\Node\kt-template-online-api
pnpm exec jest --runTestsByPath test/modules/admin/environment-dashboard/environment-dashboard.events.controller.spec.ts --runInBand
```
Expected output: authenticated request can subscribe to SSE, `Last-Event-ID` is passed to stream service, and no dashboard polling timer exists in API code.
- [ ] Run API typecheck:
```powershell
cd D:\MyFiles\KT\Node\kt-template-online-api
@ -516,20 +556,23 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
```powershell
curl.exe -H "Authorization: Bearer <local-admin-token>" http://127.0.0.1:<api-port>/system/environment/dashboard
curl.exe -X POST -H "Authorization: Bearer <local-admin-token>" http://127.0.0.1:<api-port>/system/environment/self-check
curl.exe -N -H "Authorization: Bearer <local-admin-token>" http://127.0.0.1:<api-port>/system/environment/events/stream
```
Expected output: both return `code: 0` or existing success wrapper equivalent, four site records, and at least one `unwired` evidence item when remote env vars are absent.
Expected output: dashboard and self-check return `code: 0` or existing success wrapper equivalent, four site records, and at least one `unwired` evidence item when remote env vars are absent; SSE stream returns `text/event-stream` and emits a heartbeat or synthetic environment event without closing immediately.
## Task 9: Admin API Wrapper And RED Tests
- [ ] Create `apps/web-antdv-next/src/api/system/environment.ts` with exported interfaces matching the API response and functions:
- `getEnvironmentDashboard()`
- `runEnvironmentSelfCheck()`
- `getEnvironmentDashboardEventsUrl(lastEventId?)`
- [ ] Add `apps/web-antdv-next/src/api/system/environment.spec.ts`:
```ts
import { requestClient } from '#/api/request';
import {
getEnvironmentDashboard,
getEnvironmentDashboardEventsUrl,
runEnvironmentSelfCheck,
} from './environment';
@ -550,6 +593,11 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
await runEnvironmentSelfCheck();
expect(requestClient.post).toHaveBeenCalledWith('/system/environment/self-check');
});
it('builds the SSE stream url without exposing MQTT config', () => {
expect(getEnvironmentDashboardEventsUrl()).toContain('/system/environment/events/stream');
expect(getEnvironmentDashboardEventsUrl('evt-1')).toContain('lastEventId=evt-1');
});
});
```
@ -601,12 +649,25 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
## Task 11: Admin Page Integration, Locales, And Tests
- [ ] Wire `index.vue`:
- load dashboard on mount
- load dashboard once on mount
- start `useEnvironmentDashboardStream.ts` EventSource after the first snapshot succeeds
- refresh with `getEnvironmentDashboard`
- self-check with `runEnvironmentSelfCheck`
- process `environment-event` and `environment-signal` without fetching dashboard again
- process `snapshot-required` by running exactly one dashboard snapshot request
- ignore `heartbeat` for state updates
- preserve selected site/service when IDs still exist after refresh
- select first degraded/down/blocked service by default, else first site
- display explicit empty/error state when API fails
- close EventSource on route leave/unmount
- [ ] Create `composables/useEnvironmentDashboardStream.ts`:
- wraps browser `EventSource`
- uses `getEnvironmentDashboardEventsUrl(lastEventId?)`
- stores last accepted event id in component state only
- exposes connection state for the status bar
- does not use `setInterval`, `setTimeout` refresh loops, or background polling
- lets native EventSource reconnect; after server sends `snapshot-required`, caller performs one snapshot fetch
- [ ] Update locale title from analytics sample to environment dashboard:
- `apps/web-antdv-next/src/locales/langs/zh-CN/page.json`: `环境总览`
@ -617,6 +678,9 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
- disabled write actions render disabled
- unwired Jenkins/K8s evidence renders as evidence, not healthy state
- MQTT-origin recent event renders only after API returns it; page does not instantiate an MQTT client
- SSE `environment-signal` updates one node without calling `getEnvironmentDashboard`
- SSE `snapshot-required` calls `getEnvironmentDashboard` once
- no dashboard polling or timer-based refresh is registered
- self-check button calls the POST wrapper
- [ ] Run:
@ -644,6 +708,8 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
- right panel shows evidence for selected item
- bottom event stream appears
- event stream can display an API-provided MQTT-origin event without exposing broker configuration in frontend state
- Network tab shows one dashboard snapshot and one `events/stream` long connection after page load, with no repeated dashboard polling
- closing/reopening the route closes the old EventSource before creating a new one
- Jenkins/K8s missing config appears as `unwired` or `unknown`, never green
- disabled write actions cannot be clicked
@ -654,6 +720,7 @@ const severityWeight: Record<EnvironmentHealthStatus, number> = {
Store under `.kt-workspace/test-artifacts/environment-dashboard/`.
- [ ] Make one real local HTTP call from Admin session to both endpoints through the browser network panel or request logs. Evidence must include endpoint, HTTP status, and presence of four site IDs.
- [ ] Make one real local SSE connection from Admin session. Evidence must include endpoint, HTTP status, `text/event-stream`, and no repeated dashboard GET requests after the initial snapshot.
- [ ] Stop Node/Vite processes started by this task if they were not already running before the task.
@ -732,6 +799,7 @@ Online completion evidence must include:
- `GET /system/environment/dashboard` returns four sites.
- `POST /system/environment/self-check` returns fresh evidence.
- `GET /system/environment/events/stream` establishes an SSE stream; a backend environment event reaches Admin without polling.
- MQTT event bus is either `local` with explicit configured evidence, or `mqtt` with broker connection status shown as evidence.
- A synthetic non-secret MQTT signal event invalidates dashboard cache and appears in recent events; an expired retained signal does not create a green status.
- Jenkins/K8s nodes show live evidence when configured, or explicit missing-key evidence when not configured.

View File

@ -63,7 +63,7 @@ Admin 当前 `/dashboard/analytics` 仍是 Vben 示例分析页,展示用户
9. Jenkins/K8s 必须接入只读观测缺配置、403、超时或网络失败时显示失败证据不能伪造成健康。
10. 腾讯云/r4se 第一版以只读观测为目标;无法直接观测的 WireGuard 全局 peer 状态必须明确显示证据缺口,不抓取私有 WebUI。
11. Figma 官方写入不作为本轮主链路;页面布局通过本地 visual companion 和中文 spec 收敛,避免 Figma 官方限流。
12. 事件层采用 HTTP 快照 + MQTT 收口的混合方案:`GET /system/environment/dashboard` 和 `POST /system/environment/self-check` 继续作为状态真相源与兜底MQTT 只负责 topic 事件订阅、recent event materialize 和 cache invalidation。Admin 前端不直连 MQTT。
12. 事件层采用 HTTP 快照 + 后端 MQTT 收口 + API SSE 推送的混合方案:`GET /system/environment/dashboard` 和 `POST /system/environment/self-check` 继续作为状态快照与兜底MQTT 只负责后端 topic 事件订阅、recent event materialize 和 cache invalidation`GET /system/environment/events/stream` 负责把已经脱敏和聚合后的事件推给 Admin。Admin 前端不直连 MQTT,也不跑轮询或定时刷新
## 目标
@ -72,6 +72,7 @@ Admin 当前 `/dashboard/analytics` 仍是 Vben 示例分析页,展示用户
- 每个节点必须带证据:来源、检查时间、实时/缓存/推导状态、失败原因、是否脱敏。
- 让 Jenkins/K8s、腾讯云、r4se 不再是“未接入健康假象”,而是明确展示只读证据或缺口。
- 引入平台级 MQTT 事件入口,统一收口 QQBot/NapCat、Plugin Task、Jenkins/K8s、腾讯云、Caddy、WireGuard、Mihomo/OpenClash 等运行态事件。
- 提供 API SSE 实时出口,让 Admin 页面在无轮询、无定时刷新条件下接收状态变化和事件流增量。
- 后续可扩展远程站点探针,但第一版不引入写权限和高风险远程控制。
## 非目标
@ -83,6 +84,7 @@ Admin 当前 `/dashboard/analytics` 仍是 Vben 示例分析页,展示用户
- 不新增长期后台巡检表或历史趋势表,除非实施计划后续单独确认。
- 不让 MQTT 成为唯一状态真相源broker 断连、retained message 过期或远程 agent 未接入时dashboard 必须回退到 HTTP 快照/只读自检和明确缺口证据。
- 不让 Admin 浏览器直接连接 MQTT broker不向前端暴露 broker 用户名、密码、token 或内部 topic。
- 不在 Admin 页面使用轮询、定时刷新或后台 `setInterval` 拉取 dashboardSSE 断线后的浏览器自动重连和服务端连接保活不属于状态刷新轮询。
## 页面布局
@ -253,7 +255,21 @@ MQTT 语义:
- 状态信号和关键任务结果使用 QoS 1普通日志摘要事件使用 QoS 0。
- payload 入库、日志、API 返回前三层都要脱敏。
- broker 断连是一个 `EnvironmentEvent`,同时使 dashboard cache 失效;不能因为 MQTT 断连把所有服务判定为 down。
- Admin 不直连 MQTT前端通过 dashboard HTTP 接口、self-check 或后续 API SSE/WebSocket 读取已经脱敏和聚合后的状态。
- Admin 不直连 MQTT前端通过 dashboard HTTP 首屏快照、self-check 主动按钮和 API SSE 长连接读取已经脱敏和聚合后的状态变化。
### API SSE 实时出口
Admin 页面打开时只做一次 `GET /system/environment/dashboard` 获取首屏快照,然后建立 `GET /system/environment/events/stream` SSE 长连接。后端收到 MQTT 或本地事件后,先经过 `EnvironmentEventBus`、`EnvironmentEventMaterializer` 和脱敏,再向 SSE 订阅者推送增量事件。页面不得设置定时刷新;只有用户点击刷新/只读自检,或后端 SSE 推送 `snapshot-required` 时,才允许再次请求 dashboard 快照。
SSE 事件类型:
- `environment-event`:追加 recent event并按事件里的 site/node/service/signal 更新局部状态。
- `environment-signal`:更新某个 signal 的状态、证据和时间。
- `snapshot-required`:服务端认为客户端事件缺口过大或 cache generation 已失效,前端执行一次 dashboard 快照请求。
- `heartbeat`:连接保活,不触发状态刷新。
- `error`:展示连接或事件解析失败摘要,不能覆盖服务真实状态。
SSE 必须支持浏览器 `Last-Event-ID` 或等价 query 参数续接。服务端只保留有限最近事件用于断线恢复;超过 replay window 时推送 `snapshot-required`,避免客户端悄悄停留在旧状态。
## API 合同
@ -262,9 +278,10 @@ MQTT 语义:
```text
GET /system/environment/dashboard
POST /system/environment/self-check
GET /system/environment/events/stream
```
`GET /system/environment/dashboard` 返回当前聚合状态。`POST /system/environment/self-check` 执行同一批只读检查,可以绕过 dashboard TTL但不得触发任何写入、重启、部署、容器重建或远程配置变更。
`GET /system/environment/dashboard` 返回当前聚合状态。`POST /system/environment/self-check` 执行同一批只读检查,可以绕过 dashboard TTL但不得触发任何写入、重启、部署、容器重建或远程配置变更。`GET /system/environment/events/stream` 是受保护 SSE 长连接,只推送脱敏后的环境事件和 snapshot 指令,不暴露 MQTT broker 或内部 topic。
建议目录:
@ -276,6 +293,7 @@ src/modules/admin/platform-config/environment-dashboard/
environment-dashboard.dto.ts
application/
environment-dashboard.service.ts
environment-event-stream.service.ts
environment-dashboard-site.mapper.ts
environment-dashboard-status.mapper.ts
environment-dashboard-action.catalog.ts
@ -341,6 +359,25 @@ interface EnvironmentEventEnvelopeDto {
evidence?: EnvironmentEvidenceDto[];
}
type EnvironmentStreamEventType =
| 'environment-event'
| 'environment-signal'
| 'snapshot-required'
| 'heartbeat'
| 'error';
interface EnvironmentStreamEventDto {
id: string;
type: EnvironmentStreamEventType;
data: EnvironmentEventEnvelopeDto | EnvironmentDashboardDto | EnvironmentStreamNoticeDto;
}
interface EnvironmentStreamNoticeDto {
reason: string;
summary: string;
generatedAt: string;
}
interface EnvironmentSiteDto {
key: string;
title: string;
@ -913,6 +950,7 @@ EnvironmentDashboardPage
### API 验收
- `GET /system/environment/dashboard` 本地真实请求返回 Vben 包装。
- `GET /system/environment/events/stream` 本地真实请求返回 SSE 流,能收到 heartbeat 或 synthetic event。
- 响应包含 `sites`、`summaryCards`、`topology.nodes`、`topology.edges`、`actions`、`recentEvents`。
- NAS、local-dev、Tencent Cloud、r4se 至少有节点和状态。
- Jenkins/K8s 节点由只读 adapter 提供真实 build/deployment/pod 证据或失败证据。
@ -929,12 +967,14 @@ EnvironmentDashboardPage
- enabled action 可刷新、只读自检或跳转。
- disabled high-risk action 可见且说明禁用原因。
- 页面切换无 Vue `non-element root node` / transition 空白问题。
- 页面不使用轮询或定时刷新 dashboard状态变化由 SSE 增量事件驱动。
### 线上验收
- API/Admin 推送部署后Jenkins/K8s 发布状态按现有 deploy observation 流程验证。
- 线上 Admin 页面可打开。
- 线上 dashboard 接口 200。
- 线上 events stream 可建立 SSE 连接,断线重连后能通过 `Last-Event-ID``snapshot-required` 恢复一致性。
- 线上页面展示 NAS、local-dev、Tencent Cloud、r4se 四类站点。
- Jenkins/K8s 展示真实只读观测证据Jenkins 最近 build/commit/resultK8s Deployment ready/updated、Pod image/restartCount。
- 腾讯云/Caddy/WireGuard/r4se/OpenClash/Mihomo 展示真实只读证据或明确缺口,不显示健康假象。
@ -947,9 +987,10 @@ EnvironmentDashboardPage
- API service 单测状态严重度聚合、site 聚合、partial failure、isolated/unknown/unwired、action catalog。
- API adapter 单测Jenkins/K8s/Tencent/Caddy/Mihomo/WireGuard 的 200、403、timeout、unreachable、failed/degraded mapping。
- API event bus 单测MQTT topic mapping、retained message expiry、broker disconnect event、QQBot bridge 脱耦。
- API SSE 单测event replay、Last-Event-ID 续接、heartbeat 不触发快照、replay window 缺口推 `snapshot-required`
- API controller contract spec鉴权路由、Vben response shape、self-check 只读行为。
- Admin API wrapper Vitest请求路径和响应类型。
- Admin 页面组件测试站点导航、拓扑节点、证据抽屉、disabled actions、事件流过滤。
- Admin 页面组件测试站点导航、拓扑节点、证据抽屉、disabled actions、事件流过滤、EventSource 消费、无 dashboard 轮询
- 本地真实接口请求:启动或复用 API 服务调用 dashboard 接口。
- 本地浏览器 smoke打开 `/dashboard/analytics`,检查 console、首屏、抽屉和动作。
- 线上 smoke部署后调用线上接口和页面。