feat: add bgp observability and admin ui improvements
This commit is contained in:
159
frontend/src/pages/BGP/BGP.tsx
Normal file
159
frontend/src/pages/BGP/BGP.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Alert, Card, Col, Row, Space, Statistic, Table, Tag, Typography } from 'antd'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
|
||||
const { Title, Text } = Typography
|
||||
|
||||
interface BGPAnomaly {
|
||||
id: number
|
||||
source: string
|
||||
anomaly_type: string
|
||||
severity: string
|
||||
status: string
|
||||
prefix: string | null
|
||||
origin_asn: number | null
|
||||
new_origin_asn: number | null
|
||||
confidence: number
|
||||
summary: string
|
||||
created_at: string | null
|
||||
}
|
||||
|
||||
interface Summary {
|
||||
total: number
|
||||
by_type: Record<string, number>
|
||||
by_severity: Record<string, number>
|
||||
by_status: Record<string, number>
|
||||
}
|
||||
|
||||
function severityColor(severity: string) {
|
||||
if (severity === 'critical') return 'red'
|
||||
if (severity === 'high') return 'orange'
|
||||
if (severity === 'medium') return 'gold'
|
||||
return 'blue'
|
||||
}
|
||||
|
||||
function BGP() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [anomalies, setAnomalies] = useState<BGPAnomaly[]>([])
|
||||
const [summary, setSummary] = useState<Summary | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const [anomaliesRes, summaryRes] = await Promise.all([
|
||||
axios.get('/api/v1/bgp/anomalies', { params: { page_size: 100 } }),
|
||||
axios.get('/api/v1/bgp/anomalies/summary'),
|
||||
])
|
||||
setAnomalies(anomaliesRes.data.data || [])
|
||||
setSummary(summaryRes.data)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
load()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 4 }}>BGP观测</Title>
|
||||
<Text type="secondary">查看实时与回放阶段归一化出的路由异常。</Text>
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="该视图展示的是控制平面异常,不代表真实业务流量路径。"
|
||||
/>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={8}>
|
||||
<Card>
|
||||
<Statistic title="异常总数" value={summary?.total || 0} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card>
|
||||
<Statistic title="Critical" value={summary?.by_severity?.critical || 0} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card>
|
||||
<Statistic title="Active" value={summary?.by_status?.active || 0} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card title="异常列表">
|
||||
<Table<BGPAnomaly>
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={anomalies}
|
||||
pagination={{ pageSize: 10 }}
|
||||
columns={[
|
||||
{
|
||||
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: 'prefix',
|
||||
width: 180,
|
||||
render: (value: string | null) => value || '-',
|
||||
},
|
||||
{
|
||||
title: 'ASN',
|
||||
key: 'asn',
|
||||
width: 160,
|
||||
render: (_, record) => {
|
||||
if (record.origin_asn && record.new_origin_asn) {
|
||||
return `AS${record.origin_asn} -> AS${record.new_origin_asn}`
|
||||
}
|
||||
if (record.origin_asn) {
|
||||
return `AS${record.origin_asn}`
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '来源',
|
||||
dataIndex: 'source',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '置信度',
|
||||
dataIndex: 'confidence',
|
||||
width: 120,
|
||||
render: (value: number) => `${Math.round((value || 0) * 100)}%`,
|
||||
},
|
||||
{
|
||||
title: '摘要',
|
||||
dataIndex: 'summary',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default BGP
|
||||
Reference in New Issue
Block a user