diff --git a/Plugin/unraid/README-unraid.md b/Plugin/unraid/README-unraid.md index b427396..90a3f15 100644 --- a/Plugin/unraid/README-unraid.md +++ b/Plugin/unraid/README-unraid.md @@ -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 diff --git a/Plugin/unraid/css/varaverk.css b/Plugin/unraid/css/varaverk.css index 5118a3e..3fda842 100644 --- a/Plugin/unraid/css/varaverk.css +++ b/Plugin/unraid/css/varaverk.css @@ -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 */ diff --git a/Plugin/unraid/include/monitor_board.php b/Plugin/unraid/include/monitor_board.php new file mode 100644 index 0000000..7021414 --- /dev/null +++ b/Plugin/unraid/include/monitor_board.php @@ -0,0 +1,280 @@ += 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); +} diff --git a/Plugin/unraid/pages/monitor.php b/Plugin/unraid/pages/monitor.php index 4c9740e..840f351 100644 --- a/Plugin/unraid/pages/monitor.php +++ b/Plugin/unraid/pages/monitor.php @@ -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]); -
+ + - -
+ + +
+ + +
Loading...
-
+

@@ -123,7 +134,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -134,7 +145,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -145,7 +156,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -156,8 +167,8 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

- -
+ +

@@ -168,7 +179,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -178,7 +189,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -189,7 +200,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -200,8 +211,8 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

- -
+ +

@@ -211,7 +222,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -222,7 +233,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -233,7 +244,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -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. --> -
+

@@ -261,8 +272,8 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

- -
+ +

@@ -272,7 +283,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -283,7 +294,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -295,7 +306,7 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Loading...

-
+

@@ -309,6 +320,36 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);

+ +