1cd8e926de
PR 2 of 4 for configurable dashboard widgets.
- Add grafana_url and prometheus_url settings (config.py + compose/env).
- Create WidgetSource protocol and adapters for jellyfin, backups, grafana,
prometheus, ssh_task, and static sources.
- Add GET /api/widgets/instances/{id}/data endpoint.
- Extract shared dashboard helpers into domain/dashboard.py so widgets and
the dashboard router reuse the same logic.
- Add adapter and data-endpoint tests.
- Update apply-progress.md.
Verification: ruff clean; backend pytest 200 passed; frontend lint/build green.
117 lines
3.7 KiB
Python
117 lines
3.7 KiB
Python
"""Dashboard router — media counts, per-library breakdown, activity."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends
|
|
|
|
from media_library_viewer_api.clients.jellyfin import JellyfinClient
|
|
from media_library_viewer_api.dependencies import (
|
|
get_jellyfin_client,
|
|
get_settings_store,
|
|
get_user_id,
|
|
)
|
|
from media_library_viewer_api.domain.dashboard import (
|
|
_map_sessions_to_activity_rows,
|
|
build_backup_dashboard_summary,
|
|
)
|
|
from media_library_viewer_api.models.backups import BackupDashboardSummary
|
|
from media_library_viewer_api.services.settings_store import SettingsStore
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
|
|
|
|
|
|
@router.get("/counts")
|
|
def get_counts(
|
|
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
user_id: str = Depends(get_user_id),
|
|
) -> dict[str, int]:
|
|
"""Return total movie/series/episode counts."""
|
|
counts = client.media_counts(user_id)
|
|
logger.info("Dashboard counts user_id=%s counts=%s", user_id, counts)
|
|
return counts
|
|
|
|
|
|
@router.get("/libraries")
|
|
def get_library_counts(
|
|
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
user_id: str = Depends(get_user_id),
|
|
) -> list[dict[str, Any]]:
|
|
"""Return per-library item counts broken down by type."""
|
|
libraries = client.libraries(user_id)
|
|
logger.info("Dashboard libraries user_id=%s count=%s", user_id, len(libraries))
|
|
return client.library_item_counts(user_id, libraries)
|
|
|
|
|
|
@router.get("/shortcuts")
|
|
def get_shortcuts(
|
|
store=Depends(get_settings_store),
|
|
) -> list[dict[str, Any]]:
|
|
"""Return dashboard shortcut records."""
|
|
shortcuts = store.list_shortcuts()
|
|
logger.info("Dashboard shortcuts count=%s", len(shortcuts))
|
|
return shortcuts
|
|
|
|
|
|
@router.post("/shortcuts")
|
|
def create_shortcut(
|
|
payload: dict[str, Any],
|
|
store=Depends(get_settings_store),
|
|
) -> dict[str, Any]:
|
|
shortcut = store.upsert_shortcut(payload)
|
|
logger.info("Created dashboard shortcut id=%s type=%s", shortcut.get("id"), shortcut.get("shortcut_type"))
|
|
return shortcut
|
|
|
|
|
|
@router.put("/shortcuts/{shortcut_id}")
|
|
def update_shortcut(
|
|
shortcut_id: str,
|
|
payload: dict[str, Any],
|
|
store=Depends(get_settings_store),
|
|
) -> dict[str, Any]:
|
|
shortcut = store.upsert_shortcut(payload, shortcut_id)
|
|
logger.info("Updated dashboard shortcut id=%s type=%s", shortcut.get("id"), shortcut.get("shortcut_type"))
|
|
return shortcut
|
|
|
|
|
|
@router.delete("/shortcuts/{shortcut_id}")
|
|
def delete_shortcut(
|
|
shortcut_id: str,
|
|
store=Depends(get_settings_store),
|
|
) -> dict[str, str]:
|
|
store.delete_shortcut(shortcut_id)
|
|
logger.info("Deleted dashboard shortcut id=%s", shortcut_id)
|
|
return {"status": "deleted"}
|
|
|
|
|
|
@router.get("/activity")
|
|
def get_activity(
|
|
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
) -> list[dict[str, Any]]:
|
|
"""Return activity rows for active sessions (playing and idle/logged-in)."""
|
|
sessions = client.sessions()
|
|
rows = _map_sessions_to_activity_rows(sessions)
|
|
state_rank = {"playing": 0, "paused": 1, "idle": 2}
|
|
rows.sort(key=lambda r: (state_rank.get(str(r.get("state")), 9), str(r.get("user", "")).lower()))
|
|
logger.info("Dashboard activity sessions=%s rows=%s", len(sessions), len(rows))
|
|
return rows
|
|
|
|
|
|
@router.get("/now-playing")
|
|
def get_now_playing(
|
|
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
) -> list[dict[str, Any]]:
|
|
"""Backward-compatible alias; returns full activity rows."""
|
|
return get_activity(client)
|
|
|
|
|
|
@router.get("/backups")
|
|
def get_backup_dashboard(
|
|
store: SettingsStore = Depends(get_settings_store),
|
|
) -> BackupDashboardSummary:
|
|
return build_backup_dashboard_summary(store)
|