Auth stack certs tab, arrs db fallbacks, cert monitor cache, conf parser fix
- Auth stack: fold cert monitor into Auth Stack page as fourth tab (Certs); remove standalone cert page and top-level tab - cert_monitor.sh: write JSON status cache to State_Files/cert_status.json after each run; expose per-domain days/expiry via _CERT_DAYS/_CERT_EXPIRY globals - api/cert.php: new — serves cached cert status; falls back to configured domains as UNKN when no cache exists; POST action=run triggers live check - arrs db fallbacks: vv_arr_cleanup_stats/discovery_stats/recovery_stats now read from data/*.db files when log JSON files don't yet exist - config.php vv_conf_vars(): unescape bash \$ → $ so passwords with dollar signs read correctly from conf files - host1.conf: fill in HOST1_NPM_USER/PASS and HOST1_LLDAP_USER/PASS - Partnership adapter pattern: Unraid-specific container logic extracted to Plugin/unraid/Partnership/; platform-agnostic structure stays in Partnership/ - First-run wizard: uniform multi-step flow for all hosts; HOST2 pull moved to checklist; auto SSH keygen and API key creation on save - api/checklist.php: live setup checklist with pull_master action - Fullscreen toggle: hide Unraid header/menu; state persists via localStorage
This commit is contained in:
+103
-37
@@ -131,29 +131,51 @@ function vv_arr_cleanup_stats(string $type): array {
|
||||
'orphans' => 0, 'orphans_sz' => '0B', 'junk' => 0];
|
||||
|
||||
$jf = $base . '.json';
|
||||
if (!file_exists($jf)) return $out;
|
||||
$meta = json_decode(file_get_contents($jf), true) ?: [];
|
||||
$out['last_run'] = $meta['start'] ?? null;
|
||||
$out['end'] = $meta['end'] ?? null;
|
||||
$out['status'] = $meta['status'] ?? null;
|
||||
if (file_exists($jf)) {
|
||||
$meta = json_decode(file_get_contents($jf), true) ?: [];
|
||||
$out['last_run'] = $meta['start'] ?? null;
|
||||
$out['end'] = $meta['end'] ?? null;
|
||||
$out['status'] = $meta['status'] ?? null;
|
||||
|
||||
$lf = $base . '.log';
|
||||
if (!file_exists($lf)) return $out;
|
||||
$log = file_get_contents($lf);
|
||||
$parts = preg_split('/━{3,}[^\n]*SUMMARY[^\n]*/u', $log);
|
||||
$blk = count($parts) > 1 ? end($parts) : $log;
|
||||
$lf = $base . '.log';
|
||||
if (file_exists($lf)) {
|
||||
$log = file_get_contents($lf);
|
||||
$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)) {
|
||||
$out['tracked'] = (int)str_replace(',', '', $m[1]);
|
||||
$out['total'] = (int)str_replace(',', '', $m[2]);
|
||||
if (preg_match('/Tracked:\s*([\d,]+)\s*files\s*\(([\d,]+)/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]);
|
||||
$out['orphans_sz'] = trim($m[2]);
|
||||
}
|
||||
if (preg_match('/Junk:\s*([\d,]+)\s*files/u', $blk, $m)) {
|
||||
$out['junk'] = (int)str_replace(',', '', $m[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (preg_match('/Orphans:\s*([\d,]+)\s*files\s*\(([^)]+)\)/u', $blk, $m)) {
|
||||
$out['orphans'] = (int)str_replace(',', '', $m[1]);
|
||||
$out['orphans_sz'] = trim($m[2]);
|
||||
}
|
||||
if (preg_match('/Junk:\s*([\d,]+)\s*files/u', $blk, $m)) {
|
||||
$out['junk'] = (int)str_replace(',', '', $m[1]);
|
||||
|
||||
// Fallback: daily aggregate db — date|arr|orphan_count|orphan_bytes|junk_count|junk_bytes|recent_count|tracked_count
|
||||
if ($out['last_run'] === null) {
|
||||
$dbFile = DATA_DIR . '/arr_cleanup_stats.db';
|
||||
if (file_exists($dbFile)) {
|
||||
$last = null;
|
||||
foreach (file($dbFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
||||
$p = explode('|', $line);
|
||||
if (count($p) >= 8 && $p[1] === $type) $last = $p;
|
||||
}
|
||||
if ($last) {
|
||||
$out['last_run'] = strtotime($last[0] . ' 23:59:00') ?: null;
|
||||
$out['status'] = 'ok';
|
||||
$out['orphans'] = (int)$last[2];
|
||||
$out['junk'] = (int)$last[4];
|
||||
$out['tracked'] = (int)$last[7];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
@@ -165,17 +187,39 @@ function vv_arr_discovery_stats(string $type): array {
|
||||
$out = ['last_run' => null, 'status' => null, 'added' => null];
|
||||
|
||||
$jf = $base . '.json';
|
||||
if (!file_exists($jf)) return $out;
|
||||
$meta = json_decode(file_get_contents($jf), true) ?: [];
|
||||
$out['last_run'] = $meta['start'] ?? null;
|
||||
$out['status'] = $meta['status'] ?? null;
|
||||
if (file_exists($jf)) {
|
||||
$meta = json_decode(file_get_contents($jf), true) ?: [];
|
||||
$out['last_run'] = $meta['start'] ?? null;
|
||||
$out['status'] = $meta['status'] ?? null;
|
||||
|
||||
$lf = $base . '.log';
|
||||
if (file_exists($lf)) {
|
||||
$log = file_get_contents($lf);
|
||||
if (preg_match('/Added[:\s]+(\d+)/i', $log, $m)) $out['added'] = (int)$m[1];
|
||||
elseif (preg_match('/(\d+)\s+added/i', $log, $m)) $out['added'] = (int)$m[1];
|
||||
$lf = $base . '.log';
|
||||
if (file_exists($lf)) {
|
||||
$log = file_get_contents($lf);
|
||||
if (preg_match('/Added[:\s]+(\d+)/i', $log, $m)) $out['added'] = (int)$m[1];
|
||||
elseif (preg_match('/(\d+)\s+added/i', $log, $m)) $out['added'] = (int)$m[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: per-title history db — status|id|date[|title]
|
||||
if ($out['last_run'] === null) {
|
||||
$dbFile = DATA_DIR . '/' . $type . '_discovery_history.db';
|
||||
if (file_exists($dbFile)) {
|
||||
$lastDate = null; $added = 0;
|
||||
foreach (file($dbFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
||||
$p = explode('|', $line);
|
||||
if (count($p) < 3) continue;
|
||||
$date = $p[2];
|
||||
if ($date !== $lastDate) { $lastDate = $date; $added = 0; }
|
||||
if ($p[0] === 'ACCEPT') $added++;
|
||||
}
|
||||
if ($lastDate) {
|
||||
$out['last_run'] = strtotime($lastDate . ' 23:59:00') ?: null;
|
||||
$out['status'] = 'ok';
|
||||
$out['added'] = $added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
@@ -214,17 +258,39 @@ function vv_arr_recovery_stats(): array {
|
||||
$out = ['last_run' => null, 'status' => null, 'fixed' => 0, 'searched' => 0];
|
||||
|
||||
$jf = $base . '.json';
|
||||
if (!file_exists($jf)) return $out;
|
||||
$meta = json_decode(file_get_contents($jf), true) ?: [];
|
||||
$out['last_run'] = $meta['start'] ?? null;
|
||||
$out['status'] = $meta['status'] ?? null;
|
||||
if (file_exists($jf)) {
|
||||
$meta = json_decode(file_get_contents($jf), true) ?: [];
|
||||
$out['last_run'] = $meta['start'] ?? null;
|
||||
$out['status'] = $meta['status'] ?? null;
|
||||
|
||||
$lf = $base . '.log';
|
||||
if (file_exists($lf)) {
|
||||
$log = file_get_contents($lf);
|
||||
if (preg_match('/Removed[:\s]+(\d+)/i', $log, $m)) $out['fixed'] = (int)$m[1];
|
||||
if (preg_match('/Re-searched[:\s]+(\d+)/i',$log, $m)) $out['searched'] = (int)$m[1];
|
||||
$lf = $base . '.log';
|
||||
if (file_exists($lf)) {
|
||||
$log = file_get_contents($lf);
|
||||
if (preg_match('/Removed[:\s]+(\d+)/i', $log, $m)) $out['fixed'] = (int)$m[1];
|
||||
if (preg_match('/Re-searched[:\s]+(\d+)/i',$log, $m)) $out['searched'] = (int)$m[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: daily aggregate db — date|time|count|bytes
|
||||
if ($out['last_run'] === null) {
|
||||
$dbFile = DATA_DIR . '/arr_recovery_stats.db';
|
||||
if (file_exists($dbFile)) {
|
||||
$lines = file($dbFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$last = $lines ? end($lines) : null;
|
||||
if ($last) {
|
||||
$p = explode('|', $last);
|
||||
if (count($p) >= 3) {
|
||||
$ts = strtotime(($p[0] ?? '') . ' ' . ($p[1] ?? '00:00')) ?: null;
|
||||
if ($ts) {
|
||||
$out['last_run'] = $ts;
|
||||
$out['status'] = 'ok';
|
||||
$out['fixed'] = (int)($p[2] ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ function vv_cpu_per_core(): array {
|
||||
$raw[$m[1]] = [(int)$m[2],(int)$m[3],(int)$m[4],(int)$m[5],(int)$m[6],(int)$m[7],(int)$m[8]];
|
||||
}
|
||||
|
||||
$stateFile = '/tmp/vv_cpu_stat.json';
|
||||
$stateFile = VV_CACHE_DIR . '/vv_cpu_stat.json';
|
||||
$prev = file_exists($stateFile) ? (json_decode(file_get_contents($stateFile), true) ?: []) : [];
|
||||
// Atomic write — concurrent fast/slow polls read a consistent snapshot
|
||||
$tmp = $stateFile . '.tmp';
|
||||
@@ -292,7 +292,7 @@ function vv_network_stats(): array {
|
||||
break;
|
||||
}
|
||||
|
||||
$stateFile = '/tmp/vv_net_stat.json';
|
||||
$stateFile = VV_CACHE_DIR . '/vv_net_stat.json';
|
||||
$now = ['rx' => $rxBytes, 'tx' => $txBytes, 'ts' => microtime(true)];
|
||||
$prev = file_exists($stateFile) ? (json_decode(file_get_contents($stateFile), true) ?: []) : [];
|
||||
$tmp = $stateFile . '.tmp';
|
||||
@@ -536,7 +536,7 @@ function vv_array_disks(): array {
|
||||
}
|
||||
|
||||
function vv_disk_io_rates(): array {
|
||||
$snapFile = '/tmp/vv_diskio_snap.json';
|
||||
$snapFile = VV_CACHE_DIR . '/vv_diskio_snap.json';
|
||||
$now = microtime(true);
|
||||
|
||||
// Read current whole-disk stats from /proc/diskstats
|
||||
@@ -631,7 +631,7 @@ function vv_remote_hosts_stats(): array {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cacheFile = "/tmp/vv_remote_{$id}.json";
|
||||
$cacheFile = VV_CACHE_DIR . "/vv_remote_{$id}.json";
|
||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 30) {
|
||||
$cached = json_decode(file_get_contents($cacheFile), true);
|
||||
if ($cached) { $results[$id] = $cached; continue; }
|
||||
@@ -727,7 +727,7 @@ function vv_parse_bash_array(string $raw, string $varName): array {
|
||||
|
||||
function vv_transcode_sessions(): array {
|
||||
$v = vv_conf_vars();
|
||||
$stateDir = rtrim($v['STATE_DIR'] ?? '/boot/config/plugins/varaverk/State_Files', '/');
|
||||
$stateDir = rtrim($v['STATE_DIR'] ?? STATE_DIR, '/');
|
||||
$stateFile = "$stateDir/transcode_state.db";
|
||||
if (!file_exists($stateFile)) return ['available' => false];
|
||||
|
||||
@@ -773,7 +773,7 @@ function vv_transcode_sessions(): array {
|
||||
// Last cleanup values from transcode management log
|
||||
$lastRdFreed = null;
|
||||
$lastSsdFreed = null;
|
||||
$logFile = '/var/log/varaverk/Orchestrators/transcode_management.log';
|
||||
$logFile = LOG_DIR . '/Orchestrators/transcode_management.log';
|
||||
if (file_exists($logFile)) {
|
||||
$lines = file($logFile, FILE_IGNORE_NEW_LINES) ?: [];
|
||||
foreach (array_reverse($lines) as $line) {
|
||||
|
||||
@@ -24,7 +24,7 @@ const VV_SCRIPT_CONF_SECTIONS = [
|
||||
'Watchdogs/docker_watchdog.sh' => ['Docker Watchdog'],
|
||||
'Watchdogs/resource_watchdog.sh' => ['Pressure Levels'],
|
||||
'Watchdogs/System/network_watchdog.sh' => ['Network Watchdog'],
|
||||
'Watchdogs/System/webgui_watchdog.sh' => ['WebGUI Watchdog'],
|
||||
'Plugin/unraid/Watchdogs/System/webgui_watchdog.sh' => ['WebGUI Watchdog'],
|
||||
// Media
|
||||
'Media/media_cleaner.sh' => ['Media Cleaner'],
|
||||
'Media/media_shares_permissions.sh' => ['Media Permissions'],
|
||||
|
||||
@@ -13,6 +13,7 @@ define('LOG_DIR', '/var/log/varaverk');
|
||||
unset($_vv_cfg);
|
||||
|
||||
define('VV_SETUP_STATE_FILE', STATE_DIR . '/varaverk_setup.db');
|
||||
define('VV_CACHE_DIR', '/tmp/vv_cache');
|
||||
|
||||
// Read the setup state file into a key=>value array.
|
||||
function vv_setup_state_read(): array {
|
||||
@@ -202,7 +203,7 @@ function vv_conf_vars(): array {
|
||||
// Match: VAR_NAME="value" or VAR_NAME=value (no quotes)
|
||||
preg_match_all('/^\s*([A-Z0-9_]+)\s*=\s*["\']?([^"\'#\n]*?)["\']?\s*(?:#.*)?$/m', $raw, $m);
|
||||
foreach ($m[1] as $i => $key) {
|
||||
$vars[$key] = trim($m[2][$i]);
|
||||
$vars[$key] = str_replace('\\$', '$', trim($m[2][$i]));
|
||||
}
|
||||
}
|
||||
return $vars;
|
||||
@@ -263,7 +264,7 @@ function vv_unraid_api_query(string $hostId, string $gql, int $timeoutSec = 5, s
|
||||
}
|
||||
|
||||
if ($resp === false || $resp === '' || ($httpCode !== 0 && $httpCode !== 200)) {
|
||||
@file_put_contents('/tmp/vv_api_debug.json', json_encode([
|
||||
@file_put_contents(VV_CACHE_DIR . '/vv_api_debug.json', json_encode([
|
||||
'ts' => time(),
|
||||
'host' => $hostId,
|
||||
'url' => $url,
|
||||
@@ -278,7 +279,7 @@ function vv_unraid_api_query(string $hostId, string $gql, int $timeoutSec = 5, s
|
||||
|
||||
// If the API returned GraphQL errors, log them for diagnosis.
|
||||
if (!empty($decoded['errors'])) {
|
||||
@file_put_contents('/tmp/vv_api_debug.json', json_encode([
|
||||
@file_put_contents(VV_CACHE_DIR . '/vv_api_debug.json', json_encode([
|
||||
'ts' => time(),
|
||||
'host' => $hostId,
|
||||
'url' => $url,
|
||||
@@ -294,8 +295,6 @@ function vv_unraid_api_query(string $hostId, string $gql, int $timeoutSec = 5, s
|
||||
|
||||
// ── File-based API cache (/tmp/vv_cache — tmpfs, cleared on reboot) ───────────
|
||||
|
||||
define('VV_CACHE_DIR', '/tmp/vv_cache');
|
||||
|
||||
// Read a cached payload. Returns null if missing or older than $maxAge seconds.
|
||||
function vv_cache_read(string $key, int $maxAge = 90): ?array {
|
||||
$f = VV_CACHE_DIR . '/' . $key . '.json';
|
||||
@@ -358,6 +357,38 @@ function vv_known_hosts(): array {
|
||||
return $hosts ?: ['host1' => 'HOST1'];
|
||||
}
|
||||
|
||||
// Create (or overwrite) the Varaverk Unraid API key and write it into host conf.
|
||||
// Returns ['ok'=>true,'key_preview'=>'...'] or ['ok'=>false,'error'=>'...'].
|
||||
function vv_auto_create_api_key(string $hostId, string $confFile): array {
|
||||
$varName = strtoupper($hostId) . '_UNRAID_API_KEY';
|
||||
$output = shell_exec('timeout 10 /usr/local/sbin/unraid-api apikey --name "Varaverk" --create --overwrite --description "Varaverk plugin" --roles ADMIN --json </dev/null 2>&1');
|
||||
if (!$output) {
|
||||
return ['ok' => false, 'error' => 'unraid-api returned no output'];
|
||||
}
|
||||
$data = json_decode(trim($output), true);
|
||||
$key = $data['key'] ?? null;
|
||||
if (!$key) {
|
||||
return ['ok' => false, 'error' => 'No key in response'];
|
||||
}
|
||||
$raw = vv_read_conf_raw($confFile);
|
||||
if ($raw === '') {
|
||||
return ['ok' => false, 'error' => 'Cannot read ' . $confFile];
|
||||
}
|
||||
if (!str_contains($raw, $varName)) {
|
||||
foreach ([strtoupper($hostId) . '_OWNER_EMAIL', strtoupper($hostId) . '_SSH_KEY'] as $anchor) {
|
||||
if (str_contains($raw, $anchor)) {
|
||||
$raw = preg_replace('/^(\s*' . preg_quote($anchor, '/') . '\s*=.*$)/m',
|
||||
'$1' . "\n " . $varName . '=""', $raw, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$raw = preg_replace('/^(\s*' . preg_quote($varName, '/') . '\s*=\s*)"[^"]*"/m',
|
||||
'${1}"' . $key . '"', $raw);
|
||||
vv_write_conf_raw($confFile, $raw);
|
||||
return ['ok' => true, 'key_preview' => substr($key, 0, 8) . '...' . substr($key, -4)];
|
||||
}
|
||||
|
||||
// Local LAN IP via routing table — static-cached per request.
|
||||
// Previously duplicated in include/docker_folders.php and inline in include/docker.php.
|
||||
function vv_local_ip(): string {
|
||||
|
||||
@@ -149,7 +149,7 @@ function vv_script_suggested_cron(string $path): array {
|
||||
// Parse user_script_plug-in.sh into an array of script blocks.
|
||||
// Each block: title, schedule, desc (array of lines), scripts (array of {rel, cron})
|
||||
function vv_parse_user_script_template(): array {
|
||||
$file = SCRIPTS_DIR . '/user_script_plug-in.sh';
|
||||
$file = SCRIPTS_DIR . '/Plugin/unraid/user_script_plug-in.sh';
|
||||
if (!file_exists($file)) return [];
|
||||
$lines = file($file, FILE_IGNORE_NEW_LINES);
|
||||
$prefix = rtrim(SCRIPTS_DIR, '/') . '/';
|
||||
@@ -231,41 +231,69 @@ function vv_script_description(string $path): string {
|
||||
}
|
||||
|
||||
function vv_tools_scripts(): array {
|
||||
$dir = SCRIPTS_DIR . '/Tools';
|
||||
// Background writers managed automatically — not user-facing tools
|
||||
static $EXCLUDE = ['api_cache_writer.sh', 'remote_arr_cache_writer.sh'];
|
||||
|
||||
$schedule = vv_schedule_load();
|
||||
$scripts = [];
|
||||
foreach (glob("$dir/*.sh") ?: [] as $path) {
|
||||
$rel = 'Tools/' . basename($path);
|
||||
$entry = $schedule[$rel] ?? [];
|
||||
$scripts[] = [
|
||||
'id' => $rel,
|
||||
'label' => vv_pretty_label(basename($path, '.sh')),
|
||||
'desc' => vv_script_description($path),
|
||||
'enabled' => (bool)($entry['enabled'] ?? false),
|
||||
'cron' => $entry['cron'] ?? '',
|
||||
'log_enabled' => (bool)($entry['log_enabled'] ?? false),
|
||||
];
|
||||
|
||||
$collect = function(string $dir, string $relPrefix) use ($schedule, $EXCLUDE, &$scripts): void {
|
||||
foreach (glob("$dir/*.sh") ?: [] as $path) {
|
||||
$base = basename($path);
|
||||
if (in_array($base, $EXCLUDE, true)) continue;
|
||||
$rel = $relPrefix . $base;
|
||||
$entry = $schedule[$rel] ?? [];
|
||||
$scripts[] = [
|
||||
'id' => $rel,
|
||||
'label' => vv_pretty_label(basename($path, '.sh')),
|
||||
'desc' => vv_script_description($path),
|
||||
'enabled' => (bool)($entry['enabled'] ?? false),
|
||||
'cron' => $entry['cron'] ?? '',
|
||||
'log_enabled' => (bool)($entry['log_enabled'] ?? false),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// General tools
|
||||
$collect(SCRIPTS_DIR . '/Tools', 'Tools/');
|
||||
|
||||
// Platform adapter tools (Plugin/<platform>/Tools/)
|
||||
foreach (glob(SCRIPTS_DIR . '/Plugin/*/Tools') ?: [] as $toolsDir) {
|
||||
$platform = basename(dirname($toolsDir));
|
||||
$collect($toolsDir, "Plugin/$platform/Tools/");
|
||||
}
|
||||
|
||||
usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
function vv_custom_scripts(): array {
|
||||
$dir = SCRIPTS_DIR . '/Custom';
|
||||
$schedule = vv_schedule_load();
|
||||
$scripts = [];
|
||||
foreach (glob("$dir/*.sh") ?: [] as $path) {
|
||||
$rel = 'Custom/' . basename($path);
|
||||
$entry = $schedule[$rel] ?? [];
|
||||
$scripts[] = [
|
||||
'id' => $rel,
|
||||
'label' => vv_pretty_label(basename($path, '.sh')),
|
||||
'desc' => vv_script_description($path),
|
||||
'enabled' => (bool)($entry['enabled'] ?? false),
|
||||
'cron' => $entry['cron'] ?? '',
|
||||
'log_enabled' => (bool)($entry['log_enabled'] ?? false),
|
||||
];
|
||||
|
||||
$collect = function(string $dir, string $relPrefix) use ($schedule, &$scripts): void {
|
||||
foreach (glob("$dir/*.sh") ?: [] as $path) {
|
||||
$rel = $relPrefix . basename($path);
|
||||
$entry = $schedule[$rel] ?? [];
|
||||
$scripts[] = [
|
||||
'id' => $rel,
|
||||
'label' => vv_pretty_label(basename($path, '.sh')),
|
||||
'desc' => vv_script_description($path),
|
||||
'enabled' => (bool)($entry['enabled'] ?? false),
|
||||
'cron' => $entry['cron'] ?? '',
|
||||
'log_enabled' => (bool)($entry['log_enabled'] ?? false),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
$collect(SCRIPTS_DIR . '/Custom', 'Custom/');
|
||||
|
||||
// Platform adapter custom scripts (Plugin/<platform>/Custom/)
|
||||
foreach (glob(SCRIPTS_DIR . '/Plugin/*/Custom') ?: [] as $customDir) {
|
||||
$platform = basename(dirname($customDir));
|
||||
$collect($customDir, "Plugin/$platform/Custom/");
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
@@ -287,7 +315,7 @@ function vv_orch_conf_arrays(string $orchPath): array {
|
||||
}
|
||||
|
||||
// Return .sh scripts that exist in SCRIPTS_DIR but are not referenced in any
|
||||
// master.conf *_SCRIPTS array and are not orchestrators or custom scripts.
|
||||
// master.conf *_SCRIPTS array and are not shown in any other scheduler card.
|
||||
function vv_script_library(): array {
|
||||
$scriptsDir = SCRIPTS_DIR;
|
||||
$confMap = vv_conf_script_map();
|
||||
@@ -295,7 +323,17 @@ function vv_script_library(): array {
|
||||
foreach (glob("$scriptsDir/Orchestrators/*.sh") ?: [] as $p) {
|
||||
$orchIds[] = 'Orchestrators/' . basename($p);
|
||||
}
|
||||
$exclude = ['Plugin', '.git', 'Orchestrators', 'Custom', 'Configurations'];
|
||||
// Scripts already shown in their own cards are not "unlisted"
|
||||
$schedule = vv_schedule_load();
|
||||
$cardIds = array_flip(array_merge(
|
||||
array_column(vv_tools_scripts(), 'id'),
|
||||
array_column(vv_custom_scripts(), 'id')
|
||||
));
|
||||
|
||||
// UI-only subdirs under Plugin/<platform>/ — no runnable scripts
|
||||
$pluginUiDirs = ['api', 'include', 'pages', 'css', 'js', 'icons', 'event'];
|
||||
|
||||
$exclude = ['.git', 'Orchestrators', 'Custom', 'Configurations'];
|
||||
$library = [];
|
||||
try {
|
||||
$ri = new RecursiveIteratorIterator(
|
||||
@@ -306,8 +344,16 @@ function vv_script_library(): array {
|
||||
if (!$rf->isFile() || strtolower($rf->getExtension()) !== 'sh') continue;
|
||||
$rel = ltrim(str_replace($base, '', $rf->getPathname()), '/');
|
||||
$parts = explode('/', $rel);
|
||||
if (count($parts) < 2 || in_array($parts[0], $exclude)) continue;
|
||||
if (in_array($rel, $orchIds) || isset($confMap[$rel])) continue;
|
||||
if (in_array($parts[0], $exclude)) continue;
|
||||
if ($parts[0] === 'Plugin') {
|
||||
// Require Plugin/<platform>/<category>/<script>.sh — skip root-level adapter files
|
||||
if (count($parts) < 4) continue;
|
||||
// Skip UI-only category dirs
|
||||
if (in_array($parts[2], $pluginUiDirs)) continue;
|
||||
} elseif (count($parts) < 2) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($rel, $orchIds) || isset($confMap[$rel]) || isset($cardIds[$rel]) || isset($schedule[$rel])) continue;
|
||||
$library[] = ['id' => $rel, 'label' => vv_pretty_label(basename($rel, '.sh'))];
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
|
||||
Reference in New Issue
Block a user