Show SABnzbd's queue in the header on every page
All checks were successful
Deploy / deploy (push) Successful in 21s

Adds a live download figure next to the header clock, present on
every page (not just Activity/System). It's populated by the same
/api/sabnzbd poll those pages already use, extended to run
unconditionally and to fill in a new #head-dl span — never a
server-rendered fetch, so an unreachable SABnzbd can't add latency to
pages that don't otherwise touch it. Quiet ink at "0 IN QUEUE" when
idle, live cyan with a speed reading once something's moving.
This commit is contained in:
Barely Removable 2026-08-30 08:54:41 -07:00
parent cef66e7361
commit d672c3f02d
3 changed files with 29 additions and 7 deletions

View file

@ -33,7 +33,6 @@
text-transform: uppercase;
}
.tt-head-clock {
margin-left: auto;
color: var(--live);
white-space: nowrap;
min-width: 0;
@ -45,6 +44,16 @@
letter-spacing: 0.3em;
}
.tt-entry[hidden] { display: none; }
/* SABnzbd's queue, live on every page — filled in by service.js, empty
(and so invisible) until its first poll returns. Quiet ink when
idle; the live colour only while something is actually moving. */
.tt-head-dl {
margin-left: auto;
color: var(--ink-dim);
white-space: nowrap;
min-width: 0;
}
.tt-head-dl.live { color: var(--live); }
/* ── Page ───────────────────────────────────────────────
One measure for every page in the service. Nothing is ever
@ -602,6 +611,7 @@ a.tt-row:focus-visible .tt-row-sub { color: var(--tt-black); }
.tt-row-title { white-space: normal; }
.tt-row-val { margin-left: auto; white-space: normal; }
.tt-head-name { letter-spacing: 0.12em; }
.tt-head-dl { margin-left: 0; flex-basis: 100%; }
.tt-head-clock { margin-left: 0; flex-basis: 100%; }
/* The System disk table swaps for its row reflow here — see the

View file

@ -160,14 +160,15 @@
The queue and the active streams are the only two numbers
that must be accurate to the second, so they are the only
two that poll. Everything else is cached server-side and
arrives with the page. */
arrives with the page. The queue also drives the header's
download row, present on every page, so it polls
unconditionally rather than only where #sab-queue exists. */
function setText(id, value) {
var el = document.getElementById(id);
if (el) el.textContent = value;
}
function pollSab() {
if (!document.getElementById('sab-queue')) return;
fetch(BASE + '/api/sabnzbd')
.then(function (r) { return r.json(); })
.then(function (d) {
@ -175,6 +176,14 @@
setText('sab-queue', d.queue_count);
setText('sab-speed', d.speed || '0');
setText('sab-remaining', d.sizeleft || '—');
var dl = document.getElementById('head-dl');
if (dl) {
var active = d.queue_count > 0;
dl.textContent = active
? d.queue_count + ' IN QUEUE · ' + (d.speed || '0')
: '0 IN QUEUE';
dl.classList.toggle('live', active);
}
})
.catch(function () { /* offline is the dev baseline, not an error */ });
}
@ -190,10 +199,8 @@
.catch(function () { /* as above */ });
}
if (document.getElementById('sab-queue')) {
pollSab();
setInterval(pollSab, 5000);
}
if (document.getElementById('tt-streams')) {
pollTautulli();
setInterval(pollTautulli, 15000);

View file

@ -42,6 +42,11 @@ provenance.
<span class="tt-head-num">P{{ here[1] }}</span>
<span class="tt-head-name">{{ here[2] }}</span>
<span class="tt-entry" id="page-entry" hidden aria-live="polite"></span>
{#- SABnzbd's queue, on every page — the same live /api/sabnzbd poll
Activity and System already use, not a server-rendered fetch, so
an unreachable SABnzbd never adds latency to a page that doesn't
otherwise need it. Starts blank and fills in within a second. -#}
<span class="tt-head-dl" id="head-dl" aria-live="polite"></span>
<span class="tt-head-clock" id="head-clock"></span>
</header>