feat(c3): ADR-073-C C3 — WebSocket 飛輪即時推送

後端:
- stats.py 新增 @router.websocket('/flywheel/ws')
- 每 10 秒推送 flywheel_summary JSON

前端 FlywheelKPICard:
- WebSocket 優先,WS 斷線自動降級到 30s HTTP 輪詢
- onopen 時停止 HTTP polling,onclose 時恢復

2026-04-12 ogt (ADR-073-C C3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-12 15:40:20 +08:00
parent 4b51f9b60d
commit 0c2892ac19
2 changed files with 84 additions and 8 deletions

View File

@@ -19,9 +19,11 @@
# @see feedback_lewooogo_modular_enforcement.md
# =============================================================================
import asyncio
import json
from typing import Annotated, Any
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import PlainTextResponse
from pydantic import BaseModel, Field
@@ -552,3 +554,35 @@ async def get_flywheel_prometheus_metrics(svc: FlywheelStatsDep) -> PlainTextRes
content=metrics.to_prometheus_lines(),
media_type="text/plain; version=0.0.4; charset=utf-8",
)
# =============================================================================
# ADR-073-C C3: WebSocket 即時飛輪推送
# =============================================================================
@router.websocket("/flywheel/ws")
async def flywheel_websocket(websocket: WebSocket) -> None:
"""
WebSocket 即時飛輪健康度推送 — ADR-073-C C3
每 10 秒推送一次 FlywheelSummary JSON。
前端連線路徑ws(s)://<host>/api/v1/stats/flywheel/ws
Protocol:
Server → Client: {"type": "flywheel_summary", "data": {...}, "ts": "ISO8601"}
Client → Server: (ignored)
"""
svc = get_flywheel_stats_service()
await websocket.accept()
try:
while True:
metrics = await svc.compute()
payload = json.dumps({
"type": "flywheel_summary",
"data": metrics.to_summary_api_dict(),
"ts": metrics.computed_at.isoformat(),
})
await websocket.send_text(payload)
await asyncio.sleep(10)
except WebSocketDisconnect:
pass