Coverage and settings above the chat, tier delays and the probe target alongside them, coverage in three columns

This commit is contained in:
Gmer4Lfe
2026-08-22 01:43:07 -04:00
parent ce613bcf9d
commit e5f4ede3f2
2 changed files with 113 additions and 34 deletions
+9
View File
@@ -356,6 +356,15 @@ function vv_fb_all(): array {
'handback_req' => $handbackReq, 'handback_req' => $handbackReq,
'check_interval' => $checkInterval, 'check_interval' => $checkInterval,
'suspend_after' => $suspendAfter, '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, 'nodes' => $nodes,
]; ];
} }
+99 -29
View File
@@ -205,26 +205,6 @@ if (vv_ai_ui_on()) vv_ai_chat_assets();
<div style="grid-column:1/-1;color:#444;font-size:12px;padding:16px 0;text-align:center;">Loading…</div> <div style="grid-column:1/-1;color:#444;font-size:12px;padding:16px 0;text-align:center;">Loading…</div>
</div> </div>
<!-- Assistant + mesh chat -->
<?php if (vv_ai_ui_on()): ?>
<div class="vv-card" id="vv-fb-ai-card" style="margin-bottom:12px;">
<?php vv_ai_chat_markup('vv-fb-ai', [
'profile' => '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.',
]); ?>
</div>
<?php endif; ?>
<!-- Failover coverage — this host's own tiers --> <!-- Failover coverage — this host's own tiers -->
<!-- <!--
Originally on the Partnership page (e8ee5b0), removed the same day in 1a836da because it Originally on the Partnership page (e8ee5b0), removed the same day in 1a836da because it
@@ -282,12 +262,75 @@ if (vv_ai_ui_on()) vv_ai_chat_assets();
<span class="vv-fb-set-unit">minutes</span> <span class="vv-fb-set-unit">minutes</span>
</div> </div>
</div> </div>
<!-- Tier delays live in THIS host's conf and pair with the coverage picker above: the picker
says which tier a container is in, these say how long that tier waits. Tier 1 has no delay
by definition — it is what goes over immediately — so there is nothing to show for it. -->
<div class="vv-fb-qcard">
<div class="vv-fb-qlbl">Tier 2 delay</div>
<div class="vv-fb-qsub">How long this host must be dark before the partner starts its Tier 2 containers.</div>
<div class="vv-fb-qin">
<input class="vv-fb-set-inp" id="vv-fb-t2" type="number" min="0" max="10080">
<span class="vv-fb-set-unit">minutes</span>
</div>
</div>
<div class="vv-fb-qcard">
<div class="vv-fb-qlbl">Tier 3 delay</div>
<div class="vv-fb-qsub">Same for Tier 3 — secondary services that can wait out a short outage.</div>
<div class="vv-fb-qin">
<input class="vv-fb-set-inp" id="vv-fb-t3" type="number" min="0" max="10080">
<span class="vv-fb-set-unit">minutes</span>
</div>
</div>
<div class="vv-fb-qcard">
<div class="vv-fb-qlbl">Tier 4 delay</div>
<div class="vv-fb-qsub">Same for Tier 4 — the last to move, usually arrs and downloaders.</div>
<div class="vv-fb-qin">
<input class="vv-fb-set-inp" id="vv-fb-t4" type="number" min="0" max="10080">
<span class="vv-fb-set-unit">minutes</span>
</div>
</div>
<!-- Not a timing knob but the most consequential value on this page: it decides whether an
unreachable partner means "they are down" (FALLBACK) or "we are" (NO_INTERNET), and those
two branches do opposite things to DDNS. CONTAINER_VERIFY_WAIT was the obvious fourth card
here and is deliberately absent — it is a script local, not a conf var, so a settings row
for it would write a key nothing reads. -->
<div class="vv-fb-qcard">
<div class="vv-fb-qlbl">Internet probe target</div>
<div class="vv-fb-qsub">Pinged to tell "the partner is down" apart from "our own line is down". Must be reachable from both hosts.</div>
<div class="vv-fb-qin">
<input class="vv-fb-set-inp" id="vv-fb-extip" type="text" style="width:120px;" placeholder="8.8.8.8">
</div>
</div>
</div> </div>
<div style="display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-bottom:14px;"> <div style="display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-bottom:14px;">
<span id="vv-fb-set-fb" style="font-size:11px;"></span> <span id="vv-fb-set-fb" style="font-size:11px;"></span>
<button class="vv-fb-save-btn" id="vv-fb-save-btn" onclick="vvFbSaveSettings()">Save</button> <button class="vv-fb-save-btn" id="vv-fb-save-btn" onclick="vvFbSaveSettings()">Save</button>
</div> </div>
<!-- Assistant + mesh chat -->
<?php if (vv_ai_ui_on()): ?>
<div class="vv-card" id="vv-fb-ai-card" style="margin-bottom:12px;">
<?php vv_ai_chat_markup('vv-fb-ai', [
'profile' => '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.',
]); ?>
</div>
<?php endif; ?>
<?php <?php
// The conf sections this page is about, drawn by the shared renderer. They were reachable // The conf sections this page is about, drawn by the shared renderer. They were reachable
@@ -617,11 +660,17 @@ function _setToggles(data) {
} }
function _setInputs(data) { function _setInputs(data) {
const td = data.tier_delays ?? {};
const fields = [ const fields = [
['vv-fb-interval', data.check_interval ?? 30], ['vv-fb-interval', data.check_interval ?? 30],
['vv-fb-strikes', data.handback_req ?? 3], ['vv-fb-strikes', data.handback_req ?? 3],
['vv-fb-suspend', data.suspend_after ?? 120], ['vv-fb-suspend', data.suspend_after ?? 120],
['vv-fb-t2', td.tier2 ?? 240],
['vv-fb-t3', td.tier3 ?? 720],
['vv-fb-t4', td.tier4 ?? 1440],
['vv-fb-extip', data.external_ip ?? ''],
]; ];
_vvFbMyConf = data.my_conf || _vvFbMyConf;
fields.forEach(([id, val]) => { fields.forEach(([id, val]) => {
const el = document.getElementById(id); const el = document.getElementById(id);
if (el && el !== document.activeElement) el.value = val; if (el && el !== document.activeElement) el.value = val;
@@ -734,28 +783,45 @@ window.vvFbToggle = function(track, key) {
.catch(() => track.classList.toggle('on', !on)); .catch(() => track.classList.toggle('on', !on));
}; };
// Which conf each field belongs to. Tier delays are per-host and live in host<n>.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() { window.vvFbSaveSettings = function() {
const ivEl = document.getElementById('vv-fb-interval'); const num = id => parseInt(document.getElementById(id).value, 10);
const stEl = document.getElementById('vv-fb-strikes');
const suEl = document.getElementById('vv-fb-suspend');
const fb = document.getElementById('vv-fb-set-fb'); const fb = document.getElementById('vv-fb-set-fb');
const btn = document.getElementById('vv-fb-save-btn'); const btn = document.getElementById('vv-fb-save-btn');
const interval = parseInt(ivEl.value, 10); const interval = num('vv-fb-interval'), strikes = num('vv-fb-strikes'), suspend = num('vv-fb-suspend');
const strikes = parseInt(stEl.value, 10); const t2 = num('vv-fb-t2'), t3 = num('vv-fb-t3'), t4 = num('vv-fb-t4');
const suspend = parseInt(suEl.value, 10); 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; 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 = ''; btn.disabled = true; btn.textContent = 'Saving…'; fb.textContent = '';
const H = _vvFbMyConf.replace('.conf', '').toUpperCase();
const fd = new URLSearchParams(); const fd = new URLSearchParams();
fd.append('id', 'fallback'); fd.append('id', 'fallback');
fd.append('changes', JSON.stringify([ fd.append('changes', JSON.stringify([
{ file: 'master.conf', key: 'FALLBACK_CHECK_INTERVAL', value: String(interval), 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_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_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 }) fetch('/plugins/varaverk/api/confform.php', { method: 'POST', body: fd })
.then(r => r.json()) .then(r => r.json())
@@ -763,7 +829,7 @@ window.vvFbSaveSettings = function() {
btn.disabled = false; btn.textContent = 'Save'; btn.disabled = false; btn.textContent = 'Save';
fb.style.color = d.ok ? '#4caf50' : '#ef5350'; fb.style.color = d.ok ? '#4caf50' : '#ef5350';
fb.textContent = d.ok ? 'Saved ✓' : (d.error || 'Failed'); fb.textContent = d.ok ? 'Saved ✓' : (d.error || 'Failed');
if (d.ok) setTimeout(() => { fb.textContent = ''; }, 3000); if (d.ok) { setTimeout(() => { fb.textContent = ''; }, 3000); vvFbLoad(); }
}) })
.catch(() => { .catch(() => {
btn.disabled = false; btn.textContent = 'Save'; btn.disabled = false; btn.textContent = 'Save';
@@ -819,7 +885,11 @@ function _vvFbCovRender() {
</div>`; </div>`;
}).join(''); }).join('');
body.innerHTML = `<div style="max-height:320px;overflow:auto;">${rows}</div>`; // 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 = '<div style="max-height:340px;overflow:auto;display:grid;'
+ 'grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:0 14px;">'
+ rows + '</div>';
} }
// Tier 0 means "not covered" — the picker's way of removing something, since an array the // Tier 0 means "not covered" — the picker's way of removing something, since an array the