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) 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)) 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)