Scheduler UI: - Arrange mode: drag scripts between orchs and reorder within arrays; right panel shows unassigned script pool; Save Arrangement commits to master.conf - + Folder: named collapsible subfolders for Custom Scripts stored in schedule.json - Rsync children: hide Run/Dry Run/Log/location when orch is ON; show standalone location + cron controls when orch is OFF; cron only fires when both filled - Non-conf-managed children (transcode): toggles now show enabled when orch is on - Right panel height sync: fix ResizeObserver feedback loop via align-self:flex-start on left panel and left.offsetHeight in vvFitRight - How do I use this: updated to cover arrange, folders, rsync standalone, transcode New API endpoints: - board.php, clearlock.php, movescript.php, rawconf.php, readscript.php - reorderarray.php, rsync_standalone.php, savefolders.php run.php / dryrun.php: accept optional --location= arg for standalone rsync calls
89 lines
3.9 KiB
PHP
89 lines
3.9 KiB
PHP
<?php
|
|
// Live board data: locks, recent errors, partner reachability.
|
|
header('Content-Type: application/json');
|
|
require_once dirname(__DIR__) . '/include/config.php';
|
|
|
|
$out = ['ok' => true];
|
|
|
|
// ── Active locks ──────────────────────────────────────────────────────────
|
|
$lockDir = '/tmp/unraid_locks';
|
|
$locks = [];
|
|
if (is_dir($lockDir)) {
|
|
foreach (glob($lockDir . '/*.lock') ?: [] as $lf) {
|
|
$name = basename($lf, '.lock');
|
|
$age = time() - (int)filemtime($lf);
|
|
$content = trim(file_get_contents($lf) ?: '');
|
|
// content is "PID:scriptname" — extract PID
|
|
$pid = preg_match('/^(\d+)/', $content, $pm) ? $pm[1] : '';
|
|
// Skip if PID is still alive (it's legitimately running)
|
|
if ($pid && file_exists("/proc/$pid")) continue;
|
|
$locks[] = ['name' => $name, 'file' => basename($lf), 'age' => $age];
|
|
}
|
|
}
|
|
$out['locks'] = $locks;
|
|
|
|
// ── Recent errors ──────────────────────────────────────────────────────────
|
|
$errors = [];
|
|
if (is_dir(LOG_DIR)) {
|
|
$cutoff = time() - 7 * 86400; // only logs touched in last 7 days
|
|
foreach (glob(LOG_DIR . '/*.log') ?: [] as $lf) {
|
|
if (filemtime($lf) < $cutoff) continue;
|
|
$script = basename($lf, '.log');
|
|
$lines = array_slice(@file($lf) ?: [], -200);
|
|
$lastErr = null;
|
|
foreach (array_reverse($lines) as $raw) {
|
|
// Strip ANSI escape codes
|
|
$clean = preg_replace('/\033\[[0-9;]*[mK]/', '', rtrim($raw));
|
|
if (!$clean) continue;
|
|
if (preg_match('/\[(?:ERROR|WARN|CRITICAL|FAILED)\]/i', $clean) ||
|
|
preg_match('/\b(?:ERROR|CRITICAL|FAILED):\s/i', $clean) ||
|
|
str_contains($clean, '✗') ||
|
|
(str_contains($clean, '⚠') && !str_contains($clean, '♥'))) {
|
|
$lastErr = mb_substr($clean, 0, 220);
|
|
break;
|
|
}
|
|
}
|
|
if ($lastErr !== null) {
|
|
$errors[] = ['script' => $script, 'line' => $lastErr, 'ts' => (int)filemtime($lf)];
|
|
}
|
|
}
|
|
usort($errors, fn($a, $b) => $b['ts'] - $a['ts']);
|
|
}
|
|
$out['errors'] = array_slice($errors, 0, 20);
|
|
|
|
// ── Partner reachability ───────────────────────────────────────────────────
|
|
$cacheFile = '/tmp/vv_partner_cache.json';
|
|
$cacheTtl = 30;
|
|
$partnerData = null;
|
|
|
|
if (file_exists($cacheFile) && (time() - (int)filemtime($cacheFile)) < $cacheTtl) {
|
|
$partnerData = json_decode(file_get_contents($cacheFile), true);
|
|
} else {
|
|
$confRaw = vv_read_conf_raw('master.conf');
|
|
preg_match('/^\s*HOST1(?:_NAME)?\s*=\s*["\']?(\S+?)["\']?\s*(?:#.*)?$/m', $confRaw, $m1);
|
|
preg_match('/^\s*HOST2(?:_NAME)?\s*=\s*["\']?(\S+?)["\']?\s*(?:#.*)?$/m', $confRaw, $m2);
|
|
$host1 = $m1[1] ?? '';
|
|
$host2 = $m2[1] ?? '';
|
|
$mine = vv_get_hostname();
|
|
$partnerHost = null;
|
|
if ($mine && $host1 && strcasecmp($mine, $host1) === 0) $partnerHost = $host2;
|
|
if ($mine && $host2 && strcasecmp($mine, $host2) === 0) $partnerHost = $host1;
|
|
|
|
if ($partnerHost) {
|
|
$start = microtime(true);
|
|
$result = shell_exec('ping -c1 -W1 ' . escapeshellarg($partnerHost) . ' 2>&1');
|
|
$elapsed = (int)round((microtime(true) - $start) * 1000);
|
|
$reached = str_contains((string)$result, '1 received')
|
|
|| str_contains((string)$result, '1 packets received');
|
|
$partnerData = [
|
|
'host' => $partnerHost,
|
|
'reachable' => $reached,
|
|
'latency' => $reached ? $elapsed : null,
|
|
];
|
|
@file_put_contents($cacheFile, json_encode($partnerData));
|
|
}
|
|
}
|
|
$out['partner'] = $partnerData;
|
|
|
|
echo json_encode($out);
|