""" Superadmin monitoring endpoints for host metrics and alerts. """ from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, Query from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_superadmin, get_db from app.models.alert import Alert from app.models.host_metrics import HostMetrics from app.models.security_event import SecurityEvent from app.services.alert_service import alert_service router = APIRouter() @router.get("/metrics") async def get_current_metrics( _current_user=Depends(get_current_superadmin), ): """ Get current system metrics (latest snapshot) for dashboard cards. Returns PostgreSQL, ClickHouse, and HTTP/API metrics. """ from app.services.host_monitor import host_monitor # Get the most recent metrics from the monitor latest_metrics = host_monitor.latest_metrics if not latest_metrics: return { "postgresql": None, "clickhouse": None, "http": None, } return { "postgresql": latest_metrics.get("postgresql"), "clickhouse": latest_metrics.get("clickhouse"), "http": latest_metrics.get("http"), } @router.get("/host-metrics/recent") async def get_recent_host_metrics( limit: int = Query(default=60, le=1000), db: AsyncSession = Depends(get_db), _current_user=Depends(get_current_superadmin), ): """Get recent host metrics for dashboard charts (default: last 60 data points).""" result = await db.execute( select(HostMetrics) .order_by(HostMetrics.timestamp.desc()) .limit(limit) ) metrics = list(result.scalars().all()) # Return in chronological order return { "metrics": [ { "timestamp": m.timestamp.isoformat(), "cpu_percent": m.cpu_percent, "cpu_count": m.cpu_count, "cpu_per_core": m.cpu_per_core, "cpu_steal": m.cpu_steal, "context_switches_per_sec": m.context_switches_per_sec, "interrupts_per_sec": m.interrupts_per_sec, "memory_total": m.memory_total, "memory_used": m.memory_used, "memory_percent": m.memory_percent, "memory_available": m.memory_available, "memory_buffers": m.memory_buffers, "memory_cached": m.memory_cached, "swap_total": m.swap_total, "swap_used": m.swap_used, "swap_percent": m.swap_percent, "load_1": m.load_1, "load_5": m.load_5, "load_15": m.load_15, "disk_read_bytes": m.disk_read_bytes, "disk_write_bytes": m.disk_write_bytes, "disk_read_iops": m.disk_read_iops, "disk_write_iops": m.disk_write_iops, "disk_read_mbps": m.disk_read_mbps, "disk_write_mbps": m.disk_write_mbps, "disk_io_time_ms": m.disk_io_time_ms, "disk_usage_percent": m.disk_usage_percent, "net_sent_bytes": m.net_sent_bytes, "net_recv_bytes": m.net_recv_bytes, "net_in_mbps": m.net_in_mbps, "net_out_mbps": m.net_out_mbps, "net_packets_in_per_sec": m.net_packets_in_per_sec, "net_packets_out_per_sec": m.net_packets_out_per_sec, "net_errors_in": m.net_errors_in, "net_errors_out": m.net_errors_out, "net_drops_in": m.net_drops_in, "net_drops_out": m.net_drops_out, "process_count": m.process_count, "thread_count": m.thread_count, "top_cpu_processes": m.top_cpu_processes, "top_mem_processes": m.top_mem_processes, # PostgreSQL "pg_active_connections": m.pg_active_connections, "pg_total_connections": m.pg_total_connections, "pg_database_size_bytes": m.pg_database_size_bytes, "pg_cache_hit_ratio": m.pg_cache_hit_ratio, "pg_transactions_per_sec": m.pg_transactions_per_sec, "pg_deadlocks": m.pg_deadlocks, "pg_temp_files": m.pg_temp_files, # ClickHouse "ch_active_queries": m.ch_active_queries, "ch_database_size_bytes": m.ch_database_size_bytes, "ch_queries_per_sec": m.ch_queries_per_sec, "ch_rows_read_per_sec": m.ch_rows_read_per_sec, "ch_memory_usage_bytes": m.ch_memory_usage_bytes, # HTTP/API "http_requests_per_sec": m.http_requests_per_sec, "http_avg_response_time_ms": m.http_avg_response_time_ms, "http_error_rate": m.http_error_rate, "http_active_requests": m.http_active_requests, } for m in reversed(metrics) ] } @router.get("/host-metrics/history") async def get_host_metrics_history( start_date: datetime = Query(...), end_date: datetime = Query(...), db: AsyncSession = Depends(get_db), _current_user=Depends(get_current_superadmin), ): """Get historical host metrics for specified date range.""" result = await db.execute( select(HostMetrics) .where(HostMetrics.timestamp >= start_date) .where(HostMetrics.timestamp <= end_date) .order_by(HostMetrics.timestamp.asc()) ) metrics = list(result.scalars().all()) return [ { "timestamp": m.timestamp.isoformat(), "cpu_percent": m.cpu_percent, "cpu_count": m.cpu_count, "memory_total": m.memory_total, "memory_used": m.memory_used, "memory_percent": m.memory_percent, "load_1": m.load_1, "load_5": m.load_5, "load_15": m.load_15, "disk_read_bytes": m.disk_read_bytes, "disk_write_bytes": m.disk_write_bytes, "disk_usage_percent": m.disk_usage_percent, "net_sent_bytes": m.net_sent_bytes, "net_recv_bytes": m.net_recv_bytes, } for m in metrics ] @router.get("/alerts") async def get_alerts( dismissed: bool = Query(default=False), limit: int = Query(default=100, le=1000), db: AsyncSession = Depends(get_db), _current_user=Depends(get_current_superadmin), ): """Get alerts (by default only active/non-dismissed alerts).""" query = select(Alert).order_by(Alert.timestamp.desc()).limit(limit) if not dismissed: query = query.where(Alert.dismissed == False) result = await db.execute(query) alerts = list(result.scalars().all()) return { "alerts": [ { "id": a.id, "timestamp": a.timestamp.isoformat(), "created_at": a.timestamp.isoformat(), # For frontend compatibility "alert_type": a.alert_type, "severity": a.severity, "title": a.title, "message": a.message, "alert_metadata": a.alert_metadata, "acknowledged": a.acknowledged, "acknowledged_at": a.acknowledged_at.isoformat() if a.acknowledged_at else None, "acknowledged_by": a.acknowledged_by, "dismissed": a.dismissed, "dismissed_at": a.dismissed_at.isoformat() if a.dismissed_at else None, "sent_dashboard": a.sent_dashboard, "sent_telegram": a.sent_telegram, "sent_email": a.sent_email, } for a in alerts ] } @router.post("/alerts/{alert_id}/acknowledge") async def acknowledge_alert( alert_id: int, db: AsyncSession = Depends(get_db), current_user=Depends(get_current_superadmin), ): """Mark alert as acknowledged.""" await alert_service.acknowledge_alert(db, alert_id, current_user.id) return {"status": "ok"} @router.post("/alerts/{alert_id}/dismiss") async def dismiss_alert( alert_id: int, db: AsyncSession = Depends(get_db), _current_user=Depends(get_current_superadmin), ): """Mark alert as dismissed (hide from dashboard).""" await alert_service.dismiss_alert(db, alert_id) return {"status": "ok"} @router.get("/security-events") async def get_security_events( resolved: bool = Query(default=False), limit: int = Query(default=100, le=1000), db: AsyncSession = Depends(get_db), _current_user=Depends(get_current_superadmin), ): """Get security events (by default only unresolved events).""" query = select(SecurityEvent).order_by(SecurityEvent.timestamp.desc()).limit(limit) if not resolved: query = query.where(SecurityEvent.resolved == False) result = await db.execute(query) events = list(result.scalars().all()) return [ { "id": e.id, "timestamp": e.timestamp.isoformat(), "event_type": e.event_type, "severity": e.severity, "ip_address": e.ip_address, "user_agent": e.user_agent, "endpoint": e.endpoint, "description": e.description, "event_metadata": e.event_metadata, "resolved": e.resolved, "resolved_at": e.resolved_at.isoformat() if e.resolved_at else None, "resolved_by": e.resolved_by, } for e in events ] @router.post("/security-events/{event_id}/resolve") async def resolve_security_event( event_id: int, db: AsyncSession = Depends(get_db), current_user=Depends(get_current_superadmin), ): """Mark security event as resolved.""" result = await db.execute( select(SecurityEvent).where(SecurityEvent.id == event_id) ) event = result.scalar_one_or_none() if event: event.resolved = True event.resolved_at = datetime.now(timezone.utc) event.resolved_by = current_user.id await db.commit() return {"status": "ok"}