arr-summary/templates/system.html
Barely Removable 106bd16de8
All checks were successful
Deploy / deploy (push) Successful in 39s
Show who requested each show on Admin rows
Sonarr has no concept of a requester, so this cross-references Ombi's
TV request list by title to attribute the show to whoever's request
triggered the grab, falling back to "Requester unknown" for anything
added straight in Sonarr or when Ombi can't be reached.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPJxtE9tPURsaWDZ6rC4Dt
2026-09-05 09:55:02 -07:00

203 lines
10 KiB
HTML

{% extends "_shell.html" %}
{% from "_macros.html" import service_error, bar, disk_state, disk_row, trend %}
{% block title %}P105 System{% endblock %}
{% block content %}
<h1 class="tt-dh bloom tt-masthead">SYSTEM</h1>
{#- ── Service transmission status ───────────────────────────────
Six services, one row each, colour carrying up or down. This is
what the old header pills were, given room to say something. Each
row opens that service's own instance, via the reverse proxy. -#}
<div class="tt-field"><span>Transmission</span></div>
<div class="tt-rows">
{% for name, key, svc in [('Sonarr', 'sonarr', sonarr), ('Radarr', 'radarr', radarr), ('SABnzbd', 'sabnzbd', sabnzbd),
('Tautulli', 'tautulli', tautulli), ('Prowlarr', 'prowlarr', prowlarr), ('Ombi', 'ombi', ombi)] %}
<a class="tt-row" href="{{ service_urls[key] }}" target="_blank" rel="noopener">
<span class="tt-row-name">{{ name }} <span class="tt-row-ext" aria-hidden="true">&#8599;</span></span>
<span class="tt-row-body"></span>
<span class="tt-row-val {{ 'ok' if svc.status == 'ok' else 'alert' }}">
{{- 'ON AIR' if svc.status == 'ok' else 'OFF AIR' -}}
</span>
</a>
{% endfor %}
</div>
{#- ── Array ───────────────────────────────────────────────────── -#}
{% if unraid.status != 'ok' %}
<div class="tt-field crit"><span>Array</span><span class="tt-field-meta">unreachable</span></div>
{{ service_error('Unraid', unraid.error) }}
{% else %}
{%- set apct = unraid.array_used_pct | default(0) | float -%}
<div class="tt-field {% if apct >= 90 %}crit{% elif apct >= 75 %}warn{% endif %}">
<span>Array</span>
<span class="tt-field-meta">
{{ unraid.array_state | default('') }}
{%- if unraid.parities %} &middot; parity
{%- for p in unraid.parities %} {{ p.temp if p.temp is not none else '—' }}&deg;C{% endfor %}
{%- endif %}
</span>
</div>
<div class="tt-figs">
<div class="tt-fig">
<div class="tt-dh tt-fig-v {{ 'alert' if apct >= 90 else ('warn' if apct >= 75 else 'ok') }}">{{ apct | round | int }}%</div>
<div class="tt-fig-l">Used</div>
</div>
<div class="tt-fig">
<div class="tt-dh tt-fig-v">{{ unraid.array_free | default('—') }}</div>
<div class="tt-fig-l">Free</div>
</div>
<div class="tt-fig">
<div class="tt-dh tt-fig-v">{{ unraid.array_total | default('—') }}</div>
<div class="tt-fig-l">Total</div>
</div>
<div class="tt-fig">
<div class="tt-dh tt-fig-v">{{ unraid.disk_count | default(0) }}</div>
<div class="tt-fig-l">Disks</div>
</div>
</div>
{% if unraid.disks %}
{#- Desktop: a real table — columns line up, a new metric is a new
column. Hidden below 560px, where six columns stop fitting and
a horizontal scroll would hide the bar and the temperature,
the two readings that matter most on this page. -#}
<div class="tt-table-scroll">
<table class="tt-table">
<thead>
<tr>
<th scope="col">Disk</th>
<th scope="col">Device</th>
<th scope="col" class="num">Size</th>
<th scope="col" class="num">Free</th>
<th scope="col">Usage</th>
<th scope="col" class="num">Temp</th>
</tr>
</thead>
<tbody>
{% for dk in unraid.disks %}
<tr>
<th scope="row">{{ dk.name }}</th>
<td>{{ dk.device | default('—') }}</td>
<td class="num">{{ dk.total | default('—') }}</td>
<td class="num">{{ dk.free | default('—') }}</td>
<td>{{ bar(dk.used_pct | default(0)) }} <span class="val">{{ dk.used_pct | default(0) | round | int }}%</span></td>
<td class="num">{{ disk_state(dk) }}</td>
</tr>
{% endfor %}
{% for ck in unraid.caches | default([]) %}
<tr>
<th scope="row">{{ ck.name }}</th>
<td>{{ ck.device | default('—') }}</td>
<td class="num">{{ ck.total | default('—') }}</td>
<td class="num">{{ ck.free | default('—') }}</td>
<td>{{ bar(ck.used_pct | default(0)) }} <span class="val">{{ ck.used_pct | default(0) | round | int }}%</span></td>
<td class="num">{{ disk_state(ck) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{#- Phone (≤560px): the .tt-row-derived reflow — name, the shared
20-cell bar and the temperature on one line, device/size/free
beneath. Hidden above 560px in favour of the table. -#}
<div class="tt-disk-rows">
{% for dk in unraid.disks %}{{ disk_row(dk) }}{% endfor %}
{% for ck in unraid.caches | default([]) %}{{ disk_row(ck) }}{% endfor %}
</div>
{% endif %}
{% endif %}
{#- ── Downloads ─────────────────────────────────────────────────
The one place the service holds real history, so the one place a
trend arrow is honest: current speed against the 7-day average. -#}
{% if sabnzbd.status == 'ok' %}
<div class="tt-field"><span>Downloads</span><span class="tt-field-meta">{{ sabnzbd.sab_status | default('') }}</span></div>
<div class="tt-figs">
<div class="tt-fig">
<div class="tt-dh tt-fig-v"><span id="sab-queue">{{ sabnzbd.queue_count }}</span></div>
<div class="tt-fig-l">In queue</div>
</div>
<div class="tt-fig">
<div class="tt-dh tt-fig-v"><span id="sab-speed">{{ sabnzbd.speed | default('0') }}</span> {{ trend(sabnzbd.speed_kbps, sabnzbd.avg_speed_kbps) }}</div>
<div class="tt-fig-l">Speed vs 7-day avg</div>
</div>
<div class="tt-fig">
<div class="tt-dh tt-fig-v"><span id="sab-remaining">{{ sabnzbd.sizeleft | default('—') }}</span></div>
<div class="tt-fig-l">Remaining</div>
</div>
<div class="tt-fig">
<div class="tt-dh tt-fig-v">{{ sabnzbd.week_size | default('—') }}</div>
<div class="tt-fig-l">This week</div>
</div>
</div>
{% else %}
<div class="tt-field crit"><span>Downloads</span><span class="tt-field-meta">off air</span></div>
{{ service_error('SABnzbd', sabnzbd.error) }}
{% endif %}
{#- ── Indexers ────────────────────────────────────────────────── -#}
{% if prowlarr.status == 'ok' %}
<div class="tt-field">
<span>Indexers</span>
<span class="tt-field-meta">{{ prowlarr.up_count }} of {{ prowlarr.total }} up</span>
</div>
<div class="tt-rows">
{% for idx in prowlarr.indexers %}
<div class="tt-row">
<span class="tt-row-name">{{ idx.protocol | default('') }}</span>
<span class="tt-row-body">
<span class="tt-row-title">{{ idx.name }}</span>
{% if prowlarr.has_stats %}<span class="tt-row-sub">{{ idx.grabs | default(0) }} grabs &middot; {{ idx.queries | default(0) }} queries</span>{% endif %}
</span>
{% if prowlarr.has_stats %}{{ bar(idx.grab_share | default(0), 'ok' if idx.up else 'alert') }}{% endif %}
<span class="tt-row-val {{ 'ok' if idx.up else 'alert' }}">{{ 'UP' if idx.up else 'DOWN' }}</span>
</div>
{% endfor %}
</div>
{% else %}
<div class="tt-field crit"><span>Indexers</span><span class="tt-field-meta">off air</span></div>
{{ service_error('Prowlarr', prowlarr.error) }}
{% endif %}
{#- ── Admin ─────────────────────────────────────────────────────
Shows Sonarr is holding, sized, that nobody has ever played in
Tautulli and that have sat untouched for longer than the
neglect window — largest first, since the point is reclaiming
space. Delete removes the series from Sonarr with its episode
files and folder; that's permanent, so it shares the site's
two-tap arming rather than a native confirm(). -#}
{% if admin_unwatched.status != 'ok' %}
<div class="tt-field crit"><span>Admin</span><span class="tt-field-meta">off air</span></div>
{{ service_error('Sonarr/Tautulli', admin_unwatched.error) }}
{% else %}
<div class="tt-field">
<span>Admin</span>
<span class="tt-field-meta" id="admin-meta">
{{ admin_unwatched.shows | length }} unwatched &middot; {{ admin_unwatched.total_display }} reclaimable
</span>
</div>
{% if admin_unwatched.shows %}
<div class="tt-rows" id="admin-rows">
{% for show in admin_unwatched.shows %}
<div class="tt-row" data-bytes="{{ show.size }}">
<span class="tt-row-name">{{ show.ep_count }} EP</span>
<span class="tt-row-body">
<span class="tt-row-title">{{ show.title }}</span>
<span class="tt-row-sub">{% if show.year %}{{ show.year }} &middot; {% endif %}Downloaded {{ show.added[:10] if show.added else 'unknown' }} &middot; {% if show.requested_by %}Requested by {{ show.requested_by }}{% else %}Requester unknown{% endif %} &middot; Never watched</span>
</span>
<span class="tt-row-val">{{ show.size_display }}</span>
<span class="tt-row-acts">
<button class="tt-btn tt-btn-no tt-btn-sm" data-id="{{ show.sonarr_id }}"
onclick="armDelete(this)" aria-label="Delete {{ show.title }}">Delete</button>
</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="tt-empty" id="admin-empty">Nothing unwatched and older than {{ admin_unwatched.months }} months is taking up meaningful space.</p>
{% endif %}
{% endif %}
{% endblock %}