From 339b9f331013c1adae7b74fb7a084059de8c4d02 Mon Sep 17 00:00:00 2001 From: Barely Removable Date: Wed, 26 Aug 2026 08:12:41 -0700 Subject: [PATCH 1/2] Redesign dashboard: single neutral theme, urgency-ordered layout Replaces the five novelty themes with one professional dark theme built on a shared token set (surfaces, 6-step type scale, semantic colours, 44px tap target) used across dashboard, login and settings. Layout now matches the cache tiers in app.py: a "Right now" strip with live streams, the download queue and array usage sits above the fold, with library, discovery, requests and watch insights below. Previously the page opened on rarely-actionable disk stats and buried active streams under twenty recommendation rows. Also from the design review: - Discover lists show 5 with a Show more toggle instead of 10 each - RT descriptions render inline and clamped, not hover-only tooltips that touch devices could never reach - Adding a title is now two-step (tap to arm, tap to confirm) since it queues a real download - role=alert on errors, aria-live on polled regions, semantic dl/dt/dd stats, labelled search inputs - every text/surface pair verified at >= 5:1 contrast Drops the dead delete-modal JS, which threw a TypeError on every load, and the stale root-level template copies that Flask never rendered. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 25 +- app.py | 6 +- dashboard.html | 1964 -------------------------- login.html | 203 --- settings.html | 486 ------- templates/dashboard.html | 2839 ++++++++++++++++---------------------- templates/login.html | 226 ++- templates/settings.html | 563 +++----- 8 files changed, 1484 insertions(+), 4828 deletions(-) delete mode 100644 dashboard.html delete mode 100644 login.html delete mode 100644 settings.html diff --git a/CLAUDE.md b/CLAUDE.md index d935591..9783b39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,8 +3,9 @@ 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 "Greendale Media Centre." Runs as one Docker container on the -user's Unraid home server. Server-side project directory is named +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 `greendale`; the container/repo are named `arr-summary`. ## Architecture @@ -33,10 +34,20 @@ user's Unraid home server. Server-side project directory is 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`); 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. + `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. - **`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 @@ -78,5 +89,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, theme, changed login password) also persists +`settings.json` (timezone, changed login password) also persists server-side outside git. diff --git a/app.py b/app.py index 0d44548..e95eb4b 100644 --- a/app.py +++ b/app.py @@ -1001,7 +1001,6 @@ 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 = { @@ -1020,7 +1019,6 @@ 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, ) @@ -1243,7 +1241,6 @@ 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"), ) @@ -1252,7 +1249,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", "theme"} + "tautulli_api_key", "prowlarr_api_key", "ombi_api_key", "arr_host"} filtered = {k: v for k, v in data.items() if k in allowed} save_settings(filtered) return jsonify({"success": True}) @@ -1264,7 +1261,6 @@ 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 deleted file mode 100644 index 46e2979..0000000 --- a/dashboard.html +++ /dev/null @@ -1,1964 +0,0 @@ - - - - - - {% 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 %} -
-
- - -
- -
- {% if theme == 'office' %} - "I'm not superstitious, but I am a little stitious."  —  Michael Scott  |  Dunder Mifflin Media Centre - {% elif theme == 'parks' %} - "If you believe it, you can achieve it."  —  Leslie Knope  |  Pawnee Parks Media Centre - {% elif theme == 'potter' %} - "It does not do to dwell on dreams and forget to live."  —  Albus Dumbledore  |  Hogwarts Media Vault - {% elif theme == 'starwars' %} - "Do. Or do not. There is no try."  —  Yoda  |  Rebel Alliance Media Archive - {% else %} - "I discovered at a young age that I have... a gift."  —  Jeff Winger  |  Greendale Human Being Media Centre - {% endif %} -
- - - - - diff --git a/login.html b/login.html deleted file mode 100644 index 990b9a3..0000000 --- a/login.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - Greendale Media Centre — Login - - - - - - - - - - diff --git a/settings.html b/settings.html deleted file mode 100644 index 1b8bdc0..0000000 --- a/settings.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - {% 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 62e290e..0ff748e 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -3,977 +3,670 @@ - {% 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 %} + Media Centre + - {% if theme == 'office' %} - - {% elif theme == 'parks' %} - - {% elif theme == 'potter' %} - - {% elif theme == 'starwars' %} - - {% else %} - - {% endif %} + -{#- Renders a degraded service as human copy, with the raw exception tucked - behind a disclosure for debugging. -#} +{#- Degraded service: human-readable cause, raw exception behind a + disclosure with any API key redacted. -#} {% 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 %}
-
- {% 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 %} -
+
+ +

Media Centre

-
- - {% 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 %} -
+ +
+

Right now

+
- -
-
- 🖥️ -

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 }}
+ +
+
+

Now playing

+ + {%- if tautulli.status == 'ok' -%} + {{ tautulli.active_streams }} active + {%- endif -%} +
-
-
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 '?') }} +
+ {% if tautulli.status == 'ok' %} +
+ {% if tautulli.sessions %} + {% for s in tautulli.sessions %} +
+ + + + {{ s.title }} + {{ s.user }} -
{{ item.plays }}×
{% endfor %} {% else %} -
No play history available.
+

Nothing streaming right now.

{% endif %}
+ {% else %} + {{ service_error("Tautulli", tautulli.error) }} + {% 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 %} -
-
+ +
+
+

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 %} +
+
+
+ {% endif %} +
+ {% endif %}
-
- {% if theme == 'office' %} - "I'm not superstitious, but I am a little stitious."  —  Michael Scott  |  Dunder Mifflin Media Centre - {% elif theme == 'parks' %} - "If you believe it, you can achieve it."  —  Leslie Knope  |  Pawnee Parks Media Centre - {% elif theme == 'potter' %} - "It does not do to dwell on dreams and forget to live."  —  Albus Dumbledore  |  Hogwarts Media Vault - {% elif theme == 'starwars' %} - "Do. Or do not. There is no try."  —  Yoda  |  Rebel Alliance Media Archive - {% else %} - "I discovered at a young age that I have... a gift."  —  Jeff Winger  |  Greendale Human Being Media Centre - {% endif %} -
+
Media Centre
diff --git a/templates/login.html b/templates/login.html index 990b9a3..512d27a 100644 --- a/templates/login.html +++ b/templates/login.html @@ -3,200 +3,148 @@ - Greendale Media Centre — Login - - + Sign in — Media Centre + + + + -