diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index 2674f1be..3f0e6f72 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -40,7 +40,7 @@ import type { IncidentResponse } from '@/lib/api-client' // Utility: Map IncidentResponse to DualStateIncidentCard props // ============================================================================= -function mapToDualState(incident: IncidentResponse): { +function mapToDualState(incident: IncidentResponse | null | undefined): { id: string serviceName: string status: 'normal' | 'alert' @@ -48,32 +48,65 @@ function mapToDualState(incident: IncidentResponse): { message: string timestamp: string } { - // P0/P1 視為異常 (alert),P2/P3 視為正常 (normal) - const isAlert = incident.severity === 'P0' || incident.severity === 'P1' - - // Tier 判定: proposal_count > 0 且為 P0 = Tier 3, P1 = Tier 2, else Tier 1 - let tier: 1 | 2 | 3 | undefined = undefined - if (isAlert && incident.proposal_count > 0) { - tier = incident.severity === 'P0' ? 3 : 2 - } else if (isAlert) { - tier = 1 + // 防禦性檢查: 若 incident 無效則返回預設值 + if (!incident) { + return { + id: 'unknown', + serviceName: 'Unknown Service', + status: 'normal', + tier: undefined, + message: '資料載入中...', + timestamp: '-', + } } - // 格式化時間 - const date = new Date(incident.created_at) - const timestamp = date.toLocaleString('zh-TW', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }) + // P0/P1/P2 視為異常 (alert),只有 P3 視為正常 (normal) + const severity = incident.severity || 'P3' + const isAlert = severity === 'P0' || severity === 'P1' || severity === 'P2' + + // Tier 判定: P0 = Tier 3 (親核), P1 = Tier 2 (授權), P2+ = Tier 1 (自主) + let tier: 1 | 2 | 3 | undefined = undefined + if (isAlert) { + if (severity === 'P0') { + tier = 3 + } else if (severity === 'P1') { + tier = 2 + } else { + tier = 1 + } + } + + // 安全提取服務名稱 + const services = incident.affected_services || [] + const serviceName = services.length > 0 + ? services[0] + : incident.incident_id?.split('-')[2] || 'Unknown Service' + + // 格式化時間 (安全處理) + let timestamp = '-' + try { + const date = new Date(incident.created_at || Date.now()) + timestamp = date.toLocaleString('zh-TW', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }) + } catch { + timestamp = 'Invalid Date' + } + + // 安全生成訊息 + const signalCount = incident.signal_count ?? 0 + const status = incident.status || 'unknown' + const message = `[${severity}] ${signalCount} 筆告警 | ${status}` return { - id: incident.incident_id, - serviceName: incident.affected_services[0] || 'unknown', + id: incident.incident_id || 'unknown', + serviceName, status: isAlert ? 'alert' : 'normal', tier, - message: `${incident.signal_count} 筆告警 | ${incident.status}`, + message, timestamp, } } @@ -156,7 +189,7 @@ export default function Home({ params }: { params: { locale: string } }) { {/* Active Incidents Section (Phase 7: 真實血脈 + Phase 6.5b 雙態卡片) */} 0 ? 'critical' : 'healthy'} + status={(incidents?.length || 0) > 0 ? 'critical' : 'healthy'} > {isIncidentsLoading ? (
@@ -170,7 +203,7 @@ export default function Home({ params }: { params: { locale: string } }) { {incidentsError}
- ) : incidents.length === 0 ? ( + ) : (incidents?.length || 0) === 0 ? ( /* Nothing.tech 風格平靜態: 系統穩定 */
@@ -185,11 +218,11 @@ export default function Home({ params }: { params: { locale: string } }) {
{/* Phase 6.5b: 雙態戰情室卡片 (脈衝雷達 + Tier 決策層) */}
- {incidents.map((incident) => { + {(incidents || []).map((incident, index) => { const dualProps = mapToDualState(incident) return ( ) @@ -205,9 +238,9 @@ export default function Home({ params }: { params: { locale: string } }) { status="thinking" > 0 ? DEMO_DECISION_CHAIN : null} - incidentId={incidents.length > 0 ? incidents[0].incident_id : undefined} - autoPlay={incidents.length > 0} + decisionChain={(incidents?.length || 0) > 0 ? DEMO_DECISION_CHAIN : null} + incidentId={(incidents?.length || 0) > 0 ? incidents?.[0]?.incident_id : undefined} + autoPlay={(incidents?.length || 0) > 0} maxHeight="300px" />