diff --git a/CLAUDE.md b/CLAUDE.md index 9783b39..d935591 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,9 +3,8 @@ Flask dashboard aggregating status from the user's self-hosted media stack — Sonarr, Radarr, SABnzbd, Tautulli, Prowlarr, Ombi — plus Unraid array stats and a Rotten Tomatoes "what's popular that I don't have yet" feed. -Branded "Media Centre" — a single neutral, professional dark theme (the -earlier five novelty themes were removed). Runs as one Docker container on -the user's Unraid home server. Server-side project directory is still named +Branded "Greendale Media Centre." Runs as one Docker container on the +user's Unraid home server. Server-side project directory is named `greendale`; the container/repo are named `arr-summary`. ## Architecture @@ -34,20 +33,10 @@ the user's Unraid home server. Server-side project directory is still named `BeautifulSoup`, JSON-LD parsing), not via an official API — brittle to upstream HTML changes by nature. - **`templates/`** — Jinja2 templates (`dashboard.html`, `login.html`, - `settings.html`). Stale duplicates of these once sat at the repo root; - they were never rendered (Flask loads from `templates/`) and were deleted - during the redesign. - - All three share one token set defined on `:root` — surfaces, a 6-step - `--fs-*` type scale, semantic colours, and `--tap: 44px` for minimum - touch targets. Add new UI with those tokens rather than literal values. - - `dashboard.html` is ordered by urgency, deliberately mirroring the cache - tiers in `app.py`: a "Right now" strip (live streams, download queue, - array usage) sits above the fold, with library, discovery, requests and - watch insights below it. - - Degraded services render through the `service_error()` macro, which - pairs `friendly_error` (human cause) with the raw exception behind a - disclosure, passed through `redact_secrets` — SABnzbd and Tautulli put - their API key in the request URL, so it appears in raw tracebacks. + `settings.html`); duplicated at the top level too (`dashboard.html` etc. + outside `templates/`) — appears to be a leftover from an earlier project + layout or the `export-to-unraid.sh` tooling. Left as-is rather than + restructured without confirming with the user first. - **`export-to-unraid.sh`** — a pre-existing standalone deploy script (build image locally → tarball → rsync + `docker load` over SSH) that predates the Forgejo pipeline below. Still present, not removed, but the @@ -89,5 +78,5 @@ pipeline mechanics. Push to `main` on the `forgejo` remote and `LOGIN_PASSWORD` — this project was already doing this correctly before the Forgejo migration (compose file uses `${VAR}` substitution from `.env`, no hardcoded secrets), unlike `n64-tracker` which needed fixing. -`settings.json` (timezone, changed login password) also persists +`settings.json` (timezone, theme, changed login password) also persists server-side outside git. diff --git a/app.py b/app.py index e95eb4b..0d44548 100644 --- a/app.py +++ b/app.py @@ -1001,6 +1001,7 @@ def dashboard(): s = load_settings() timezone = s.get("timezone", "America/New_York") + theme = s.get("theme", "community") # Public-facing URLs for card header links (via NPM reverse proxy) arr_host_public = f"https://hippofam.com" service_urls = { @@ -1019,6 +1020,7 @@ def dashboard(): rt_movies=rt_movies, rt_tv=rt_tv, sonarr_titles=sonarr_titles, radarr_titles=radarr_titles, timezone=timezone, + theme=theme, service_urls=service_urls, ) @@ -1241,6 +1243,7 @@ def settings_page(): prowlarr_api_key=PROWLARR_API_KEY, ombi_api_key=OMBI_API_KEY, arr_host=os.environ.get("ARR_HOST", ""), + theme=s.get("theme", "community"), ) @@ -1249,7 +1252,7 @@ def settings_page(): def api_save_settings(): data = request.get_json() allowed = {"timezone", "sonarr_api_key", "radarr_api_key", "sabnzbd_api_key", - "tautulli_api_key", "prowlarr_api_key", "ombi_api_key", "arr_host"} + "tautulli_api_key", "prowlarr_api_key", "ombi_api_key", "arr_host", "theme"} filtered = {k: v for k, v in data.items() if k in allowed} save_settings(filtered) return jsonify({"success": True}) @@ -1261,6 +1264,7 @@ def api_get_settings(): s = load_settings() return jsonify({ "timezone": s.get("timezone", "America/New_York"), + "theme": s.get("theme", "community"), }) diff --git a/dashboard.html b/dashboard.html new file mode 100644 index 0000000..46e2979 --- /dev/null +++ b/dashboard.html @@ -0,0 +1,1964 @@ + + + + + + {% if theme == 'office' %}Dunder Mifflin Media Centre + {% elif theme == 'parks' %}Pawnee Parks Media Centre + {% elif theme == 'potter' %}Hogwarts Media Vault + {% elif theme == 'starwars' %}Rebel Alliance Media Archive + {% else %}Greendale Media Centre{% endif %} + {% if theme == 'office' %} + {% elif theme == 'parks' %} + {% elif theme == 'potter' %} + {% elif theme == 'starwars' %} + {% else %}{% endif %} + + + {% if theme == 'office' %} + + {% elif theme == 'parks' %} + + {% elif theme == 'potter' %} + + {% elif theme == 'starwars' %} + + {% else %} + + {% endif %} + + + + +
+
+ {% if theme == 'office' %} +
📎
+ {% elif theme == 'parks' %} +
🌳
+ {% elif theme == 'potter' %} +
⚡
+ {% elif theme == 'starwars' %} +
+ + + + + + + + + + + +
+ {% else %} + Greendale + {% endif %} +
+ {% if theme == 'office' %} +

Dunder Mifflin Media Centre

+
That's what she watched.
+ {% elif theme == 'parks' %} +

Pawnee Parks Media Centre

+
The greatest city in the world.
+ {% elif theme == 'potter' %} +

Hogwarts Media Vault

+
Mischief managed.
+ {% elif theme == 'starwars' %} +

Rebel Alliance Media Archive

+
May the downloads be with you.
+ {% else %} +

Greendale Media Centre

+
Human beings welcome. Robots too.
+ {% endif %} +
+
+
+ + + {% set up = sonarr.status == "ok" %} + + + + Sonarr + + {%- if up %}{{ sonarr.total_series }} series{% else %}offline{% endif -%} + + + + + + {% set up = radarr.status == "ok" %} + + + + Radarr + + {%- if up %}{{ radarr.total_movies }} movies{% else %}offline{% endif -%} + + + + + + {% set up = prowlarr.status == "ok" %} + + + + Prowlarr + + {%- if up %}{{ prowlarr.up_count }}/{{ prowlarr.total }} online{% else %}offline{% endif -%} + + + + + + {% set up = ombi.status == "ok" %} + + + + Ombi + + {%- if up %}{{ ombi.total_pending }} pending{% else %}offline{% endif -%} + + + + + + {% set up = sabnzbd.status == "ok" %} + + + + SABnzbd + + {%- if up %}{{ sabnzbd.speed }}{% else %}offline{% endif -%} + + + + + + + {% set up = tautulli.status == "ok" %} + + + + Tautulli + + {%- if up -%} + {%- if tautulli.active_streams > 0 -%} + {{ tautulli.active_streams }} streaming + {%- else -%} + 0 streams + {%- endif -%} + {%- else -%}offline{%- endif -%} + + + + + + +
+
+
—
+
+ {% if theme == 'office' %}Dunder Mifflin HQ + {% elif theme == 'parks' %}Pawnee City Hall + {% elif theme == 'potter' %}Hogwarts Great Hall + {% elif theme == 'starwars' %}Death Star Control Room + {% else %}Study Room F{% endif %} +  ·  ⚙ Settings +
+
+
+ +
+ +
+ {% if theme == 'office' %} + 📋  Monday morning meeting: checking downloads, missing episodes, and who took the last of the coffee. + {% elif theme == 'parks' %} + 📋  Today's agenda: checking what's missing, what's downloading, and making Pawnee great again. + {% elif theme == 'potter' %} + 📋  Today's scroll: checking what spells are missing, what's being conjured, and what deserves a watch. + {% elif theme == 'starwars' %} + 📋  Rebel briefing: checking what's missing, what's downloading, and what the Force recommends watching. + {% else %} + 📋  Today's agenda: checking what's missing, what's downloading, and what deserves to be watched. + {% endif %} +
+ + +
+
+ 🖥️ +

Unraid

+ {% if unraid.status == "ok" %} + + {% if unraid.array_state %}{{ unraid.array_state | capitalize }}{% endif %} + {% if unraid.disk_count %} ·  {{ unraid.disk_count }} disk{{ 's' if unraid.disk_count != 1 }}{% endif %} + {% if unraid.os_version %} ·  v{{ unraid.os_version }}{% endif %} + + {% endif %} +
+
+ {% if unraid.status == "ok" %} +
+
+
Array Total
+
{{ unraid.array_total }}
+
+
+
Array Used
+
{{ unraid.array_used }}
+
+
+
Array Free
+
{{ unraid.array_free }}
+
+
+ +
+
+ Array Usage + {{ unraid.array_used_pct }}% +
+
+
+
+
+ {% if unraid.source == "local" %} +
+ ⓘ Showing container disk usage ({{ unraid.mount }}). Add UNRAID_API_KEY for full Unraid stats. +
+ {% endif %} + {% else %} +
Could not load system stats: {{ unraid.error }}
+ {% endif %} +
+
+ + +
+
+ 📺 +

Sonarr

+
+
+ +
+
+
+
+
+ +
+ + {% if sonarr.status == "ok" %} +
+
+
Total Series
+
{{ sonarr.total_series }}
+
+
+
Missing Episodes
+
{{ sonarr.missing_episodes }}
+
+
+ {% else %} +
Could not reach Sonarr: {{ sonarr.error }}
+ {% endif %} + +
🍅 Popular on RT
+ {% if rt_tv and rt_tv[0] is defined and rt_tv[0].error is defined %} +
Failed to load TV data: {{ rt_tv[0].error }}
+ {% elif rt_tv %} +
+ {% for show in rt_tv %} +
+
+ {{ show.title }}
+ {{ show.description[:180] if show.description else '' }}{% if show.description and show.description|length > 180 %}…{% endif %} +
+
{{ loop.index }}
+ {% if show.poster %} + {% else %}
{% endif %} +
+
{{ show.title }}{% if show.year %} ({{ show.year }}){% endif %}
+
+ {% if show.certified %}✅ Certified Fresh + {% elif show.critics_score %}🍅 {{ show.critics_score }}%{% endif %} + {% if show.audience_score %}🍿 {{ show.audience_score }}{% endif %} +
+
+
+ +
+
+ {% endfor %} +
+ {% else %} +
No TV data available.
+ {% endif %} +
+
+ + +
+
+ 🎬 +

Radarr

+
+
+ +
+
+
+
+
+ +
+ + {% if radarr.status == "ok" %} +
+
+
Total Movies
+
{{ radarr.total_movies }}
+
+
+
Missing
+
{{ radarr.missing_movies }}
+
+
+ {% else %} +
Could not reach Radarr: {{ radarr.error }}
+ {% endif %} + +
🍅 Top Box Office on RT
+ {% if rt_movies and rt_movies[0] is defined and rt_movies[0].error is defined %} +
Failed to load movie data: {{ rt_movies[0].error }}
+ {% elif rt_movies %} +
+ {% for movie in rt_movies %} +
+
+ {{ movie.title }}
+ {{ movie.description[:180] if movie.description else '' }}{% if movie.description and movie.description|length > 180 %}…{% endif %} +
+
{{ loop.index }}
+ {% if movie.poster %} + {% else %}
{% endif %} +
+
{{ movie.title }}{% if movie.year %} ({{ movie.year }}){% endif %}
+
+ {% if movie.certified %}✅ Certified Fresh + {% elif movie.critics_score %}🍅 {{ movie.critics_score }}%{% endif %} + {% if movie.audience_score %}🍿 {{ movie.audience_score }}{% endif %} +
+
+
+ +
+
+ {% endfor %} +
+ {% else %} +
No movie data available.
+ {% endif %} +
+
+ + +
+
+ 🔍 +

Prowlarr — Indexers

+ {% if prowlarr.status == "ok" %} + + {{ prowlarr.up_count }}/{{ prowlarr.total }} online + + {% endif %} +
+
+ {% if prowlarr.status == "ok" %} +
+ {% for idx in prowlarr.indexers %} +
+ + {{ idx.name }} +
+ {% endfor %} +
+ {% else %} +
Could not reach Prowlarr: {{ prowlarr.error }}
+ {% endif %} +
+
+ + +
+
+ ⬇️ +

SABnzbd

+ {% if sabnzbd.status == "ok" %} + {% set s = sabnzbd.sab_status | lower %} + + {{ sabnzbd.sab_status }} + + {% endif %} +
+
+ {% if sabnzbd.status == "ok" %} +
+
+
Current Speed
+
{{ sabnzbd.speed }}
+
+
+
Avg Speed (7d)
+
{{ sabnzbd.avg_speed }}
+
+
+
Queue Items
+
{{ sabnzbd.queue_count }}
+
+
+
Remaining
+
{{ sabnzbd.sizeleft }}
+
+
+
Downloads (7d)
+
{{ sabnzbd.completed_7d }}
+
+
+
Downloaded (7d)
+
{{ sabnzbd.week_size }}
+
+
+ + + {% else %} +
Could not reach SABnzbd: {{ sabnzbd.error }}
+ {% endif %} +
+
+ + +
+
+ 🎟️ +

Ombi — Requests

+ {% if ombi.status == "ok" %} +
+ {{ ombi.total_requests }} total + {{ ombi.total_pending }} pending +
+ {% endif %} +
+
+ {% if ombi.status == "ok" %} + {% if ombi.recent %} +
+ {% for req in ombi.recent %} +
+ {{ req.type }} +
+
{{ req.title }}
+
{{ req.user }}  ·  {{ req.date_display }}
+
+ {{ req.status }} +
+ {% endfor %} +
+ {% else %} +
No recent requests.
+ {% endif %} + {% else %} +
Could not reach Ombi: {{ ombi.error }}
+ {% endif %} +
+
+ + +
+
+ ▶️ +

Tautulli

+ {% if tautulli.status == "ok" %} +
+ {% if tautulli.active_streams > 0 %} + + {{ tautulli.active_streams }} active stream{{ 's' if tautulli.active_streams != 1 }} + {% else %} + No active streams + {% endif %} +
+ {% endif %} +
+
+ {% if tautulli.status == "ok" %} +
+ + +
+
+
📺
+
Top TV — 30d
+
+ {% if tautulli.top_tv_list %} +
+ {% for item in tautulli.top_tv_list %} +
+
{{ loop.index }}
+
{{ item.title }}
+
{{ item.plays }} plays
+
+ {% endfor %} +
+ {% else %} +
No data yet
+ {% endif %} +
+ + +
+
+
🎬
+
Top Movies — 30d
+
+ {% if tautulli.top_movie_list %} +
+ {% for item in tautulli.top_movie_list %} +
+
{{ loop.index }}
+
{{ item.title }}
+
{{ item.plays }} plays
+
+ {% endfor %} +
+ {% else %} +
No data yet
+ {% endif %} +
+ +
+ + +
+ + +
+
🏆 Top Played (30d)
+
+ {% if tautulli.top_played %} + {% for item in tautulli.top_played %} +
+
{{ loop.index }}
+
+
{{ item.title }}
+
by {{ item.user }}
+
+ {% set mt = item.media_type | lower %} + + {{ 'TV' if mt == 'episode' else ('Movie' if mt == 'movie' else mt or '?') }} + +
{{ item.plays }}×
+
+ {% endfor %} + {% else %} +
No play history available.
+ {% endif %} +
+
+ + +
+
👤 Top Viewers (30d)
+
+ {% if tautulli.top_users %} + {% for u in tautulli.top_users %} +
+
{{ loop.index }}
+
👤
+
+
{{ u.username }}
+
{{ u.plays }} plays
+
+
{{ u.plays }}×
+
+ {% endfor %} + {% else %} +
No viewer data available.
+ {% endif %} +
+
+ +
+ + {% else %} +
Could not reach Tautulli: {{ tautulli.error }}
+ {% endif %} +
+
+ + +
+ + + + + + + diff --git a/login.html b/login.html new file mode 100644 index 0000000..990b9a3 --- /dev/null +++ b/login.html @@ -0,0 +1,203 @@ + + + + + + Greendale Media Centre — Login + + + + + + +
+ +
+ Greendale Community College +

Greendale Media Centre

+

Human beings welcome. Robots too.

+
+ +
+

🔒  Study Room F — Access Required

+ + {% if error %} +
⚠️ {{ error }}
+ {% endif %} + +
+
+ + +
+ +
+
+ + + +
+ + + diff --git a/settings.html b/settings.html new file mode 100644 index 0000000..1b8bdc0 --- /dev/null +++ b/settings.html @@ -0,0 +1,486 @@ + + + + + + {% if theme == 'office' %}Settings — Dunder Mifflin Media Centre + {% elif theme == 'parks' %}Settings — Pawnee Parks Media Centre + {% elif theme == 'potter' %}Settings — Hogwarts Media Vault + {% elif theme == 'starwars' %}Settings — Rebel Alliance Media Archive + {% else %}Settings — Greendale Media Centre{% endif %} + {% if theme == 'office' %} + {% elif theme == 'parks' %} + {% elif theme == 'potter' %} + {% elif theme == 'starwars' %} + {% else %}{% endif %} + {% if theme == 'office' %} + + {% elif theme == 'parks' %} + + {% elif theme == 'potter' %} + + {% elif theme == 'starwars' %} + + {% else %} + + {% endif %} + + + + +
+ +

Settings

+ ← Dashboard +
+ +
+
+ + +
+
+ 🌍 +

General

+
+
+
+ + +
+
+ + +
+ +
+
+ + +
+
+ 🎨 +

Theme

+
+
+
+
+
🏫
+
Community
+
Greendale College
+
+
+
📎
+
The Office
+
Dunder Mifflin
+
+
+
🌳
+
Parks & Rec
+
Pawnee, Indiana
+
+
+
⚡
+
Harry Potter
+
Hogwarts
+
+
+
🌌
+
Star Wars
+
A long time ago…
+
+
+ +
+
+ + +
+
+ 🔐 +

Change Password

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
📺

Sonarr

+
+
+ + +
+ +
+
+ + +
+
🎬

Radarr

+
+
+ + +
+ +
+
+ + +
+
⬇️

SABnzbd

+
+
+ + +
+ +
+
+ + +
+
▶️

Tautulli

+
+
+ + +
+ +
+
+ + +
+
🔍

Prowlarr

+
+
+ + +
+ +
+
+ + +
+
🎟️

Ombi

+
+
+ + +
+ +
+
+ +
+
+ +
✓ Saved
+ + + + + diff --git a/templates/dashboard.html b/templates/dashboard.html index f355601..62e290e 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -3,670 +3,977 @@ - Media Centre - + {% if theme == 'office' %}Dunder Mifflin Media Centre + {% elif theme == 'parks' %}Pawnee Parks Media Centre + {% elif theme == 'potter' %}Hogwarts Media Vault + {% elif theme == 'starwars' %}Rebel Alliance Media Archive + {% else %}Greendale Media Centre{% endif %} + {% if theme == 'office' %} + {% elif theme == 'parks' %} + {% elif theme == 'potter' %} + {% elif theme == 'starwars' %} + {% else %}{% endif %} - + {% if theme == 'office' %} + + {% elif theme == 'parks' %} + + {% elif theme == 'potter' %} + + {% elif theme == 'starwars' %} + + {% else %} + + {% endif %} -{#- Degraded service: human-readable cause, raw exception behind a - disclosure with any API key redacted. -#} +{#- Renders a degraded service as human copy, with the raw exception tucked + behind a disclosure for debugging. -#} {% macro service_error(name, raw) %} {% endmacro %} - -{#- One service status pill in the header strip. -#} -{% macro svc_pill(key, label, url, up, stat) %} - - - {{ label }} - {{ stat }} - -{% endmacro %}
-
- -

Media Centre

+
+ {% if theme == 'office' %} +
📎
+ {% elif theme == 'parks' %} +
🌳
+ {% elif theme == 'potter' %} +
⚡
+ {% elif theme == 'starwars' %} +
+ + + + + + + + + + + +
+ {% else %} + Greendale + {% endif %} +
+ {% if theme == 'office' %} +

Dunder Mifflin Media Centre

+
That's what she watched.
+ {% elif theme == 'parks' %} +

Pawnee Parks Media Centre

+
The greatest city in the world.
+ {% elif theme == 'potter' %} +

Hogwarts Media Vault

+
Mischief managed.
+ {% elif theme == 'starwars' %} +

Rebel Alliance Media Archive

+
May the downloads be with you.
+ {% else %} +

Greendale Media Centre

+
Human beings welcome. Robots too.
+ {% endif %} +
+
- + + {% set up = sonarr.status == "ok" %} + + + + Sonarr + + {%- if up %}{{ sonarr.total_series }} series{% else %}offline{% endif -%} + + + + + {% set up = radarr.status == "ok" %} + + + + Radarr + + {%- if up %}{{ radarr.total_movies }} movies{% else %}offline{% endif -%} + + + + + + {% set up = prowlarr.status == "ok" %} + + + + Prowlarr + + {%- if up %}{{ prowlarr.up_count }}/{{ prowlarr.total }} online{% else %}offline{% endif -%} + + + + + + {% set up = ombi.status == "ok" %} + + + + Ombi + + {%- if up %}{{ ombi.total_pending }} pending{% else %}offline{% endif -%} + + + + + + {% set up = sabnzbd.status == "ok" %} + + + + SABnzbd + + {%- if up %}{{ sabnzbd.speed }}{% else %}offline{% endif -%} + + + + + + + {% set up = tautulli.status == "ok" %} + + + + Tautulli + + {%- if up -%} + {%- if tautulli.active_streams > 0 -%} + {{ tautulli.active_streams }} streaming + {%- else -%} + 0 streams + {%- endif -%} + {%- else -%}offline{%- endif -%} + + + + + + +
- -
-

Right now

-
- - -
-
-

Now playing

- - {%- if tautulli.status == 'ok' -%} - {{ tautulli.active_streams }} active - {%- endif -%} - -
-
- {% if tautulli.status == 'ok' %} -
- {% if tautulli.sessions %} - {% for s in tautulli.sessions %} -
- - - - {{ s.title }} - {{ s.user }} - -
- {% endfor %} - {% else %} -

Nothing streaming right now.

- {% endif %} -
- {% else %} - {{ service_error("Tautulli", tautulli.error) }} - {% endif %} -
-
- - -
-
-

Downloads

- {% if sabnzbd.status == 'ok' %} - {% set st = sabnzbd.sab_status | lower %} - - {{ sabnzbd.sab_status }} - - {% endif %} -
-
- {% if sabnzbd.status == 'ok' %} -
-
{{ sabnzbd.speed }}
-
- {{ sabnzbd.queue_count }} queued · - {{ sabnzbd.sizeleft }} left - -
-
-
-
Avg speed (7d)
{{ sabnzbd.avg_speed }}
-
Grabbed (7d)
{{ sabnzbd.completed_7d }}
-
Volume (7d)
{{ sabnzbd.week_size }}
-
- {% else %} - {{ service_error("SABnzbd", sabnzbd.error) }} - {% endif %} -
-
- - -
-
-

Storage

- {% if unraid.status == 'ok' and unraid.array_state %} - {{ unraid.array_state | capitalize }} - {% endif %} -
-
- {% if unraid.status == 'ok' %} - {% set pct = unraid.array_used_pct %} -
- {{ pct }}% -
-
{{ unraid.array_free }} free of {{ unraid.array_total }}
-
-
-
-
- {{ unraid.array_used }} used - {% if unraid.disk_count %}{{ unraid.disk_count }} disk{{ 's' if unraid.disk_count != 1 }}{% endif %} -
- {% if unraid.source == "local" %} -

Container disk usage ({{ unraid.mount }}). Set UNRAID_API_KEY for full array stats.

- {% endif %} - {% else %} - {{ service_error("Unraid", unraid.error) }} - {% endif %} -
-
- -
-
- - -
-

Library

-
- - -
- -
- -
-
-
-
-
- -
- - {% if sonarr.status == 'ok' %} -
-
Series
{{ sonarr.total_series }}
-
Missing episodes
{{ sonarr.missing_episodes }}
-
- {% else %} - {{ service_error("Sonarr", sonarr.error) }} - {% endif %} -
-
- - -
- -
- -
-
-
-
-
- -
- - {% if radarr.status == 'ok' %} -
-
Movies
{{ radarr.total_movies }}
-
Missing
{{ radarr.missing_movies }}
-
- {% else %} - {{ service_error("Radarr", radarr.error) }} - {% endif %} -
-
- -
-
- - -
-

Discover — not in your library yet

-
- - -
-

Popular TV on Rotten Tomatoes

-
- {% if rt_tv and rt_tv[0] is defined and rt_tv[0].error is defined %} -

Couldn't load TV recommendations.

- {% elif rt_tv %} -
- {% for show in rt_tv %} -
- {{ loop.index }} - {% if show.poster %} - {% else %}{% endif %} -
-
{{ show.title }}{% if show.year %} ({{ show.year }}){% endif %}
-
- {% if show.certified %}Certified Fresh - {% elif show.critics_score %}{{ show.critics_score }}% critics{% endif %} - {% if show.audience_score %}{{ show.audience_score }} audience{% endif %} -
-

{{ show.description[:180] ~ '…' if show.description and show.description|length > 180 else (show.description or '') }}

-
- -
- {% endfor %} -
- {% if rt_tv | length > 5 %} - - {% endif %} - {% else %} -

No TV recommendations available.

- {% endif %} -
-
- - -
-

Top box office on Rotten Tomatoes

-
- {% if rt_movies and rt_movies[0] is defined and rt_movies[0].error is defined %} -

Couldn't load movie recommendations.

- {% elif rt_movies %} -
- {% for movie in rt_movies %} -
- {{ loop.index }} - {% if movie.poster %} - {% else %}{% endif %} -
-
{{ movie.title }}{% if movie.year %} ({{ movie.year }}){% endif %}
-
- {% if movie.certified %}Certified Fresh - {% elif movie.critics_score %}{{ movie.critics_score }}% critics{% endif %} - {% if movie.audience_score %}{{ movie.audience_score }} audience{% endif %} -
-

{{ movie.description[:180] ~ '…' if movie.description and movie.description|length > 180 else (movie.description or '') }}

-
- -
- {% endfor %} -
- {% if rt_movies | length > 5 %} - - {% endif %} - {% else %} -

No movie recommendations available.

- {% endif %} -
-
- -
-
- - -
-

Requests & indexers

-
- - -
-
-

Requests — Ombi

- {% if ombi.status == 'ok' %} - {{ ombi.total_pending }} pending of {{ ombi.total_requests }} - {% endif %} -
-
- {% if ombi.status == 'ok' %} - {% if ombi.recent %} -
- {% for req in ombi.recent %} -
- {{ req.type }} -
-
{{ req.title }}
-
{{ req.user }} · {{ req.date_display }}
-
- {{ req.status }} -
- {% endfor %} -
- {% else %} -

No recent requests.

- {% endif %} - {% else %} - {{ service_error("Ombi", ombi.error) }} - {% endif %} -
-
- - -
-
-

Indexers — Prowlarr

- {% if prowlarr.status == 'ok' %} - - {{ prowlarr.up_count }}/{{ prowlarr.total }} online - - {% endif %} -
-
- {% if prowlarr.status == 'ok' %} -
- {% for idx in prowlarr.indexers %} -
- - {{ idx.name }} -
- {% endfor %} -
- {% else %} - {{ service_error("Prowlarr", prowlarr.error) }} - {% endif %} -
-
- -
-
- - - {% if tautulli.status == 'ok' %} -
-

Watch insights — last 30 days

-
- -
-

Most played

-
- {% if tautulli.top_played %} -
- {% for item in tautulli.top_played %} -
- {{ loop.index }} -
-
{{ item.title }}
-
{{ item.user }}
-
- {% set mt = item.media_type | lower %} - {{ 'TV' if mt == 'episode' else ('Movie' if mt == 'movie' else mt or '?') }} - {{ item.plays }}× -
- {% endfor %} -
- {% else %} -

No play history yet.

- {% endif %} -
-
- -
-

Top viewers

-
- {% if tautulli.top_users %} -
- {% for u in tautulli.top_users %} -
- {{ loop.index }} -
-
{{ u.username }}
-
- {{ u.plays }} plays -
- {% endfor %} -
- {% else %} -

No viewer data yet.

- {% endif %} -
-
- -
- - {% if tautulli.top_tv_list or tautulli.top_movie_list %} -
-
-

Top TV

-
- {% if tautulli.top_tv_list %} -
- {% for item in tautulli.top_tv_list %} -
- {{ loop.index }} -
{{ item.title }}
- {{ item.plays }} plays -
- {% endfor %} -
- {% else %}

No data yet.

{% endif %} -
-
-
-

Top movies

-
- {% if tautulli.top_movie_list %} -
- {% for item in tautulli.top_movie_list %} -
- {{ loop.index }} -
{{ item.title }}
- {{ item.plays }} plays -
- {% endfor %} -
- {% else %}

No data yet.

{% endif %} -
-
-
+
+ {% if theme == 'office' %} + 📋  Monday morning meeting: checking downloads, missing episodes, and who took the last of the coffee. + {% elif theme == 'parks' %} + 📋  Today's agenda: checking what's missing, what's downloading, and making Pawnee great again. + {% elif theme == 'potter' %} + 📋  Today's scroll: checking what spells are missing, what's being conjured, and what deserves a watch. + {% elif theme == 'starwars' %} + 📋  Rebel briefing: checking what's missing, what's downloading, and what the Force recommends watching. + {% else %} + 📋  Today's agenda: checking what's missing, what's downloading, and what deserves to be watched. {% endif %} -
- {% endif %} + + + +
+
+ 🖥️ +

Unraid

+ {% if unraid.status == "ok" %} + + {% if unraid.array_state %}{{ unraid.array_state | capitalize }}{% endif %} + {% if unraid.disk_count %} ·  {{ unraid.disk_count }} disk{{ 's' if unraid.disk_count != 1 }}{% endif %} + {% if unraid.os_version %} ·  v{{ unraid.os_version }}{% endif %} + + {% endif %} +
+
+ {% if unraid.status == "ok" %} +
+
+
Array Total
+
{{ unraid.array_total }}
+
+
+
Array Used
+
{{ unraid.array_used }}
+
+
+
Array Free
+
{{ unraid.array_free }}
+
+
+ +
+
+ Array Usage + {{ unraid.array_used_pct }}% +
+
+
+
+
+ {% if unraid.source == "local" %} +
+ ⓘ Showing container disk usage ({{ unraid.mount }}). Add UNRAID_API_KEY for full Unraid stats. +
+ {% endif %} + {% else %} + {{ service_error("Unraid", unraid.error) }} + {% endif %} +
+
+ + +
+
+ 📺 +

Sonarr

+
+
+ +
+
+
+
+
+ +
+ + {% if sonarr.status == "ok" %} +
+
+
Total Series
+
{{ sonarr.total_series }}
+
+
+
Missing Episodes
+
{{ sonarr.missing_episodes }}
+
+
+ {% else %} + {{ service_error("Sonarr", sonarr.error) }} + {% endif %} + +
🍅 Popular on RT
+ {% if rt_tv and rt_tv[0] is defined and rt_tv[0].error is defined %} +
Failed to load TV data: {{ rt_tv[0].error }}
+ {% elif rt_tv %} +
+ {% for show in rt_tv %} +
+
+ {{ show.title }}
+ {{ show.description[:180] if show.description else '' }}{% if show.description and show.description|length > 180 %}…{% endif %} +
+
{{ loop.index }}
+ {% if show.poster %} + {% else %}
{% endif %} +
+
{{ show.title }}{% if show.year %} ({{ show.year }}){% endif %}
+
+ {% if show.certified %}✅ Certified Fresh + {% elif show.critics_score %}🍅 {{ show.critics_score }}%{% endif %} + {% if show.audience_score %}🍿 {{ show.audience_score }}{% endif %} +
+
+
+ +
+
+ {% endfor %} +
+ {% else %} +
No TV data available.
+ {% endif %} +
+
+ + +
+
+ 🎬 +

Radarr

+
+
+ +
+
+
+
+
+ +
+ + {% if radarr.status == "ok" %} +
+
+
Total Movies
+
{{ radarr.total_movies }}
+
+
+
Missing
+
{{ radarr.missing_movies }}
+
+
+ {% else %} + {{ service_error("Radarr", radarr.error) }} + {% endif %} + +
🍅 Top Box Office on RT
+ {% if rt_movies and rt_movies[0] is defined and rt_movies[0].error is defined %} +
Failed to load movie data: {{ rt_movies[0].error }}
+ {% elif rt_movies %} +
+ {% for movie in rt_movies %} +
+
+ {{ movie.title }}
+ {{ movie.description[:180] if movie.description else '' }}{% if movie.description and movie.description|length > 180 %}…{% endif %} +
+
{{ loop.index }}
+ {% if movie.poster %} + {% else %}
{% endif %} +
+
{{ movie.title }}{% if movie.year %} ({{ movie.year }}){% endif %}
+
+ {% if movie.certified %}✅ Certified Fresh + {% elif movie.critics_score %}🍅 {{ movie.critics_score }}%{% endif %} + {% if movie.audience_score %}🍿 {{ movie.audience_score }}{% endif %} +
+
+
+ +
+
+ {% endfor %} +
+ {% else %} +
No movie data available.
+ {% endif %} +
+
+ + +
+
+ 🔍 +

Prowlarr — Indexers

+ {% if prowlarr.status == "ok" %} + + {{ prowlarr.up_count }}/{{ prowlarr.total }} online + + {% endif %} +
+
+ {% if prowlarr.status == "ok" %} +
+ {% for idx in prowlarr.indexers %} +
+ + {{ idx.name }} +
+ {% endfor %} +
+ {% else %} + {{ service_error("Prowlarr", prowlarr.error) }} + {% endif %} +
+
+ + +
+
+ ⬇️ +

SABnzbd

+ {% if sabnzbd.status == "ok" %} + {% set s = sabnzbd.sab_status | lower %} + + {{ sabnzbd.sab_status }} + + {% endif %} +
+
+ {% if sabnzbd.status == "ok" %} +
+
+
Current Speed
+
{{ sabnzbd.speed }}
+
+
+
Avg Speed (7d)
+
{{ sabnzbd.avg_speed }}
+
+
+
Queue Items
+
{{ sabnzbd.queue_count }}
+
+
+
Remaining
+
{{ sabnzbd.sizeleft }}
+
+
+
Downloads (7d)
+
{{ sabnzbd.completed_7d }}
+
+
+
Downloaded (7d)
+
{{ sabnzbd.week_size }}
+
+
+ + + {% else %} + {{ service_error("SABnzbd", sabnzbd.error) }} + {% endif %} +
+
+ + +
+
+ 🎟️ +

Ombi — Requests

+ {% if ombi.status == "ok" %} +
+ {{ ombi.total_requests }} total + {{ ombi.total_pending }} pending +
+ {% endif %} +
+
+ {% if ombi.status == "ok" %} + {% if ombi.recent %} +
+ {% for req in ombi.recent %} +
+ {{ req.type }} +
+
{{ req.title }}
+
{{ req.user }}  ·  {{ req.date_display }}
+
+ {{ req.status }} +
+ {% endfor %} +
+ {% else %} +
No recent requests.
+ {% endif %} + {% else %} + {{ service_error("Ombi", ombi.error) }} + {% endif %} +
+
+ + +
+
+ ▶️ +

Tautulli

+ {% if tautulli.status == "ok" %} +
+ {% if tautulli.active_streams > 0 %} + + {{ tautulli.active_streams }} active stream{{ 's' if tautulli.active_streams != 1 }} + {% else %} + No active streams + {% endif %} +
+ {% endif %} +
+
+ {% if tautulli.status == "ok" %} +
+ + +
+
+
📺
+
Top TV — 30d
+
+ {% if tautulli.top_tv_list %} +
+ {% for item in tautulli.top_tv_list %} +
+
{{ loop.index }}
+
{{ item.title }}
+
{{ item.plays }} plays
+
+ {% endfor %} +
+ {% else %} +
No data yet
+ {% endif %} +
+ + +
+
+
🎬
+
Top Movies — 30d
+
+ {% if tautulli.top_movie_list %} +
+ {% for item in tautulli.top_movie_list %} +
+
{{ loop.index }}
+
{{ item.title }}
+
{{ item.plays }} plays
+
+ {% endfor %} +
+ {% else %} +
No data yet
+ {% endif %} +
+ +
+ + +
+ + +
+
🏆 Top Played (30d)
+
+ {% if tautulli.top_played %} + {% for item in tautulli.top_played %} +
+
{{ loop.index }}
+
+
{{ item.title }}
+
by {{ item.user }}
+
+ {% set mt = item.media_type | lower %} + + {{ 'TV' if mt == 'episode' else ('Movie' if mt == 'movie' else mt or '?') }} + +
{{ item.plays }}×
+
+ {% endfor %} + {% else %} +
No play history available.
+ {% endif %} +
+
+ + +
+
👤 Top Viewers (30d)
+
+ {% if tautulli.top_users %} + {% for u in tautulli.top_users %} +
+
{{ loop.index }}
+
👤
+
+
{{ u.username }}
+
{{ u.plays }} plays
+
+
{{ u.plays }}×
+
+ {% endfor %} + {% else %} +
No viewer data available.
+ {% endif %} +
+
+ +
+ + {% else %} + {{ service_error("Tautulli", tautulli.error) }} + {% endif %} +
+
+
- + diff --git a/templates/login.html b/templates/login.html index 08ed664..990b9a3 100644 --- a/templates/login.html +++ b/templates/login.html @@ -3,148 +3,200 @@ - Sign in — Media Centre - - - - + Greendale Media Centre — Login + + -
+
- -

Media Centre

+ Greendale Community College +

Greendale Media Centre

+

Human beings welcome. Robots too.

-
+ + +
diff --git a/templates/settings.html b/templates/settings.html index 71bb751..1b8bdc0 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -3,207 +3,245 @@ - Settings — Media Centre - - - - + {% if theme == 'office' %}Settings — Dunder Mifflin Media Centre + {% elif theme == 'parks' %}Settings — Pawnee Parks Media Centre + {% elif theme == 'potter' %}Settings — Hogwarts Media Vault + {% elif theme == 'starwars' %}Settings — Rebel Alliance Media Archive + {% else %}Settings — Greendale Media Centre{% endif %} + {% if theme == 'office' %} + {% elif theme == 'parks' %} + {% elif theme == 'potter' %} + {% elif theme == 'starwars' %} + {% else %}{% endif %} + {% if theme == 'office' %} + + {% elif theme == 'parks' %} + + {% elif theme == 'potter' %} + + {% elif theme == 'starwars' %} + + {% else %} + + {% endif %}
- +

Settings

- ← Dashboard + ← Dashboard
-
+
-
-

General

+
+
+ 🌍 +

General

+
- +
- + -

LAN address of the machine running Sonarr, Radarr, and the rest.

- +
- -
-

Change password

+ +
+
+ 🎨 +

Theme

+
+
+
+
+
🏫
+
Community
+
Greendale College
+
+
+
📎
+
The Office
+
Dunder Mifflin
+
+
+
🌳
+
Parks & Rec
+
Pawnee, Indiana
+
+
+
⚡
+
Harry Potter
+
Hogwarts
+
+
+
🌌
+
Star Wars
+
A long time ago…
+
+
+ +
+
+ + +
+
+ 🔐 +

Change Password

+
- - + +
- - + +
- - - + + +
- +
- - {% for svc, label in [ - ('sonarr','Sonarr'), ('radarr','Radarr'), ('sabnzbd','SABnzbd'), - ('tautulli','Tautulli'), ('prowlarr','Prowlarr'), ('ombi','Ombi') - ] %} +
-

{{ label }}

+
📺

Sonarr

- - + +
- + +
+
+ + +
+
🎬

Radarr

+
+
+ + +
+ +
+
+ + +
+
⬇️

SABnzbd

+
+
+ + +
+ +
+
+ + +
+
▶️

Tautulli

+
+
+ + +
+ +
+
+ + +
+
🔍

Prowlarr

+
+
+ + +
+ +
+
+ + +
+
🎟️

Ombi

+
+
+ + +
+
- {% endfor %}
-
+
✓ Saved