Declare the Monitor board in one place, so a card's width and position stop living in eighteen inline styles and four media queries
This commit is contained in:
@@ -103,7 +103,17 @@ once, and the endpoint, the cache writer, and the page all pick it up together.
|
||||
|-------|-------|------|
|
||||
| `pages/` | 11 | One per WebGUI tab — monitor, docker, arrs, fallback, watchdog, rsync, partnership, scheduler, auth, settings, setup |
|
||||
| `api/` | 50 | JSON endpoints the pages poll, plus action endpoints (run a script, stop a job, toggle a flag) |
|
||||
| `include/` | 16 | Shared builders and helpers — `vv_monitor_*`, `vv_arrs_*`, `vv_docker_*`, config read/write, auth |
|
||||
| `include/` | 27 | Shared builders and helpers — `vv_monitor_*`, `vv_arrs_*`, `vv_docker_*`, config read/write, auth |
|
||||
|
||||
**The Monitor board is declared, not laid out.** `include/monitor_board.php` holds one array
|
||||
naming every card on the Monitor grid, its width and its order, and generates the whole layout
|
||||
from it — the column ladder, the span clamps at each width, the row-height cap, and the
|
||||
compensation when a conditional card is absent. Column counts are 8/4/2/1 and spans are 1/2/4/8,
|
||||
which is what lets the board re-cut itself at any width with no holes and no hand-placed card.
|
||||
Breakpoints are arithmetic over `VV_MON_CARD_FLOOR`, never chosen by eye. `pages/monitor.php`
|
||||
carries the card bodies and nothing about where they go; moving a card is moving a line in that
|
||||
array. The page cross-checks the declaration against the cards that actually rendered and says so
|
||||
in the browser if they disagree.
|
||||
|
||||
**Caching.** Several endpoints serve from `$VV_CACHE_DIR` (`/tmp/varaverk/api`, tmpfs) rather than hitting live
|
||||
APIs on every page view, refreshed by `Tools/api_cache_writer.sh`. `?live=1` bypasses the
|
||||
|
||||
@@ -190,7 +190,32 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
.vv-nb-settings { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
||||
padding: 5px 10px; background: #101010; border-bottom: 1px solid #1a1a1a; }
|
||||
|
||||
/* ── Monitor: locked row height + scrollable cards ────────────────────────── */
|
||||
/* ── Monitor: the board ───────────────────────────────────────────────────────
|
||||
Only the half of the layout that is the same at every width lives here. Which cards exist,
|
||||
how wide each one is, what order they sit in, how many columns the board has at this width
|
||||
and how tall a row may be are all generated by include/monitor_board.php and arrive as
|
||||
--vv-cols, --vv-sp and --vv-rowdiv. Do not put a column count or a breakpoint in this file;
|
||||
they are arithmetic over the card floor, and that arithmetic has one home.
|
||||
|
||||
minmax(0, 1fr), not 1fr: bare 1fr means minmax(auto, 1fr), and that auto floor resolves to the
|
||||
larger of min-content and min-width — one card holding an unbreakable string would push its
|
||||
column past its share and drag the grid out of the container. The row axis carried this guard
|
||||
for years; the column axis never did.
|
||||
|
||||
min-width: 0 on the cards for the same reason, overriding the 200px .vv-card floor the flex
|
||||
layouts on other pages need. On this board the ladder is what guarantees a card's width, so a
|
||||
card defending its own floor can only overflow the track the ladder just sized for it. */
|
||||
#vv-monitor {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--vv-cols, 8), minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#vv-monitor > .vv-card {
|
||||
grid-column: span var(--vv-sp, 1);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Cards on the monitor grid are flex columns — h3 pins, body scrolls */
|
||||
#vv-monitor .vv-card {
|
||||
@@ -213,29 +238,38 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
/* Dynamic row heights capped per screen tier — rows size to content, never exceed the cap.
|
||||
minmax(0, Xpx): track is content-driven but capped; align-items:stretch makes all cards
|
||||
in a row fill the track, so short cards (Pools, Watchdog) match tall ones (Array).
|
||||
Breakpoints are viewport height (after browser chrome), not screen height. */
|
||||
Breakpoints are viewport height (after browser chrome), not screen height.
|
||||
|
||||
The divisor is --vv-rowdiv, not a literal 4. It used to be 4 because the board happened to be
|
||||
four rows at eight columns, so the two numbers were the same by accident and only one of them
|
||||
moved when the column count changed. monitor_board.php derives it as min(rows at this rung,
|
||||
VV_MON_ROWS_PER_SCREEN): the cap means "no card taller than this fraction of the screen", and a
|
||||
board short enough to fit divides by its own row count and fills the screen instead. */
|
||||
|
||||
/* ~720p (viewport ≤ 700px) */
|
||||
@media (min-width: 481px) and (max-height: 700px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 160px) / 4)); }
|
||||
}
|
||||
/* ~1080p (viewport 701–1100px) */
|
||||
@media (min-width: 481px) and (min-height: 701px) and (max-height: 1100px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 240px) / 4)); }
|
||||
@media (min-height: 701px) and (max-height: 1100px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 240px) / var(--vv-rowdiv, 4))); }
|
||||
}
|
||||
/* ~1440p (viewport 1101–1450px) — calibrated on 15" 1440p display */
|
||||
@media (min-width: 481px) and (min-height: 1101px) and (max-height: 1450px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 335px) / 4)); }
|
||||
@media (min-height: 1101px) and (max-height: 1450px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 335px) / var(--vv-rowdiv, 4))); }
|
||||
}
|
||||
/* ~4K (viewport > 1450px) */
|
||||
@media (min-width: 481px) and (min-height: 1451px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 500px) / 4)); }
|
||||
@media (min-height: 1451px) {
|
||||
#vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 500px) / var(--vv-rowdiv, 4))); }
|
||||
}
|
||||
|
||||
/* Mobile: natural heights, let page scroll */
|
||||
@media (max-width: 480px) {
|
||||
#vv-monitor { grid-auto-rows: auto !important; }
|
||||
#vv-monitor .vv-card { overflow: visible !important; }
|
||||
/* Natural heights, page scrolls. Height is what makes a phone in landscape unusable, not width:
|
||||
this hatch was keyed to max-width:480 alone, so portrait (393px wide) got content-sized rows and
|
||||
worked, while landscape (851x393) missed it and inherited the four-row cap — 58px cards with
|
||||
overflow:hidden and scrollbars disabled. Nothing was visible and nothing said so.
|
||||
|
||||
The width half is the two-column rung from monitor_board.php (below 696px). It is written as a
|
||||
literal here because a media query cannot read a custom property; if VV_MON_CARD_FLOOR changes,
|
||||
this number and the AI row's two below are the only three places that have to follow it. */
|
||||
@media (max-width: 695px), (max-height: 700px) {
|
||||
#vv-monitor { grid-auto-rows: auto; }
|
||||
#vv-monitor .vv-card { overflow: visible; }
|
||||
#vv-monitor .vv-card > div { overflow-y: visible; min-height: auto; }
|
||||
}
|
||||
|
||||
@@ -251,13 +285,17 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
default stays start so a card added later has to say so. */
|
||||
#vv-monitor-ai {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: start;
|
||||
}
|
||||
/* Same reason as the board above: the ladder guarantees the width, so a card defending the 200px
|
||||
.vv-card floor can only overflow the track it was given. At the four-column rung's own minimum
|
||||
(696px) a column is 160px, and that floor would have pushed this row out of the page. */
|
||||
#vv-monitor-ai > .vv-card { min-width: 0; }
|
||||
|
||||
/* Explicit placement, two rows. The left column carries AI over Tokens; Conversations and the
|
||||
Assistant span both rows, so the Assistant sets the height and the two stacked cards divide
|
||||
@@ -354,8 +392,14 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
#vv-ai-tokens-card .vv-ai-tok-head:first-child { margin-top: 0; }
|
||||
|
||||
/* Medium width — 4 columns, mirroring #vv-monitor's own breakpoint. The 1/2/5 spans do not
|
||||
survive the narrower grid: 5 of 4 would silently overflow the track. */
|
||||
@media (max-width: 1024px) {
|
||||
survive the narrower grid: 5 of 4 would silently overflow the track.
|
||||
|
||||
1383 and 695 below are the four- and two-column rungs from include/monitor_board.php, minus
|
||||
one. This row keeps its own placement — the 1/2/5 split is not a power of two and does not want
|
||||
to be — but it cannot keep its own breakpoints: at any width where these two disagree the board
|
||||
above is four columns while this row is still eight, and the page visibly stops being one
|
||||
board. If VV_MON_CARD_FLOOR moves, these move with it. */
|
||||
@media (max-width: 1383px) {
|
||||
#vv-monitor-ai { grid-template-columns: repeat(4, 1fr) !important; }
|
||||
#vv-ai-stats-card { grid-column: 1 / span 1 !important; grid-row: 1 !important; }
|
||||
#vv-ai-tokens-card { grid-column: 1 / span 1 !important; grid-row: 2 !important; }
|
||||
@@ -365,10 +409,14 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
#vv-ai-assistant-card { grid-column: 1 / span 4 !important; grid-row: 3 !important; }
|
||||
}
|
||||
|
||||
/* Phone — single column, natural heights, page scrolls. Matching heights is meaningless once
|
||||
/* Narrow — single column, natural heights, page scrolls. Matching heights is meaningless once
|
||||
the cards are stacked, and the inner scroll has to go with it: on a page that already scrolls,
|
||||
a scroll region inside it is a trap for a thumb. */
|
||||
@media (max-width: 480px) {
|
||||
a scroll region inside it is a trap for a thumb.
|
||||
|
||||
695, where the board above drops to two columns, rather than the 480 this used to use. There is
|
||||
no two-column layout for this row and there should not be: its 1/1/3/4 split at four columns is
|
||||
already the narrowest arrangement in which the Assistant transcript is readable. */
|
||||
@media (max-width: 695px) {
|
||||
#vv-monitor-ai { grid-template-columns: 1fr !important; }
|
||||
/* grid-row has to be released along with grid-column. The explicit rows above are the whole
|
||||
reason Tokens sits under AI, and left in place on a single-column grid they would stack
|
||||
@@ -389,14 +437,14 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
.vv-cpu-core { min-width: 7px !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
#vv-monitor { grid-template-columns: repeat(4, 1fr) !important; }
|
||||
#vv-docker { grid-column: span 4 !important; }
|
||||
/* Reset explicit placements so cards reflow in the 4-col grid */
|
||||
#vv-docker-folders { grid-column: span 4 !important; }
|
||||
#vv-parity-card { grid-column: auto !important; }
|
||||
#vv-storage-card { grid-column: auto !important; }
|
||||
#vv-array-card { grid-column: auto !important; }
|
||||
/* The four-column rung. Nothing about placement is here any more: the column count and every
|
||||
span come from the generated ladder, and the six !important overrides this block used to carry
|
||||
existed only to out-shout the inline styles the cards no longer have. Two of them
|
||||
(#vv-storage-card, #vv-array-card) were resetting hand-placed column indexes — 3/span 2 and
|
||||
5/span 4 — which is the pair that made the grid invent implicit columns at any count but eight.
|
||||
What is left is the one thing that genuinely belongs to width: at this rung the Streams card is
|
||||
half the board rather than a quarter, and its right-hand chips no longer fit. */
|
||||
@media (max-width: 1383px) {
|
||||
/* Streams header: hide right chip group entirely, keep server badges + media type */
|
||||
.vv-stream-right { display: none; }
|
||||
}
|
||||
@@ -441,10 +489,7 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
.vv-cpu-cores { gap: 2px !important; }
|
||||
.vv-cpu-core { min-width: 6px !important; }
|
||||
|
||||
/* Monitor single-column — explicit placement cards need override too */
|
||||
#vv-monitor { grid-template-columns: 1fr !important; }
|
||||
#vv-monitor > .vv-card { grid-column: 1 / -1 !important; }
|
||||
#vv-docker-folders { grid-column: 1 / -1 !important; }
|
||||
/* The single-column rung is generated — see include/monitor_board.php. */
|
||||
}
|
||||
|
||||
/* Shared footer (Save Schedule left, info right) — same min-height so log card ends level with script cards */
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// PURPOSE
|
||||
// The Monitor board declared as data. One array below decides which cards exist, what width
|
||||
// each one takes, and what order they sit in. Everything about the page's layout — the column
|
||||
// ladder, the span clamps at each rung, the row-height cap, the compensation when a card is
|
||||
// absent — is generated from that array by vv_mon_board_css().
|
||||
//
|
||||
// pages/monitor.php holds the card bodies and nothing about where they go.
|
||||
//
|
||||
// DESIGN PRINCIPLES
|
||||
// Powers of two, all the way down.
|
||||
// Column counts are 8, 4, 2, 1 and every span is 1, 2, 4 or 8. That pairing is what makes
|
||||
// the board tile with no holes at every width without a single hand-placed card: 32 span
|
||||
// units divide into 8 columns as 4 rows, into 4 as 8 rows, into 2 as 16, into 1 as 32.
|
||||
// A rung of 6 or 10 columns does not divide a span of 4, which is why the previous ladder
|
||||
// needed a per-breakpoint override for every wide card and still left holes.
|
||||
//
|
||||
// Breakpoints are arithmetic, not taste.
|
||||
// A column count is viable exactly when the cards still fit:
|
||||
//
|
||||
// window >= N * card-floor + (N-1) * gap + wrap-padding
|
||||
//
|
||||
// vv_mon_rung_min() is that line. Change VV_MON_CARD_FLOOR and every breakpoint moves with
|
||||
// it. No number in the generated CSS is chosen by eye.
|
||||
//
|
||||
// Order comes from this file, not from document order.
|
||||
// Each card is emitted with a CSS `order`, which grid auto-placement honours. Moving a card
|
||||
// on the board is moving a line in this array; the markup in pages/monitor.php never has to
|
||||
// be cut and pasted, which is how the old board accumulated hand-placed columns.
|
||||
//
|
||||
// A card that disappears has to give its width to someone.
|
||||
// Only one card on this board is conditional — the second GPU, hidden by JS on a one-GPU
|
||||
// host. Absent, its row summed to 7 of 8 and the board carried a hole on HOST2 that nothing
|
||||
// reported. 'absorbs' names the card whose width is taken over, and the JS toggles one class
|
||||
// on the container to apply it.
|
||||
//
|
||||
// OPERATIONAL SAFEGUARDS
|
||||
// Row sums are checked, not assumed. vv_mon_board_check() verifies every declared row adds to
|
||||
// the top rung and that every span is a power of two; failures are handed to the page, which
|
||||
// reports them in the browser rather than rendering a quietly broken board.
|
||||
//
|
||||
// The declaration and the markup are cross-checked at runtime. vv_mon_board_js() ships the
|
||||
// declared ids to the page, which compares them against the cards actually present and flags
|
||||
// either direction — a card in the markup nobody declared, or a declaration with no card.
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// The narrowest a card may be drawn. Every breakpoint is a function of this number — see
|
||||
// vv_mon_rung_min(). 160 rather than the 200 the cards carried before: at 200 the 8-column rung
|
||||
// needed 1704px, which put a 1720px half-screen 16px inside the margin and dropped a 1327px
|
||||
// tablet to 4 columns. 160 moves that rung to 1384 and gives both real headroom.
|
||||
const VV_MON_CARD_FLOOR = 160;
|
||||
|
||||
// Below three columns the constraint stops being the card and starts being the content: a label
|
||||
// and its value need about 240px before they wrap into nonsense. A phone in portrait is 393px, so
|
||||
// this is what keeps it at one column instead of two 180px cards.
|
||||
const VV_MON_PHONE_FLOOR = 240;
|
||||
|
||||
const VV_MON_GAP = 12; // grid gap, must match the gap in #vv-monitor
|
||||
const VV_MON_PAD = 20; // horizontal padding the Unraid page wrapper takes off the viewport
|
||||
|
||||
// The column ladder, widest first.
|
||||
const VV_MON_RUNGS = [8, 4, 2, 1];
|
||||
|
||||
// Cap on how many rows may share one screen height. The row-height cap divides the viewport by
|
||||
// this, so a card can never grow past a quarter of the screen and push the rest off it. Rungs
|
||||
// with fewer rows than this divide by their own row count instead and fill the screen.
|
||||
const VV_MON_ROWS_PER_SCREEN = 4;
|
||||
|
||||
// ── The board ─────────────────────────────────────────────────────────────────────────────────
|
||||
// Grouped by the row each group forms at the top rung. The grouping is documentation and a
|
||||
// checkable invariant — every group must sum to 8 — not placement: cards are auto-placed in the
|
||||
// flattened order below, which is what lets a narrower rung re-cut them into different rows.
|
||||
function vv_mon_board(): array {
|
||||
return [
|
||||
// The host itself.
|
||||
'Host' => [
|
||||
['id' => 'vv-system', 'span' => 1],
|
||||
['id' => 'vv-ups-card', 'span' => 1],
|
||||
['id' => 'vv-cpu', 'span' => 2],
|
||||
['id' => 'vv-memory', 'span' => 2],
|
||||
['id' => 'vv-network', 'span' => 2],
|
||||
],
|
||||
// What is running on it, and who is covering it.
|
||||
'Workload' => [
|
||||
['id' => 'vv-scripts-card', 'span' => 1],
|
||||
['id' => 'vv-fallback', 'span' => 1],
|
||||
['id' => 'vv-partner', 'span' => 2],
|
||||
['id' => 'vv-docker-folders', 'span' => 4],
|
||||
],
|
||||
// The media path, from the sync that feeds it to the sessions coming out of it.
|
||||
'Media' => [
|
||||
['id' => 'vv-rsync-card', 'span' => 1],
|
||||
['id' => 'vv-gpu-card', 'span' => 1],
|
||||
['id' => 'vv-gpu1-card', 'span' => 1],
|
||||
// Takes the second GPU's column on a one-GPU host — see DESIGN PRINCIPLES.
|
||||
['id' => 'vv-transcode', 'span' => 1, 'absorbs' => 'vv-gpu1-card'],
|
||||
['id' => 'vv-streams', 'span' => 4],
|
||||
],
|
||||
// Storage and the things that watch it.
|
||||
'Storage' => [
|
||||
['id' => 'vv-watchdog-card', 'span' => 1],
|
||||
['id' => 'vv-parity-card', 'span' => 1],
|
||||
['id' => 'vv-storage-card', 'span' => 2],
|
||||
['id' => 'vv-array-card', 'span' => 4],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// The board in placement order, one flat list.
|
||||
function vv_mon_board_cards(): array {
|
||||
$flat = [];
|
||||
foreach (vv_mon_board() as $row => $cards) {
|
||||
foreach ($cards as $card) {
|
||||
$card['row'] = $row;
|
||||
$flat[] = $card;
|
||||
}
|
||||
}
|
||||
return $flat;
|
||||
}
|
||||
|
||||
function vv_mon_board_total(): int {
|
||||
$total = 0;
|
||||
foreach (vv_mon_board_cards() as $card) $total += $card['span'];
|
||||
return $total;
|
||||
}
|
||||
|
||||
// The narrowest window at which $cols columns still hold cards of at least the floor width.
|
||||
function vv_mon_rung_min(int $cols): int {
|
||||
$floor = ($cols <= 2) ? VV_MON_PHONE_FLOOR : VV_MON_CARD_FLOOR;
|
||||
return $cols * $floor + ($cols - 1) * VV_MON_GAP + VV_MON_PAD;
|
||||
}
|
||||
|
||||
// How many rows the board occupies at a given column count, and the divisor the row-height cap
|
||||
// uses there. They differ once the board is taller than one screen: at 4 columns the board is 8
|
||||
// rows and the page is meant to scroll, with each card still a readable quarter-screen tall.
|
||||
// Counted by walking the same sparse auto-placement the browser does, not as total spans over
|
||||
// columns. Those two agree only while nothing is clamped: at two columns a span-4 card occupies
|
||||
// two units, not four, so the board is 13 rows there and the division says 16.
|
||||
function vv_mon_rung_rows(int $cols): int {
|
||||
$row = 0;
|
||||
$col = 0;
|
||||
foreach (vv_mon_board_cards() as $card) {
|
||||
$span = min($card['span'], $cols);
|
||||
if ($col + $span > $cols) { $row++; $col = 0; }
|
||||
$col += $span;
|
||||
if ($col >= $cols) { $row++; $col = 0; }
|
||||
}
|
||||
return $col === 0 ? $row : $row + 1;
|
||||
}
|
||||
function vv_mon_rung_rowdiv(int $cols): int {
|
||||
return min(vv_mon_rung_rows($cols), VV_MON_ROWS_PER_SCREEN);
|
||||
}
|
||||
|
||||
// The class the page puts on #vv-monitor when a card is not rendered, so the generated absorb
|
||||
// rules can fire. Derived from the id on both sides — PHP writes the rule, JS writes the class —
|
||||
// so the two can never drift apart by a typo.
|
||||
function vv_mon_absent_class(string $id): string {
|
||||
return 'vv-absent-' . $id;
|
||||
}
|
||||
|
||||
// Problems with the declaration itself, as human-readable lines. Empty means the board is sound.
|
||||
function vv_mon_board_check(): array {
|
||||
$problems = [];
|
||||
$top = VV_MON_RUNGS[0];
|
||||
$seen = [];
|
||||
|
||||
foreach (vv_mon_board() as $row => $cards) {
|
||||
$sum = 0;
|
||||
foreach ($cards as $card) {
|
||||
$span = $card['span'];
|
||||
if ($span < 1 || ($span & ($span - 1)) !== 0) {
|
||||
$problems[] = "{$card['id']}: span $span is not a power of two";
|
||||
}
|
||||
if ($span > $top) {
|
||||
$problems[] = "{$card['id']}: span $span exceeds the $top-column board";
|
||||
}
|
||||
if (isset($seen[$card['id']])) {
|
||||
$problems[] = "{$card['id']}: declared twice";
|
||||
}
|
||||
$seen[$card['id']] = true;
|
||||
$sum += $span;
|
||||
}
|
||||
if ($sum !== $top) {
|
||||
$problems[] = "row '$row' sums to $sum, not $top";
|
||||
}
|
||||
}
|
||||
|
||||
foreach (vv_mon_board_cards() as $card) {
|
||||
if (isset($card['absorbs']) && !isset($seen[$card['absorbs']])) {
|
||||
$problems[] = "{$card['id']}: absorbs '{$card['absorbs']}', which is not on the board";
|
||||
}
|
||||
}
|
||||
|
||||
return $problems;
|
||||
}
|
||||
|
||||
// ── Generated CSS ─────────────────────────────────────────────────────────────────────────────
|
||||
// Everything positional. The static half — display:grid, the gap, the row-height tiers — lives in
|
||||
// css/varaverk.css and reads --vv-cols, --vv-sp and --vv-rowdiv from here.
|
||||
function vv_mon_board_css(): string {
|
||||
$cards = vv_mon_board_cards();
|
||||
$top = VV_MON_RUNGS[0];
|
||||
$out = '';
|
||||
|
||||
$out .= "#vv-monitor{--vv-cols:$top;--vv-rowdiv:" . vv_mon_rung_rowdiv($top) . ";}\n";
|
||||
|
||||
$order = 0;
|
||||
foreach ($cards as $card) {
|
||||
$order++;
|
||||
$out .= "#{$card['id']}{order:$order;--vv-sp:{$card['span']};}\n";
|
||||
}
|
||||
$out .= vv_mon_absorb_css($top);
|
||||
|
||||
foreach (VV_MON_RUNGS as $cols) {
|
||||
if ($cols === $top) continue;
|
||||
|
||||
// One below the next rung up: the band this column count owns ends where that one begins.
|
||||
$max = vv_mon_rung_min($cols * 2) - 1;
|
||||
$out .= "@media (max-width:{$max}px){";
|
||||
$out .= "#vv-monitor{--vv-cols:$cols;--vv-rowdiv:" . vv_mon_rung_rowdiv($cols) . ";}";
|
||||
|
||||
if ($cols === 1) {
|
||||
// Every span at once, and through the child selector rather than per id: it outranks
|
||||
// the per-card rules above on specificity, so nothing can leave a span behind and
|
||||
// make the grid invent an implicit column.
|
||||
$out .= "#vv-monitor>.vv-card{--vv-sp:1;}";
|
||||
} else {
|
||||
$wide = [];
|
||||
foreach ($cards as $card) {
|
||||
if ($card['span'] > $cols) $wide[] = '#' . $card['id'];
|
||||
}
|
||||
if ($wide) $out .= implode(',', $wide) . "{--vv-sp:$cols;}";
|
||||
}
|
||||
|
||||
$out .= vv_mon_absorb_css($cols);
|
||||
$out .= "}\n";
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
// Absorb rules for one rung. Higher specificity than both the per-card span and the one-column
|
||||
// child selector, which is why every rung has to restate them — at one column an unclamped
|
||||
// absorb would be the only span left above 1.
|
||||
function vv_mon_absorb_css(int $cols): string {
|
||||
$out = '';
|
||||
foreach (vv_mon_board_cards() as $card) {
|
||||
if (!isset($card['absorbs'])) continue;
|
||||
$donor = null;
|
||||
foreach (vv_mon_board_cards() as $other) {
|
||||
if ($other['id'] === $card['absorbs']) { $donor = $other; break; }
|
||||
}
|
||||
if ($donor === null) continue;
|
||||
|
||||
$span = min($card['span'] + $donor['span'], $cols);
|
||||
$cls = vv_mon_absent_class($donor['id']);
|
||||
$out .= "#vv-monitor.$cls #{$card['id']}{--vv-sp:$span;}";
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// What the page's runtime guard needs: the declared ids in order, the absent-class names for the
|
||||
// conditional cards, and any problem the declaration already knows about.
|
||||
function vv_mon_board_js(): string {
|
||||
$ids = [];
|
||||
$absent = [];
|
||||
foreach (vv_mon_board_cards() as $card) {
|
||||
$ids[] = $card['id'];
|
||||
if (isset($card['absorbs'])) {
|
||||
$absent[$card['absorbs']] = vv_mon_absent_class($card['absorbs']);
|
||||
}
|
||||
}
|
||||
return json_encode([
|
||||
'ids' => $ids,
|
||||
'absent' => $absent,
|
||||
'problems' => vv_mon_board_check(),
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
// api/ai.php the AI row's turns and conversation store
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
require_once dirname(__DIR__) . '/include/monitor.php';
|
||||
require_once dirname(__DIR__) . '/include/monitor_board.php';
|
||||
require_once dirname(__DIR__) . '/include/ai_chat.php';
|
||||
require_once dirname(__DIR__) . '/include/docs.php';
|
||||
if (vv_ai_ui_on()) vv_ai_chat_assets();
|
||||
@@ -104,14 +105,24 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
|
||||
<div id="vv-api-banner" style="display:none;border-radius:4px;padding:5px 10px;margin-bottom:8px;font-size:11px;"></div>
|
||||
|
||||
<div id="vv-monitor" style="display:grid;grid-template-columns:repeat(8,1fr);gap:12px;width:100%;box-sizing:border-box;">
|
||||
<!-- Placement for every card below comes from include/monitor_board.php — spans, order, the
|
||||
column ladder and the row-height cap. Nothing here carries a grid-column of its own, which is
|
||||
the point: an inline style outranks the whole stylesheet, so the cards that used to carry one
|
||||
could only be overridden by shouting !important at them from four media queries. -->
|
||||
<style><?= vv_mon_board_css() ?></style>
|
||||
|
||||
<!-- Row 1: System | Power | CPU | Memory | Network -->
|
||||
<div class="vv-card" id="vv-system" style="grid-column:span 1;">
|
||||
<div id="vv-board-warn" style="display:none;border-radius:4px;padding:5px 10px;margin-bottom:8px;font-size:11px;background:#2a1414;border:1px solid #5a1e1e;color:#e0a0a0;"></div>
|
||||
|
||||
<div id="vv-monitor">
|
||||
|
||||
<!-- Board group 'Host' — membership and width are declared in include/monitor_board.php.
|
||||
Listing them here too would be a second answer to the same question, and the one that
|
||||
drifts: the group below is only what the board happens to render at eight columns. -->
|
||||
<div class="vv-card" id="vv-system">
|
||||
<div id="vv-system-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-ups-card" style="grid-column:span 1;">
|
||||
<div class="vv-card" id="vv-ups-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<svg width="9" height="14" viewBox="0 0 11 18" fill="none" style="opacity:0.4;flex-shrink:0;">
|
||||
@@ -123,7 +134,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-ups-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-cpu" style="grid-column:span 2;">
|
||||
<div class="vv-card" id="vv-cpu">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><rect x="3" y="3" width="6" height="6" rx="0.8"/><line x1="4.5" y1="3" x2="4.5" y2="1.2"/><line x1="7.5" y1="3" x2="7.5" y2="1.2"/><line x1="4.5" y1="9" x2="4.5" y2="10.8"/><line x1="7.5" y1="9" x2="7.5" y2="10.8"/><line x1="3" y1="4.5" x2="1.2" y2="4.5"/><line x1="3" y1="7.5" x2="1.2" y2="7.5"/><line x1="9" y1="4.5" x2="10.8" y2="4.5"/><line x1="9" y1="7.5" x2="10.8" y2="7.5"/></svg></span>
|
||||
@@ -134,7 +145,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-cpu-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-memory" style="grid-column:span 2;">
|
||||
<div class="vv-card" id="vv-memory">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="14" height="8" viewBox="0 0 14 8" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><rect x="0.6" y="2.5" width="12.8" height="3" rx="0.5"/><line x1="3" y1="2.5" x2="3" y2="0.8"/><line x1="5.5" y1="2.5" x2="5.5" y2="0.8"/><line x1="8" y1="2.5" x2="8" y2="0.8"/><line x1="10.5" y1="2.5" x2="10.5" y2="0.8"/><line x1="3" y1="5.5" x2="3" y2="7.2"/><line x1="5.5" y1="5.5" x2="5.5" y2="7.2"/><line x1="8" y1="5.5" x2="8" y2="7.2"/><line x1="10.5" y1="5.5" x2="10.5" y2="7.2"/></svg></span>
|
||||
@@ -145,7 +156,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-memory-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-network" style="grid-column:span 2;">
|
||||
<div class="vv-card" id="vv-network">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="13" height="12" viewBox="0 0 13 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><rect x="4.5" y="0.5" width="4" height="3" rx="0.7"/><rect x="0.5" y="8" width="4" height="3" rx="0.7"/><rect x="8.5" y="8" width="4" height="3" rx="0.7"/><line x1="6.5" y1="3.5" x2="6.5" y2="6"/><line x1="6.5" y1="6" x2="2.5" y2="6"/><line x1="2.5" y1="6" x2="2.5" y2="8"/><line x1="6.5" y1="6" x2="10.5" y2="6"/><line x1="10.5" y1="6" x2="10.5" y2="8"/></svg></span>
|
||||
@@ -156,8 +167,8 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-network-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: Scripts | Fallback | Partner | Containers & VMs -->
|
||||
<div class="vv-card" id="vv-scripts-card" style="grid-column:span 1;">
|
||||
<!-- Board group 'Workload' -->
|
||||
<div class="vv-card" id="vv-scripts-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="1" width="10" height="10" rx="1.5"/><line x1="1" y1="4.5" x2="11" y2="4.5"/><polyline points="3.5,7 5,8 3.5,9"/><line x1="6" y1="9" x2="8.5" y2="9"/></svg></span>
|
||||
@@ -168,7 +179,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-scripts-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-fallback" style="grid-column:span 1;">
|
||||
<div class="vv-card" id="vv-fallback">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="10" height="12" viewBox="0 0 10 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><path d="M5 0.8L9.2 2.8V6.2C9.2 9 5 11.2 5 11.2C5 11.2 0.8 9 0.8 6.2V2.8Z"/></svg></span>
|
||||
@@ -178,7 +189,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-fallback-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-partner" style="grid-column:span 2;">
|
||||
<div class="vv-card" id="vv-partner">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><rect x="0.5" y="1.5" width="4" height="7" rx="0.8"/><rect x="9.5" y="1.5" width="4" height="7" rx="0.8"/><line x1="4.5" y1="5" x2="9.5" y2="5"/><circle cx="7" cy="5" r="1" fill="#888" stroke="none"/></svg></span>
|
||||
@@ -189,7 +200,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-partner-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-docker-folders" style="grid-column:span 4;">
|
||||
<div class="vv-card" id="vv-docker-folders">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="0.5" width="12" height="2.5" rx="0.5"/><rect x="1" y="3.8" width="12" height="2.5" rx="0.5"/><rect x="1" y="7" width="12" height="2.5" rx="0.5"/></svg></span>
|
||||
@@ -200,8 +211,8 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-docker-folders-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 3: Rsync | GPU 0 | GPU 1 | Transcode | Streams -->
|
||||
<div class="vv-card" id="vv-rsync-card" style="grid-column:span 1;">
|
||||
<!-- Board group 'Media' -->
|
||||
<div class="vv-card" id="vv-rsync-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><polyline points="2,4 10,4 8,2"/><polyline points="10,8 2,8 4,10"/></svg></span>
|
||||
@@ -211,7 +222,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-rsync-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-gpu-card" style="grid-column:span 1;">
|
||||
<div class="vv-card" id="vv-gpu-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="15" height="10" viewBox="0 0 16 10" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><rect x="0.8" y="0.8" width="14.4" height="7" rx="1.2"/><rect x="2.5" y="2.5" width="3.5" height="3.5" rx="0.5"/><line x1="8" y1="3" x2="13" y2="3"/><line x1="8" y1="5" x2="11" y2="5"/><rect x="3" y="7.8" width="2" height="1.8" rx="0.3" stroke="none" fill="#666"/><rect x="6.5" y="7.8" width="2" height="1.8" rx="0.3" stroke="none" fill="#666"/><rect x="10" y="7.8" width="2" height="1.8" rx="0.3" stroke="none" fill="#666"/></svg></span>
|
||||
@@ -222,7 +233,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-gpu-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-gpu1-card" style="grid-column:span 1;">
|
||||
<div class="vv-card" id="vv-gpu1-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="15" height="10" viewBox="0 0 16 10" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><rect x="0.8" y="0.8" width="14.4" height="7" rx="1.2"/><rect x="2.5" y="2.5" width="3.5" height="3.5" rx="0.5"/><line x1="8" y1="3" x2="13" y2="3"/><line x1="8" y1="5" x2="11" y2="5"/><rect x="3" y="7.8" width="2" height="1.8" rx="0.3" stroke="none" fill="#666"/><rect x="6.5" y="7.8" width="2" height="1.8" rx="0.3" stroke="none" fill="#666"/><rect x="10" y="7.8" width="2" height="1.8" rx="0.3" stroke="none" fill="#666"/></svg></span>
|
||||
@@ -233,7 +244,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-gpu1-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-transcode" style="grid-column:span 1;">
|
||||
<div class="vv-card" id="vv-transcode">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="14" height="12" viewBox="0 0 14 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><rect x="0.5" y="1" width="4" height="10" rx="0.8"/><line x1="0.5" y1="3.5" x2="2.3" y2="3.5"/><line x1="0.5" y1="8.5" x2="2.3" y2="8.5"/><line x1="2.7" y1="3.5" x2="4.5" y2="3.5"/><line x1="2.7" y1="8.5" x2="4.5" y2="8.5"/><line x1="6" y1="6" x2="9.5" y2="6"/><polyline points="8.2,4.3 10.2,6 8.2,7.7"/><rect x="11" y="1" width="2.5" height="10" rx="0.5"/></svg></span>
|
||||
@@ -247,7 +258,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
every poll, and a control that is re-rendered underneath a click is a control that loses
|
||||
one. Hidden until a partner is known — on a single-host install the two buttons would be
|
||||
two names for the same view, which is how the Media Stack tab handles it too. -->
|
||||
<div class="vv-card" id="vv-streams" style="grid-column:span 4;">
|
||||
<div class="vv-card" id="vv-streams">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="11" height="12" viewBox="0 0 11 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><polygon points="1.5,1 1.5,11 10,6"/></svg></span>
|
||||
@@ -261,8 +272,8 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-streams-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 4: Watchdog | Parity | Pools | Array -->
|
||||
<div class="vv-card" id="vv-watchdog-card" style="grid-column:span 1;">
|
||||
<!-- Board group 'Storage' -->
|
||||
<div class="vv-card" id="vv-watchdog-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="14" height="9" viewBox="0 0 14 8" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><path d="M1 4C3.2 1 5 0.5 7 0.5C9 0.5 10.8 1 13 4C10.8 7 9 7.5 7 7.5C5 7.5 3.2 7 1 4Z"/><circle cx="7" cy="4" r="1.5"/></svg></span>
|
||||
@@ -272,7 +283,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-watchdog-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-parity-card" style="grid-column:span 1;">
|
||||
<div class="vv-card" id="vv-parity-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="5"/><polyline points="3.5,6 5.5,8 8.5,4"/></svg></span>
|
||||
@@ -283,7 +294,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-parity-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-storage-card" style="grid-column:3/span 2;">
|
||||
<div class="vv-card" id="vv-storage-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><ellipse cx="6" cy="3" rx="4.5" ry="1.5"/><line x1="1.5" y1="3" x2="1.5" y2="9"/><line x1="10.5" y1="3" x2="10.5" y2="9"/><ellipse cx="6" cy="9" rx="4.5" ry="1.5"/></svg></span>
|
||||
@@ -295,7 +306,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
<div id="vv-storage-body">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-card" id="vv-array-card" style="grid-column:5/span 4;">
|
||||
<div class="vv-card" id="vv-array-card">
|
||||
<h3>
|
||||
<span style="display:flex;align-items:center;gap:5px;">
|
||||
<span class="vv-ico"><svg width="14" height="12" viewBox="0 0 14 12" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><rect x="0.7" y="0.7" width="12.6" height="3" rx="0.8"/><rect x="0.7" y="4.5" width="12.6" height="3" rx="0.8"/><rect x="0.7" y="8.3" width="12.6" height="3" rx="0.8"/><circle cx="11.5" cy="2.2" r="0.8" fill="#888" stroke="none"/><circle cx="11.5" cy="6" r="0.8" fill="#888" stroke="none"/><circle cx="11.5" cy="9.8" r="0.8" fill="#888" stroke="none"/></svg></span>
|
||||
@@ -309,6 +320,36 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// The board is declared in one file and rendered in another, so the two can drift: a card added
|
||||
// to the markup nobody declared gets order 0 and lands first, and a declaration with no card
|
||||
// leaves the row it belonged to one span short. Neither shows as an error — the board just comes
|
||||
// out subtly wrong — so both are checked here against what actually rendered, alongside anything
|
||||
// vv_mon_board_check() already found wrong with the declaration itself.
|
||||
window.VV_BOARD = <?= vv_mon_board_js() ?>;
|
||||
(function vvBoardGuard() {
|
||||
const grid = document.getElementById('vv-monitor');
|
||||
const warn = document.getElementById('vv-board-warn');
|
||||
if (!grid || !warn) return;
|
||||
|
||||
const rendered = Array.from(grid.children)
|
||||
.filter(el => el.classList.contains('vv-card'))
|
||||
.map(el => el.id);
|
||||
|
||||
const undeclared = rendered.filter(id => !window.VV_BOARD.ids.includes(id));
|
||||
const missing = window.VV_BOARD.ids.filter(id => !rendered.includes(id));
|
||||
const problems = window.VV_BOARD.problems.slice();
|
||||
|
||||
if (undeclared.length) problems.push('cards in the markup that the board does not declare: ' + undeclared.join(', '));
|
||||
if (missing.length) problems.push('cards the board declares that did not render: ' + missing.join(', '));
|
||||
if (!problems.length) return;
|
||||
|
||||
warn.textContent = 'Monitor board: ' + problems.join(' · ');
|
||||
warn.style.display = 'block';
|
||||
console.warn('[varaverk] monitor board', problems);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php if (vv_ai_ui_on()): ?>
|
||||
<!-- ── AI row — its own grid, deliberately ──────────────────────────────────────
|
||||
Same eight columns and the same gap, so it reads as the last row of the one above. It is a
|
||||
@@ -2015,14 +2056,17 @@ function vvPollMonitor(live) {
|
||||
renderGpuCard(gpuList[0], 'vv-gpu-body', 'vv-gpu-label', 'GPU');
|
||||
renderGpuCard(gpuList[1], 'vv-gpu1-body', 'vv-gpu1-label', 'GPU 1');
|
||||
|
||||
// Row 3 is 8 columns wide: Rsync(1) + GPU0(1) + GPU1(1) + Transcode(1) + Streams(4).
|
||||
// On a single-GPU host the second card is hidden and Transcode widens back to 2 so the
|
||||
// row still fills exactly — otherwise it would leave a one-column hole.
|
||||
// On a single-GPU host the second GPU card is hidden, and its column has to go somewhere or
|
||||
// the row is one span short at every rung. Which card absorbs it is declared on the board
|
||||
// ('absorbs' => 'vv-gpu1-card'); all this does is say the card is gone. The width itself is
|
||||
// set by generated CSS, per rung — this used to write style.gridColumn='span 2' directly,
|
||||
// which an inline style made unclampable, so a one-GPU phone got a span of 2 in a
|
||||
// one-column grid and scrolled sideways.
|
||||
const gpu1Card = document.getElementById('vv-gpu1-card');
|
||||
const transcodeCard = document.getElementById('vv-transcode');
|
||||
const twoGpus = gpuList.length > 1;
|
||||
if (gpu1Card) gpu1Card.style.display = twoGpus ? '' : 'none';
|
||||
if (transcodeCard) transcodeCard.style.gridColumn = twoGpus ? 'span 1' : 'span 2';
|
||||
const twoGpus = gpuList.length > 1;
|
||||
if (gpu1Card) gpu1Card.style.display = twoGpus ? '' : 'none';
|
||||
const gpu1Absent = window.VV_BOARD?.absent?.['vv-gpu1-card'];
|
||||
if (gpu1Absent) document.getElementById('vv-monitor')?.classList.toggle(gpu1Absent, !twoGpus);
|
||||
|
||||
// ── Scripts ─────────────────────────────────────────────────────────────
|
||||
vvLastScripts = d.scripts ?? {};
|
||||
|
||||
Reference in New Issue
Block a user