From 2dedd4e9152925f9048dd8069aeaa62d5ac4d98c Mon Sep 17 00:00:00 2001 From: Barely Removable Date: Sat, 5 Sep 2026 10:01:51 -0700 Subject: [PATCH] Add a manual refresh button to the Array section on System MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unraid's stats were already fetched live on every page load (no server-side cache), so this doesn't bypass anything — it just lets the section be re-fetched without a full page reload, for confirming it's current without waiting on a reload. Array section moves into its own partial (_array_section.html) so a new /api/array/refresh route can re-render just that fragment and swap it into #array-section, rather than duplicating the table/bar/disk-row markup in JS. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LPJxtE9tPURsaWDZ6rC4Dt --- app.py | 12 ++++ static/js/service.js | 21 +++++++ templates/_array_section.html | 100 ++++++++++++++++++++++++++++++++++ templates/system.html | 91 ++----------------------------- 4 files changed, 137 insertions(+), 87 deletions(-) create mode 100644 templates/_array_section.html diff --git a/app.py b/app.py index 9874dc1..8ab45a1 100644 --- a/app.py +++ b/app.py @@ -1855,6 +1855,18 @@ def system_page(): ombi=r["ombi"], admin_unwatched=r["admin_unwatched"]) +@app.route("/api/array/refresh") +@login_required +def api_array_refresh(): + """Re-renders just the Array section's markup for P105's refresh + button. Unraid's stats aren't cached (get_unraid_stats hits the + GraphQL API live on every call), so this is a plain re-fetch — the + button exists for the visitor's own peace of mind, not to bypass a + cache. Returns HTML, not JSON: swapped directly into #array-section + rather than re-implementing the table/bar/disk-row markup in JS.""" + return render_template("_array_section.html", unraid=get_unraid_stats()) + + @app.route("/activity") @login_required def activity_page(): diff --git a/static/js/service.js b/static/js/service.js index e14ef99..7110945 100644 --- a/static/js/service.js +++ b/static/js/service.js @@ -307,6 +307,27 @@ if (armed && !armed.contains(e.target)) disarm(); }); + /* ── Array refresh (P105) ───────────────────────────── + Unraid's stats are fetched live already, never cached server-side + (see get_unraid_stats), so this button doesn't bypass a cache — + it exists so the section can be re-fetched without a full page + reload, for the visitor's own peace of mind that it's current. */ + window.refreshArray = function (btn) { + if (btn.disabled) return; + btn.disabled = true; + btn.textContent = 'REFRESHING'; + fetch(BASE + '/api/array/refresh') + .then(function (r) { return r.text(); }) + .then(function (html) { + var section = document.getElementById('array-section'); + if (section) section.innerHTML = html; + }) + .catch(function () { + btn.disabled = false; + btn.textContent = 'FAILED'; + }); + }; + /* ── Live figures ───────────────────────────────────── The queue and the active streams are the only two numbers that must be accurate to the second, so they are the only diff --git a/templates/_array_section.html b/templates/_array_section.html new file mode 100644 index 0000000..6e7d164 --- /dev/null +++ b/templates/_array_section.html @@ -0,0 +1,100 @@ +{% from "_macros.html" import service_error, bar, disk_state, disk_row %} + +{#- ── Array ───────────────────────────────────────────────────── + Its own partial (rather than inline in system.html) so the + refresh button can re-fetch and swap in just this section via + `/api/array/refresh` without a full page reload — Unraid's stats + aren't cached (see get_unraid_stats), so a stale reading here + only ever means "haven't reloaded the page since", and the button + exists purely for the peace-of-mind of confirming that in place. -#} +{% if unraid.status != 'ok' %} +
+ Array + unreachable + + +
+ {{ service_error('Unraid', unraid.error) }} +{% else %} + {%- set apct = unraid.array_used_pct | default(0) | float -%} +
+ Array + + {{ unraid.array_state | default('') }} + {%- if unraid.parities %} · parity + {%- for p in unraid.parities %} {{ p.temp if p.temp is not none else '—' }}°C{% endfor %} + {%- endif %} + + +
+ +
+
+
{{ apct | round | int }}%
+
Used
+
+
+
{{ unraid.array_free | default('—') }}
+
Free
+
+
+
{{ unraid.array_total | default('—') }}
+
Total
+
+
+
{{ unraid.disk_count | default(0) }}
+
Disks
+
+
+ + {% 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. -#} +
+ + + + + + + + + + + + + {% for dk in unraid.disks %} + + + + + + + + + {% endfor %} + {% for ck in unraid.caches | default([]) %} + + + + + + + + + {% endfor %} + +
DiskDeviceSizeFreeUsageTemp
{{ dk.name }}{{ dk.device | default('—') }}{{ dk.total | default('—') }}{{ dk.free | default('—') }}{{ bar(dk.used_pct | default(0)) }} {{ dk.used_pct | default(0) | round | int }}%{{ disk_state(dk) }}
{{ ck.name }}{{ ck.device | default('—') }}{{ ck.total | default('—') }}{{ ck.free | default('—') }}{{ bar(ck.used_pct | default(0)) }} {{ ck.used_pct | default(0) | round | int }}%{{ disk_state(ck) }}
+
+ + {#- 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. -#} +
+ {% for dk in unraid.disks %}{{ disk_row(dk) }}{% endfor %} + {% for ck in unraid.caches | default([]) %}{{ disk_row(ck) }}{% endfor %} +
+ {% endif %} +{% endif %} diff --git a/templates/system.html b/templates/system.html index d25cb65..267b239 100644 --- a/templates/system.html +++ b/templates/system.html @@ -1,5 +1,5 @@ {% extends "_shell.html" %} -{% from "_macros.html" import service_error, bar, disk_state, disk_row, trend %} +{% from "_macros.html" import service_error, bar, trend %} {% block title %}P105 System{% endblock %} {% block content %} @@ -23,92 +23,9 @@ {% endfor %} - {#- ── Array ───────────────────────────────────────────────────── -#} - {% if unraid.status != 'ok' %} -
Arrayunreachable
- {{ service_error('Unraid', unraid.error) }} - {% else %} - {%- set apct = unraid.array_used_pct | default(0) | float -%} -
- Array - - {{ unraid.array_state | default('') }} - {%- if unraid.parities %} · parity - {%- for p in unraid.parities %} {{ p.temp if p.temp is not none else '—' }}°C{% endfor %} - {%- endif %} - -
- -
-
-
{{ apct | round | int }}%
-
Used
-
-
-
{{ unraid.array_free | default('—') }}
-
Free
-
-
-
{{ unraid.array_total | default('—') }}
-
Total
-
-
-
{{ unraid.disk_count | default(0) }}
-
Disks
-
-
- - {% 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. -#} -
- - - - - - - - - - - - - {% for dk in unraid.disks %} - - - - - - - - - {% endfor %} - {% for ck in unraid.caches | default([]) %} - - - - - - - - - {% endfor %} - -
DiskDeviceSizeFreeUsageTemp
{{ dk.name }}{{ dk.device | default('—') }}{{ dk.total | default('—') }}{{ dk.free | default('—') }}{{ bar(dk.used_pct | default(0)) }} {{ dk.used_pct | default(0) | round | int }}%{{ disk_state(dk) }}
{{ ck.name }}{{ ck.device | default('—') }}{{ ck.total | default('—') }}{{ ck.free | default('—') }}{{ bar(ck.used_pct | default(0)) }} {{ ck.used_pct | default(0) | round | int }}%{{ disk_state(ck) }}
-
- - {#- 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. -#} -
- {% for dk in unraid.disks %}{{ disk_row(dk) }}{% endfor %} - {% for ck in unraid.caches | default([]) %}{{ disk_row(ck) }}{% endfor %} -
- {% endif %} - {% endif %} +
+ {% include "_array_section.html" %} +
{#- ── Downloads ───────────────────────────────────────────────── The one place the service holds real history, so the one place a