From 0b433c2592d4c63eecc564766ca6112dd9269619 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 19:30:55 +0800 Subject: [PATCH] feat(platform): add alert handling command queue --- .../apps/web/src/pages/NotificationRules.tsx | 65 ++++++++++++++++++- .../apps/web/src/styles/global.css | 44 +++++++++++++ .../apps/web/src/test/App.test.tsx | 7 +- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx b/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx index 1adc8991..1a95a5cf 100644 --- a/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx +++ b/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx @@ -76,6 +76,19 @@ function priorityIssueDigest(plan: QualityNotificationPlan | null, release?: str ].join('\n'); } +function countEvidenceLinks(issue: QualityPriorityIssue) { + return [issue.vehicleHash, issue.realtimeHash, issue.historyHash, issue.rawHash].filter(Boolean).length; +} + +function evidenceSummary(issue: QualityPriorityIssue) { + const count = countEvidenceLinks(issue); + return count >= 4 ? '证据完整' : `缺 ${4 - count} 类证据`; +} + +function evidenceColor(issue: QualityPriorityIssue): 'green' | 'orange' { + return countEvidenceLinks(issue) >= 4 ? 'green' : 'orange'; +} + function navigateHash(hash: string) { if (!hash) return; window.location.hash = hash; @@ -119,6 +132,10 @@ export function NotificationRules() { const activeRuleCount = plan?.activeRuleCount ?? rules.filter((rule) => rule.count > 0).length; const p0RuleCount = plan?.p0RuleCount ?? rules.filter((rule) => rule.count > 0 && rule.level === 'P0').length; const release = health?.runtime?.platformRelease ?? ''; + const p0IssueCount = priorityIssues.filter((issue) => issue.priority === 'P0').length; + const evidenceCompleteCount = priorityIssues.filter((issue) => countEvidenceLinks(issue) >= 4).length; + const primaryOwner = rules.find((rule) => rule.count > 0)?.owner || policies[0]?.target || '-'; + const nextPriorityIssue = priorityIssues[0]; return (
@@ -146,6 +163,51 @@ export function NotificationRules() {
+ 告警处置队列} + style={{ marginTop: 16 }} + > +
+
+ {[ + { label: '待通知告警', value: priorityIssues.length.toLocaleString(), color: priorityIssues.length > 0 ? 'orange' as const : 'green' as const }, + { label: 'P0优先', value: p0IssueCount.toLocaleString(), color: p0IssueCount > 0 ? 'red' as const : 'green' as const }, + { label: '证据完整', value: `${evidenceCompleteCount}/${priorityIssues.length || 0}`, color: evidenceCompleteCount === priorityIssues.length ? 'green' as const : 'orange' as const }, + { label: '主责任方', value: primaryOwner, color: 'blue' as const } + ].map((item) => ( +
+ {item.label} +
{item.value}
+
+ ))} +
+
+
下一步处置
+ {nextPriorityIssue ? ( + <> + {nextPriorityIssue.vehicleLabel} + {nextPriorityIssue.actionDetail || nextPriorityIssue.actionLabel} + + {nextPriorityIssue.priority} + {evidenceSummary(nextPriorityIssue)} + {nextPriorityIssue.sla} + + + + + + + + + ) : ( + 暂无待通知告警 + )} +
+
+
+ 规则运行手册} @@ -185,7 +247,7 @@ export function NotificationRules() { 当前待通知告警} + title="当前待通知告警" style={{ marginTop: 16 }} > @@ -197,6 +259,7 @@ export function NotificationRules() { { title: '车辆', width: 210, dataIndex: 'vehicleLabel' }, { title: '问题', width: 130, render: (_: unknown, row: QualityPriorityIssue) => qualityIssueLabel(row.issueType) }, { title: 'SLA', width: 120, dataIndex: 'sla' }, + { title: '证据完整度', width: 120, render: (_: unknown, row: QualityPriorityIssue) => {evidenceSummary(row)} }, { title: '建议动作', width: 160, dataIndex: 'actionLabel' }, { title: '最后时间', width: 170, dataIndex: 'lastSeen' }, { diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index a6b8976d..5a2133b0 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -681,6 +681,48 @@ button.vp-realtime-command-item:focus-visible { line-height: 28px; } +.vp-alert-command-board { + display: grid; + grid-template-columns: minmax(0, 1fr) 360px; + gap: 16px; +} + +.vp-alert-command-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-alert-command-item { + min-height: 92px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + display: grid; + align-content: space-between; + gap: 10px; +} + +.vp-alert-command-item > div { + color: var(--vp-text); + font-size: 22px; + font-weight: 700; + line-height: 28px; + word-break: break-word; +} + +.vp-alert-next-action { + min-height: 132px; + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.28); + border-radius: var(--vp-radius); + background: #f5f9ff; + display: grid; + align-content: start; + gap: 10px; +} + .vp-risk-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -1184,6 +1226,8 @@ button.vp-realtime-command-item:focus-visible { .vp-conclusion-grid, .vp-monitor-layout, .vp-alert-flow, + .vp-alert-command-board, + .vp-alert-command-grid, .vp-risk-grid, .vp-alert-ops-grid, .vp-stat-workspace, diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index 7973cf69..4be9f4da 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -3349,8 +3349,13 @@ test('renders notification rules as a standalone operations page', async () => { expect(screen.getByText('车辆无任何来源证据')).toBeInTheDocument(); expect(screen.getByText('P0 实时中断')).toBeInTheDocument(); expect(screen.getByText('接入运维 + 业务责任人')).toBeInTheDocument(); + expect(screen.getByText('告警处置队列')).toBeInTheDocument(); + expect(screen.getByText('下一步处置')).toBeInTheDocument(); + expect(screen.getByText('主责任方')).toBeInTheDocument(); + expect(screen.getAllByText('证据完整').length).toBeGreaterThan(0); + expect(screen.getByText('核对平台转发配置')).toBeInTheDocument(); expect(screen.getByText('当前待通知告警')).toBeInTheDocument(); - expect(screen.getByText('粤A规则1 / VIN-RULES-001')).toBeInTheDocument(); + expect(screen.getAllByText('粤A规则1 / VIN-RULES-001').length).toBeGreaterThan(0); expect(screen.getByText('确认平台转发')).toBeInTheDocument(); expect(screen.getByText('通知规则页待通知告警')).toBeInTheDocument(); expect(screen.getByText('platform-rules-test')).toBeInTheDocument();