Surface the three media jobs that appeared in no tab at all
play_state_sync, media_shares_permissions and media_cleaner work on the same files the arrs manage and were visible only by opening the Scheduler and reading an orchestrator's log. Readable at all because run_orch_child() now writes a run record and a per-script log for its children — this could not have been written yesterday. Each card shows the sentence the script itself ended on rather than a count re-derived here, since the three word their outcome differently and the wording is the part worth reading.
This commit is contained in:
@@ -54,6 +54,7 @@
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
require_once __DIR__ . '/config.php';
|
require_once __DIR__ . '/config.php';
|
||||||
|
require_once __DIR__ . '/media.php'; // vv_media_jobs() — the media-side jobs shown on this page
|
||||||
|
|
||||||
// ── Conf helpers ──────────────────────────────────────────────────────────────
|
// ── Conf helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -450,6 +451,10 @@ function vv_arrs_all(): array {
|
|||||||
'nodes' => $result,
|
'nodes' => $result,
|
||||||
'sync' => vv_arr_sync_stats(),
|
'sync' => vv_arr_sync_stats(),
|
||||||
'recovery' => vv_arr_recovery_stats(),
|
'recovery' => vv_arr_recovery_stats(),
|
||||||
|
// The three media jobs that operate on the same files the arrs manage and had no tab of
|
||||||
|
// their own. Cheap — three run records and three log tails — and it rides this payload
|
||||||
|
// rather than a fourth endpoint because it is shown on this page and cached with it.
|
||||||
|
'media_jobs' => function_exists('vv_media_jobs') ? vv_media_jobs() : [],
|
||||||
'host' => $currentHost,
|
'host' => $currentHost,
|
||||||
'settings' => [
|
'settings' => [
|
||||||
'arr_sync_enabled' => ($vars['ARR_SYNC_ENABLED'] ?? 'true') !== 'false',
|
'arr_sync_enabled' => ($vars['ARR_SYNC_ENABLED'] ?? 'true') !== 'false',
|
||||||
|
|||||||
@@ -257,3 +257,66 @@ function vv_media_sessions(): array {
|
|||||||
'server_count' => count($servers),
|
'server_count' => count($servers),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── The media jobs that had nowhere to be seen ────────────────────────────────────────────────
|
||||||
|
// Three scripts that operate on the media files every day and appeared in no tab: play state sync
|
||||||
|
// every thirty minutes, permissions and the cleaner nightly. The only way to know whether any of
|
||||||
|
// them had run was to open the Scheduler and read an orchestrator's log.
|
||||||
|
//
|
||||||
|
// Readable now because run_orch_child() writes a run record and a per-script log for its children.
|
||||||
|
// Before that this function could not have been written: the record did not exist, and the output
|
||||||
|
// was interleaved into a parent log with forty other scripts behind headings that name no script.
|
||||||
|
const VV_MEDIA_JOBS = [
|
||||||
|
'Media/play_state_sync' => 'Play state sync',
|
||||||
|
'Media/media_shares_permissions' => 'Media permissions',
|
||||||
|
'Media/media_cleaner' => 'Media cleaner',
|
||||||
|
];
|
||||||
|
|
||||||
|
// The last line the script chose to end on. All three close with a SUMMARY block whose final 🏁
|
||||||
|
// line is the sentence a human would read out — "done — 21 shares updated", "clean — nothing to
|
||||||
|
// remove" — so that line is taken verbatim rather than re-derived from counts this would have to
|
||||||
|
// parse differently per script.
|
||||||
|
//
|
||||||
|
// Decorations are stripped, not matched: the three write 🏁 with varying spacing and only some
|
||||||
|
// prefix "Status:" or "[OK]". Matching those shapes would break the first time one was reworded,
|
||||||
|
// and the words after them are the part with the meaning.
|
||||||
|
function vv_media_job_summary(string $id): string {
|
||||||
|
$path = LOG_DIR . '/' . $id . '.log';
|
||||||
|
$raw = @file_get_contents($path);
|
||||||
|
if ($raw === false) return '';
|
||||||
|
|
||||||
|
$lines = array_slice(explode("\n", rtrim($raw)), -60);
|
||||||
|
$out = '';
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (!str_contains($line, '🏁')) continue;
|
||||||
|
$t = trim(str_replace('🏁', '', $line));
|
||||||
|
$t = preg_replace('/^\[(OK|WARN|INFO|ERROR)\]\s*/u', '', trim($t));
|
||||||
|
$t = preg_replace('/^Status:\s*/u', '', $t);
|
||||||
|
$t = trim($t);
|
||||||
|
// "Done ✅" carries nothing the status field does not already say; keep looking for a line
|
||||||
|
// that does. If nothing better appears, the earlier one already captured is kept.
|
||||||
|
if ($t === '' || preg_match('/^done\s*✅?$/iu', $t)) continue;
|
||||||
|
$out = $t;
|
||||||
|
}
|
||||||
|
return mb_substr($out, 0, 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
function vv_media_jobs(): array {
|
||||||
|
$out = [];
|
||||||
|
foreach (VV_MEDIA_JOBS as $id => $label) {
|
||||||
|
$rec = @json_decode((string)@file_get_contents(LOG_DIR . '/' . $id . '.json'), true);
|
||||||
|
$rec = is_array($rec) ? $rec : [];
|
||||||
|
$start = isset($rec['start']) ? (int)$rec['start'] : 0;
|
||||||
|
$end = isset($rec['end']) ? (int)$rec['end'] : 0;
|
||||||
|
$out[] = [
|
||||||
|
'id' => $id . '.sh',
|
||||||
|
'label' => $label,
|
||||||
|
'last_run' => $start ?: null,
|
||||||
|
'status' => $rec['status'] ?? null,
|
||||||
|
'exit' => isset($rec['exit']) ? (int)$rec['exit'] : null,
|
||||||
|
'duration' => ($start && $end) ? $end - $start : null,
|
||||||
|
'summary' => vv_media_job_summary($id),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|||||||
@@ -374,6 +374,40 @@ function _syncSection(sync, recovery, settings) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Media jobs ────────────────────────────────────────────────────────────────
|
||||||
|
// Play state sync, permissions and the cleaner. They work on the same files the arrs manage and
|
||||||
|
// appeared in no tab at all — the only way to know whether one had run was to open the Scheduler
|
||||||
|
// and read an orchestrator's log. Readable at all only because run_orch_child() now writes a run
|
||||||
|
// record and a per-script log for its children.
|
||||||
|
//
|
||||||
|
// Each shows the sentence the script itself ended on rather than a count re-derived here. The
|
||||||
|
// three word their outcome differently — "done — 21 shares updated", "clean — nothing to remove" —
|
||||||
|
// and the wording is the part worth reading.
|
||||||
|
function _mediaJobsSection(jobs) {
|
||||||
|
if (!jobs || !jobs.length) return '';
|
||||||
|
const cards = jobs.map(j => {
|
||||||
|
const st = j.status || null;
|
||||||
|
const col = !st ? '#444' : st === 'ok' ? '#4caf50' : st === 'running' ? '#4a9eff'
|
||||||
|
: st === 'warn' ? '#ffb74d' : '#ef5350';
|
||||||
|
const pill = !st ? 'never run' : st;
|
||||||
|
const dur = j.duration != null ? _dur(j.duration) : '';
|
||||||
|
return `<div class="vv-arr-card">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;gap:8px;">
|
||||||
|
<span style="font-size:11px;font-weight:700;color:#555;text-transform:uppercase;letter-spacing:.07em;">${vvEscHtml(j.label)}</span>
|
||||||
|
<span class="vv-arr-pill" style="color:${col};border:1px solid ${col}33;background:${col}14;">${vvEscHtml(pill)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:11px;color:#555;">
|
||||||
|
${j.last_run ? _relTime(j.last_run) : 'no run recorded'}${dur ? ' · ' + dur : ''}
|
||||||
|
</div>
|
||||||
|
${j.summary ? `<div style="font-size:10px;color:#3f3f3f;margin-top:5px;line-height:1.45;">${vvEscHtml(j.summary)}</div>` : ''}
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
return `<div style="grid-column:1/-1;">
|
||||||
|
<div style="font-size:11px;font-weight:700;color:#555;text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;">Media jobs</div>
|
||||||
|
<div class="vv-arr-cards">${cards}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Settings card ─────────────────────────────────────────────────────────────
|
// ── Settings card ─────────────────────────────────────────────────────────────
|
||||||
function _settingsCard(s) {
|
function _settingsCard(s) {
|
||||||
if (!s) return '';
|
if (!s) return '';
|
||||||
@@ -440,6 +474,7 @@ function _render(data) {
|
|||||||
let html = '';
|
let html = '';
|
||||||
for (const node of nodes) html += _nodeSection(node);
|
for (const node of nodes) html += _nodeSection(node);
|
||||||
html += _syncSection(data.sync || {}, data.recovery || {}, data.settings);
|
html += _syncSection(data.sync || {}, data.recovery || {}, data.settings);
|
||||||
|
html += _mediaJobsSection(data.media_jobs);
|
||||||
html += _settingsCard(data.settings);
|
html += _settingsCard(data.settings);
|
||||||
|
|
||||||
document.getElementById('vv-arrs-grid').innerHTML = html;
|
document.getElementById('vv-arrs-grid').innerHTML = html;
|
||||||
|
|||||||
Reference in New Issue
Block a user