Files
onever_drive/backend/app/api/stats.py
T

99 lines
3.9 KiB
Python

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
}