feat: improve bgp incident visibility
This commit is contained in:
@@ -86,6 +86,28 @@ async def list_bgp_events(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/events/summary")
|
||||
async def get_bgp_event_summary(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(BGPObservation).where(BGPObservation.source.in_(BGP_SOURCES)))
|
||||
records = result.scalars().all()
|
||||
|
||||
collectors = sorted({record.collector for record in records if record.collector})
|
||||
prefixes = sorted({record.prefix for record in records if record.prefix})
|
||||
by_type: dict[str, int] = {}
|
||||
for record in records:
|
||||
by_type[record.event_type] = by_type.get(record.event_type, 0) + 1
|
||||
|
||||
return {
|
||||
"total": len(records),
|
||||
"collector_count": len(collectors),
|
||||
"prefix_count": len(prefixes),
|
||||
"by_type": by_type,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/events/{event_id}")
|
||||
async def get_bgp_event(
|
||||
event_id: int,
|
||||
@@ -211,6 +233,36 @@ async def list_bgp_incidents(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/incidents/summary")
|
||||
async def get_bgp_incident_summary(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
total_result = await db.execute(select(func.count(BGPIncident.id)))
|
||||
type_result = await db.execute(
|
||||
select(BGPIncident.incident_type, func.count(BGPIncident.id))
|
||||
.group_by(BGPIncident.incident_type)
|
||||
.order_by(func.count(BGPIncident.id).desc())
|
||||
)
|
||||
severity_result = await db.execute(
|
||||
select(BGPIncident.severity, func.count(BGPIncident.id))
|
||||
.group_by(BGPIncident.severity)
|
||||
.order_by(func.count(BGPIncident.id).desc())
|
||||
)
|
||||
status_result = await db.execute(
|
||||
select(BGPIncident.status, func.count(BGPIncident.id))
|
||||
.group_by(BGPIncident.status)
|
||||
.order_by(func.count(BGPIncident.id).desc())
|
||||
)
|
||||
|
||||
return {
|
||||
"total": total_result.scalar() or 0,
|
||||
"by_type": {row[0]: row[1] for row in type_result.fetchall()},
|
||||
"by_severity": {row[0]: row[1] for row in severity_result.fetchall()},
|
||||
"by_status": {row[0]: row[1] for row in status_result.fetchall()},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/incidents/{incident_id}")
|
||||
async def get_bgp_incident(
|
||||
incident_id: int,
|
||||
|
||||
@@ -15,6 +15,7 @@ from app.core.satellite_tle import build_tle_lines_from_elements
|
||||
from app.core.time import to_iso8601_utc
|
||||
from app.db.session import get_db
|
||||
from app.models.bgp_anomaly import BGPAnomaly
|
||||
from app.models.bgp_incident import BGPIncident
|
||||
from app.models.collected_data import CollectedData
|
||||
from app.services.cable_graph import build_graph_from_data, CableGraph
|
||||
from app.services.collectors.bgp_common import RIPE_RIS_COLLECTOR_COORDS
|
||||
@@ -439,6 +440,58 @@ def convert_bgp_collectors_to_geojson() -> Dict[str, Any]:
|
||||
return {"type": "FeatureCollection", "features": features}
|
||||
|
||||
|
||||
def convert_bgp_incidents_to_geojson(records: List[BGPIncident]) -> Dict[str, Any]:
|
||||
features = []
|
||||
|
||||
for record in records:
|
||||
regions = record.affected_regions or []
|
||||
if not regions:
|
||||
continue
|
||||
|
||||
valid_regions = [
|
||||
region
|
||||
for region in regions
|
||||
if isinstance(region, dict)
|
||||
and isinstance(region.get("latitude"), (int, float))
|
||||
and isinstance(region.get("longitude"), (int, float))
|
||||
]
|
||||
if not valid_regions:
|
||||
continue
|
||||
|
||||
avg_lat = sum(float(region["latitude"]) for region in valid_regions) / len(valid_regions)
|
||||
avg_lon = sum(float(region["longitude"]) for region in valid_regions) / len(valid_regions)
|
||||
|
||||
features.append(
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [avg_lon, avg_lat],
|
||||
},
|
||||
"properties": {
|
||||
"id": record.id,
|
||||
"incident_key": record.incident_key,
|
||||
"incident_type": record.incident_type,
|
||||
"title": record.title,
|
||||
"summary": record.summary,
|
||||
"severity": record.severity,
|
||||
"status": record.status,
|
||||
"confidence": record.confidence,
|
||||
"affected_prefixes": record.affected_prefixes or [],
|
||||
"affected_asns": record.affected_asns or [],
|
||||
"affected_collectors": record.affected_collectors or [],
|
||||
"affected_regions": valid_regions,
|
||||
"related_cables": record.related_cables or [],
|
||||
"related_ixps": record.related_ixps or [],
|
||||
"created_at": to_iso8601_utc(record.created_at),
|
||||
"started_at": to_iso8601_utc(record.started_at),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return {"type": "FeatureCollection", "features": features}
|
||||
|
||||
|
||||
# ============== API Endpoints ==============
|
||||
|
||||
|
||||
@@ -667,6 +720,25 @@ async def get_bgp_anomalies_geojson(
|
||||
return {**geojson, "count": len(geojson.get("features", []))}
|
||||
|
||||
|
||||
@router.get("/geo/bgp-incidents")
|
||||
async def get_bgp_incidents_geojson(
|
||||
severity: Optional[str] = Query(None),
|
||||
status: Optional[str] = Query("active"),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
stmt = select(BGPIncident).order_by(BGPIncident.created_at.desc()).limit(limit)
|
||||
if severity:
|
||||
stmt = stmt.where(BGPIncident.severity == severity)
|
||||
if status:
|
||||
stmt = stmt.where(BGPIncident.status == status)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
records = list(result.scalars().all())
|
||||
geojson = convert_bgp_incidents_to_geojson(records)
|
||||
return {**geojson, "count": len(geojson.get("features", []))}
|
||||
|
||||
|
||||
@router.get("/geo/bgp-collectors")
|
||||
async def get_bgp_collectors_geojson():
|
||||
geojson = convert_bgp_collectors_to_geojson()
|
||||
|
||||
Reference in New Issue
Block a user