feat: add bgp observability and admin ui improvements
This commit is contained in:
@@ -5,6 +5,7 @@ from app.models.data_snapshot import DataSnapshot
|
||||
from app.models.datasource import DataSource
|
||||
from app.models.datasource_config import DataSourceConfig
|
||||
from app.models.alert import Alert, AlertSeverity, AlertStatus
|
||||
from app.models.bgp_anomaly import BGPAnomaly
|
||||
from app.models.system_setting import SystemSetting
|
||||
|
||||
__all__ = [
|
||||
@@ -18,4 +19,5 @@ __all__ = [
|
||||
"Alert",
|
||||
"AlertSeverity",
|
||||
"AlertStatus",
|
||||
]
|
||||
"BGPAnomaly",
|
||||
]
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Optional
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey, Enum as SQLEnum
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.time import to_iso8601_utc
|
||||
from app.db.session import Base
|
||||
|
||||
|
||||
@@ -50,8 +51,8 @@ class Alert(Base):
|
||||
"acknowledged_by": self.acknowledged_by,
|
||||
"resolved_by": self.resolved_by,
|
||||
"resolution_notes": self.resolution_notes,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
"acknowledged_at": self.acknowledged_at.isoformat() if self.acknowledged_at else None,
|
||||
"resolved_at": self.resolved_at.isoformat() if self.resolved_at else None,
|
||||
"created_at": to_iso8601_utc(self.created_at),
|
||||
"updated_at": to_iso8601_utc(self.updated_at),
|
||||
"acknowledged_at": to_iso8601_utc(self.acknowledged_at),
|
||||
"resolved_at": to_iso8601_utc(self.resolved_at),
|
||||
}
|
||||
|
||||
58
backend/app/models/bgp_anomaly.py
Normal file
58
backend/app/models/bgp_anomaly.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""BGP anomaly model for derived routing intelligence."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, Index, Integer, JSON, String, Text
|
||||
|
||||
from app.core.time import to_iso8601_utc
|
||||
from app.db.session import Base
|
||||
|
||||
|
||||
class BGPAnomaly(Base):
|
||||
__tablename__ = "bgp_anomalies"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
snapshot_id = Column(Integer, ForeignKey("data_snapshots.id"), nullable=True, index=True)
|
||||
task_id = Column(Integer, ForeignKey("collection_tasks.id"), nullable=True, index=True)
|
||||
source = Column(String(100), nullable=False, index=True)
|
||||
anomaly_type = Column(String(50), nullable=False, index=True)
|
||||
severity = Column(String(20), nullable=False, index=True)
|
||||
status = Column(String(20), nullable=False, default="active", index=True)
|
||||
entity_key = Column(String(255), nullable=False, index=True)
|
||||
prefix = Column(String(64), nullable=True, index=True)
|
||||
origin_asn = Column(Integer, nullable=True, index=True)
|
||||
new_origin_asn = Column(Integer, nullable=True, index=True)
|
||||
peer_scope = Column(JSON, default=list)
|
||||
started_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow, index=True)
|
||||
ended_at = Column(DateTime(timezone=True), nullable=True)
|
||||
confidence = Column(Float, nullable=False, default=0.5)
|
||||
summary = Column(Text, nullable=False)
|
||||
evidence = Column(JSON, default=dict)
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow, index=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_bgp_anomalies_source_created", "source", "created_at"),
|
||||
Index("idx_bgp_anomalies_type_status", "anomaly_type", "status"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"snapshot_id": self.snapshot_id,
|
||||
"task_id": self.task_id,
|
||||
"source": self.source,
|
||||
"anomaly_type": self.anomaly_type,
|
||||
"severity": self.severity,
|
||||
"status": self.status,
|
||||
"entity_key": self.entity_key,
|
||||
"prefix": self.prefix,
|
||||
"origin_asn": self.origin_asn,
|
||||
"new_origin_asn": self.new_origin_asn,
|
||||
"peer_scope": self.peer_scope or [],
|
||||
"started_at": to_iso8601_utc(self.started_at),
|
||||
"ended_at": to_iso8601_utc(self.ended_at),
|
||||
"confidence": self.confidence,
|
||||
"summary": self.summary,
|
||||
"evidence": self.evidence or {},
|
||||
"created_at": to_iso8601_utc(self.created_at),
|
||||
}
|
||||
@@ -4,6 +4,7 @@ from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, T
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.core.collected_data_fields import get_record_field
|
||||
from app.core.time import to_iso8601_utc
|
||||
from app.db.session import Base
|
||||
|
||||
|
||||
@@ -74,15 +75,11 @@ class CollectedData(Base):
|
||||
"value": get_record_field(self, "value"),
|
||||
"unit": get_record_field(self, "unit"),
|
||||
"metadata": self.extra_data,
|
||||
"collected_at": self.collected_at.isoformat()
|
||||
if self.collected_at is not None
|
||||
else None,
|
||||
"reference_date": self.reference_date.isoformat()
|
||||
if self.reference_date is not None
|
||||
else None,
|
||||
"collected_at": to_iso8601_utc(self.collected_at),
|
||||
"reference_date": to_iso8601_utc(self.reference_date),
|
||||
"is_current": self.is_current,
|
||||
"previous_record_id": self.previous_record_id,
|
||||
"change_type": self.change_type,
|
||||
"change_summary": self.change_summary,
|
||||
"deleted_at": self.deleted_at.isoformat() if self.deleted_at is not None else None,
|
||||
"deleted_at": to_iso8601_utc(self.deleted_at),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user