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