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() # Override with global virtual quota if set from app.services.settings_service import get_setting global_quota_gb_str = await get_setting(db, "global_quota_gb") if global_quota_gb_str: try: global_quota_bytes = int(global_quota_gb_str) * 1024 * 1024 * 1024 # Sum up storage used by all clients used_res = await db.execute(select(func.sum(Client.storage_used_bytes))) total_used_bytes = used_res.scalar() or 0 storage_stats["total_bytes"] = global_quota_bytes storage_stats["used_bytes"] = total_used_bytes storage_stats["free_bytes"] = max(0, global_quota_bytes - total_used_bytes) storage_stats["usage_percent"] = round((total_used_bytes / global_quota_bytes) * 100, 2) if global_quota_bytes > 0 else 0 except ValueError: pass 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 }