Add a manual refresh button to the Array section on System
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPJxtE9tPURsaWDZ6rC4Dt
This commit is contained in:
parent
106bd16de8
commit
2dedd4e915
4 changed files with 137 additions and 87 deletions
12
app.py
12
app.py
|
|
@ -1855,6 +1855,18 @@ def system_page():
|
||||||
ombi=r["ombi"], admin_unwatched=r["admin_unwatched"])
|
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")
|
@app.route("/activity")
|
||||||
@login_required
|
@login_required
|
||||||
def activity_page():
|
def activity_page():
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,27 @@
|
||||||
if (armed && !armed.contains(e.target)) disarm();
|
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 ─────────────────────────────────────
|
/* ── Live figures ─────────────────────────────────────
|
||||||
The queue and the active streams are the only two numbers
|
The queue and the active streams are the only two numbers
|
||||||
that must be accurate to the second, so they are the only
|
that must be accurate to the second, so they are the only
|
||||||
|
|
|
||||||
100
templates/_array_section.html
Normal file
100
templates/_array_section.html
Normal file
|
|
@ -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' %}
|
||||||
|
<div class="tt-field crit">
|
||||||
|
<span>Array</span>
|
||||||
|
<span class="tt-field-meta">unreachable
|
||||||
|
<button class="tt-btn-quiet" type="button" id="array-refresh-btn" onclick="refreshArray(this)" aria-label="Refresh array stats">REFRESH</button>
|
||||||
|
</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 %} · parity
|
||||||
|
{%- for p in unraid.parities %} {{ p.temp if p.temp is not none else '—' }}°C{% endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
<button class="tt-btn-quiet" type="button" id="array-refresh-btn" onclick="refreshArray(this)" aria-label="Refresh array stats">REFRESH</button>
|
||||||
|
</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 %}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "_shell.html" %}
|
{% 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 title %}P105 System{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
@ -23,92 +23,9 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#- ── Array ───────────────────────────────────────────────────── -#}
|
<div id="array-section">
|
||||||
{% if unraid.status != 'ok' %}
|
{% include "_array_section.html" %}
|
||||||
<div class="tt-field crit"><span>Array</span><span class="tt-field-meta">unreachable</span></div>
|
</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 %} · parity
|
|
||||||
{%- for p in unraid.parities %} {{ p.temp if p.temp is not none else '—' }}°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 ─────────────────────────────────────────────────
|
{#- ── Downloads ─────────────────────────────────────────────────
|
||||||
The one place the service holds real history, so the one place a
|
The one place the service holds real history, so the one place a
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue