Compare commits
No commits in common. "08c71ba68dbe0aca09126dd6d94b96d19666b8a3" and "1cc5a247cf97cb3790f166cc2b8ab59c2cdc0bc3" have entirely different histories.
08c71ba68d
...
1cc5a247cf
3 changed files with 78 additions and 200 deletions
33
CLAUDE.md
33
CLAUDE.md
|
|
@ -42,17 +42,6 @@ the user's Unraid home server. Server-side project directory is still named
|
||||||
`SCRIPT_NAME` subpath. Server-rendered config (timezone, script root)
|
`SCRIPT_NAME` subpath. Server-rendered config (timezone, script root)
|
||||||
reaches JS through `<body data-*>`, never string-interpolated into a
|
reaches JS through `<body data-*>`, never string-interpolated into a
|
||||||
script block.
|
script block.
|
||||||
- **No `style="..."` attributes for anything static.** A one-off tweak
|
|
||||||
(button height, a margin, a font size) belongs in the matching
|
|
||||||
`static/css/*.css` file, scoped with a structural selector (an
|
|
||||||
adjacent-sibling or `:last-child` rule tied to the surrounding markup)
|
|
||||||
or a small modifier class — not inline on the element. The only
|
|
||||||
legitimate inline `style=` left in `dashboard.html` are per-request
|
|
||||||
computed values Jinja has to interpolate (a meter's `width: {{ pct
|
|
||||||
}}%`, a colour chosen by threshold) and JS-toggled initial visibility
|
|
||||||
(`display:none` on something `dashboard.js` reveals) — those can't
|
|
||||||
live in a static stylesheet. Everything else should read as a class
|
|
||||||
name, not a value.
|
|
||||||
- **`static/css/base.css`** — the single source of truth for the design
|
- **`static/css/base.css`** — the single source of truth for the design
|
||||||
tokens, shared by all three templates: surfaces, a 6-step `--fs-*` type
|
tokens, shared by all three templates: surfaces, a 6-step `--fs-*` type
|
||||||
scale, semantic colours, and `--tap: 44px` for minimum touch targets.
|
scale, semantic colours, and `--tap: 44px` for minimum touch targets.
|
||||||
|
|
@ -67,28 +56,6 @@ the user's Unraid home server. Server-side project directory is still named
|
||||||
tiers in `app.py`: a "Right now" strip (live streams, download queue,
|
tiers in `app.py`: a "Right now" strip (live streams, download queue,
|
||||||
array usage) sits above the fold, with library, discovery, requests and
|
array usage) sits above the fold, with library, discovery, requests and
|
||||||
watch insights below it.
|
watch insights below it.
|
||||||
- **Cards in a row must earn their height — never leave dead space.** A
|
|
||||||
`.row` is a CSS grid, so every card stretches to the tallest one in that
|
|
||||||
row. A card whose content outgrows its neighbours doesn't just get tall,
|
|
||||||
it hollows out everything beside it. This has bitten once already: the
|
|
||||||
per-disk Storage list stacked ten disks at `--row-h` each, turning one
|
|
||||||
third of the "Right now" strip into a ~950px tower and leaving ~1500px of
|
|
||||||
empty card across Now playing and Downloads.
|
|
||||||
- Before adding a repeating list to a card in a multi-column row, check
|
|
||||||
what the row's other cards hold at their **quietest** — one stream, an
|
|
||||||
idle queue, zero pending requests — and keep the tallest card within
|
|
||||||
roughly 1.5× of that.
|
|
||||||
- When the content genuinely won't fit, the fix is **more width**: give
|
|
||||||
the card its own full-width row (`grid-template-columns: 1fr`) and lay
|
|
||||||
its list out in columns inside, or use more columns within the existing
|
|
||||||
card. Ten items down one narrow column is three tidy rows of tiles once
|
|
||||||
the card spans the page.
|
|
||||||
- The fix is **never** an inner scroll area, a truncated list, or content
|
|
||||||
hidden behind a toggle the user has to find — see the "nothing clipped
|
|
||||||
or hidden" rule the header pills already follow.
|
|
||||||
- Where a short card still can't be avoided, centre its content
|
|
||||||
vertically rather than reaching for `align-items: start`; equal-height
|
|
||||||
cards are a settled preference.
|
|
||||||
- Degraded services render through the `service_error()` macro, which
|
- Degraded services render through the `service_error()` macro, which
|
||||||
pairs `friendly_error` (human cause) with the raw exception behind a
|
pairs `friendly_error` (human cause) with the raw exception behind a
|
||||||
disclosure, passed through `redact_secrets` — SABnzbd and Tautulli put
|
disclosure, passed through `redact_secrets` — SABnzbd and Tautulli put
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,6 @@ main {
|
||||||
}
|
}
|
||||||
.row.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
.row.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
||||||
.row.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
.row.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||||
/* A section with two stacked rows (Watch insights) needs the same gap
|
|
||||||
between them as the grid uses between columns. */
|
|
||||||
.row + .row { margin-top: var(--gap); }
|
|
||||||
|
|
||||||
/* ── Card ───────────────────────────────────────────── */
|
/* ── Card ───────────────────────────────────────────── */
|
||||||
.card {
|
.card {
|
||||||
|
|
@ -196,13 +193,13 @@ main {
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.stream:first-child { border-top: none; padding-top: 0; }
|
.stream:first-child { border-top: none; padding-top: 0; }
|
||||||
.stream-icon { font-size: var(--fs-lg); flex-shrink: 0; }
|
.stream-icon { font-size: var(--fs-base); flex-shrink: 0; }
|
||||||
.stream-info { min-width: 0; flex: 1; }
|
.stream-info { min-width: 0; flex: 1; }
|
||||||
.stream-title {
|
.stream-title {
|
||||||
font-size: var(--fs-base); font-weight: 600;
|
font-size: var(--fs-sm); font-weight: 600;
|
||||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.stream-user { font-size: var(--fs-sm); color: var(--text-3); }
|
.stream-user { font-size: var(--fs-xs); color: var(--text-3); }
|
||||||
.live-dot {
|
.live-dot {
|
||||||
width: 7px; height: 7px; border-radius: 50%;
|
width: 7px; height: 7px; border-radius: 50%;
|
||||||
background: var(--ok); flex-shrink: 0;
|
background: var(--ok); flex-shrink: 0;
|
||||||
|
|
@ -245,10 +242,6 @@ dl.stats {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||||
}
|
}
|
||||||
/* Only the Downloads card stacks stats below the hero figure/sub block —
|
|
||||||
give that pairing breathing room without affecting stats blocks that
|
|
||||||
open a card (Sonarr/Radarr put dl.stats right after the search bar). */
|
|
||||||
.card-body > [aria-live="polite"] + dl.stats { margin-top: 0.9rem; }
|
|
||||||
dl.stats > div {
|
dl.stats > div {
|
||||||
background: var(--surface-2);
|
background: var(--surface-2);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
@ -539,68 +532,32 @@ dd.is-accent { color: var(--accent); }
|
||||||
}
|
}
|
||||||
.req-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
.req-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
||||||
|
|
||||||
/* ── Storage — per-disk table ──────────────────────────── */
|
/* ── Storage — per-disk breakdown ──────────────────────── */
|
||||||
/* Storage owns a full-width row of its own, so the per-disk breakdown is a
|
/* Same row anatomy as indexer/request rows: name/sub on the left, a status
|
||||||
real table rather than a stack of cards: columns line up, digits are
|
badge on the right. Data + cache disks get a usage meter; parity disks
|
||||||
tabular, and a new metric is a new column instead of a redesign. Stacking
|
(no filesystem) are rendered compact — name and temp only. */
|
||||||
these as rows in a third-of-a-page column is what hollowed out the two
|
.disk-list { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.85rem; }
|
||||||
cards beside it — see the dead-space rule in CLAUDE.md. */
|
.disk-item {
|
||||||
/* Storage's header meta is a sentence, not a badge. .card-meta is nowrap so
|
display: flex;
|
||||||
short status words never break; this one has to wrap or it pushes the whole
|
align-items: center;
|
||||||
page sideways on a phone. */
|
gap: 0.7rem;
|
||||||
.card-meta.card-meta-wrap { white-space: normal; text-align: right; }
|
min-height: var(--row-h);
|
||||||
.array-meter.meter { margin-top: 0; margin-bottom: 1.1rem; }
|
padding: 0.6rem 0.7rem;
|
||||||
.table-scroll { overflow-x: auto; }
|
background: var(--surface-2);
|
||||||
.disk-table {
|
border: 1px solid var(--border);
|
||||||
width: 100%;
|
border-radius: var(--radius-sm);
|
||||||
border-collapse: collapse;
|
min-width: 0;
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
}
|
||||||
.disk-table th, .disk-table td { padding: 0.45rem 0.6rem; text-align: left; }
|
.disk-info { min-width: 0; flex: 1; }
|
||||||
.disk-table th:first-child, .disk-table td:first-child { padding-left: 0; }
|
.disk-name {
|
||||||
.disk-table th:last-child, .disk-table td:last-child { padding-right: 0; }
|
font-size: var(--fs-sm); font-weight: 600;
|
||||||
.disk-table thead th {
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
font-size: var(--fs-xs);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-3);
|
|
||||||
white-space: nowrap;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.disk-table tbody th {
|
|
||||||
font-size: var(--fs-sm);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.disk-table tbody td {
|
|
||||||
font-size: var(--fs-sm);
|
|
||||||
color: var(--text-2);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.disk-table tbody th { border-bottom: 1px solid var(--border); }
|
|
||||||
.disk-table tbody tr:last-child th,
|
|
||||||
.disk-table tbody tr:last-child td { border-bottom: none; }
|
|
||||||
.disk-table .num { text-align: right; white-space: nowrap; }
|
|
||||||
/* The bar and its percentage share one cell, so the bar flexes with the
|
|
||||||
table while the number stays a fixed, right-aligned column of digits. */
|
|
||||||
.disk-table .usage { width: 32%; min-width: 140px; }
|
|
||||||
.usage-cell { display: flex; align-items: center; gap: 0.6rem; }
|
|
||||||
.usage-cell .meter { margin-top: 0; flex: 1; height: 5px; }
|
|
||||||
.usage-pct {
|
|
||||||
font-size: var(--fs-xs);
|
|
||||||
color: var(--text-3);
|
|
||||||
width: 2.6em;
|
|
||||||
text-align: right;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.disk-null { color: var(--text-3); }
|
|
||||||
.disk-table tr.is-parity th { color: var(--text-2); }
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
/* Below this the six columns stop fitting; the table scrolls in its own
|
|
||||||
container rather than squeezing the sizes into unreadable wraps. */
|
|
||||||
.table-scroll .disk-table { min-width: 540px; }
|
|
||||||
}
|
}
|
||||||
|
.disk-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
||||||
|
.disk-meter.meter { margin-top: 0.4rem; height: 4px; }
|
||||||
|
.disk-list-compact { margin-top: 0.5rem; }
|
||||||
|
.disk-item-compact { justify-content: space-between; padding: 0.5rem 0.7rem; min-height: auto; }
|
||||||
|
.disk-item-compact .disk-name { flex: 1; }
|
||||||
.badge.temp-ok { background: var(--ok-dim); color: var(--ok); }
|
.badge.temp-ok { background: var(--ok-dim); color: var(--ok); }
|
||||||
.badge.temp-warn { background: var(--warn-dim); color: var(--warn); }
|
.badge.temp-warn { background: var(--warn-dim); color: var(--warn); }
|
||||||
.badge.temp-danger { background: var(--danger-dim); color: var(--danger); }
|
.badge.temp-danger { background: var(--danger-dim); color: var(--danger); }
|
||||||
|
|
@ -638,8 +595,6 @@ dd.is-accent { color: var(--accent); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty { color: var(--text-3); font-size: var(--fs-sm); }
|
.empty { color: var(--text-3); font-size: var(--fs-sm); }
|
||||||
/* Storage card's usage note trails the meter with no room to spare below it. */
|
|
||||||
.meter-legend + .empty { margin-bottom: 0; }
|
|
||||||
|
|
||||||
/* ── Errors ─────────────────────────────────────────── */
|
/* ── Errors ─────────────────────────────────────────── */
|
||||||
.error {
|
.error {
|
||||||
|
|
|
||||||
|
|
@ -25,19 +25,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% 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 }}°C</span>
|
|
||||||
{%- else -%}
|
|
||||||
<span class="disk-null">—</span>
|
|
||||||
{%- endif -%}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{#- One service status pill in the header strip. -#}
|
{#- One service status pill in the header strip. -#}
|
||||||
{% macro svc_pill(key, label, url, up, stat) %}
|
{% macro svc_pill(key, label, url, up, stat) %}
|
||||||
<a class="svc-pill {{ 'up' if up else 'down' }}" id="svc-{{ key }}"
|
<a class="svc-pill {{ 'up' if up else 'down' }}" id="svc-{{ key }}"
|
||||||
|
|
@ -86,10 +73,7 @@
|
||||||
<!-- ══ Right now — the time-sensitive row, above the fold ══ -->
|
<!-- ══ Right now — the time-sensitive row, above the fold ══ -->
|
||||||
<section class="section hero" aria-labelledby="h-now">
|
<section class="section hero" aria-labelledby="h-now">
|
||||||
<h2 id="h-now">Right now</h2>
|
<h2 id="h-now">Right now</h2>
|
||||||
{# Two cards here, not three. Storage carries a per-disk table that no
|
<div class="row cols-3">
|
||||||
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 -->
|
<!-- Now playing -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
@ -146,7 +130,7 @@
|
||||||
<span id="sab-timeleft-row" style="display:none;"> · ETA <span id="sab-timeleft"></span></span>
|
<span id="sab-timeleft-row" style="display:none;"> · ETA <span id="sab-timeleft"></span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dl class="stats">
|
<dl class="stats" style="margin-top:0.9rem;">
|
||||||
<div><dt>Avg speed (7d)</dt><dd>{{ sabnzbd.avg_speed }}</dd></div>
|
<div><dt>Avg speed (7d)</dt><dd>{{ sabnzbd.avg_speed }}</dd></div>
|
||||||
<div><dt>Grabbed (7d)</dt><dd>{{ sabnzbd.completed_7d }}</dd></div>
|
<div><dt>Grabbed (7d)</dt><dd>{{ sabnzbd.completed_7d }}</dd></div>
|
||||||
<div><dt>Volume (7d)</dt><dd>{{ sabnzbd.week_size }}</dd></div>
|
<div><dt>Volume (7d)</dt><dd>{{ sabnzbd.week_size }}</dd></div>
|
||||||
|
|
@ -157,89 +141,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- Storage -->
|
||||||
|
|
||||||
{# 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">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3><a href="{{ service_urls.unraid }}" target="_blank" rel="noopener">Storage</a></h3>
|
<h3><a href="{{ service_urls.unraid }}" target="_blank" rel="noopener">Storage</a></h3>
|
||||||
{% if unraid.status == 'ok' %}
|
{% if unraid.status == 'ok' and unraid.array_state %}
|
||||||
<span class="card-meta card-meta-wrap">
|
<span class="card-meta">{{ unraid.array_state | capitalize }}</span>
|
||||||
{{ 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if unraid.status == 'ok' %}
|
{% 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 %}
|
{% set pct = unraid.array_used_pct %}
|
||||||
<div class="meter array-meter">
|
<div class="hero-figure {% if pct > 85 %}is-danger{% elif pct > 70 %}is-warn{% endif %}"
|
||||||
<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">—</span></td>
|
|
||||||
<td class="num"><span class="disk-null">—</span></td>
|
|
||||||
<td class="num"><span class="disk-null">—</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 %};">
|
style="color: {% if pct > 85 %}var(--danger){% elif pct > 70 %}var(--warn){% else %}var(--text){% endif %};">
|
||||||
{{ pct }}%
|
{{ pct }}%
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -250,10 +163,52 @@
|
||||||
role="progressbar" aria-valuenow="{{ pct }}" aria-valuemin="0" aria-valuemax="100"
|
role="progressbar" aria-valuenow="{{ pct }}" aria-valuemin="0" aria-valuemax="100"
|
||||||
aria-label="Array usage"></div>
|
aria-label="Array usage"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="meter-legend"><span>{{ unraid.array_used }} used</span></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>
|
||||||
{% if unraid.source == "local" %}
|
{% if unraid.source == "local" %}
|
||||||
<p class="empty" style="margin-bottom:0;">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 %}
|
{% 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 }}°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 }}°C</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -261,6 +216,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -564,7 +520,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if tautulli.top_tv_list or tautulli.top_movie_list %}
|
{% if tautulli.top_tv_list or tautulli.top_movie_list %}
|
||||||
<div class="row cols-2">
|
<div class="row cols-2" style="margin-top: var(--gap);">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h3>Top TV</h3></div>
|
<div class="card-header"><h3>Top TV</h3></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue