feat: expand bgp observability surfaces

This commit is contained in:
linkong
2026-03-31 14:07:28 +08:00
parent ac63bba2a2
commit 552e49bde0
16 changed files with 1319 additions and 47 deletions

View File

@@ -30,6 +30,26 @@ interface BGPEvent {
observed_at: string | null
}
interface BGPCollectorCoverage {
collector: string
city?: string | null
country?: string | null
observation_count: number
recent_24h_observation_count: number
recent_7d_observation_count: number
prefix_count: number
recent_24h_prefix_count: number
recent_7d_prefix_count: number
origin_asn_count: number
peer_asn_count: number
latest_observed_at: string | null
latest_event_type: string | null
baseline_scope: {
countries: string[]
cities: string[]
}
}
interface BGPIncident {
id: number
incident_type: string
@@ -42,6 +62,13 @@ interface BGPIncident {
affected_asns: number[]
affected_collectors: string[]
affected_regions: Array<{ country?: string; city?: string }>
related_cables: Array<{
landing_point?: string
city?: string
country?: string
distance_km?: number
cable_names?: string[]
}>
created_at: string | null
started_at: string | null
}
@@ -60,6 +87,15 @@ interface EventSummary {
by_type: Record<string, number>
}
interface CollectorSummary {
total: number
active_collectors: number
observed_prefixes: number
observed_origins: number
recent_24h_events: number
recent_7d_events: number
}
function severityColor(severity: string) {
if (severity === 'critical') return 'red'
if (severity === 'high') return 'orange'
@@ -72,21 +108,25 @@ function BGP() {
const [incidents, setIncidents] = useState<BGPIncident[]>([])
const [anomalies, setAnomalies] = useState<BGPAnomaly[]>([])
const [events, setEvents] = useState<BGPEvent[]>([])
const [collectors, setCollectors] = useState<BGPCollectorCoverage[]>([])
const [incidentSummary, setIncidentSummary] = useState<Summary | null>(null)
const [anomalySummary, setAnomalySummary] = useState<Summary | null>(null)
const [eventSummary, setEventSummary] = useState<EventSummary | null>(null)
const [collectorSummary, setCollectorSummary] = useState<CollectorSummary | null>(null)
useEffect(() => {
const load = async () => {
setLoading(true)
try {
const [incidentsRes, incidentSummaryRes, anomaliesRes, anomalySummaryRes, eventsRes, eventSummaryRes] = await Promise.all([
const [incidentsRes, incidentSummaryRes, anomaliesRes, anomalySummaryRes, eventsRes, eventSummaryRes, collectorsRes, collectorSummaryRes] = await Promise.all([
axios.get('/api/v1/bgp/incidents', { params: { page_size: 50 } }),
axios.get('/api/v1/bgp/incidents/summary'),
axios.get('/api/v1/bgp/anomalies', { params: { page_size: 100 } }),
axios.get('/api/v1/bgp/anomalies/summary'),
axios.get('/api/v1/bgp/events', { params: { page_size: 20 } }),
axios.get('/api/v1/bgp/events/summary'),
axios.get('/api/v1/bgp/collectors'),
axios.get('/api/v1/bgp/collectors/summary'),
])
setIncidents(incidentsRes.data.data || [])
setIncidentSummary(incidentSummaryRes.data)
@@ -94,6 +134,8 @@ function BGP() {
setAnomalySummary(anomalySummaryRes.data)
setEvents(eventsRes.data.data || [])
setEventSummary(eventSummaryRes.data)
setCollectors(collectorsRes.data.data || [])
setCollectorSummary(collectorSummaryRes.data)
} finally {
setLoading(false)
}
@@ -119,17 +161,17 @@ function BGP() {
<Row gutter={16}>
<Col xs={24} md={8}>
<Card>
<Statistic title="观测事件" value={eventSummary?.total || 0} />
<Statistic title="近24h事件" value={collectorSummary?.recent_24h_events || 0} />
</Card>
</Col>
<Col xs={24} md={8}>
<Card>
<Statistic title="观测站" value={eventSummary?.collector_count || 0} />
<Statistic title="活跃观测站" value={collectorSummary?.active_collectors || 0} />
</Card>
</Col>
<Col xs={24} md={8}>
<Card>
<Statistic title="观测前缀" value={eventSummary?.prefix_count || 0} />
<Statistic title="观测前缀" value={collectorSummary?.observed_prefixes || eventSummary?.prefix_count || 0} />
</Card>
</Col>
</Row>
@@ -152,6 +194,64 @@ function BGP() {
</Col>
</Row>
<Card title="观测站覆盖">
<Table<BGPCollectorCoverage>
rowKey="collector"
loading={loading}
dataSource={collectors}
pagination={{ pageSize: 8 }}
columns={[
{
title: '观测站',
dataIndex: 'collector',
width: 120,
},
{
title: '位置',
width: 180,
render: (_, record) => [record.city, record.country].filter(Boolean).join(', ') || '-',
},
{
title: '近24h事件数',
dataIndex: 'recent_24h_observation_count',
width: 120,
},
{
title: '近7d事件数',
dataIndex: 'recent_7d_observation_count',
width: 120,
},
{
title: '前缀数',
dataIndex: 'prefix_count',
width: 120,
},
{
title: 'Origin ASN 数',
dataIndex: 'origin_asn_count',
width: 140,
},
{
title: '最近事件',
width: 220,
render: (_, record) => {
const time = formatDateTimeZhCN(record.latest_observed_at)
return record.latest_event_type ? `${record.latest_event_type} @ ${time}` : time
},
},
{
title: '日常覆盖范围',
dataIndex: 'baseline_scope',
render: (value: BGPCollectorCoverage['baseline_scope']) => {
const cities = value?.cities?.slice(0, 3).join(' / ') || ''
const countries = value?.countries?.slice(0, 3).join(' / ') || ''
return cities && countries ? `${cities} | ${countries}` : cities || countries || '-'
},
},
]}
/>
</Card>
<Card title="事件列表">
<Table<BGPIncident>
rowKey="id"
@@ -200,6 +300,23 @@ function BGP() {
.join(' / ')
},
},
{
title: '附近基础设施',
dataIndex: 'related_cables',
width: 260,
render: (value: BGPIncident['related_cables']) => {
if (!value || value.length === 0) return '-'
return value
.slice(0, 2)
.map((item) => {
const landing = item.landing_point || [item.city, item.country].filter(Boolean).join(', ')
const cable = item.cable_names && item.cable_names.length > 0 ? item.cable_names[0] : '附近登陆点'
const distance = item.distance_km !== undefined ? ` ${item.distance_km}km` : ''
return `${landing} (${cable}${distance})`
})
.join(' / ')
},
},
{
title: '置信度',
dataIndex: 'confidence',