From 475f2e452d7827a8934bbf26f19cf4ed8ba62430 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 14 May 2026 23:09:12 +0800 Subject: [PATCH] feat(frontend): expand incident timeline event details --- apps/web/messages/en.json | 8 +- apps/web/messages/zh-TW.json | 8 +- .../src/components/incident/incident-card.tsx | 114 +++++++++++++++++- docs/LOGBOOK.md | 25 ++++ 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 04b45a6f6..171b5aff9 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -391,7 +391,13 @@ "affectedServices": "Affected Services", "signalCount": "Signals", "statusLabel": "Status", - "aiProposal": "AI Proposal" + "aiProposal": "AI Proposal", + "processingTimeline": "Processing Timeline", + "timelineLoading": "Loading processing timeline...", + "timelineEvents": "Event Details", + "timelineSource": "Source", + "timelineRoute": "MCP", + "timelineWrites": "Writes" } }, "status": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index aad30e3de..e4a5a3d1b 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -392,7 +392,13 @@ "affectedServices": "影響服務", "signalCount": "信號數", "statusLabel": "狀態", - "aiProposal": "AI 提案" + "aiProposal": "AI 提案", + "processingTimeline": "處理歷程", + "timelineLoading": "載入處理歷程...", + "timelineEvents": "事件明細", + "timelineSource": "來源", + "timelineRoute": "MCP", + "timelineWrites": "寫入" } }, "status": { diff --git a/apps/web/src/components/incident/incident-card.tsx b/apps/web/src/components/incident/incident-card.tsx index 297b1f95c..809ba3732 100644 --- a/apps/web/src/components/incident/incident-card.tsx +++ b/apps/web/src/components/incident/incident-card.tsx @@ -101,6 +101,40 @@ function formatTimelineTime(value: string | null): string { } } +function asRecord(value: unknown): Record { + return value && typeof value === 'object' && !Array.isArray(value) + ? value as Record + : {} +} + +function eventContext(event: IncidentTimelineResponse['events'][number]): Record { + const data = asRecord(event.data) + return asRecord(data.context) +} + +function eventMcpRoute(event: IncidentTimelineResponse['events'][number]): string | null { + const context = eventContext(event) + const route = asRecord(context.mcp_route) + const agent = route.agent_id + const tool = route.tool_name + const scope = route.required_scope + if (!agent && !tool && !scope) return null + return [agent, tool, scope].filter(Boolean).map(String).join('/') +} + +function eventWriteSummary(event: IncidentTimelineResponse['events'][number]): string | null { + const context = eventContext(event) + const incident = context.writes_incident_state + const autoRepair = context.writes_auto_repair_result + if (incident == null && autoRepair == null) return null + return `incident=${String(incident)} autoRepair=${String(autoRepair)}` +} + +function compactTimelineText(value: string | null | undefined, fallback = '--'): string { + if (!value) return fallback + return value.length > 96 ? `${value.slice(0, 96)}...` : value +} + // ============================================================================= // 2026-04-02 Claude Code: Phase R-UI2 handleApprove/Reject 重複邏輯抽取 // useApprovalAction — 統一 setup/teardown:loading 狀態、timeout、error 處理 @@ -425,7 +459,7 @@ export function IncidentCard({ incident, decision, onApprovalChange }: IncidentC fontFamily: 'inherit', }} > - {timelineExpanded ? '▾' : '▸'} 處理歷程 + {timelineExpanded ? '▾' : '▸'} {t('processingTimeline')} {timelineData?.ascii_timeline && ( {timelineLoading && ( -
載入處理歷程...
+
{t('timelineLoading')}
)} {timelineError && (
{timelineError}
@@ -502,6 +536,82 @@ export function IncidentCard({ incident, decision, onApprovalChange }: IncidentC ))} + {timelineData.events.length > 0 && ( +
+
+ {t('timelineEvents')} +
+
+ {timelineData.events.slice(-8).reverse().map((event, index) => { + const route = eventMcpRoute(event) + const writes = eventWriteSummary(event) + return ( +
+
+ + {event.stage} + + + {event.title} + +
+
+ {t('timelineSource')}: {event.source_table ?? '--'} · {formatTimelineTime(event.timestamp)} +
+ {event.description && ( +
+ {compactTimelineText(event.description)} +
+ )} + {route && ( +
+ {t('timelineRoute')}: {route} +
+ )} + {writes && ( +
+ {t('timelineWrites')}: {writes} +
+ )} +
+ ) + })} +
+
+ )} )} diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 11e6a900b..9fef1feab 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,28 @@ +## 2026-05-14 | T28 IncidentCard 展開 timeline events,告警詳情不再只有壓縮 ascii + +**背景**:T27 已讓 remediation dry-run history 能從 governance 與 Telegram 摘要看到,但前端 IncidentCard 展開「處理歷程」時仍主要顯示 stage 摘要與 ascii timeline。Operator 仍無法在告警卡內直接看到每筆事件來自 `timeline_events` 還是 `alert_operation_log`,以及該事件的 MCP route / write flags。 + +**修正**: +- `IncidentCard` 展開處理歷程時,除了 stage summary,新增最近 8 筆 timeline event 明細。 +- 每筆明細顯示 stage、title、source table、time、description。 +- 如果 event data 內含 `alert_operation_log.context.mcp_route`,會顯示 `agent/tool/scope`。 +- 如果 event data 內含 `writes_incident_state / writes_auto_repair_result`,會直接顯示 write flags,讓 operator 看得出 dry-run 是否真的只讀。 +- `incident.card` 文案補齊 `zh-TW` / `en` i18n。 + +**本地驗證**: +- i18n JSON parse:pass。 +- `pnpm --filter @awoooi/web typecheck`:pass。 +- `pnpm --dir apps/web exec next lint --file src/components/incident/incident-card.tsx`:pass with pre-existing literal-string warnings in legacy approve/reject/proposal markup。 +- `NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --filter @awoooi/web build`:pass。 + +**推版與 production 驗證**: +- 待 T28 commit 推 Gitea main 後驗證。 + +**目前整體進度**: +- Alertmanager 低風險自動修復主線:約 98%。 +- 完整 AI 自動化管理產品化:約 93%。 +- T28 把 Incident 詳情往「可追流程」再推一段;下一段可把同一套事件明細拉到 AwoooP Work Items / Run Timeline,讓產品面不只治理頁能看。 + ## 2026-05-14 | T27 Remediation history read model,前端與 Telegram 可看見試跑重複次數與 MCP 路徑 **背景**:T26 已讓 ADR-100 remediation dry-run 寫入 `alert_operation_log` 與 `timeline_events`,但 governance 頁重新整理後仍看不到某筆補救工作過去試跑幾次、上次跑到哪個 preview、是否有用 MCP、是否仍只讀。Telegram 詳情 / 歷史也還沒有把這段 dry-run history 明確帶出。