Dashboard detail, library filtering/pagination, simplified playlist times,

job descriptions

Dashboard: format breakdown (FLAC/MP3/etc. counts), approximate library
size (bitrate*length/8, same approximation `beet stats` itself uses --
verified to match its "100.5 GiB" output exactly against the real
library), and a credential auth-state summary per scope (configured y/n
plus, for Bandcamp, cookie expiry parsed locally from the stored cookie
jar -- deliberately not a live network probe like pipeline-status.sh's
Qobuz check, since this renders on every dashboard load).

Library: was one unfiltered page dumping all 4072 tracks. Added search
(artist/title/album), playlist and format filter dropdowns, and real
SQL-level pagination (LIMIT/OFFSET, not fetch-everything-then-slice).
beets_service gained count_items()/distinct_formats() to support this.

Playlists: cron_expr is still the stored/scheduled representation, but the
UI now shows and edits a plain daily time picker instead of raw cron
syntax -- every playlist schedule today is a simple daily HH:MM anyway.
playlist_service.cron_to_time()/time_to_cron() convert at the router
boundary; verified round-trip against all real playlist cron values.

Jobs: each maintenance job now shows a short one-line description of what
it actually does (MAINTENANCE_JOB_DESCRIPTIONS), and "next run" is
formatted consistently with playlists' time style (HH:MM, with a day
qualifier for non-today runs -- maintenance jobs can be weekly/monthly,
unlike playlists' plain daily schedule).

Verified end-to-end against the real production data (4072 tracks, 100.5
GiB, real credentials): stats/format-breakdown/search/pagination all
correct, all 7 credential scopes report configured with Bandcamp's real
cookie expiry (325 days), and a full authenticated page-render sweep
(dashboard, library with every filter combination, playlist detail,
jobs) all returned 200 with no template errors.
This commit is contained in:
andrew
2026-07-08 15:39:22 -06:00
parent ee21251f3e
commit c87ddc2649
13 changed files with 355 additions and 34 deletions
+25
View File
@@ -153,6 +153,31 @@ MAINTENANCE_JOBS: dict[str, tuple[dict, callable]] = {
),
}
# One short line per job for the /jobs page -- what it actually does, not
# just its key. Kept separate from MAINTENANCE_JOBS so the schedule/function
# mapping above stays easy to scan.
MAINTENANCE_JOB_DESCRIPTIONS: dict[str, str] = {
"maintenance:sync_bandcamp": "Pull new Bandcamp purchases and import them.",
"maintenance:normalize_casing": "Fix artist-name casing to match your canonical list.",
"maintenance:dedup": "Scan for duplicate tracks (dry-run only; confirm deletes in Dedup).",
"maintenance:gen_djmix_playlist": "Rebuild the DJ-mix M3U from configured albums.",
"maintenance:gen_vgm_playlist": "Rebuild the video-game-soundtrack M3U.",
"maintenance:navidrome_scan": "Trigger a Navidrome library rescan (share-health gated).",
"maintenance:export_laptop_playlists": "Export dj-* Navidrome playlists for the laptop.",
"maintenance:enrich_buy_url": "Look up buy links for tracks without one yet.",
"maintenance:build_fingerprint_index": "Refresh the acoustic fingerprint index.",
"maintenance:pipeline_status_report": "Write the daily health snapshot / Telegram digest.",
"maintenance:log_rotation": "Delete job logs older than 30 days.",
"maintenance:strip_mb_tags": "Strip stale MusicBrainz tags that fragment Navidrome albums.",
"maintenance:strip_watermark_art": "Remove Soulseek-uploader watermark cover art.",
"maintenance:scrub_watermark_text": "Remove Soulseek-uploader watermark text frames.",
"maintenance:clean_sldl_index": "Prune stale entries from each playlist's download index.",
"maintenance:clear_bad_genres": "Blank malformed/junk GENRE tags before Spotify refill.",
"maintenance:spotify_genre": "Refill GENRE tags from Spotify per-artist data.",
"maintenance:beets_update_sync": "Sync the beets DB to on-disk tag changes.",
"maintenance:upgrade_mp3_to_flac": "Look for FLAC replacements for MP3 tracks (monthly).",
}
# Module-level singleton so other services (playlist_service, future
# routers) can reach the live scheduler without main.py threading it through