diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php b/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php index 318a745..8822aa9 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php @@ -3,8 +3,9 @@ header('Content-Type: application/json'); require_once dirname(__DIR__) . '/include/monitor.php'; echo json_encode([ - 'fallback' => vv_fallback_state(), - 'partner' => vv_partner_state(), + 'fallback' => vv_fallback_state(), + 'fallback_active' => vv_fallback_active(), + 'partner' => vv_partner_state(), 'resources' => vv_system_resources(), 'cpu' => vv_cpu_per_core(), 'mem' => vv_memory_breakdown(), diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php b/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php index 687af05..a801a27 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php @@ -308,6 +308,61 @@ function vv_fallback_state(): array { ]; } +function vv_parse_bash_array(string $raw, string $varName): array { + if (!preg_match('/^\s*' . preg_quote($varName, '/') . '\s*=\s*\(([^)]*)\)/ms', $raw, $m)) return []; + $items = []; + foreach (explode("\n", $m[1]) as $line) { + $line = trim(preg_replace('/#.*$/', '', $line), " \t\"'"); + if ($line !== '') $items[] = $line; + } + return $items; +} + +function vv_fallback_active(): array { + $vars = vv_conf_vars(); + $myName = trim(shell_exec('hostname -s') ?: ''); + + // Identify which HOST id we are + $myId = null; + foreach (['HOST1', 'HOST2', 'HOST3', 'HOST4'] as $id) { + if (strcasecmp($vars[$id] ?? '', $myName) === 0) { $myId = $id; break; } + } + if (!$myId) return []; + + // Running containers: name → image + $running = []; + $psOut = shell_exec("docker ps --format '{\"n\":\"{{.Names}}\",\"i\":\"{{.Image}}\"}' 2>/dev/null") ?: ''; + foreach (explode("\n", trim($psOut)) as $line) { + $c = json_decode($line, true); + if ($c) $running[strtolower($c['n'])] = $c['i']; + } + + // Parse FALLBACK arrays from this host's conf + $confFile = strtolower($myId) . '.conf'; + $rawConf = vv_read_conf_raw($confFile); + + $result = []; + foreach (['HOST1', 'HOST2', 'HOST3', 'HOST4'] as $covered) { + if ($covered === $myId) continue; + $coveredHostname = $vars[$covered] ?? ''; + if (!$coveredHostname) continue; + + $names = []; + for ($tier = 1; $tier <= 4; $tier++) + $names = array_merge($names, vv_parse_bash_array($rawConf, "FALLBACK_{$myId}_COVERS_{$covered}_TIER{$tier}")); + + $active = []; + foreach ($names as $name) { + if (isset($running[strtolower($name)])) + $active[] = ['name' => $name, 'image' => $running[strtolower($name)]]; + } + + if ($active) $result[] = ['host_id' => $covered, 'hostname' => $coveredHostname, 'containers' => $active]; + } + + return $result; +} + function vv_transcode_sessions(): array { $stateFile = '/tmp/transcode_state.db'; if (!file_exists($stateFile)) return ['available' => false]; diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php b/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php index 7944978..adc5be4 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php @@ -3,10 +3,9 @@
-
+

Fallback State

-
-
+
Loading...
@@ -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 = `
${dIcon} ${dText}
`; + 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 ? `US` : ''; + fbHtml += `
+
+ ${h.id} + ${h.owner || h.hostname}${meTag} +
${h.hostname}
+
+ ● ${label} +
`; + }); + + // State line + fbHtml += `
${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 += `
Duration: ${h}h ${m}m
`; + 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 += `
Active: ${tiers.join(' ')}
`; } - document.getElementById('vv-fallback-desc').innerHTML = descHtml; + fbHtml += `
`; + + // Active fallback containers + if (fbActive.length) { + fbActive.forEach(group => { + fbHtml += `
Running for ${group.hostname}
`; + group.containers.forEach(c => { + const img = c.image.includes('/') ? c.image.split('/').pop() : c.image; + fbHtml += `
+ ${c.name} + ${img} +
`; + }); + }); + } + + document.getElementById('vv-fallback-body').innerHTML = fbHtml; // ── Partner ───────────────────────────────────────────────────────────── const pt = d.partner ?? {};