From 359b051e6474c079ddcffb05b0d1bce46b7fe868 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 24 May 2026 12:00:09 -0400 Subject: [PATCH] varaverk: System card (identity/time/uptime/buttons); merge Fallback+Partner into one card --- .../emhttp/plugins/varaverk/api/monitor.php | 1 + .../emhttp/plugins/varaverk/api/system.php | 20 +++ .../emhttp/plugins/varaverk/css/varaverk.css | 8 ++ .../plugins/varaverk/include/monitor.php | 41 ++++++ .../emhttp/plugins/varaverk/pages/monitor.php | 126 +++++++++++------- 5 files changed, 147 insertions(+), 49 deletions(-) create mode 100644 Plugin/usr/local/emhttp/plugins/varaverk/api/system.php diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php b/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php index 8822aa9..bb895b0 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/api/monitor.php @@ -3,6 +3,7 @@ header('Content-Type: application/json'); require_once dirname(__DIR__) . '/include/monitor.php'; echo json_encode([ + 'system' => vv_system_info(), 'fallback' => vv_fallback_state(), 'fallback_active' => vv_fallback_active(), 'partner' => vv_partner_state(), diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/api/system.php b/Plugin/usr/local/emhttp/plugins/varaverk/api/system.php new file mode 100644 index 0000000..9631f5c --- /dev/null +++ b/Plugin/usr/local/emhttp/plugins/varaverk/api/system.php @@ -0,0 +1,20 @@ + false, 'error' => 'invalid action']); + exit; +} + +$cmd = match($action) { + 'stop' => '/usr/local/sbin/mdcmd stop', + 'shutdown' => '/sbin/shutdown -h now', + 'restart' => '/sbin/shutdown -r now', +}; + +exec($cmd . ' > /dev/null 2>&1 &'); +echo json_encode(['ok' => true]); diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css b/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css index 030e006..11ed20d 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css +++ b/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css @@ -16,6 +16,14 @@ .vv-card h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; color: #888; letter-spacing: 0.05em; } +/* System card — no h3, no top padding waste */ +#vv-system { padding-top: 14px; } + +/* System action buttons */ +.vv-sys-btn { background: #2a2a2a; border: 1px solid #444; color: #888; border-radius: 4px; + padding: 3px 7px; font-size: 13px; cursor: pointer; line-height: 1; } +.vv-sys-btn:hover { background: #3a3a3a; color: #ccc; border-color: #666; } + /* Fallback state badge */ .vv-state-badge { font-size: 20px; font-weight: bold; padding: 4px 0; margin-bottom: 2px; } .vv-state-normal { color: #4caf50; } diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php b/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php index a801a27..ea6d531 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/include/monitor.php @@ -3,6 +3,47 @@ require_once __DIR__ . '/config.php'; // Monitor helpers — docker, GPU, resources, transcode sessions, fallback state. +function vv_system_info(): array { + // Identity from ident.cfg + $ident = @parse_ini_file('/boot/config/ident.cfg') ?: []; + + // Registration from var.ini + $var = []; + foreach (@file('/var/local/emhttp/var.ini') ?: [] as $line) { + if (preg_match('/^(\w+)="([^"]*)"/', $line, $m)) $var[$m[1]] = $m[2]; + } + + // CPU model + $cpu = ''; + foreach (@file('/proc/cpuinfo') ?: [] as $line) { + if (preg_match('/^model name\s*:\s*(.+)/', $line, $m)) { $cpu = trim($m[1]); break; } + } + + // Uptime + $uptimeSec = (int)explode(' ', @file_get_contents('/proc/uptime') ?: '0')[0]; + $days = intdiv($uptimeSec, 86400); + $hours = intdiv($uptimeSec % 86400, 3600); + $mins = intdiv($uptimeSec % 3600, 60); + $uptime = ($days > 0 ? "{$days}d " : '') + . ($hours > 0 ? "{$hours}h " : '') + . "{$mins}m"; + + // Array state + $arrayState = $var['mdState'] ?? 'UNKNOWN'; + + return [ + 'name' => $ident['NAME'] ?? gethostname(), + 'comment' => $ident['COMMENT'] ?? '', + 'timezone' => $ident['timeZone'] ?? 'UTC', + 'cpu_model' => $ident['SYS_MODEL'] ?? $cpu, + 'reg_type' => 'Unraid OS ' . ($var['regTy'] ?? ''), + 'reg_to' => $var['regTo'] ?? '', + 'uptime' => $uptime, + 'array_state' => $arrayState, + 'version' => trim(@file_get_contents('/etc/unraid-version') ?: ''), + ]; +} + function vv_docker_containers(): array { $out = shell_exec('docker ps --format \'{"name":"{{.Names}}","status":"{{.Status}}","image":"{{.Image}}"}\' 2>/dev/null'); $containers = []; diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php b/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php index 07c9021..0bf68e5 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/pages/monitor.php @@ -3,13 +3,12 @@
-
-

Fallback State

-
Loading...
+
+
Loading...
-

Partner

+

Partner & Fallback

Loading...
@@ -290,13 +289,43 @@ function vvPollMonitor() { .then(r => r.json()) .then(d => { - // ── Fallback state ────────────────────────────────────────────────────── - // ── Fallback state ────────────────────────────────────────────────────── + // ── System ────────────────────────────────────────────────────────────── + const sys = d.system ?? {}; + const now = new Date(); + const timeStr = now.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); + const dateStr = now.toLocaleDateString([], {weekday:'short', day:'numeric', month:'long', year:'numeric'}); + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone.split('/').pop().replace('_',' '); + const arrayColor = (sys.array_state === 'STARTED') ? '#4caf50' : '#f44336'; + const ver = (sys.version || '').replace('version=','').replace(/"/g,''); + + document.getElementById('vv-system-body').innerHTML = + `
+
+
${sys.name}
+
${sys.comment}
+
+
+ + + +
+
+
${timeStr}
+
${dateStr}, ${tz}
+
+ Model ${sys.cpu_model} + Registration${sys.reg_type} + Uptime ${sys.uptime} + Array ${sys.array_state} + Version ${ver} +
`; + + // ── Partner & Fallback ─────────────────────────────────────────────────── + const pt = d.partner ?? {}; + const ptHosts = pt.hosts ?? []; const fb = d.fallback ?? {}; - const fbHosts = (d.partner ?? {}).hosts ?? []; const fbActive = d.fallback_active ?? []; const state = (fb.state ?? 'UNKNOWN').toUpperCase(); - const stateMap = { NORMAL: ['#4caf50', '✓ Nominal'], FAILOVER: ['#f44336', '⚠ Failover active'], @@ -305,41 +334,44 @@ function vvPollMonitor() { UNKNOWN: ['#444', '— No state file'], }; const [sColor, sLabel] = stateMap[state] ?? ['#444', state]; + const ptStatus = pt.enabled + ? `enabled · sync every ${pt.sync_min}min` + : `disabled`; - // Host rows - let fbHtml = ''; - fbHosts.forEach(h => { + let ptHtml = `
${ptStatus}
`; + ptHosts.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 += `
+ const tags = [ + h.is_me ? `US` : '', + h.is_owner ? `OWNER` : '', + ].join(''); + ptHtml += `
${h.id} - ${h.owner || h.hostname}${meTag} + ${h.owner || h.hostname}${tags}
${h.hostname}
● ${label}
`; }); - // State line - fbHtml += `
${sLabel}`; + ptHtml += `
${sLabel}`; if (state === 'FAILOVER') { const start = parseInt(fb.failover_start ?? 0); if (start > 0) { const sec = Math.floor(Date.now() / 1000) - start; - fbHtml += ` · ${Math.floor(sec/3600)}h ${Math.floor((sec%3600)/60)}m`; + ptHtml += ` · ${Math.floor(sec/3600)}h ${Math.floor((sec%3600)/60)}m`; } } - fbHtml += `
`; + ptHtml += `
`; - // Active fallback containers if (fbActive.length) { fbActive.forEach(group => { - fbHtml += `
Running for ${group.hostname}
`; + ptHtml += `
Running for ${group.hostname}
`; group.containers.forEach(c => { const img = c.image.includes('/') ? c.image.split('/').pop() : c.image; - fbHtml += `
${c.name} ${img} @@ -348,34 +380,6 @@ function vvPollMonitor() { }); } - document.getElementById('vv-fallback-body').innerHTML = fbHtml; - - // ── Partner ───────────────────────────────────────────────────────────── - const pt = d.partner ?? {}; - const ptHosts = pt.hosts ?? []; - const ptStatus = pt.enabled - ? `enabled · sync every ${pt.sync_min}min` - : `disabled`; - let ptHtml = `
${ptStatus}
`; - ptHosts.forEach(h => { - const dot = h.online === null ? '#555' : h.online ? '#4caf50' : '#f44336'; - const label = h.online === null ? 'unknown' : h.online ? 'online' : 'offline'; - const tags = [ - h.is_me ? `US` : '', - h.is_owner ? `OWNER` : '', - ].join(''); - ptHtml += `
-
- ${h.id} - ${h.owner || h.hostname} - ${tags} -
${h.hostname}
-
-
- ● ${label} -
-
`; - }); document.getElementById('vv-partner-body').innerHTML = ptHtml; // ── CPU ───────────────────────────────────────────────────────────────── @@ -592,4 +596,28 @@ function vvPollStreams() { vvPollStreams(); setInterval(vvPollStreams, 12000); + +// ── System clock tick (updates time every 30s without a full poll) ──────────── +function vvTickClock() { + const el = document.getElementById('vv-system-body'); + if (!el) return; + const now = new Date(); + const timeStr = now.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); + const t = el.querySelector('.vv-clock'); + if (t) t.textContent = timeStr; +} +setInterval(vvTickClock, 30000); + +// ── Array actions ───────────────────────────────────────────────────────────── +function vvArrayAction(action) { + const labels = {stop: 'Stop Array', shutdown: 'Shutdown', restart: 'Restart'}; + if (!confirm(`${labels[action] ?? action} — are you sure?`)) return; + fetch('/plugins/varaverk/api/system.php', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({action}), + }).then(r => r.json()).then(d => { + if (!d.ok) alert('Error: ' + (d.error ?? 'unknown')); + }).catch(() => alert('Request failed')); +}