diff --git a/Plugin/unraid/include/arrs.php b/Plugin/unraid/include/arrs.php index 771772b..1da7eb1 100644 --- a/Plugin/unraid/include/arrs.php +++ b/Plugin/unraid/include/arrs.php @@ -174,14 +174,37 @@ function vv_fetch_arr_live(array $arr): array { // ── Log stats ───────────────────────────────────────────────────────────────── +// Bytes → a short human string, matching the shape the log SUMMARY already produces ("1.4G") so +// the two sources of orphans_sz read alike rather than one saying "1.4G" and the other "1.4 GB". +// +// Decimal, like every other capacity figure in the plugin — see _sz() in js/varaverk.js and +// _vv_api_fs_gb() in include/unraid_api.php for the same choice made on the other two sides. +function vv_arr_fmt_bytes(int $b): string { + if ($b <= 0) return '0B'; + if ($b >= 1e12) return round($b / 1e12, 1) . 'T'; + if ($b >= 1e9) return round($b / 1e9, 1) . 'G'; + if ($b >= 1e6) return round($b / 1e6) . 'M'; + if ($b >= 1e3) return round($b / 1e3) . 'K'; + return $b . 'B'; +} + function vv_arr_cleanup_stats(string $type): array { $slugs = ['sonarr' => 'Arrs_Stack/sonarr_cleanup', 'radarr' => 'Arrs_Stack/radarr_cleanup', 'lidarr' => 'Arrs_Stack/lidarr_cleanup']; $base = LOG_DIR . '/' . ($slugs[$type] ?? ''); + // 'total' is gone: nothing populated it on the path that actually runs, and no consumer ever + // read it. It came from the log SUMMARY's "Tracked: N files (M total)", which the daily + // aggregate has no equivalent for — so it was null in every payload this page has ever served. + // + // orphans_sz was in the same position — carried and never rendered — which is why nobody + // noticed it was stuck at its default. It stays, and the page now shows it, because a count + // of orphans is not actionable and a size is: 12 orphans is a shrug, 12 orphans at 400GB is + // an afternoon. $out = ['last_run' => null, 'end' => null, 'status' => null, - 'tracked' => null, 'total' => null, - 'orphans' => 0, 'orphans_sz' => '0B', 'junk' => 0]; + 'tracked' => null, + 'orphans' => 0, 'orphans_sz' => '0B', + 'junk' => 0, 'junk_sz' => '0B']; $jf = $base . '.json'; if (file_exists($jf)) { @@ -196,9 +219,8 @@ function vv_arr_cleanup_stats(string $type): array { $parts = preg_split('/━{3,}[^\n]*SUMMARY[^\n]*/u', $log); $blk = count($parts) > 1 ? end($parts) : $log; - if (preg_match('/Tracked:\s*([\d,]+)\s*files\s*\(([\d,]+)/u', $blk, $m)) { + if (preg_match('/Tracked:\s*([\d,]+)\s*files/u', $blk, $m)) { $out['tracked'] = (int)str_replace(',', '', $m[1]); - $out['total'] = (int)str_replace(',', '', $m[2]); } if (preg_match('/Orphans:\s*([\d,]+)\s*files\s*\(([^)]+)\)/u', $blk, $m)) { $out['orphans'] = (int)str_replace(',', '', $m[1]); @@ -210,7 +232,17 @@ function vv_arr_cleanup_stats(string $type): array { } } - // Fallback: daily aggregate db — date|arr|orphan_count|orphan_bytes|junk_count|junk_bytes|recent_count|tracked_count + // Daily aggregate db — date|arr|orphan_count|orphan_bytes|junk_count|junk_bytes|recent_count|tracked_count + // + // Called the fallback, and it is in fact the only path that ever runs. The block above needs + // LOG_DIR/Arrs_Stack/_cleanup.json, which run_job.sh writes for a top-level job — and + // the cleanups are not top-level jobs. They are entries in DAILY_MAINTENANCE_SCRIPTS, which + // daily_sync_maintenance.sh invokes with plain `bash`, so their output lands inside the + // orchestrator's log and no run record is written for them at all. + // + // The block above is therefore live only when someone runs a cleanup by hand from the + // Scheduler tab, which does go through run_job.sh. It is kept for that case rather than + // deleted, but on the nightly path everything below is what the page shows. if ($out['last_run'] === null) { $dbFile = DB_DIR . '/arr_cleanup_stats.db'; if (file_exists($dbFile)) { @@ -225,6 +257,13 @@ function vv_arr_cleanup_stats(string $type): array { $out['orphans'] = (int)$last[2]; $out['junk'] = (int)$last[4]; $out['tracked'] = (int)$last[7]; + // Sizes were being dropped on the floor. The db records orphan_bytes beside the + // count and this read every column except that one, so orphans_sz kept its '0B' + // default — which is invisible today at zero orphans and would have read "12 + // orphans (0B)" the first time there were any, on the one number that decides + // whether it is worth acting on. + $out['orphans_sz'] = vv_arr_fmt_bytes((int)$last[3]); + $out['junk_sz'] = vv_arr_fmt_bytes((int)$last[5]); } } } diff --git a/Plugin/unraid/pages/arrs.php b/Plugin/unraid/pages/arrs.php index 6e8fa30..ff63c3a 100644 --- a/Plugin/unraid/pages/arrs.php +++ b/Plugin/unraid/pages/arrs.php @@ -250,7 +250,7 @@ function _arrCard(arr) { ${_relTime(cl.last_run)} ${st} ${cl.tracked != null ? `
- ${_n(cl.tracked)} files · ${orph} orphans${junk ? ' · ' + junk + ' junk' : ''} + ${_n(cl.tracked)} files · ${orph} orphans${orph && cl.orphans_sz && cl.orphans_sz !== '0B' ? ' (' + vvEscHtml(cl.orphans_sz) + ')' : ''}${junk ? ' · ' + junk + ' junk' + (cl.junk_sz && cl.junk_sz !== '0B' ? ' (' + vvEscHtml(cl.junk_sz) + ')' : '') : ''}
` : ''}`; } @@ -326,11 +326,14 @@ function _nodeSection(node) { // ── Sync + Recovery row ─────────────────────────────────────────────────────── function _syncSection(sync, recovery, settings) { - const _card = (title, data, items, extra) => { + // offTxt distinguishes "has not happened yet" from "cannot happen". Both render with no run + // history, and "never run" on a switched-off subsystem reads as a fault waiting to clear — + // it says the thing has not got round to it, when in fact it has been told not to. + const _card = (title, data, items, extra, offTxt) => { const ok = data.last_run && data.status === 'ok'; const bad = data.last_run && data.status !== 'ok'; const pillCls = !data.last_run ? 'off' : ok ? 'ok' : 'err'; - const pillTxt = !data.last_run ? 'never run' : ok ? 'ok' : data.status || 'error'; + const pillTxt = !data.last_run ? (offTxt || 'never run') : ok ? 'ok' : data.status || 'error'; let body = ''; if (data.last_run) { body += `
${_relTime(data.last_run)}
`; @@ -359,14 +362,14 @@ function _syncSection(sync, recovery, settings) { ? _item(`+${_n(sync.added)}`, 'Added') + (sync.nodes != null ? _item(_n(sync.nodes), 'Nodes') : '') + (sync.blocklist_count != null ? _item(_n(sync.blocklist_count), 'Blocklist') : '') - : `No run history`; + : `${syncEnabled ? 'No run history' : 'Nothing to show while disabled'}`; const recItems = recovery.last_run ? _item(_n(recovery.fixed), 'Fixed') + _item(_n(recovery.searched), 'Re-searched') : `No run history`; return `
- ${_card('Arr Sync', sync, syncItems, syncExtra)} + ${_card('Arr Sync', sync, syncItems, syncExtra, syncEnabled ? null : 'disabled')} ${_card('Failed / Stalled Recovery', recovery, recItems, '')}
`; }