实现 Gateway 健康监控(P0-1)。后端 Hermes CLI 封装 + API + 前端页面。 ## 第一部分: Hermes CLI 服务(真实数据源) 创建 backend/app/services/hermes_cli.py: - 封装 `hermes gateway status` CLI 调用 - 用 subprocess.run 执行,解析 JSON 输出 - 出错时返回空数据 + 日志告警,不抛异常 - 函数: gateway_status_all() -> list[GatewayInfo] - 数据类型: id, name, status(online/offline/warn), downtime_minutes, restarts_today, last_event ## 第二部分: Gateway API 创建 backend/app/api/gateway.py,全部 require_auth ### GET /api/v1/gateway/status 返回全部 Profile 的 Gateway 状态列表,支持: - query param `?status=online|offline|warn` 筛选 - 每行: name, status(🟢/🔴/🟡), downtime, restarts_today, last_event, profile_type - 数据来源: hermes_cli.gateway_status_all() ### GET /api/v1/gateway/events 返回最近 24h 异常事件: ```json [ {"time": "2026-07-22T18:45", "profile": "monitor", "type": "heartbeat_timeout", "detail": "连续 3 次心跳无响应"}, {"time": "2026-07-22T14:20", "profile": "feishu-gw", "type": "feishu_disconnect", "detail": "WebSocket 断开 8 分钟"}, {"time": "2026-07-22T10:30", "profile": "gateway-monitor", "type": "restart", "detail": "Gateway 重启"} ] ``` 数据来源: hermes CLI 日志或系统日志解析。暂用占位数据(TODO 标注)。 ### POST /api/v1/gateway/{profile}/start, /stop, /restart Gateway 启停操作: - 执行 `hermes gateway start/stop/restart --profile {profile}` - 需要 operator 或 owner 角色(require_role("owner","operator")) - 返回操作结果 + 写入审计日志 - 操作前二次确认(前端处理) ## 第三部分: 前端 Gateway 页面 修改 frontend/src/pages/GatewayPage.tsx: ### 顶部: 异常事件时间线 - Timeline 组件,24h 横向展示 - 事件类型: 🔴心跳超时 🟠飞书断连 🔵Gateway 重启 - Hover 显示详情 tooltip ### 筛选栏 - 状态筛选: 全部/在线/离线/异常 - 蜂型筛选: 全部/工蜂/蜂王 - 筛选器即时生效(前端过滤,不重新请求) ### 状态表 - Table 组件,10s 轮询 (usePolling hook) - 列: Profile 名, 状态 Badge, 掉线时长, 今日重启, 最近异常, 操作 - 异常行标红: Gateway 连续重启≥3次 或 掉线≥30分钟 - 操作按钮: 启动/停止/重启 (二次确认 Modal) ### 四态覆盖 - Loading: Skeleton (table 形状) - Empty: "全部 Gateway 正常运行 🎉" - Error: 错误 + 重试 - Normal: 数据展示 ### usePolling hook 创建 frontend/src/hooks/usePolling.ts: ```typescript import { useEffect, useRef } from 'react' export function usePolling(callback: () => void, intervalMs: number, enabled: boolean = true) { const savedCallback = useRef(callback) useEffect(() => { savedCallback.current = callback }, [callback]) useEffect(() => { if (!enabled) return savedCallback.current() // 立即执行一次 const id = setInterval(() => savedCallback.current(), intervalMs) return () => clearInterval(id) }, [intervalMs, enabled]) } ``` 注册路由: main.py 添加 `from app.api import gateway; app.include_router(gateway.router, prefix="/api/v1")` 完成后提交。