Files

154 lines
6.8 KiB
Markdown
Raw Permalink Normal View History

# 审计 — 子机离线告警 + 仪表盘统计/舰队趋势(`7209f53`)
**Changelog**:
- `docs/changelog/2026-06-08-server-offline-alert.md`
- `docs/changelog/2026-06-09-dashboard-stat-cards-align.md`
- `docs/changelog/2026-06-09-fleet-trend-alerts.md`
- `docs/changelog/2026-06-09-fleet-trend-interaction.md`
- `docs/changelog/2026-06-09-offline-count-accuracy.md`
**部署验证**: `docs/reports/2026-06-09-server-offline-alert-deploy-verification.md`
## 审计信息
- **日期**: 2026-06-09
- **审计人**: Cursor Agent
- **触发原因**: 新功能(离线 Telegram 告警、设置开关)+ 仪表盘统计/趋势增强;已部署生产,补全门控审计
## 审计范围
| 文件 | 行数 | 状态 |
|------|------|------|
| `server/background/server_offline_monitor.py` | 88 | ☑ 已审 |
| `server/api/websocket.py` | +37 | ☑ 已审 |
| `server/infrastructure/telegram/__init__.py` | +37 | ☑ 已审 |
| `server/utils/notify_toggles.py` | +5 | ☑ 已审 |
| `server/config.py` | +2 | ☑ 已审 |
| `server/api/settings.py` | +1 | ☑ 已审 |
| `server/main.py` | +6 | ☑ 已审 |
| `server/application/server_connectivity.py` | +7 | ☑ 已审 |
| `server/utils/fleet_alert_buckets.py` | 88 | ☑ 已审 |
| `server/api/servers.py` | +18 | ☑ 已审 |
| `frontend/src/types/api.ts` | +1 | ☑ 已审 |
| `frontend/src/composables/useServerStatsCards.ts` | 76 | ☑ 已审 |
| `frontend/src/components/StatCardsRow.vue` | 136 | ☑ 已审 |
| `frontend/src/pages/DashboardPage.vue` | Δ | ☑ 已审 |
| `frontend/src/pages/ServersPage.vue` | Δ | ☑ 已审 |
| `frontend/src/components/dashboard/DashboardFleetTrends.vue` | 363 | ☑ 已审 |
| `frontend/src/components/dashboard/FleetTrendEChart.vue` | 新 | ☑ 已审 |
| `frontend/src/components/dashboard/FleetTrendKpiStrip.vue` | 新 | ☑ 已审 |
| `frontend/src/utils/fleetTrendAnalytics.ts` | 新 | ☑ 已审 |
| `frontend/package.json` / `package-lock.json` | echarts 依赖 | ☑ 已审 |
| `tests/test_server_offline_monitor.py` | 85 | ☑ 已审 |
| `tests/test_fleet_alert_buckets.py` | 新 | ☑ 已审 |
## 审计8步结果
### Step 1: 登记 ✅
提交 `7209f53`,范围见上表;设计/计划文档齐全。
### Step 2: 全文 Read ✅
上述文件均已通读;无跨层直连(monitor → websocket → telegramfleet-metrics → repo)。
### Step 3: 规则扫描 H
| ID | 规则 | 命中点 | 初判 |
|----|------|--------|------|
| H1 | 无明文密钥 | config/settings | SAFE |
| H2 | 无静默吞错 | `server_offline_monitor` L86-87 `exc_info=True` | SAFE |
| H3 | Telegram HTML 转义 | `send_telegram_offline_alert` `html.escape` name/hb | SAFE |
| H4 | 通知开关门控 | `offline_alert_notify_enabled` + settings `MUTABLE_KEYS` | SAFE |
| H5 | 边沿触发防轰炸 | `_prev_online` + `_should_push_telegram` 5min | SAFE |
| H6 | 冷启动不误报 | `prev_online is None` 只记录 | SAFE |
| H7 | 仅 primary worker | `main.py` 与 self_monitor 同锁 | SAFE |
| H8 | API 鉴权 | `/fleet-metrics` `get_current_admin` | SAFE |
| H9 | CUD 审计 | `notify_alert_offline` 走既有 PUT settings + audit | SAFE |
| H10 | 离线判定一致性 | `heartbeat_indicates_online` 与 stats/list 共用 | SAFE |
| H11 | 前端 XSS | 统计数字字符串化;ECharts 无 `v-html` 用户输入 | SAFE |
| H12 | 依赖体积 | echarts ~625kB chunk | OBS(已知,懒加载页内) |
### Step 4: Closure表
| ID | 判定 | 依据 |
|----|------|------|
| H1 | SAFE | 无新增密钥字段 |
| H2 | SAFE | tick 失败打 error 日志,不掩盖 |
| H3 | SAFE | 与现有 `send_telegram_alert` 一致转义 |
| H4 | SAFE | 关开关不推 Telegram`alert_logs`/WS 仍记录(与资源告警策略一致) |
| H5 | SAFE | 持续离线不重复;恢复后再离线可再推 |
| H6 | SAFE | 单测 `test_cold_start_no_alert` |
| H7 | SAFE | `_background_tasks` 仅 primary 注册 |
| H8 | SAFE | 管理员 JWT |
| H9 | SAFE | `MUTABLE_KEYS``notify_alert_offline` |
| H10 | SAFE | `test_server_connectivity` |
| H11 | SAFE | Vue 文本绑定 |
| H12 | ACCEPT | 仪表盘页专用 chunk,可后续 code-split 优化 |
### Step 5: 入口表
| 入口 | 鉴权 | 说明 |
|------|------|------|
| 后台 `server_offline_monitor_loop` | primary worker | Redis 读 + DB 读 |
| `broadcast_offline_alert` | 内部调用 | WS + alert_logs + Telegram |
| `PUT /api/settings/notify_alert_offline` | admin JWT | 开关 |
| `GET /api/servers/fleet-metrics` | admin JWT | 趋势+告警挂载 |
| `GET /api/servers/stats` | admin JWT | 统计卡(既有) |
| 设置页 `NOTIFY_TOGGLE_ITEMS` | 前端 | 展示开关 |
### Step 6: 输入 → Sink
| 路径 | Sink | 防护 |
|------|------|------|
| Redis `heartbeat:*` | 离线判定 | 只读;server_id 来自 DB 整数主键 |
| `server_name` / `last_heartbeat` | Telegram HTML | `html.escape` |
| `hours` query | fleet-metrics | `max(1,min(hours,168))` |
| settings value | DB settings | 布尔字符串白名单流程 |
### Step 7: 归类
```
server_offline_monitor.py 88行 12H 0FINDING
websocket.py (offline块) 37行 12H 0FINDING
telegram/__init__.py 37行 12H 0FINDING
fleet_alert_buckets.py 88行 12H 0FINDING
servers.py (fleet-metrics) 18行 12H 0FINDING
frontend (统计+趋势) — 12H 0FINDING
tests — 12H 0FINDING
──────────────────────────────────────────────
总计 12H 0FINDING
```
### Step 8: DoD ✅
- [x] 设计文档 + 实施说明 + changelog
- [x] 单测:`test_server_offline_monitor` 4、`test_fleet_alert_buckets``test_server_connectivity``test_fleet_metrics`
- [x] `pytest` 相关 51 项通过(offline+fleet+notify
- [x] 门控 pre_deploy 7/7(部署前)
- [x] 生产部署 `/health` ok、`/app/` 200`7209f53`
- [ ] 浏览器终验:设置页「子机离线告警」开关 + 实机离线 Telegram(运维操作)
## FINDING 列表
无(0 FINDING
## OBSERVATION(非阻断)
1. **离线发现延迟**:与 UI 一致,依赖 Redis `heartbeat:*` TTL`FLUSH_INTERVAL×1.5` ≈ 15min)或键缺失;monitor 30s 轮询但状态变更是 TTL 驱动。设计文档已说明,与既有在线判定同源。
2. **舰队趋势 tooltip**`fleet_alert_buckets.ALERT_TYPE_LABELS` 未含 `offline`,趋势图钉显示原始 `offline` 而非「离线」——纯展示,可后续 1 行补齐。
3. **`_prev_online` 内存**:删除子机后字典条目残留,量级可忽略;primary 重启会冷启动。
## 验证命令
```bash
pytest tests/test_server_offline_monitor.py tests/test_fleet_alert_buckets.py \
tests/test_server_connectivity.py tests/test_fleet_metrics.py \
tests/test_notify_toggle_sync.py -q
# 51 passed
```
## 结论
**审计通过,0 FINDING**,已部署生产;待用户浏览器/实机离线 Telegram 终验。