From 3e71c314d7414eeb4389218257b23fb6f2a506a2 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Mon, 1 Jun 2026 21:10:44 -0400 Subject: [PATCH] Add Rsync card to monitor page Row 3 (before GPU) New span-1 card shows global RSYNC_ENABLED gate, per-window badges (C/D/I/W), active profile names + elapsed time from lock files, and last-sync timestamp per orchestrator window from script log files. GPU shrunk from span 2 to span 1 to make room. Row 3 is now: Rsync(1) | GPU(1) | Transcode(2) | Streams(4) --- Plugin/unraid/api/monitor.php | 1 + Plugin/unraid/include/monitor.php | 55 +++++++++++++++++++ Plugin/unraid/pages/monitor.php | 87 ++++++++++++++++++++++++++++++- 3 files changed, 141 insertions(+), 2 deletions(-) diff --git a/Plugin/unraid/api/monitor.php b/Plugin/unraid/api/monitor.php index 30fc135..abb8f95 100644 --- a/Plugin/unraid/api/monitor.php +++ b/Plugin/unraid/api/monitor.php @@ -28,6 +28,7 @@ echo json_encode([ 'disk_io' => vv_disk_io_rates(), 'watchdog' => vv_watchdog_summary(), 'scripts' => vv_scripts_status(), + 'rsync' => vv_rsync_status(), 'thresholds' => vv_disk_thresholds(), 'vms' => vv_get_vms(), 'docker_folders' => vv_get_docker_folders(), diff --git a/Plugin/unraid/include/monitor.php b/Plugin/unraid/include/monitor.php index a7c9079..d00e5d1 100644 --- a/Plugin/unraid/include/monitor.php +++ b/Plugin/unraid/include/monitor.php @@ -319,3 +319,58 @@ function vv_scripts_status(): array { 'error_count' => count(array_filter($scripts, fn($s) => $s['status'] === 'error')), ]; } + +function vv_rsync_status(): array { + $vars = vv_conf_vars(); + + $enabled = ($vars['RSYNC_ENABLED'] ?? 'true') !== 'false'; + + $windows = [ + 'critical' => ($vars['CRITICAL_RSYNC_ENABLED'] ?? 'false') !== 'false', + 'daily' => ($vars['DAILY_RSYNC_ENABLED'] ?? 'false') !== 'false', + 'intermediate' => ($vars['INTERMEDIATE_RSYNC_ENABLED'] ?? 'true') !== 'false', + 'weekly' => ($vars['WEEKLY_RSYNC_ENABLED'] ?? 'false') !== 'false', + ]; + + // Active rsync profiles — from lock files + $lockDir = '/tmp/unraid_locks'; + $active = []; + foreach (glob("$lockDir/rsync_*.lock") ?: [] as $lf) { + $content = trim(@file_get_contents($lf) ?: ''); + [$pid, $locked_name] = array_pad(explode(':', $content, 2), 2, ''); + if (!$pid || !file_exists("/proc/$pid")) continue; + $profile = preg_replace('/^rsync_/', '', $locked_name ?: basename($lf, '.lock')); + $active[] = [ + 'profile' => $profile, + 'pid' => (int)$pid, + 'elapsed' => time() - (int)filemtime($lf), + ]; + } + + // Last completed run per window (orchestrator log files) + $scriptMap = [ + 'critical' => 'critical_sync_maintenance', + 'daily' => 'daily_sync_maintenance', + 'intermediate' => 'intermediate_sync_maintenance', + 'weekly' => 'weekly_sync_maintenance', + ]; + $lastSync = []; + foreach ($scriptMap as $key => $scriptName) { + $logFile = LOG_DIR . "/$scriptName.json"; + if (!file_exists($logFile)) continue; + $stat = json_decode(@file_get_contents($logFile) ?: '{}', true) ?: []; + $lastSync[$key] = [ + 'ts' => (int)($stat['end'] ?? $stat['start'] ?? 0), + 'status' => $stat['status'] ?? 'unknown', + 'duration' => isset($stat['start'], $stat['end']) + ? (int)$stat['end'] - (int)$stat['start'] : null, + ]; + } + + return [ + 'enabled' => $enabled, + 'windows' => $windows, + 'active' => $active, + 'last_sync' => $lastSync, + ]; +} diff --git a/Plugin/unraid/pages/monitor.php b/Plugin/unraid/pages/monitor.php index 6ef1706..0f85fea 100644 --- a/Plugin/unraid/pages/monitor.php +++ b/Plugin/unraid/pages/monitor.php @@ -98,8 +98,18 @@
Loading...
- -
+ +
+

+ + + Rsync + +

+
Loading...
+
+ +

@@ -1306,6 +1316,79 @@ function vvPollMonitor() { else if (sys.array_state && sys.array_state !== 'UNKNOWN') c.classList.add('vv-accent-err'); })(); + // ── Rsync ──────────────────────────────────────────────────────────────── + (function() { + const rs = d.rsync ?? {}; + const enabled = rs.enabled ?? true; + const windows = rs.windows ?? {}; + const active = rs.active ?? []; + const lastSync = rs.last_sync ?? {}; + const now = Math.floor(Date.now() / 1000); + + const el = document.getElementById('vv-rsync-body'); + if (!el) return; + + const gColor = enabled ? '#4caf50' : '#555'; + let html = `
● ${enabled ? 'enabled' : 'disabled'}
`; + + // Window badges: C D I W + html += '
'; + [['C','critical'],['D','daily'],['I','intermediate'],['W','weekly']].forEach(([s,k]) => { + const on = windows[k] ?? false; + html += `${s}`; + }); + html += '
'; + + // Active sessions + if (active.length) { + active.forEach(a => { + const sec = a.elapsed ?? 0; + const dur = sec < 60 ? sec+'s' : sec < 3600 ? Math.floor(sec/60)+'m'+String(sec%60).padStart(2,'0')+'s' : Math.floor(sec/3600)+'h'+Math.floor((sec%3600)/60)+'m'; + html += `
+ ⟳ ${a.profile} + ${dur} +
`; + }); + html += `
`; + } + + // Last sync per window + const syncRows = [['critical','critical'],['daily','daily'],['intermediate','interm'],['weekly','weekly']]; + let hasSyncs = false; + let syncHtml = ''; + syncRows.forEach(([key, label]) => { + const s = lastSync[key]; + if (!s || !s.ts) return; + hasSyncs = true; + const diff = now - s.ts; + const ago = diff < 60 ? diff+'s' : diff < 3600 ? Math.floor(diff/60)+'m' : diff < 86400 ? Math.floor(diff/3600)+'h' : Math.floor(diff/86400)+'d'; + const ok = s.status === 'ok' || s.status === 'success'; + const warn = s.status === 'warn'; + const run = s.status === 'running'; + const col = ok ? '#4caf50' : warn ? '#ff9800' : run ? '#4fc3f7' : '#f44336'; + const icon = ok ? '✓' : warn ? '!' : run ? '●' : '✗'; + syncHtml += `
+ ${label} + ${ago} + ${icon} +
`; + }); + if (hasSyncs) { + html += `
Last sync
`; + html += syncHtml; + } + + el.innerHTML = html; + + const card = document.getElementById('vv-rsync-card'); + if (card) { + card.classList.remove('vv-accent-ok','vv-accent-warn','vv-accent-err'); + if (active.length) card.classList.add('vv-accent-ok'); + } + })(); + // ── GPU ───────────────────────────────────────────────────────────────── const gpu = d.gpu ?? {}; const gpuProcs = d.gpu_procs ?? [];