feat(platform): add alert handling command queue

This commit is contained in:
lingniu
2026-07-04 19:30:55 +08:00
parent 41c5e0b338
commit 0b433c2592
3 changed files with 114 additions and 2 deletions

View File

@@ -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 (
<div className="vp-page">
@@ -146,6 +163,51 @@ export function NotificationRules() {
</Card>
</div>
<Card
bordered
loading={loading}
title={<Space><span></span><Button size="small" aria-label="复制待通知汇总" disabled={priorityIssues.length === 0} icon={<IconCopy />} onClick={() => copyText(priorityIssueDigest(plan, release), '待通知告警')}></Button></Space>}
style={{ marginTop: 16 }}
>
<div className="vp-alert-command-board">
<div className="vp-alert-command-grid">
{[
{ 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) => (
<div key={item.label} className="vp-alert-command-item">
<Tag color={item.color}>{item.label}</Tag>
<div>{item.value}</div>
</div>
))}
</div>
<div className="vp-alert-next-action">
<div className="vp-current-service-title"></div>
{nextPriorityIssue ? (
<>
<Typography.Text strong>{nextPriorityIssue.vehicleLabel}</Typography.Text>
<Typography.Text type="secondary">{nextPriorityIssue.actionDetail || nextPriorityIssue.actionLabel}</Typography.Text>
<Space wrap>
<Tag color={nextPriorityIssue.priority === 'P0' ? 'red' : 'orange'}>{nextPriorityIssue.priority}</Tag>
<Tag color={evidenceColor(nextPriorityIssue)}>{evidenceSummary(nextPriorityIssue)}</Tag>
<Tag color="blue">{nextPriorityIssue.sla}</Tag>
</Space>
<Space wrap>
<Button size="small" aria-label="下一步车辆服务" disabled={!nextPriorityIssue.vehicleHash} onClick={() => navigateHash(nextPriorityIssue.vehicleHash)}></Button>
<Button size="small" aria-label="下一步实时证据" disabled={!nextPriorityIssue.realtimeHash} onClick={() => navigateHash(nextPriorityIssue.realtimeHash)}></Button>
<Button size="small" aria-label="下一步轨迹证据" disabled={!nextPriorityIssue.historyHash} onClick={() => navigateHash(nextPriorityIssue.historyHash)}></Button>
<Button size="small" aria-label="下一步RAW证据" disabled={!nextPriorityIssue.rawHash} onClick={() => navigateHash(nextPriorityIssue.rawHash)}>RAW证据</Button>
</Space>
</>
) : (
<Tag color="green"></Tag>
)}
</div>
</div>
</Card>
<Card
bordered
title={<Space><span></span><Button size="small" aria-label="复制通知规则Runbook" icon={<IconCopy />} onClick={() => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')}>Runbook</Button><Button size="small" loading={loading} onClick={load}></Button></Space>}
@@ -185,7 +247,7 @@ export function NotificationRules() {
<Card
bordered
loading={loading}
title={<Space><span></span><Button size="small" aria-label="复制待通知汇总" disabled={priorityIssues.length === 0} icon={<IconCopy />} onClick={() => copyText(priorityIssueDigest(plan, release), '待通知告警')}></Button></Space>}
title="当前待通知告警"
style={{ marginTop: 16 }}
>
<Table<QualityPriorityIssue>
@@ -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) => <Tag color={evidenceColor(row)}>{evidenceSummary(row)}</Tag> },
{ title: '建议动作', width: 160, dataIndex: 'actionLabel' },
{ title: '最后时间', width: 170, dataIndex: 'lastSeen' },
{