| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- """
- 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("/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"}
|