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,
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- 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 -->
|
||||
<!--
|
||||
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>
|
||||
</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 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>
|
||||
<button class="vv-fb-save-btn" id="vv-fb-save-btn" onclick="vvFbSaveSettings()">Save</button>
|
||||
</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
|
||||
// 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) {
|
||||
const td = data.tier_delays ?? {};
|
||||
const fields = [
|
||||
['vv-fb-interval', data.check_interval ?? 30],
|
||||
['vv-fb-strikes', data.handback_req ?? 3],
|
||||
['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]) => {
|
||||
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<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() {
|
||||
const ivEl = document.getElementById('vv-fb-interval');
|
||||
const stEl = document.getElementById('vv-fb-strikes');
|
||||
const suEl = document.getElementById('vv-fb-suspend');
|
||||
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: '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() {
|
||||
</div>`;
|
||||
}).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
|
||||
|
||||
Reference in New Issue
Block a user