diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 4cfd0c1f..7ced2ba6 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -417,6 +417,19 @@ "signalCount": "Signals", "statusLabel": "Status", "aiProposal": "AI Proposal", + "aiProposalPreview": "AI Proposal: {action}", + "flowCurrentLabel": "Current stage", + "flowNextLabel": "Next step", + "flowComplete": "Complete", + "flowStages": { + "alert": "Alert received", + "detection": "AI detection", + "analysis": "AI analysis", + "proposal": "Proposal generated", + "approval": "Waiting approval", + "execution": "Repair execution", + "resolved": "Complete" + }, "processingTimeline": "Processing Timeline", "timelineLoading": "Loading processing timeline...", "timelineEvents": "Event Details", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index a86f0a32..8101a6b6 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -418,6 +418,19 @@ "signalCount": "信號數", "statusLabel": "狀態", "aiProposal": "AI 提案", + "aiProposalPreview": "AI 提案:{action}", + "flowCurrentLabel": "目前階段", + "flowNextLabel": "下一步", + "flowComplete": "已完成", + "flowStages": { + "alert": "告警收到", + "detection": "AI 偵測", + "analysis": "AI 分析", + "proposal": "提案生成", + "approval": "等待授權", + "execution": "執行修復", + "resolved": "完成" + }, "processingTimeline": "處理歷程", "timelineLoading": "載入處理歷程...", "timelineEvents": "事件明細", diff --git a/apps/web/src/components/incident/incident-card.tsx b/apps/web/src/components/incident/incident-card.tsx index b4cc3f39..95eb9d8c 100644 --- a/apps/web/src/components/incident/incident-card.tsx +++ b/apps/web/src/components/incident/incident-card.tsx @@ -46,6 +46,16 @@ const SEV_CONFIG = { P3: { barColor: '#22C55E', label: 'P3', labelBg: 'rgba(34,197,94,0.1)', labelColor: '#16a34a' }, } as const +const FLOW_STAGE_ORDER: FlowStage[] = [ + 'alert', + 'detection', + 'analysis', + 'proposal', + 'approval', + 'execution', + 'resolved', +] + /** 根據 incident + decision evidence 對應 FlowStage */ function toFlowStage(status: string, severity: string, decision?: DecisionInfo | null): FlowStage { const normalizedStatus = status.toLowerCase() @@ -66,6 +76,13 @@ function toFlowStage(status: string, severity: string, decision?: DecisionInfo | return severity === 'P0' ? 'alert' : 'detection' } +function nextFlowStage(stage: FlowStage, isResolved: boolean): FlowStage | null { + if (isResolved) return null + const index = FLOW_STAGE_ORDER.indexOf(stage) + if (index < 0 || index >= FLOW_STAGE_ORDER.length - 1) return null + return FLOW_STAGE_ORDER[index + 1] +} + /** 格式化持續時間 */ function formatDuration(createdAt: string | undefined): string { if (!createdAt) return '--' @@ -217,6 +234,16 @@ export function IncidentCard({ incident, decision, onApprovalChange }: IncidentC const sevCfg = SEV_CONFIG[sev] ?? SEV_CONFIG.P3 const flowStage = toFlowStage(incidentStatus, incident.severity, decision) const isResolved = incidentStatus === 'resolved' || incidentStatus === 'closed' + const nextStage = nextFlowStage(flowStage, isResolved) + const flowStageLabels: Record = { + alert: t('flowStages.alert'), + detection: t('flowStages.detection'), + analysis: t('flowStages.analysis'), + proposal: t('flowStages.proposal'), + approval: t('flowStages.approval'), + execution: t('flowStages.execution'), + resolved: t('flowStages.resolved'), + } const serviceName = incident.affected_services?.[0] ?? '--' const duration = formatDuration(incident.created_at) @@ -315,9 +342,9 @@ export function IncidentCard({ incident, decision, onApprovalChange }: IncidentC ) case 'approved': - return ✓ {t('approved')} + return {t('approved')} case 'rejected': - return ✗ {t('rejected')} + return {t('rejected')} case 'error': case 'timeout': return ( @@ -352,7 +379,7 @@ export function IncidentCard({ incident, decision, onApprovalChange }: IncidentC }} title={isAnalyzing ? t('analyzing') : decisionAction || t('authorizeExecution')} > - ✓ {t('authorize')} + {t('authorize')} - {isAnalyzing && } + {isAnalyzing && ( + + )} ) } @@ -425,6 +463,31 @@ export function IncidentCard({ incident, decision, onApprovalChange }: IncidentC {/* 流程狀態圖 */} +
+ + {t('flowCurrentLabel')}: {flowStageLabels[flowStage]} + + / + + {t('flowNextLabel')}: {nextStage ? flowStageLabels[nextStage] : t('flowComplete')} + +
+ {/* Impact 指標列 */}
- ▸ {t('aiProposal')}: {decisionAction.slice(0, 50)}{decisionAction.length > 50 ? '...' : ''} + {t('aiProposalPreview', { action: compactTimelineText(decisionAction, '') })} {aiExpanded && (