from datetime import datetime, timezone, timedelta from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from app.core.database import get_db from app.models.models import Client, BackupJob, BackupSession, User from app.schemas.schemas import DashboardStatsResponse from app.api.deps import get_current_user from app.storage.local import storage_provider router = APIRouter(prefix="/stats", tags=["Dashboard Statistics"]) @router.get("", response_model=DashboardStatsResponse) async def get_dashboard_stats( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): # Clients aggregation total_clients_res = await db.execute(select(func.count(Client.id)).where(Client.is_active == True)) total_clients = total_clients_res.scalar() or 0 # Consider online if last_seen_at was in the last 5 minutes cutoff_online = datetime.now(timezone.utc) - timedelta(minutes=5) online_clients_res = await db.execute( select(func.count(Client.id)).where( Client.is_active == True, Client.status == "ONLINE", Client.last_seen_at >= cutoff_online ) ) online_clients = online_clients_res.scalar() or 0 offline_clients = max(0, total_clients - online_clients) # Jobs count total_jobs_res = await db.execute(select(func.count(BackupJob.id)).where(BackupJob.is_active == True)) total_jobs = total_jobs_res.scalar() or 0 # Backups today today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) backups_today_res = await db.execute( select(func.count(BackupSession.id)).where(BackupSession.started_at >= today_start) ) backups_today = backups_today_res.scalar() or 0 backups_success_res = await db.execute( select(func.count(BackupSession.id)).where( BackupSession.started_at >= today_start, BackupSession.status == "SUCCESS" ) ) backups_success = backups_success_res.scalar() or 0 backups_failed_res = await db.execute( select(func.count(BackupSession.id)).where( BackupSession.started_at >= today_start, BackupSession.status == "FAILED" ) ) backups_failed = backups_failed_res.scalar() or 0 # Active uploads active_uploads_res = await db.execute( select(func.count(BackupSession.id)).where(BackupSession.status.in_(["PENDING", "UPLOADING", "ASSEMBLING"])) ) active_uploads = active_uploads_res.scalar() or 0 # Storage metrics storage_stats = await storage_provider.get_storage_stats() return { "total_clients": total_clients, "online_clients": online_clients, "offline_clients": offline_clients, "total_jobs": total_jobs, "backups_today_count": backups_today, "backups_today_success": backups_success, "backups_today_failed": backups_failed, "active_uploads_count": active_uploads, "storage": storage_stats }