feat: expand bgp observability surfaces
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user