feat: add monitoring alert read state

This commit is contained in:
hectorzhao
2026-08-16 15:04:26 +08:00
parent 79f5d3f215
commit 482f7ac1ae
15 changed files with 196 additions and 21 deletions
@@ -151,7 +151,7 @@ function severityTag(severity: InfrastructureAlert['severity']) {
return <Tag tone="info"></Tag>;
}
const alertColumns: Array<TableColumn<InfrastructureAlert>> = [
function makeAlertColumns(onMarkRead: (alert: InfrastructureAlert) => void, readingFingerprint: string): Array<TableColumn<InfrastructureAlert>> { return [
{ key: 'severity', title: '级别', width: '82px', render: (record) => severityTag(record.severity) },
{
key: 'alert', title: '告警', width: '280px', render: (record) => (
@@ -162,7 +162,12 @@ const alertColumns: Array<TableColumn<InfrastructureAlert>> = [
{ key: 'value', title: '当前值 / 阈值', width: '150px', render: (record) => `${record.currentValue || '—'} / ${record.threshold || '—'}` },
{ key: 'startedAt', title: '开始时间', width: '150px', render: (record) => formatTime(record.startedAt) },
{ key: 'duration', title: '持续时间', width: '120px', render: (record) => formatDuration(record.startedAt) },
];
{
key: 'actions', title: '操作', width: '112px', render: (record) => record.acknowledged
? <Tag tone="neutral"></Tag>
: <Button disabled={readingFingerprint === record.fingerprint} icon={<CheckCircle2 size={14} />} onClick={() => onMarkRead(record)} size="sm" variant="ghost">{readingFingerprint === record.fingerprint ? '处理中' : '标记已读'}</Button>,
},
]; }
export function AdminSystemMonitoringPage() {
const [range, setRange] = useState<InfrastructureMonitoringRange>('24h');
@@ -174,6 +179,8 @@ export function AdminSystemMonitoringPage() {
const [showSettings, setShowSettings] = useState(false);
const [settingsError, setSettingsError] = useState('');
const [savingSettings, setSavingSettings] = useState(false);
const [readingFingerprint, setReadingFingerprint] = useState('');
const [readError, setReadError] = useState('');
const requestSequence = useRef(0);
const pendingRequests = useRef(0);
@@ -226,6 +233,25 @@ export function AdminSystemMonitoringPage() {
}
}, [draftThresholds, loadData, settings]);
const markAlertRead = useCallback(async (alert: InfrastructureAlert) => {
setReadingFingerprint(alert.fingerprint);
setReadError('');
try {
const result = await adminApi.markInfrastructureAlertRead(alert.fingerprint, alert.startedAt);
setOverview((current) => current ? {
...current,
alerts: current.alerts.map((item) => item.fingerprint === result.fingerprint && Date.parse(item.startedAt) === Date.parse(result.activeAt)
? { ...item, acknowledged: true, acknowledgedAt: result.acknowledgedAt }
: item),
} : current);
window.dispatchEvent(new Event('cmpp-infrastructure-alert-count-refresh'));
} catch (reason) {
setReadError(reason instanceof Error ? reason.message : '活动告警标记已读失败');
} finally {
setReadingFingerprint('');
}
}, []);
useEffect(() => {
void loadData(true);
void loadSettings();
@@ -263,6 +289,7 @@ export function AdminSystemMonitoringPage() {
const metrics = overview?.metrics;
const serviceHealthy = overview?.summary.serviceHealthy ?? 0;
const serviceTotal = overview?.summary.serviceTotal ?? 6;
const alertColumns = useMemo(() => makeAlertColumns((alert) => { void markAlertRead(alert); }, readingFingerprint), [markAlertRead, readingFingerprint]);
return (
<section className="page-stack admin-system-monitoring-page">
@@ -364,6 +391,7 @@ export function AdminSystemMonitoringPage() {
<section className="surface system-monitoring-alerts" id="active-alerts">
<header><div><AlertTriangle size={18} /><strong></strong><Tag tone={overview?.summary.activeAlerts ? 'warning' : 'success'}>{overview?.summary.activeAlerts ?? 0}</Tag></div><span><Clock3 size={14} /> {formatTime(overview?.collectedAt ?? null)}</span></header>
{readError ? <div className="system-monitoring-unavailable" role="alert"><AlertTriangle size={18} /><div><strong></strong><span>{readError}</span></div></div> : null}
<Table columns={alertColumns} data={overview?.alerts ?? []} emptyText={overview?.available ? '当前没有活动告警' : '监控不可用,无法读取活动告警'} pagination={false} rowKey="fingerprint" />
</section>