diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css b/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css index 6c915fd..6e69f36 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css +++ b/Plugin/usr/local/emhttp/plugins/varaverk/css/varaverk.css @@ -103,6 +103,33 @@ .vv-dry-btn:hover { background: #e65100 !important; color: #fff !important; border-color: #ff9800 !important; } +/* Suggestions panel — accordion */ +#vv-suggestions { overflow-y: auto; } +.vv-sug-block { border-bottom: 1px solid #1e1e1e; } +.vv-sug-header { display: flex; align-items: center; gap: 8px; padding: 7px 4px; + cursor: pointer; user-select: none; flex-wrap: wrap; } +.vv-sug-header:hover { background: rgba(255,255,255,0.03); } +.vv-sug-chevron { color: #555; font-size: 11px; flex-shrink: 0; width: 10px; } +.vv-sug-title { color: #7a9eb5; font-weight: bold; font-size: 13px; flex: 1; min-width: 120px; } +.vv-sug-cron { color: #ddd; background: #111; border: 1px solid #333; padding: 1px 6px; + border-radius: 3px; font-size: 11px; font-family: monospace; white-space: nowrap; flex-shrink: 0; } +.vv-sug-label { color: #666; font-size: 12px; flex: 1; min-width: 0; white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; } +.vv-sug-status { font-size: 11px; flex-shrink: 0; margin-left: auto; } +.vv-sug-on { color: #4caf50; } +.vv-sug-off { color: #777; } +.vv-sug-none { color: #444; } +.vv-sug-body { padding: 0 14px 10px; } +.vv-sug-desc { font-size: 12px; color: #888; white-space: pre-wrap; word-break: break-word; + background: none; border: none; margin: 4px 0 8px; padding: 0; + font-family: inherit; line-height: 1.6; } +.vv-sug-scripts { display: flex; flex-direction: column; gap: 4px; } +.vv-sug-script-row { display: flex; align-items: center; gap: 8px; font-size: 12px; } +.vv-sug-inline-cron { color: #aaa; background: #111; padding: 1px 5px; border-radius: 3px; + font-size: 11px; white-space: nowrap; } +.vv-sug-path { color: #888; font-family: monospace; } +.vv-sug-configured { color: #4caf50; font-size: 11px; } + /* Log panel */ .vv-log-panel { margin-top: 10px; border-top: 1px solid #333; padding-top: 8px; } .vv-log-toolbar { display: flex; justify-content: space-between; align-items: center; diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php b/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php index 1d97ce6..91d525c 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php @@ -66,6 +66,78 @@ function vv_cron_rebuild(array $schedule): bool { return file_put_contents(CRON_FILE, implode("\n", $lines)) !== false; } +// Extract the suggested cron and label from a bash script header. +// Looks for: # Schedule: */7 * * * * (every 7 minutes via User Scripts plugin) +function vv_script_suggested_cron(string $path): array { + if (!file_exists($path)) return ['cron' => '', 'label' => '']; + $lines = array_slice(file($path) ?: [], 0, 30); + foreach ($lines as $raw) { + $raw = rtrim($raw); + if (!preg_match('/^#\s*Schedule:\s*(.+)$/i', $raw, $m)) continue; + $tail = trim($m[1]); + $parts = preg_split('/\s+/', $tail, 6); + $cron = implode(' ', array_slice($parts, 0, 5)); + $label = isset($parts[5]) ? trim($parts[5], '() ') : ''; + return ['cron' => $cron, 'label' => $label]; + } + return ['cron' => '', 'label' => '']; +} + +// 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'; + if (!file_exists($file)) return []; + $lines = file($file, FILE_IGNORE_NEW_LINES); + $prefix = rtrim(SCRIPTS_DIR, '/') . '/'; + $blocks = []; + $cur = null; + + foreach ($lines as $line) { + // Block header: # ── TITLE ──... + if (preg_match('/^# ── (.+?) ─/', $line, $m)) { + if ($cur) $blocks[] = $cur; + $cur = ['title' => trim($m[1]), 'schedule' => '', 'desc' => [], 'scripts' => []]; + continue; + } + if (!$cur) continue; + + // Schedule / Background — captured but not added to desc + if (preg_match('/^# (Schedule|Background):\s+(.+)$/i', $line, $m)) { + if (strtolower($m[1]) === 'schedule') $cur['schedule'] = trim($m[2]); + continue; + } + + // Sunday block inline cron: # 0 6 * * 0 bash /path/script.sh [args] + if (preg_match('/^#\s+(\S+ +\S+ +\S+ +\S+ +\S+)\s+bash\s+(\S+\.sh)/', $line, $m)) { + $rel = str_replace($prefix, '', trim($m[2])); + $cur['scripts'][] = ['rel' => $rel, 'cron' => preg_replace('/\s+/', ' ', trim($m[1]))]; + continue; + } + + // Standard bash line: # bash /prefix/path/script.sh [args] + if (preg_match('/^# bash\s+(\S+\.sh)/', $line, $m)) { + $rel = str_replace($prefix, '', $m[1]); + // Only add primary command (dedupe by rel) + $rels = array_column($cur['scripts'], 'rel'); + if (!in_array($rel, $rels)) { + $cur['scripts'][] = ['rel' => $rel, 'cron' => '']; + } + continue; + } + + // Description line + if (preg_match('/^# ?(.*)$/', $line, $m)) { + $inner = $m[1]; + if (!preg_match('/^[─━=\-]{3,}\s*$/', $inner) && !preg_match('/^█/', $inner)) { + $cur['desc'][] = $inner; + } + } + } + if ($cur) $blocks[] = $cur; + return $blocks; +} + // Extract a one-line description from a bash script header. // Supports two patterns: // 1. # PURPOSE (or # DESCRIPTION) block — returns first non-separator line after it @@ -109,15 +181,18 @@ function vv_job_tree(): array { foreach (glob($orchPattern) ?: [] as $path) { $id = 'Orchestrators/' . basename($path); $entry = $schedule[$id] ?? ['enabled' => false, 'cron' => '']; + $suggested = vv_script_suggested_cron($path); $orchs[] = [ - 'id' => $id, - 'label' => basename($path, '.sh'), - 'desc' => vv_script_description($path), - 'type' => 'orchestrator', - 'enabled' => (bool)($entry['enabled'] ?? false), - 'cron' => $entry['cron'] ?? '', - 'log_enabled' => (bool)($entry['log_enabled'] ?? false), - 'children' => vv_script_children($path, $schedule), + 'id' => $id, + 'label' => basename($path, '.sh'), + 'desc' => vv_script_description($path), + 'type' => 'orchestrator', + 'enabled' => (bool)($entry['enabled'] ?? false), + 'cron' => $entry['cron'] ?? '', + 'log_enabled' => (bool)($entry['log_enabled'] ?? false), + 'suggested_cron' => $suggested['cron'], + 'suggested_label' => $suggested['label'], + 'children' => vv_script_children($path, $schedule), ]; } return $orchs;