Rehome the failover coverage picker on the Fallback tab, five days after it was dropped from Partnership for belonging here
This commit is contained in:
@@ -225,6 +225,33 @@ if (vv_ai_ui_on()) vv_ai_chat_assets();
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Failover coverage — this host's own tiers -->
|
||||
<!--
|
||||
Originally on the Partnership page (e8ee5b0), removed the same day in 1a836da because it
|
||||
describes what runs during an outage, and never rehomed. This is that card, back where it
|
||||
belongs.
|
||||
|
||||
Deliberately edits THIS host's tiers only. FALLBACK_<host>_TIER* lives in that host's conf and
|
||||
says what someone ELSE starts for it, so this card configures what the partner does for us —
|
||||
and the partner's own coverage is configured from the partner, because sparse checkout means
|
||||
this host holds only a read-only RAM-cache copy of their conf.
|
||||
-->
|
||||
<div class="vv-card" id="vv-fb-cov-card" style="margin-bottom:12px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:4px;">
|
||||
<h3 style="margin:0;">Failover coverage</h3>
|
||||
<span style="font-size:10px;color:#444;" id="vv-fb-cov-sub">Loading…</span>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#3f3f3f;margin-bottom:9px;line-height:1.45;">
|
||||
Which of <b style="color:#666;">this host's</b> containers the partner starts when this host goes dark.
|
||||
Tier 1 is immediate; later tiers wait out the delays set in this host's conf.
|
||||
To change what <span id="vv-fb-cov-partner" style="color:#666;">the partner</span> hands to us, open this page there.
|
||||
</div>
|
||||
<div id="vv-fb-cov-body" style="color:#444;font-size:12px;">Loading…</div>
|
||||
<div style="display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-top:10px;padding-top:8px;border-top:1px solid #1e1e1e;">
|
||||
<span id="vv-fb-cov-fb" style="font-size:11px;"></span>
|
||||
<button class="vv-fb-save-btn" id="vv-fb-cov-save" onclick="vvFbCovSave()">Save coverage</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Quick settings -->
|
||||
<div style="font-size:13px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px;padding:0 2px;">Quick settings</div>
|
||||
|
||||
@@ -606,6 +633,10 @@ function _render(data) {
|
||||
_setToggles(data);
|
||||
_setInputs(data);
|
||||
_verdict(data);
|
||||
// Names the partner in the coverage hint, so "open this page there" points somewhere.
|
||||
const _pn = (data.nodes || []).find(n => !n.is_me);
|
||||
const _pe = document.getElementById('vv-fb-cov-partner');
|
||||
if (_pe && _pn) _pe.textContent = _pn.id + ' (' + _pn.hostname + ')';
|
||||
|
||||
const grid = document.getElementById('vv-fb-grid');
|
||||
|
||||
@@ -740,8 +771,117 @@ window.vvFbSaveSettings = function() {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ── Failover coverage picker ──────────────────────────────────────────────────
|
||||
// Holds the whole edit in memory and writes all four tiers in one POST. Per-row saves would
|
||||
// leave the four arrays briefly disagreeing, and fallback.sh reads them as a set.
|
||||
let _vvFbCov = null; // { containers:[], cover:{name:tier}, missing:[] }
|
||||
|
||||
function vvFbCovLoad() {
|
||||
fetch('/plugins/varaverk/api/fallback_coverage.php')
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (!d.ok) throw new Error(d.error || 'load failed');
|
||||
_vvFbCov = d;
|
||||
_vvFbCovRender();
|
||||
})
|
||||
.catch(e => {
|
||||
const b = document.getElementById('vv-fb-cov-body');
|
||||
if (b) b.innerHTML = `<span style="color:#ef5350;font-size:11px;">Could not load coverage — ${vvEscHtml(String(e))}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
function _vvFbCovRender() {
|
||||
const d = _vvFbCov;
|
||||
const body = document.getElementById('vv-fb-cov-body');
|
||||
if (!d || !body) return;
|
||||
|
||||
const sub = document.getElementById('vv-fb-cov-sub');
|
||||
const n = Object.keys(d.cover || {}).length;
|
||||
if (sub) sub.textContent = `${d.host} · ${n} container${n !== 1 ? 's' : ''} covered`;
|
||||
|
||||
// Names in a tier that are not installed here. Kept and shown rather than dropped: a tier
|
||||
// entry for a container that does not exist is a line fallback.sh fails on mid-outage.
|
||||
const miss = new Set((d.missing || []).map(s => s.toLowerCase()));
|
||||
const all = [...new Set([...(d.containers || []), ...Object.keys(d.cover || {})])]
|
||||
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
||||
|
||||
const rows = all.map(name => {
|
||||
const cur = d.cover?.[name] ?? 0;
|
||||
const gone = miss.has(name.toLowerCase());
|
||||
const opts = [0, 1, 2, 3, 4].map(t =>
|
||||
`<option value="${t}"${t === cur ? ' selected' : ''}>${t === 0 ? '—' : 'T' + t}</option>`).join('');
|
||||
return `<div style="display:flex;align-items:center;gap:8px;padding:3px 0;border-bottom:1px solid #171717;">
|
||||
<select class="vv-fb-set-inp vv-fb-cov-sel" style="width:56px;flex-shrink:0;"
|
||||
data-cov="${vvEscAttr(name)}">${opts}</select>
|
||||
<span style="font-size:11px;color:${cur ? '#ccc' : '#555'};min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${vvEscHtml(name)}</span>
|
||||
${gone ? '<span class="vv-fb-leg no" style="flex-shrink:0;">not installed</span>' : ''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
body.innerHTML = `<div style="max-height:320px;overflow:auto;">${rows}</div>`;
|
||||
}
|
||||
|
||||
// Tier 0 means "not covered" — the picker's way of removing something, since an array the
|
||||
// operator emptied is a legitimate and otherwise inexpressible choice.
|
||||
// Delegated off the card, and the name travels in a data attribute rather than inside an
|
||||
// inline handler's quotes. vvEscAttr() escapes " but not ', and these names come from conf as
|
||||
// well as from Docker — a hand-typed apostrophe would have broken out of the handler string.
|
||||
document.getElementById('vv-fb-cov-card')?.addEventListener('change', ev => {
|
||||
const sel = ev.target.closest('.vv-fb-cov-sel');
|
||||
if (sel) vvFbCovSet(sel.dataset.cov, sel.value);
|
||||
});
|
||||
|
||||
function vvFbCovSet(name, val) {
|
||||
if (!_vvFbCov) return;
|
||||
const t = parseInt(val, 10);
|
||||
if (t === 0) delete _vvFbCov.cover[name];
|
||||
else _vvFbCov.cover[name] = t;
|
||||
const sub = document.getElementById('vv-fb-cov-sub');
|
||||
const n = Object.keys(_vvFbCov.cover).length;
|
||||
if (sub) sub.textContent = `${_vvFbCov.host} · ${n} container${n !== 1 ? 's' : ''} covered · unsaved`;
|
||||
}
|
||||
|
||||
window.vvFbCovSave = async function () {
|
||||
if (!_vvFbCov) return;
|
||||
const fbEl = document.getElementById('vv-fb-cov-fb');
|
||||
const btn = document.getElementById('vv-fb-cov-save');
|
||||
const n = Object.keys(_vvFbCov.cover).length;
|
||||
|
||||
// Emptying the list is allowed and is sometimes what is wanted, but it is also what a stray
|
||||
// click looks like, and the consequence only appears during an outage.
|
||||
if (n === 0 && !await vvConfirm('Save with NO containers covered?\n\nThe partner would start nothing for this host during an outage.')) return;
|
||||
|
||||
btn.disabled = true; btn.textContent = 'Saving…'; fbEl.textContent = '';
|
||||
const fd = new URLSearchParams();
|
||||
fd.append('action', 'cover');
|
||||
fd.append('tiers', JSON.stringify(_vvFbCov.cover));
|
||||
|
||||
fetch('/plugins/varaverk/api/fallback_coverage.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false; btn.textContent = 'Save coverage';
|
||||
fbEl.style.color = d.ok ? '#4caf50' : '#ef5350';
|
||||
if (d.ok) {
|
||||
const c = d.counts || {};
|
||||
fbEl.textContent = `Saved ✓ T1 ${c[1]??0} · T2 ${c[2]??0} · T3 ${c[3]??0} · T4 ${c[4]??0}`;
|
||||
setTimeout(() => { fbEl.textContent = ''; }, 5000);
|
||||
// Re-read rather than trust the local copy, and refresh the node cards whose tier
|
||||
// pills and coverage counts this just changed.
|
||||
vvFbCovLoad();
|
||||
vvFbLoad();
|
||||
} else {
|
||||
fbEl.textContent = d.error || 'Failed';
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
btn.disabled = false; btn.textContent = 'Save coverage';
|
||||
fbEl.style.color = '#ef5350'; fbEl.textContent = 'Request failed: ' + e;
|
||||
});
|
||||
};
|
||||
vvFbLoad();
|
||||
setInterval(vvFbLoad, 30000);
|
||||
vvFbCovLoad(); // once — this is an editor, not a monitor; polling would fight the operator
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user