Give Storage its own row and rebuild the disk list as a table
All checks were successful
Deploy / deploy (push) Successful in 23s

Ten disks stacked at --row-h each turned one third of the "Right now"
strip into a ~950px tower. The row is a grid, so Now playing and
Downloads stretched to match and held roughly 1500px of empty card
between them.

The information needed width, not a shorter list. "Right now" drops to
two columns and Storage moves to a full-width row beneath it, where the
same ten disks are a six-column table — disk, used, size, free, usage
bar, temp — at ~270px total. Nothing is hidden and nothing scrolls
except the table itself, which gets its own overflow container below
720px rather than wrapping the sizes into mush.

Also: the array total moves to a bar above the table and the summary
into the card header, so the hero figure isn't repeating what every row
already says. The header summary is a sentence rather than a badge, so
it needs .card-meta-wrap to opt out of the global nowrap — without it
that one string pushed the whole page sideways on a phone.

Local-fallback (no per-disk data) and error states keep the previous
hero-figure treatment. CLAUDE.md now carries the dead-space rule this
came from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Barely Removable 2026-08-27 06:52:34 -07:00
parent 326ab6cee5
commit 08c71ba68d
3 changed files with 176 additions and 74 deletions

View file

@ -25,6 +25,19 @@
</div>
{% endmacro %}
{#- Disk temperature / spin state, as one badge. A spun-down disk reports no
usable temperature, so Standby replaces the reading rather than showing
a stale or zero degree value. -#}
{% macro disk_state(dk) %}
{%- if dk.spinning == false -%}
<span class="badge idle">Standby</span>
{%- elif dk.temp is not none -%}
<span class="badge temp-{% if dk.temp >= 55 %}danger{% elif dk.temp >= 45 %}warn{% else %}ok{% endif %}">{{ dk.temp }}&deg;C</span>
{%- else -%}
<span class="disk-null">&mdash;</span>
{%- endif -%}
{% endmacro %}
{#- One service status pill in the header strip. -#}
{% macro svc_pill(key, label, url, up, stat) %}
<a class="svc-pill {{ 'up' if up else 'down' }}" id="svc-{{ key }}"
@ -73,7 +86,10 @@
<!-- ══ Right now — the time-sensitive row, above the fold ══ -->
<section class="section hero" aria-labelledby="h-now">
<h2 id="h-now">Right now</h2>
<div class="row cols-3">
{# Two cards here, not three. Storage carries a per-disk table that no
third-of-a-page column can hold without towering over these two and
hollowing them out — it gets its own full-width row below. #}
<div class="row cols-2">
<!-- Now playing -->
<div class="card">
@ -141,18 +157,89 @@
</div>
</div>
<!-- Storage -->
</div>
{# Storage spans the page so its per-disk table has room to be a table.
Ten disks down a narrow column is a tower; across the full width it
is ten short rows. #}
<div class="row">
<div class="card">
<div class="card-header">
<h3><a href="{{ service_urls.unraid }}" target="_blank" rel="noopener">Storage</a></h3>
{% if unraid.status == 'ok' and unraid.array_state %}
<span class="card-meta">{{ unraid.array_state | capitalize }}</span>
{% if unraid.status == 'ok' %}
<span class="card-meta card-meta-wrap">
{{ unraid.array_used_pct }}% used · {{ unraid.array_free }} free of {{ unraid.array_total }}
{%- if unraid.disk_count %} · {{ unraid.disk_count }} disk{{ 's' if unraid.disk_count != 1 }}{% endif %}
{%- if unraid.array_state %} · {{ unraid.array_state | capitalize }}{% endif %}
</span>
{% endif %}
</div>
<div class="card-body">
{% if unraid.status == 'ok' %}
{% if unraid.disks or unraid.caches or unraid.parities %}
{# Array total leads, then the disks that make it up. #}
{% set pct = unraid.array_used_pct %}
<div class="hero-figure {% if pct > 85 %}is-danger{% elif pct > 70 %}is-warn{% endif %}"
<div class="meter array-meter">
<div class="meter-fill {% if pct > 85 %}danger{% elif pct > 70 %}warn{% endif %}"
style="width: {{ pct }}%;"
role="progressbar" aria-valuenow="{{ pct }}" aria-valuemin="0" aria-valuemax="100"
aria-label="Array usage"></div>
</div>
<div class="table-scroll">
<table class="disk-table">
<thead>
<tr>
<th scope="col">Disk</th>
<th scope="col" class="num">Used</th>
<th scope="col" class="num">Size</th>
<th scope="col" class="num">Free</th>
<th scope="col" class="usage">Usage</th>
<th scope="col" class="num">Temp</th>
</tr>
</thead>
<tbody>
{% for dk in unraid.disks + unraid.caches %}
{% set dpct = dk.used_pct %}
<tr>
<th scope="row" title="{{ dk.device }}">{{ dk.name }}</th>
<td class="num">{{ dk.used }}</td>
<td class="num">{{ dk.total }}</td>
<td class="num">{{ dk.free }}</td>
<td class="usage">
<div class="usage-cell">
<div class="meter">
<div class="meter-fill {% if dpct > 85 %}danger{% elif dpct > 70 %}warn{% endif %}"
style="width: {{ dpct }}%;"
role="progressbar" aria-valuenow="{{ dpct }}" aria-valuemin="0" aria-valuemax="100"
aria-label="{{ dk.name }} usage"></div>
</div>
<span class="usage-pct">{{ dpct }}%</span>
</div>
</td>
<td class="num">{{ disk_state(dk) }}</td>
</tr>
{% endfor %}
{% for dk in unraid.parities %}
{# Parity mirrors the array rather than holding a filesystem,
so it has no size or usage of its own to report. #}
<tr class="is-parity">
<th scope="row" title="{{ dk.device }}">{{ dk.name }}</th>
<td class="num"><span class="disk-null">&mdash;</span></td>
<td class="num"><span class="disk-null">&mdash;</span></td>
<td class="num"><span class="disk-null">&mdash;</span></td>
<td class="usage"><span class="disk-null">Parity · no filesystem</span></td>
<td class="num">{{ disk_state(dk) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
{# Fallback: container-visible disk usage, no per-disk detail. #}
{% set pct = unraid.array_used_pct %}
<div class="hero-figure"
style="color: {% if pct > 85 %}var(--danger){% elif pct > 70 %}var(--warn){% else %}var(--text){% endif %};">
{{ pct }}%
</div>
@ -163,52 +250,10 @@
role="progressbar" aria-valuenow="{{ pct }}" aria-valuemin="0" aria-valuemax="100"
aria-label="Array usage"></div>
</div>
<div class="meter-legend">
<span>{{ unraid.array_used }} used</span>
{% if unraid.disk_count %}<span>{{ unraid.disk_count }} disk{{ 's' if unraid.disk_count != 1 }}</span>{% endif %}
</div>
<div class="meter-legend"><span>{{ unraid.array_used }} used</span></div>
{% if unraid.source == "local" %}
<p class="empty">Container disk usage ({{ unraid.mount }}). Set <code>UNRAID_API_KEY</code> for full array stats.</p>
<p class="empty" style="margin-bottom:0;">Container disk usage ({{ unraid.mount }}). Set <code>UNRAID_API_KEY</code> for full array stats.</p>
{% endif %}
{% if unraid.disks or unraid.caches %}
<div class="disk-list">
{% for dk in unraid.disks + unraid.caches %}
{% set dpct = dk.used_pct %}
<div class="disk-item">
<div class="disk-info">
<div class="disk-name" title="{{ dk.device }}">{{ dk.name }}</div>
<div class="disk-sub">{{ dk.used }} of {{ dk.total }}</div>
<div class="meter disk-meter">
<div class="meter-fill {% if dpct > 85 %}danger{% elif dpct > 70 %}warn{% endif %}"
style="width: {{ dpct }}%;"
role="progressbar" aria-valuenow="{{ dpct }}" aria-valuemin="0" aria-valuemax="100"
aria-label="{{ dk.name }} usage"></div>
</div>
</div>
{% if dk.spinning == false %}
<span class="badge idle">Standby</span>
{% elif dk.temp is not none %}
<span class="badge temp-{% if dk.temp >= 55 %}danger{% elif dk.temp >= 45 %}warn{% else %}ok{% endif %}">{{ dk.temp }}&deg;C</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% if unraid.parities %}
<div class="disk-list disk-list-compact">
{% for dk in unraid.parities %}
<div class="disk-item disk-item-compact">
<div class="disk-name" title="{{ dk.device }}">{{ dk.name }}</div>
{% if dk.spinning == false %}
<span class="badge idle">Standby</span>
{% elif dk.temp is not none %}
<span class="badge temp-{% if dk.temp >= 55 %}danger{% elif dk.temp >= 45 %}warn{% else %}ok{% endif %}">{{ dk.temp }}&deg;C</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% else %}
@ -216,7 +261,6 @@
{% endif %}
</div>
</div>
</div>
</section>