varaverk: redesign Fallback card — host online/offline list, state line, active fallback containers with image
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 ?? {};
|
||||
|
||||
Reference in New Issue
Block a user