Arrs: remote host cards via SSH cache, vv_arrs_local_node(), 2h refresh schedule
This commit is contained in:
@@ -2,6 +2,38 @@
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
// ── Manual remote refresh — runs remote_arr_cache_writer for one host ─────────
|
||||
$_action = trim($_GET['action'] ?? '');
|
||||
if ($_action === 'refresh_remote') {
|
||||
$host = strtolower(trim($_GET['host'] ?? ''));
|
||||
if (!preg_match('/^host\d+$/', $host)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid host']); exit;
|
||||
}
|
||||
$script = SCRIPTS_DIR . '/Tools/remote_arr_cache_writer.sh';
|
||||
if (!file_exists($script)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'remote_arr_cache_writer.sh not found']); exit;
|
||||
}
|
||||
set_time_limit(30);
|
||||
$out = []; $exit = 0;
|
||||
exec('bash ' . escapeshellarg($script) . ' --host=' . escapeshellarg(strtoupper($host)) . ' 2>&1', $out, $exit);
|
||||
|
||||
$cacheFile = VV_CACHE_DIR . '/arrs_remote_' . $host . '.json';
|
||||
$node = null;
|
||||
if (file_exists($cacheFile)) {
|
||||
$node = json_decode(file_get_contents($cacheFile), true) ?: null;
|
||||
if ($node) {
|
||||
$node['cached'] = true;
|
||||
$node['cache_age'] = time() - (int)filemtime($cacheFile);
|
||||
}
|
||||
}
|
||||
// Bust the main arrs cache so next poll gets fresh data
|
||||
@unlink(VV_CACHE_DIR . '/arrs.json');
|
||||
echo json_encode(['ok' => $exit === 0, 'node' => $node, 'output' => implode("\n", $out)]);
|
||||
exit;
|
||||
}
|
||||
unset($_action);
|
||||
|
||||
// ── Normal data load ──────────────────────────────────────────────────────────
|
||||
$_vv_cached = vv_cache_read('arrs', 90);
|
||||
if ($_vv_cached !== null) { echo json_encode($_vv_cached); exit; }
|
||||
unset($_vv_cached);
|
||||
|
||||
@@ -240,32 +240,61 @@ function vv_arr_recovery_stats(): array {
|
||||
return $out;
|
||||
}
|
||||
|
||||
// ── Local node only — called via SSH by remote_arr_cache_writer.sh on remote hosts ───────────
|
||||
|
||||
function vv_arrs_local_node(): array {
|
||||
$host = vv_detect_host();
|
||||
$names = vv_arr_node_names();
|
||||
$arrs = [];
|
||||
|
||||
foreach (vv_discover_arrs() as $node) {
|
||||
if ($node['host'] !== $host) continue;
|
||||
foreach ($node['arrs'] as $arr) {
|
||||
$entry = ['type' => $arr['type'], 'root' => $arr['root']];
|
||||
$entry = array_merge($entry, vv_fetch_arr_live($arr));
|
||||
$entry['cleanup'] = vv_arr_cleanup_stats($arr['type']);
|
||||
$entry['discovery'] = vv_arr_discovery_stats($arr['type']);
|
||||
$arrs[] = $entry;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'host' => $host,
|
||||
'name' => $names[$host] ?? strtoupper($host),
|
||||
'local' => true,
|
||||
'arrs' => $arrs,
|
||||
];
|
||||
}
|
||||
|
||||
// ── Entry point ───────────────────────────────────────────────────────────────
|
||||
|
||||
function vv_arrs_all(): array {
|
||||
$currentHost = vv_detect_host();
|
||||
$names = vv_arr_node_names();
|
||||
$allNodes = vv_discover_arrs();
|
||||
$result = [];
|
||||
|
||||
foreach ($allNodes as $node) {
|
||||
$h = $node['host'];
|
||||
$isLocal = ($h === $currentHost || $currentHost === 'unknown');
|
||||
$nodeOut = ['host' => $h, 'name' => $names[$h] ?? strtoupper($h), 'local' => $isLocal, 'arrs' => []];
|
||||
// Local node — full live data
|
||||
$result = [vv_arrs_local_node()];
|
||||
|
||||
foreach ($node['arrs'] as $arr) {
|
||||
$entry = ['type' => $arr['type'], 'root' => $arr['root']];
|
||||
if ($isLocal) {
|
||||
$entry = array_merge($entry, vv_fetch_arr_live($arr));
|
||||
$entry['cleanup'] = vv_arr_cleanup_stats($arr['type']);
|
||||
$entry['discovery'] = vv_arr_discovery_stats($arr['type']);
|
||||
} else {
|
||||
$entry['online'] = false;
|
||||
$entry['remote'] = true;
|
||||
}
|
||||
$nodeOut['arrs'][] = $entry;
|
||||
// Remote nodes — read from file cache written by remote_arr_cache_writer.sh
|
||||
foreach (array_keys($names) as $h) {
|
||||
if ($h === $currentHost) continue;
|
||||
$cacheFile = VV_CACHE_DIR . '/arrs_remote_' . $h . '.json';
|
||||
if (file_exists($cacheFile)) {
|
||||
$node = json_decode(file_get_contents($cacheFile), true) ?: [];
|
||||
$node['cached'] = true;
|
||||
$node['cache_age'] = time() - (int)filemtime($cacheFile);
|
||||
$result[] = $node;
|
||||
} else {
|
||||
$result[] = [
|
||||
'host' => $h,
|
||||
'name' => $names[$h],
|
||||
'local' => false,
|
||||
'cached' => false,
|
||||
'cache_miss' => true,
|
||||
'arrs' => [],
|
||||
];
|
||||
}
|
||||
$result[] = $nodeOut;
|
||||
}
|
||||
|
||||
$vars = vv_conf_vars();
|
||||
|
||||
@@ -248,16 +248,41 @@ function _arrCard(arr) {
|
||||
}
|
||||
|
||||
// ── Node section ──────────────────────────────────────────────────────────────
|
||||
function _nodeSection(node, ownerHost) {
|
||||
function _nodeSection(node) {
|
||||
const isLocal = node.local;
|
||||
const tag = isLocal ? '<span class="vv-arr-node-tag">local</span>' : '';
|
||||
const cards = node.arrs.map(_arrCard).join('');
|
||||
const isMiss = node.cache_miss;
|
||||
|
||||
let tag, body;
|
||||
|
||||
if (isLocal) {
|
||||
tag = '<span class="vv-arr-node-tag">local</span>';
|
||||
body = `<div class="vv-arr-cards">${node.arrs.map(_arrCard).join('')}</div>`;
|
||||
} else if (isMiss) {
|
||||
tag = `<span class="vv-arr-node-tag" style="background:#111;border-color:#222;color:#333;">no data</span>
|
||||
<button id="vv-arr-rfsh-${node.host}" onclick="vvArrsRefreshRemote('${node.host}')"
|
||||
style="font-size:9px;color:#4a9eff;background:#0a1a2a;border:1px solid #1a3a5a;
|
||||
border-radius:3px;padding:1px 8px;cursor:pointer;margin-left:6px;">↻ Fetch now</button>`;
|
||||
body = `<div style="color:#333;font-size:11px;padding:10px 0;">
|
||||
No remote data yet — first fetch runs within 2 hours, or click ↻ Fetch now.
|
||||
</div>`;
|
||||
} else {
|
||||
const age = node.cache_age || 0;
|
||||
const ageStr = age < 3600 ? Math.floor(age/60) + 'm ago'
|
||||
: age < 86400 ? Math.floor(age/3600) + 'h ago'
|
||||
: Math.floor(age/86400) + 'd ago';
|
||||
tag = `<span class="vv-arr-node-tag" style="background:#1a1000;border-color:#3a2800;color:#b87;">cached ${ageStr}</span>
|
||||
<button id="vv-arr-rfsh-${node.host}" onclick="vvArrsRefreshRemote('${node.host}')"
|
||||
style="font-size:9px;color:#666;background:none;border:1px solid #2a2a2a;
|
||||
border-radius:3px;padding:1px 8px;cursor:pointer;margin-left:6px;">↻</button>`;
|
||||
body = `<div class="vv-arr-cards" style="opacity:.85;">${node.arrs.map(_arrCard).join('')}</div>`;
|
||||
}
|
||||
|
||||
return `<div class="vv-arr-node">
|
||||
<div class="vv-arr-node-hdr">
|
||||
<span class="vv-arr-node-id">${node.host.toUpperCase()}</span>
|
||||
<span class="vv-arr-node-host">${node.name}</span>${tag}
|
||||
</div>
|
||||
<div class="vv-arr-cards">${cards}</div>
|
||||
${body}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -372,7 +397,7 @@ function _render(data) {
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const node of nodes) html += _nodeSection(node, data.host);
|
||||
for (const node of nodes) html += _nodeSection(node);
|
||||
html += _syncSection(data.sync || {}, data.recovery || {}, data.settings);
|
||||
html += _settingsCard(data.settings);
|
||||
|
||||
@@ -393,6 +418,20 @@ setInterval(vvArrsLoad, 60000);
|
||||
|
||||
})();
|
||||
|
||||
// ── Remote node refresh ───────────────────────────────────────────────────────
|
||||
function vvArrsRefreshRemote(host) {
|
||||
const btn = document.getElementById('vv-arr-rfsh-' + host);
|
||||
if (btn) { btn.disabled = true; btn.textContent = '↻…'; }
|
||||
fetch(`/plugins/varaverk/api/arrs.php?action=refresh_remote&host=${host}&_=` + Date.now())
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (btn) { btn.disabled = false; btn.textContent = d.ok ? '↻' : '✗'; }
|
||||
// Main cache was busted — reload to show fresh data
|
||||
setTimeout(vvArrsLoad, 500);
|
||||
})
|
||||
.catch(() => { if (btn) { btn.disabled = false; btn.textContent = '↻'; } });
|
||||
}
|
||||
|
||||
// ── Toggle handler ────────────────────────────────────────────────────────────
|
||||
function vvArrToggle(track, key, file) {
|
||||
const on = !track.classList.contains('on');
|
||||
|
||||
Reference in New Issue
Block a user