268 lines
9.3 KiB
TypeScript
268 lines
9.3 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react'
|
|
|
|
import { ReloadOutlined, RobotOutlined } from '@ant-design/icons'
|
|
import {
|
|
Alert,
|
|
Button,
|
|
Card,
|
|
Descriptions,
|
|
Modal,
|
|
Space,
|
|
Spin,
|
|
Statistic,
|
|
Table,
|
|
Tabs,
|
|
Tag,
|
|
Typography,
|
|
message,
|
|
type TableColumnsType,
|
|
} from 'antd'
|
|
|
|
import AppLayout from '../../components/AppLayout/AppLayout'
|
|
import MarkdownRenderer from '../../components/MarkdownRenderer/MarkdownRenderer'
|
|
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
|
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
|
import type { BGPAnomaly, BGPBriefRecord, BGPIncident } from '../../services/situational-awareness'
|
|
import { getSituationalAwarenessGateway } from '../../services/situational-awareness'
|
|
import { formatDateTimeZhCN } from '../../utils/datetime'
|
|
|
|
const { Text } = Typography
|
|
|
|
const gateway = getSituationalAwarenessGateway()
|
|
|
|
function severityColor(severity: string) {
|
|
if (severity === 'critical') return 'red'
|
|
if (severity === 'high') return 'orange'
|
|
if (severity === 'medium') return 'gold'
|
|
return 'blue'
|
|
}
|
|
|
|
export function BGPAlertsPanel() {
|
|
const [messageApi, contextHolder] = message.useMessage()
|
|
const [loading, setLoading] = useState(false)
|
|
const [incidents, setIncidents] = useState<BGPIncident[]>([])
|
|
const [anomalies, setAnomalies] = useState<BGPAnomaly[]>([])
|
|
const [briefLoading, setBriefLoading] = useState(false)
|
|
const [briefModalOpen, setBriefModalOpen] = useState(false)
|
|
const [brief, setBrief] = useState<BGPBriefRecord | null>(null)
|
|
|
|
const loadData = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const [incidentRows, anomalyRows] = await Promise.all([
|
|
gateway.getBGPIncidents(50),
|
|
gateway.getBGPAnomalies(80),
|
|
])
|
|
setIncidents(incidentRows)
|
|
setAnomalies(anomalyRows)
|
|
} catch (error) {
|
|
console.error('Failed to load BGP alerts:', error)
|
|
messageApi.error('BGP 告警加载失败')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
void loadData()
|
|
}, [])
|
|
|
|
const summary = useMemo(
|
|
() => ({
|
|
activeIncidents: incidents.filter((item) => item.status === 'active').length,
|
|
criticalIncidents: incidents.filter((item) => item.severity === 'critical').length,
|
|
activeAnomalies: anomalies.filter((item) => item.status === 'active').length,
|
|
highRiskAnomalies: anomalies.filter((item) => ['critical', 'high'].includes(item.severity)).length,
|
|
}),
|
|
[anomalies, incidents],
|
|
)
|
|
|
|
const handleGenerateBrief = async () => {
|
|
setBriefModalOpen(true)
|
|
setBriefLoading(true)
|
|
try {
|
|
const record = await gateway.generateBGPBrief()
|
|
setBrief(record)
|
|
messageApi.success('BGP AI 简报已生成')
|
|
} catch (error) {
|
|
console.error('Failed to generate BGP brief:', error)
|
|
messageApi.error('BGP AI 简报生成失败')
|
|
} finally {
|
|
setBriefLoading(false)
|
|
}
|
|
}
|
|
|
|
const incidentColumns: TableColumnsType<BGPIncident> = [
|
|
{
|
|
title: '开始时间',
|
|
dataIndex: 'started_at',
|
|
width: 180,
|
|
render: (value: string | null) => formatDateTimeZhCN(value),
|
|
},
|
|
{ title: '类型', dataIndex: 'incident_type', width: 180 },
|
|
{
|
|
title: '严重度',
|
|
dataIndex: 'severity',
|
|
width: 120,
|
|
render: (value: string) => <Tag color={severityColor(value)}>{value}</Tag>,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 120,
|
|
render: (value: string) => <Tag color={value === 'active' ? 'red' : 'blue'}>{value}</Tag>,
|
|
},
|
|
{
|
|
title: '影响前缀',
|
|
dataIndex: 'affected_prefixes',
|
|
width: 220,
|
|
render: (value: string[]) => (value && value.length > 0 ? value.join(', ') : '-'),
|
|
},
|
|
{ title: '摘要', dataIndex: 'summary', width: 320 },
|
|
]
|
|
|
|
const anomalyColumns: TableColumnsType<BGPAnomaly> = [
|
|
{
|
|
title: '时间',
|
|
dataIndex: 'created_at',
|
|
width: 180,
|
|
render: (value: string | null) => formatDateTimeZhCN(value),
|
|
},
|
|
{ title: '类型', dataIndex: 'anomaly_type', width: 180 },
|
|
{
|
|
title: '严重度',
|
|
dataIndex: 'severity',
|
|
width: 120,
|
|
render: (value: string) => <Tag color={severityColor(value)}>{value}</Tag>,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 120,
|
|
render: (value: string) => <Tag color={value === 'active' ? 'red' : 'blue'}>{value}</Tag>,
|
|
},
|
|
{
|
|
title: '前缀',
|
|
dataIndex: 'prefix',
|
|
width: 200,
|
|
render: (value: string | null) => value || '-',
|
|
},
|
|
{ title: '摘要', dataIndex: 'summary', width: 320 },
|
|
]
|
|
|
|
return (
|
|
<AppLayout>
|
|
<div className="alerts-tab-panel bgp-alerts-page">
|
|
{contextHolder}
|
|
<Space className="bgp-alerts-page__stack" direction="vertical" size={16} style={{ width: '100%' }}>
|
|
<div className="alerts-tab-panel__head">
|
|
<div>
|
|
<Text strong>BGP 告警</Text>
|
|
<div className="alerts-tab-panel__subtitle">把 BGP incidents 与 anomalies 当作告警工作台来快速筛查控制平面风险。</div>
|
|
</div>
|
|
<Space>
|
|
<Button type="primary" icon={<RobotOutlined />} loading={briefLoading} onClick={() => void handleGenerateBrief()}>
|
|
生成 BGP AI 简报
|
|
</Button>
|
|
<Button icon={<ReloadOutlined />} onClick={() => void loadData()}>
|
|
刷新
|
|
</Button>
|
|
</Space>
|
|
</div>
|
|
|
|
<Alert type="info" showIcon message="这里聚焦 BGP 风险信号本身,不等同于系统平台运行告警。" />
|
|
|
|
<Scrollbar className="alerts-summary-scroll bgp-alerts-page__summary-scroll">
|
|
<div className="alerts-summary-grid" style={{ gap: '12px' }}>
|
|
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
|
<Card><Statistic title="活跃事件" value={summary.activeIncidents} /></Card>
|
|
</div>
|
|
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
|
<Card><Statistic title="严重事件" value={summary.criticalIncidents} valueStyle={{ color: '#ff4d4f' }} /></Card>
|
|
</div>
|
|
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
|
<Card><Statistic title="活跃异常" value={summary.activeAnomalies} /></Card>
|
|
</div>
|
|
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
|
<Card><Statistic title="高风险异常" value={summary.highRiskAnomalies} valueStyle={{ color: '#fa8c16' }} /></Card>
|
|
</div>
|
|
</div>
|
|
</Scrollbar>
|
|
|
|
<Card className="bgp-alerts-page__table-card">
|
|
<Tabs
|
|
className="bgp-alerts-page__tabs"
|
|
items={[
|
|
{
|
|
key: 'incidents',
|
|
label: 'BGP 事件',
|
|
children: (
|
|
<TableScrollRegion className="bgp-alerts-page__table-region">
|
|
<Table<BGPIncident>
|
|
columns={incidentColumns}
|
|
dataSource={incidents}
|
|
loading={loading}
|
|
pagination={false}
|
|
rowKey="id"
|
|
scroll={{ x: 1200, y: 480 }}
|
|
tableLayout="fixed"
|
|
/>
|
|
</TableScrollRegion>
|
|
),
|
|
},
|
|
{
|
|
key: 'anomalies',
|
|
label: 'BGP 异常',
|
|
children: (
|
|
<TableScrollRegion className="bgp-alerts-page__table-region">
|
|
<Table<BGPAnomaly>
|
|
columns={anomalyColumns}
|
|
dataSource={anomalies}
|
|
loading={loading}
|
|
pagination={false}
|
|
rowKey="id"
|
|
scroll={{ x: 1100, y: 480 }}
|
|
tableLayout="fixed"
|
|
/>
|
|
</TableScrollRegion>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
</Card>
|
|
</Space>
|
|
|
|
<Modal
|
|
title="BGP AI 简报"
|
|
open={briefModalOpen}
|
|
onCancel={() => setBriefModalOpen(false)}
|
|
footer={null}
|
|
width={920}
|
|
className="bgp-page__brief-modal"
|
|
style={{ top: 24 }}
|
|
styles={{ body: { paddingTop: 12 } }}
|
|
>
|
|
{briefLoading ? (
|
|
<div className="bgp-page__brief-loading">
|
|
<Spin tip="正在生成 BGP AI 简报..." />
|
|
</div>
|
|
) : brief ? (
|
|
<Scrollbar className="bgp-page__brief-modal-body">
|
|
<Descriptions size="small" column={3} className="bgp-page__brief-meta">
|
|
<Descriptions.Item label="Provider">{brief.provider || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="模型">{brief.model || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="生成时间">{formatDateTimeZhCN(brief.generated_at)}</Descriptions.Item>
|
|
</Descriptions>
|
|
<MarkdownRenderer markdown={brief.content_markdown} />
|
|
</Scrollbar>
|
|
) : (
|
|
<Text type="secondary">当前没有可查看的 BGP 简报。</Text>
|
|
)}
|
|
</Modal>
|
|
</div>
|
|
</AppLayout>
|
|
)
|
|
}
|
|
|
|
export default BGPAlertsPanel
|