varaverk: redesign Fallback card — host online/offline list, state line, active fallback containers with image

This commit is contained in:
Gmer4Lfe
2026-05-24 11:47:08 -04:00
parent 6bb44cae8b
commit cead4eb46f
3 changed files with 109 additions and 26 deletions
@@ -3,10 +3,9 @@
<div id="vv-monitor">
<div class="vv-row">
<div class="vv-card" id="vv-fallback">
<div class="vv-card" id="vv-fallback" style="min-width:220px;">
<h3>Fallback State</h3>
<div class="vv-state-badge" id="vv-fallback-state">—</div>
<div id="vv-fallback-desc"></div>
<div id="vv-fallback-body">Loading...</div>
</div>
<div class="vv-card" id="vv-partner" style="flex:2;min-width:220px;">
@@ -292,36 +291,64 @@ function vvPollMonitor() {
.then(d => {
// ── Fallback state ──────────────────────────────────────────────────────
const fb = d.fallback ?? {};
const state = (fb.state ?? 'UNKNOWN').toUpperCase();
const badge = document.getElementById('vv-fallback-state');
badge.textContent = state;
badge.className = 'vv-state-badge vv-state-' + state.toLowerCase();
// ── Fallback state ──────────────────────────────────────────────────────
const fb = d.fallback ?? {};
const fbHosts = (d.partner ?? {}).hosts ?? [];
const fbActive = d.fallback_active ?? [];
const state = (fb.state ?? 'UNKNOWN').toUpperCase();
const descMap = {
NORMAL: ['#4caf50', '✓', 'Nominal'],
FAILOVER: ['#f44336', '⚠', 'Running peer containers'],
NO_INTERNET: ['#ff9800', '⚡', 'Internet lost'],
DARK: ['#9e9e9e', '◌', 'Isolation mode'],
UNKNOWN: ['#555', '?', 'State unavailable'],
const stateMap = {
NORMAL: ['#4caf50', '✓ Nominal'],
FAILOVER: ['#f44336', '⚠ Failover active'],
NO_INTERNET: ['#ff9800', '⚡ Internet lost'],
DARK: ['#9e9e9e', '◌ Dark mode'],
UNKNOWN: ['#444', '— No state file'],
};
const [dColor, dIcon, dText] = descMap[state] ?? ['#555', '?', ''];
let descHtml = `<div style="font-size:12px;color:${dColor};margin-top:6px;">${dIcon} ${dText}</div>`;
const [sColor, sLabel] = stateMap[state] ?? ['#444', state];
// Host rows
let fbHtml = '';
fbHosts.forEach(h => {
const dot = h.online === null ? '#555' : h.online ? '#4caf50' : '#f44336';
const label = h.online === null ? 'unknown' : h.online ? 'online' : 'offline';
const meTag = h.is_me ? `<span style="background:#1a3a1a;color:#4caf50;font-size:8px;padding:1px 5px;border-radius:3px;margin-left:4px;">US</span>` : '';
fbHtml += `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
<div>
<span style="font-size:10px;color:#555;margin-right:4px;">${h.id}</span>
<span style="font-size:12px;color:#ccc;font-weight:500;">${h.owner || h.hostname}</span>${meTag}
<div style="font-size:10px;color:#555;margin-top:1px;">${h.hostname}</div>
</div>
<span style="color:${dot};font-size:10px;white-space:nowrap;">● ${label}</span>
</div>`;
});
// State line
fbHtml += `<div style="font-size:11px;color:${sColor};margin:8px 0;padding-top:8px;border-top:1px solid #2a2a2a;">${sLabel}`;
if (state === 'FAILOVER') {
const start = parseInt(fb.failover_start ?? 0);
if (start > 0) {
const sec = Math.floor(Date.now() / 1000) - start;
const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60);
descHtml += `<div style="font-size:11px;color:#888;margin-top:3px;">Duration: ${h}h ${m}m</div>`;
fbHtml += ` · ${Math.floor(sec/3600)}h ${Math.floor((sec%3600)/60)}m`;
}
const tiers = ['tier2_started','tier3_started','tier4_started']
.filter(k => fb[k] === 'true')
.map(k => k.replace('_started','').replace('tier','T'));
if (tiers.length)
descHtml += `<div style="font-size:11px;color:#f44336;margin-top:3px;">Active: ${tiers.join(' ')}</div>`;
}
document.getElementById('vv-fallback-desc').innerHTML = descHtml;
fbHtml += `</div>`;
// Active fallback containers
if (fbActive.length) {
fbActive.forEach(group => {
fbHtml += `<div style="font-size:10px;color:#888;margin-top:6px;margin-bottom:4px;">Running for ${group.hostname}</div>`;
group.containers.forEach(c => {
const img = c.image.includes('/') ? c.image.split('/').pop() : c.image;
fbHtml += `<div style="display:flex;justify-content:space-between;align-items:center;
background:#1a1a1a;border-radius:4px;padding:4px 8px;margin-bottom:4px;">
<span style="color:#ccc;font-size:11px;font-weight:500;">${c.name}</span>
<span style="color:#555;font-size:9px;margin-left:8px;white-space:nowrap;">${img}</span>
</div>`;
});
});
}
document.getElementById('vv-fallback-body').innerHTML = fbHtml;
// ── Partner ─────────────────────────────────────────────────────────────
const pt = d.partner ?? {};