fix(web): stabilize homepage live status
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* 飛輪健康度 KPI 面板。
|
||||
* C2: 初始載入 GET /api/v1/stats/summary(HTTP fallback)
|
||||
* C3: WebSocket /api/v1/stats/flywheel/ws 即時推送(10s 更新)
|
||||
* C3: WebSocket /api/v1/stats/flywheel/ws 即時推送(旗標開啟時)
|
||||
*
|
||||
* 2026-04-12 ogt (ADR-073-C C2 + C3)
|
||||
*/
|
||||
@@ -16,6 +16,7 @@ import { FlywheelDiagram, type FlowItem } from './flywheel-diagram'
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
// ws(s):// mirror of NEXT_PUBLIC_API_URL
|
||||
const WS_BASE = API_BASE.replace(/^https/, 'wss').replace(/^http/, 'ws')
|
||||
const ENABLE_FLYWHEEL_WS = process.env.NEXT_PUBLIC_ENABLE_FLYWHEEL_WS === 'true'
|
||||
|
||||
interface FlywheelSummary {
|
||||
playbook_count: number
|
||||
@@ -37,23 +38,29 @@ export function FlywheelKPICard() {
|
||||
const [error, setError] = useState(false)
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
|
||||
// C2: HTTP fallback (initial load + 30s poll when WS unavailable)
|
||||
// C2: HTTP fallback (initial load + 30s poll)
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
let pollId: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const load = () => {
|
||||
const loadSummary = () => {
|
||||
fetch(`${API_BASE}/api/v1/stats/summary`)
|
||||
.then(r => r.ok ? r.json() : Promise.reject(r.status))
|
||||
.then(d => { if (!cancelled) { setData(d); setError(false) } })
|
||||
.catch(() => { if (!cancelled) setError(true) })
|
||||
}
|
||||
|
||||
// 載入飛輪節點狀態(C4 用)
|
||||
fetch(`${API_BASE}/api/v1/stats/flywheel`)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(d => { if (!cancelled && d) setFlowData(d) })
|
||||
.catch(() => {})
|
||||
const loadFlow = () => {
|
||||
fetch(`${API_BASE}/api/v1/stats/flywheel`)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(d => { if (!cancelled && d) setFlowData(d) })
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const load = () => {
|
||||
loadSummary()
|
||||
loadFlow()
|
||||
}
|
||||
|
||||
load()
|
||||
|
||||
@@ -63,7 +70,7 @@ export function FlywheelKPICard() {
|
||||
let wsRetryTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const connectWS = () => {
|
||||
if (!WS_BASE || cancelled) return
|
||||
if (!ENABLE_FLYWHEEL_WS || !WS_BASE || cancelled) return
|
||||
const ws = new WebSocket(`${WS_BASE}/api/v1/stats/flywheel/ws`)
|
||||
wsRef.current = ws
|
||||
|
||||
@@ -94,7 +101,7 @@ export function FlywheelKPICard() {
|
||||
}
|
||||
|
||||
connectWS()
|
||||
// Also start polling as backup until WS opens
|
||||
// Production default: HTTP polling stays authoritative unless WS is enabled explicitly.
|
||||
pollId = setInterval(load, 30_000)
|
||||
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user