import asyncio import time from fastapi import APIRouter, Depends, Request from fastapi.templating import Jinja2Templates from sqlalchemy import select from app.db import get_db from app.models import JobRun from app.security.deps import require_auth from app.services import beets_service, credential_service, network_service, playlist_service, scheduler_service router = APIRouter(tags=["dashboard"]) templates = Jinja2Templates(directory="app/templates") _RECENT_TRACKS_WINDOW_SECONDS = 24 * 60 * 60 def _human_bytes(n: int) -> str: size = float(n) for unit in ("B", "KB", "MB", "GB", "TB"): if size < 1024 or unit == "TB": return f"{size:.1f} {unit}" size /= 1024 return f"{size:.1f} TB" @router.get("/") async def dashboard(request: Request, user: dict = Depends(require_auth), db=Depends(get_db)): stats = beets_service.stats() playlists = playlist_service.list_all(db) recent_runs = list( db.execute(select(JobRun).order_by(JobRun.started_at.desc()).limit(10)).scalars() ) recent_tracks = beets_service.recently_added(time.time() - _RECENT_TRACKS_WINDOW_SECONDS) public_ip = await asyncio.to_thread(network_service.public_ip) return templates.TemplateResponse( request, "dashboard.html", { "user": user, "stats": stats, "total_size_human": _human_bytes(stats.get("total_bytes", 0)), "playlist_count": len(playlists), "active_playlist_count": len([p for p in playlists if p.active]), "recent_runs": recent_runs, "recent_tracks": recent_tracks, "auth_states": credential_service.auth_states(db), "humanize_job_key": scheduler_service.humanize_job_key, "public_ip": public_ip, }, )