Files
security-alert-center/backend/app/api/v1/dashboards.py
T
PapaTramp 01d72d0f68 fix: show RDG flap badge on 302 and 303 events in SAC UI (0.20.5)
Seaca surfaced flap via Problems; the web UI only flagged 303 in DB and had
no visible label. Resolve flap pairs at read time and show badge plus qwinsta
on both sides of 302→303.
2026-06-20 17:27:41 +10:00

157 lines
4.9 KiB
Python

from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlalchemy import func, select
from sqlalchemy.orm import Session, joinedload
from app.auth.jwt_auth import get_current_user
from app.config import get_settings
from app.database import get_db
from app.models import Event, Host, Problem
from app.schemas.list_models import EventSummary
from app.services.event_summary import event_to_summary
from app.services.host_health import DAILY_REPORT_TYPES, HEARTBEAT_TYPE, count_stale_hosts
router = APIRouter(prefix="/dashboards", tags=["dashboards"])
def fetch_recent_events(db: Session, *, limit: int = 8) -> list[EventSummary]:
"""Последние события по времени приёма ingest (received_at)."""
rows = db.scalars(
select(Event)
.join(Host)
.options(joinedload(Event.host))
.order_by(Event.received_at.desc())
.limit(limit)
).all()
return [event_to_summary(e, db) for e in rows]
class TopHostItem(BaseModel):
host_id: int
hostname: str
display_name: str | None = None
count: int
class TopTypeItem(BaseModel):
type: str
count: int
class DashboardSummary(BaseModel):
events_last_24h: int
hosts_total: int
hosts_stale: int
heartbeats_24h: int
daily_reports_24h: int
problems_open: int
problems_opened_24h: int
problems_resolved_24h: int
severity_24h: dict[str, int]
top_hosts: list[TopHostItem]
top_event_types: list[TopTypeItem]
recent_events: list[EventSummary]
@router.get("/summary", response_model=DashboardSummary)
def dashboard_summary(
db: Session = Depends(get_db),
_user: str = Depends(get_current_user),
) -> DashboardSummary:
since = datetime.now(timezone.utc) - timedelta(hours=24)
events_24h = db.scalar(
select(func.count()).select_from(Event).where(Event.received_at >= since)
) or 0
hosts_total = db.scalar(select(func.count()).select_from(Host)) or 0
settings = get_settings()
hosts_stale = count_stale_hosts(db, settings.sac_heartbeat_stale_minutes)
heartbeats_24h = db.scalar(
select(func.count())
.select_from(Event)
.where(Event.received_at >= since, Event.type == HEARTBEAT_TYPE)
) or 0
daily_reports_24h = db.scalar(
select(func.count())
.select_from(Event)
.where(Event.received_at >= since, Event.type.in_(DAILY_REPORT_TYPES))
) or 0
problems_open = (
db.scalar(select(func.count()).select_from(Problem).where(Problem.status == "open")) or 0
)
problems_opened_24h = (
db.scalar(
select(func.count()).select_from(Problem).where(
Problem.created_at >= since,
Problem.status != "resolved",
)
)
or 0
)
problems_resolved_24h = (
db.scalar(
select(func.count()).select_from(Problem).where(
Problem.status == "resolved",
Problem.updated_at >= since,
)
)
or 0
)
top_host_rows = db.execute(
select(Host.id, Host.hostname, Host.display_name, func.count())
.select_from(Event)
.join(Host, Event.host_id == Host.id)
.where(Event.received_at >= since)
.group_by(Host.id, Host.hostname, Host.display_name)
.order_by(func.count().desc())
.limit(10)
).all()
top_hosts = [
TopHostItem(host_id=row[0], hostname=row[1], display_name=row[2], count=row[3]) for row in top_host_rows
]
top_type_rows = db.execute(
select(Event.type, func.count())
.where(Event.received_at >= since)
.group_by(Event.type)
.order_by(func.count().desc())
.limit(10)
).all()
top_event_types = [TopTypeItem(type=row[0], count=row[1]) for row in top_type_rows]
severity_rows = db.execute(
select(Event.severity, func.count())
.where(Event.received_at >= since)
.group_by(Event.severity)
).all()
severity_24h = {row[0]: row[1] for row in severity_rows}
recent_events = fetch_recent_events(db, limit=8)
return DashboardSummary(
events_last_24h=events_24h,
hosts_total=hosts_total,
hosts_stale=hosts_stale,
heartbeats_24h=heartbeats_24h,
daily_reports_24h=daily_reports_24h,
problems_open=problems_open,
problems_opened_24h=problems_opened_24h,
problems_resolved_24h=problems_resolved_24h,
severity_24h=severity_24h,
top_hosts=top_hosts,
top_event_types=top_event_types,
recent_events=recent_events,
)
@router.get("/recent-events", response_model=list[EventSummary])
def dashboard_recent_events(
limit: int = Query(8, ge=1, le=50),
db: Session = Depends(get_db),
_user: str = Depends(get_current_user),
) -> list[EventSummary]:
return fetch_recent_events(db, limit=limit)