98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { adminApi } from '@/api/adminApi';
|
|
import { Button, DateRangeInput, Table, type TableColumn } from '@/components/ui';
|
|
import { formatDateTime } from '@/utils/dateTime';
|
|
|
|
type History = Awaited<ReturnType<typeof adminApi.getInfrastructureAlertHistory>>;
|
|
const columns: TableColumn<History['items'][number]>[] = [
|
|
{ key: 'name', title: '告警', width: '280px', render: (row) => row.name },
|
|
{
|
|
key: 'severity',
|
|
title: '级别',
|
|
width: '90px',
|
|
render: (row) => ({ critical: '严重', warning: '警告', info: '提示' })[row.severity] || row.severity,
|
|
},
|
|
{ key: 'instance', title: '服务 / 实例', width: '220px', render: (row) => row.service || row.instance || '主机资源' },
|
|
{ key: 'startedAt', title: '触发时间', width: '180px', render: (row) => formatDateTime(row.startedAt) },
|
|
{
|
|
key: 'lastObservedAt',
|
|
title: '范围内最后采样',
|
|
width: '180px',
|
|
render: (row) => formatDateTime(row.lastObservedAt),
|
|
},
|
|
];
|
|
|
|
export function AlertHistory() {
|
|
const [dates, setDates] = useState(() => {
|
|
const key = (time: number) => new Date(time + 8 * 3600_000).toISOString().slice(0, 10);
|
|
return { start: key(Date.now() - 6 * 86400_000), end: key(Date.now()) };
|
|
});
|
|
const [query, setQuery] = useState({ ...dates, page: 1, revision: 0 });
|
|
const [response, setResponse] = useState<{ query: typeof query; data: History | null; error: string }>();
|
|
const loading = response?.query !== query;
|
|
const data = loading ? null : response?.data;
|
|
const error = loading ? '' : response?.error;
|
|
useEffect(() => {
|
|
let current = true;
|
|
adminApi
|
|
.getInfrastructureAlertHistory(query.start, query.end, query.page)
|
|
.then((result) => {
|
|
if (current) setResponse({ query, data: result, error: '' });
|
|
})
|
|
.catch((reason) => {
|
|
if (current)
|
|
setResponse({ query, data: null, error: reason instanceof Error ? reason.message : '历史告警加载失败' });
|
|
});
|
|
return () => {
|
|
current = false;
|
|
};
|
|
}, [query]);
|
|
return (
|
|
<section className="surface system-monitoring-alerts" aria-label="历史告警记录">
|
|
<header>
|
|
<strong>历史告警记录</strong>
|
|
</header>
|
|
<div className="filter-bar">
|
|
<DateRangeInput
|
|
label="告警日期"
|
|
value={dates}
|
|
onChange={(value) => setDates({ start: value.start || '', end: value.end || '' })}
|
|
/>
|
|
<Button disabled={loading} onClick={() => setQuery({ ...dates, page: 1, revision: query.revision + 1 })}>
|
|
查询
|
|
</Button>
|
|
</div>
|
|
<p className="muted">
|
|
默认近7天,最多31天;读取 Prometheus
|
|
保留的真实触发周期(含等待触发),最后采样不代表准确恢复时间。保留期外或采集缺失的历史无法补齐。
|
|
</p>
|
|
{error ? (
|
|
<p role="alert" className="form-error">
|
|
{error}
|
|
</p>
|
|
) : null}
|
|
<Table
|
|
columns={columns}
|
|
data={loading ? [] : (data?.items ?? [])}
|
|
rowKey="id"
|
|
pagination={false}
|
|
emptyText={loading ? '历史告警加载中…' : error ? '历史告警不可用' : '所选日期没有保留的告警记录'}
|
|
/>
|
|
<div className="table-footer">
|
|
<span>
|
|
共 {data?.total ?? 0} 条 · 第 {data?.page ?? query.page} 页
|
|
</span>
|
|
<Button disabled={loading || query.page <= 1} onClick={() => setQuery({ ...query, page: query.page - 1 })}>
|
|
上一页
|
|
</Button>
|
|
<Button
|
|
disabled={loading || !data || query.page * data.pageSize >= data.total}
|
|
onClick={() => setQuery({ ...query, page: query.page + 1 })}
|
|
>
|
|
下一页
|
|
</Button>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|