Coverage and settings above the chat, tier delays and the probe target alongside them, coverage in three columns
This commit is contained in:
@@ -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,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 fb = document.getElementById('vv-fb-set-fb');
|
||||||
const suEl = document.getElementById('vv-fb-suspend');
|
const btn = document.getElementById('vv-fb-save-btn');
|
||||||
const fb = document.getElementById('vv-fb-set-fb');
|
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user