import asyncio import json from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, Query from fastapi.responses import StreamingResponse from sqlalchemy import func, select from sqlalchemy.orm import Session from app.auth.jwt_auth import verify_access_token from app.database import SessionLocal, get_db from app.models import Event, Problem from app.config import get_settings from app.services.host_health import DAILY_REPORT_TYPES, HEARTBEAT_TYPE, count_stale_hosts from app.version import APP_VERSION router = APIRouter(prefix="/stream", tags=["stream"]) SSE_INTERVAL_SEC = 5 def _dashboard_snapshot(db: Session) -> dict: 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 problems_open = ( db.scalar(select(func.count()).select_from(Problem).where(Problem.status == "open")) or 0 ) last_event = db.scalar(select(Event.id).order_by(Event.received_at.desc()).limit(1)) settings = get_settings() return { "type": "dashboard", "version": APP_VERSION, "events_last_24h": events_24h, "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": problems_open, "last_event_id": last_event, "at": datetime.now(timezone.utc).isoformat(), } async def _sse_generator(): try: while True: db = SessionLocal() try: payload = _dashboard_snapshot(db) finally: db.close() yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n" await asyncio.sleep(SSE_INTERVAL_SEC) except asyncio.CancelledError: return def _sse_auth( token: str = Query(..., description="JWT access_token"), db: Session = Depends(get_db), ) -> str: return verify_access_token(token, db=db) @router.get("/events") async def stream_events( _user: str = Depends(_sse_auth), ) -> StreamingResponse: return StreamingResponse( _sse_generator(), media_type="text/event-stream", headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}, )