From e5f4ede3f2e7e41e744e2146ebbd2bd94dd51289 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sat, 22 Aug 2026 01:43:07 -0400 Subject: [PATCH] Coverage and settings above the chat, tier delays and the probe target alongside them, coverage in three columns --- Plugin/unraid/include/fallback.php | 9 ++ Plugin/unraid/pages/fallback.php | 138 ++++++++++++++++++++++------- 2 files changed, 113 insertions(+), 34 deletions(-) diff --git a/Plugin/unraid/include/fallback.php b/Plugin/unraid/include/fallback.php index db7a612..a592946 100644 --- a/Plugin/unraid/include/fallback.php +++ b/Plugin/unraid/include/fallback.php @@ -356,6 +356,15 @@ function vv_fb_all(): array { 'handback_req' => $handbackReq, 'check_interval' => $checkInterval, 'suspend_after' => $suspendAfter, + // Tier delays are per-host and live in this host's own conf, not master.conf — the save + // path has to name the right file per field or the write is refused as an absent key. + 'my_conf' => vv_detect_host() . '.conf', + 'tier_delays' => [ + 'tier2' => (int)(vv_fb_scalar($myRaw, strtoupper($currentHost) . '_TIER2_DELAY') ?: 240), + 'tier3' => (int)(vv_fb_scalar($myRaw, strtoupper($currentHost) . '_TIER3_DELAY') ?: 720), + 'tier4' => (int)(vv_fb_scalar($myRaw, strtoupper($currentHost) . '_TIER4_DELAY') ?: 1440), + ], + 'external_ip' => vv_fb_scalar($masterRaw, 'EXTERNAL_IP'), 'nodes' => $nodes, ]; } diff --git a/Plugin/unraid/pages/fallback.php b/Plugin/unraid/pages/fallback.php index 2a4afef..769d0f2 100644 --- a/Plugin/unraid/pages/fallback.php +++ b/Plugin/unraid/pages/fallback.php @@ -205,26 +205,6 @@ if (vv_ai_ui_on()) vv_ai_chat_assets();
Loading…
- - -
- 'varaverk', - 'compact' => true, - // Fallback is the mesh's business by definition — a failover is two hosts agreeing about - // each other — so the mesh side leads here, as it does on Partnership. - 'mesh' => true, - 'meshDefault' => true, - 'title' => 'Assistant', - 'height' => '300px', - 'tall' => '500px', - 'tallLarge' => '750px', - 'empty' => 'Ask about fallback — why a tier has not fired, what the partner would ' - . 'start if this host went dark, whether a stale state file matters.', - ]); ?> -
- - +
+
Tier 2 delay
+
How long this host must be dark before the partner starts its Tier 2 containers.
+
+ + minutes +
+
+ +
+
Tier 3 delay
+
Same for Tier 3 — secondary services that can wait out a short outage.
+
+ + minutes +
+
+ +
+
Tier 4 delay
+
Same for Tier 4 — the last to move, usually arrs and downloaders.
+
+ + minutes +
+
+ + +
+
Internet probe target
+
Pinged to tell "the partner is down" apart from "our own line is down". Must be reachable from both hosts.
+
+ +
+
+ + +
+ 'varaverk', + 'compact' => true, + // Fallback is the mesh's business by definition — a failover is two hosts agreeing about + // each other — so the mesh side leads here, as it does on Partnership. + 'mesh' => true, + 'meshDefault' => true, + 'title' => 'Assistant', + 'height' => '300px', + 'tall' => '500px', + 'tallLarge' => '750px', + 'empty' => 'Ask about fallback — why a tier has not fired, what the partner would ' + . 'start if this host went dark, whether a stale state file matters.', + ]); ?> +
+ + { const el = document.getElementById(id); if (el && el !== document.activeElement) el.value = val; @@ -734,28 +783,45 @@ window.vvFbToggle = function(track, key) { .catch(() => track.classList.toggle('on', !on)); }; +// Which conf each field belongs to. Tier delays are per-host and live in host.conf; the rest +// are mesh-wide in master.conf. confform refuses a key it cannot find in the named file rather +// than appending it into an unknown section, so getting this wrong fails the save outright — +// which is the right failure, but only if the mapping is stated rather than assumed. +let _vvFbMyConf = 'host1.conf'; + window.vvFbSaveSettings = function() { - const ivEl = document.getElementById('vv-fb-interval'); - const stEl = document.getElementById('vv-fb-strikes'); - const suEl = document.getElementById('vv-fb-suspend'); - const fb = document.getElementById('vv-fb-set-fb'); - const btn = document.getElementById('vv-fb-save-btn'); + const num = id => parseInt(document.getElementById(id).value, 10); + const fb = document.getElementById('vv-fb-set-fb'); + const btn = document.getElementById('vv-fb-save-btn'); - const interval = parseInt(ivEl.value, 10); - const strikes = parseInt(stEl.value, 10); - const suspend = parseInt(suEl.value, 10); + const interval = num('vv-fb-interval'), strikes = num('vv-fb-strikes'), suspend = num('vv-fb-suspend'); + const t2 = num('vv-fb-t2'), t3 = num('vv-fb-t3'), t4 = num('vv-fb-t4'); + const extip = (document.getElementById('vv-fb-extip').value || '').trim(); - if ([interval, strikes, suspend].some(n => isNaN(n) || n < 0)) { + if ([interval, strikes, suspend, t2, t3, t4].some(n => isNaN(n) || n < 0)) { fb.style.color = '#ef5350'; fb.textContent = 'Invalid values'; return; } + // Tiers fire in order, and a later tier that fires sooner than an earlier one is a config that + // cannot do what it reads as. Caught here rather than at 3am during an outage. + if (!(t2 <= t3 && t3 <= t4)) { + fb.style.color = '#ef5350'; fb.textContent = 'Tier delays must increase: T2 ≤ T3 ≤ T4'; return; + } + if (extip === '') { + fb.style.color = '#ef5350'; fb.textContent = 'Probe target cannot be empty'; return; + } btn.disabled = true; btn.textContent = 'Saving…'; fb.textContent = ''; + const H = _vvFbMyConf.replace('.conf', '').toUpperCase(); const fd = new URLSearchParams(); fd.append('id', 'fallback'); fd.append('changes', JSON.stringify([ - { file: 'master.conf', key: 'FALLBACK_CHECK_INTERVAL', value: String(interval), type: 'scalar' }, - { file: 'master.conf', key: 'FALLBACK_HANDBACK_STRIKES', value: String(strikes), type: 'scalar' }, - { file: 'master.conf', key: 'FALLBACK_PARTNERSHIP_SUSPEND_AFTER', value: String(suspend), type: 'scalar' }, + { file: 'master.conf', key: 'FALLBACK_CHECK_INTERVAL', value: String(interval), type: 'scalar' }, + { file: 'master.conf', key: 'FALLBACK_HANDBACK_STRIKES', value: String(strikes), type: 'scalar' }, + { file: 'master.conf', key: 'FALLBACK_PARTNERSHIP_SUSPEND_AFTER', value: String(suspend), type: 'scalar' }, + { file: 'master.conf', key: 'EXTERNAL_IP', value: extip, type: 'scalar' }, + { file: _vvFbMyConf, key: H + '_TIER2_DELAY', value: String(t2), type: 'scalar' }, + { file: _vvFbMyConf, key: H + '_TIER3_DELAY', value: String(t3), type: 'scalar' }, + { file: _vvFbMyConf, key: H + '_TIER4_DELAY', value: String(t4), type: 'scalar' }, ])); fetch('/plugins/varaverk/api/confform.php', { method: 'POST', body: fd }) .then(r => r.json()) @@ -763,7 +829,7 @@ window.vvFbSaveSettings = function() { btn.disabled = false; btn.textContent = 'Save'; fb.style.color = d.ok ? '#4caf50' : '#ef5350'; fb.textContent = d.ok ? 'Saved ✓' : (d.error || 'Failed'); - if (d.ok) setTimeout(() => { fb.textContent = ''; }, 3000); + if (d.ok) { setTimeout(() => { fb.textContent = ''; }, 3000); vvFbLoad(); } }) .catch(() => { btn.disabled = false; btn.textContent = 'Save'; @@ -819,7 +885,11 @@ function _vvFbCovRender() { `; }).join(''); - body.innerHTML = `
${rows}
`; + // Three columns. One column of forty-nine rows is a scroll, not a list — you cannot see what + // is covered without dragging, which is the one thing this card exists to show. + body.innerHTML = '
' + + rows + '
'; } // Tier 0 means "not covered" — the picker's way of removing something, since an array the