scheduler: add Array Starting / Array Stopping event triggers
Two hardcoded event-triggered entries appear at the top of the scheduler (array_started.sh / array_stopping.sh). They show an ⚡ Array Start / ⚡ Array Stop badge instead of a cron field and are enabled/disabled via the normal toggle. Static event scripts fire them via Unraid's event system: - event/disks_mounted/array_start_jobs → runs in background (non-blocking) - event/disks_unmounting/array_stop_jobs → runs foreground (blocks until done) vv_cron_rebuild() skips @array_* entries so they never land in the cron file. Scripts executed on each event are managed in master.conf via ARRAY_START_SCRIPTS and ARRAY_STOP_SCRIPTS arrays.
This commit is contained in:
@@ -12,8 +12,9 @@ if (!$id) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Basic cron validation — 5 fields or empty
|
||||
if ($cron && !preg_match('/^(\S+\s+){4}\S+$/', $cron)) {
|
||||
// Basic cron validation — 5 fields, or known @event trigger, or empty
|
||||
if ($cron && !in_array($cron, ['@array_start', '@array_stop'], true)
|
||||
&& !preg_match('/^(\S+\s+){4}\S+$/', $cron)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid cron expression']);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@
|
||||
cursor: default; }
|
||||
.vv-cron { flex: 0 0 110px; width: 110px; background: #111; border: 1px solid #444; color: #ddd;
|
||||
padding: 4px 6px; border-radius: 4px; font-family: monospace; font-size: 13px; }
|
||||
.vv-event-badge { flex: 0 0 auto; padding: 3px 8px; border-radius: 4px; font-size: 12px;
|
||||
background: #1a3a1a; border: 1px solid #2e6b2e; color: #6fcf6f;
|
||||
white-space: nowrap; font-weight: 500; }
|
||||
.vv-log-label { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #888;
|
||||
cursor: pointer; white-space: nowrap; flex-shrink: 0; }
|
||||
.vv-log-label input { cursor: pointer; accent-color: #4caf50; }
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Varaverk: run array_started.sh if enabled in the schedule (background — non-blocking).
|
||||
php -r "
|
||||
require_once '/usr/local/emhttp/plugins/varaverk/include/scheduler.php';
|
||||
\$s = vv_schedule_load();
|
||||
\$e = \$s['Orchestrators/array_started.sh'] ?? [];
|
||||
if (empty(\$e['enabled'])) exit(0);
|
||||
\$runner = '/usr/local/emhttp/plugins/varaverk/run_job.sh';
|
||||
\$script = SCRIPTS_DIR . '/Orchestrators/array_started.sh';
|
||||
if (!file_exists(\$script)) exit(0);
|
||||
\$flags = !empty(\$e['log_enabled']) ? ' --log' : '';
|
||||
exec('nohup bash ' . escapeshellarg(\$runner) . ' Orchestrators/array_started.sh ' . escapeshellarg(\$script) . \$flags . ' > /dev/null 2>&1 &');
|
||||
" 2>/dev/null
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Varaverk: run array_stopping.sh if enabled in the schedule (foreground — blocks until done).
|
||||
php -r "
|
||||
require_once '/usr/local/emhttp/plugins/varaverk/include/scheduler.php';
|
||||
\$s = vv_schedule_load();
|
||||
\$e = \$s['Orchestrators/array_stopping.sh'] ?? [];
|
||||
if (empty(\$e['enabled'])) exit(0);
|
||||
\$runner = '/usr/local/emhttp/plugins/varaverk/run_job.sh';
|
||||
\$script = SCRIPTS_DIR . '/Orchestrators/array_stopping.sh';
|
||||
if (!file_exists(\$script)) exit(0);
|
||||
\$flags = !empty(\$e['log_enabled']) ? ' --log' : '';
|
||||
passthru('bash ' . escapeshellarg(\$runner) . ' Orchestrators/array_stopping.sh ' . escapeshellarg(\$script) . \$flags);
|
||||
" 2>/dev/null
|
||||
@@ -57,6 +57,8 @@ function vv_cron_rebuild(array $schedule): bool {
|
||||
$scriptsDir = SCRIPTS_DIR;
|
||||
foreach ($schedule as $entry) {
|
||||
if (empty($entry['enabled']) || empty($entry['cron']) || empty($entry['id'])) continue;
|
||||
// Event-triggered jobs (@array_start / @array_stop) are handled by static event scripts, not cron.
|
||||
if (str_starts_with($entry['cron'], '@array_')) continue;
|
||||
$script = "$scriptsDir/{$entry['id']}";
|
||||
$flags = !empty($entry['log_enabled']) ? ' --log' : '';
|
||||
$id = $entry['id'];
|
||||
@@ -193,31 +195,55 @@ function vv_custom_scripts(): array {
|
||||
}
|
||||
|
||||
// Walk the scripts repo and return the job tree:
|
||||
// orchestrators as top-level, individual scripts as children
|
||||
// hardcoded array-event entries first, then cron-scheduled orchestrators
|
||||
function vv_job_tree(): array {
|
||||
$scriptsDir = SCRIPTS_DIR;
|
||||
$schedule = vv_schedule_load();
|
||||
|
||||
// Orchestrators are in Orchestrators/ and their children are all scripts they call
|
||||
// For now: walk top-level folders, treat *_management.sh or *_maintenance.sh as orchs
|
||||
$orchPattern = "$scriptsDir/Orchestrators/*.sh";
|
||||
$orchs = [];
|
||||
|
||||
foreach (glob($orchPattern) ?: [] as $path) {
|
||||
$id = 'Orchestrators/' . basename($path);
|
||||
$entry = $schedule[$id] ?? ['enabled' => false, 'cron' => ''];
|
||||
$suggested = vv_script_suggested_cron($path);
|
||||
// Hardcoded array-event entries — always present, trigger via Unraid event scripts
|
||||
$eventDefs = [
|
||||
['id' => 'Orchestrators/array_started.sh', 'cron' => '@array_start', 'label' => 'Array Starting'],
|
||||
['id' => 'Orchestrators/array_stopping.sh', 'cron' => '@array_stop', 'label' => 'Array Stopping'],
|
||||
];
|
||||
$orchs = [];
|
||||
$eventIds = [];
|
||||
foreach ($eventDefs as $ev) {
|
||||
$id = $ev['id'];
|
||||
$path = "$scriptsDir/$id";
|
||||
$entry = $schedule[$id] ?? [];
|
||||
$eventIds[] = $id;
|
||||
$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),
|
||||
'id' => $id,
|
||||
'label' => $ev['label'],
|
||||
'desc' => vv_script_description($path),
|
||||
'type' => 'event',
|
||||
'enabled' => (bool)($entry['enabled'] ?? false),
|
||||
'cron' => $ev['cron'],
|
||||
'log_enabled' => (bool)($entry['log_enabled'] ?? false),
|
||||
'suggested_cron' => '',
|
||||
'suggested_label' => '',
|
||||
'children' => vv_script_children($path, $schedule),
|
||||
];
|
||||
}
|
||||
|
||||
// Cron-scheduled orchestrators — discovered by glob, event entries excluded
|
||||
$orchPattern = "$scriptsDir/Orchestrators/*.sh";
|
||||
foreach (glob($orchPattern) ?: [] as $path) {
|
||||
$id = 'Orchestrators/' . basename($path);
|
||||
if (in_array($id, $eventIds, true)) continue;
|
||||
$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),
|
||||
'suggested_cron' => $suggested['cron'],
|
||||
'suggested_label' => $suggested['label'],
|
||||
'children' => vv_script_children($path, $schedule),
|
||||
'children' => vv_script_children($path, $schedule),
|
||||
];
|
||||
}
|
||||
return $orchs;
|
||||
|
||||
@@ -36,8 +36,13 @@ foreach ($tree as $orch) {
|
||||
<span class="vv-slider"></span>
|
||||
</label>
|
||||
<span class="vv-job-label"><?= htmlspecialchars($orch['label']) ?></span>
|
||||
<input type="text" class="vv-cron" value="<?= htmlspecialchars($orch['cron']) ?>"
|
||||
placeholder="cron expression">
|
||||
<?php if ($orch['type'] === 'event'): ?>
|
||||
<span class="vv-event-badge"><?= $orch['cron'] === '@array_start' ? '⚡ Array Start' : '⚡ Array Stop' ?></span>
|
||||
<input type="hidden" class="vv-cron" value="<?= htmlspecialchars($orch['cron']) ?>">
|
||||
<?php else: ?>
|
||||
<input type="text" class="vv-cron" value="<?= htmlspecialchars($orch['cron']) ?>"
|
||||
placeholder="cron expression">
|
||||
<?php endif; ?>
|
||||
<span class="vv-save-check"></span>
|
||||
</div>
|
||||
<?php if (!empty($orch['desc'])): ?>
|
||||
@@ -186,6 +191,7 @@ foreach ($tree as $orch) {
|
||||
<button id="vv-save-script-btn" class="vv-btn-sm vv-save-script-btn-style" onclick="vvSaveScript()" style="display:none">Save Script</button>
|
||||
<button id="vv-save-conf-btn" class="vv-btn-sm vv-save-script-btn-style" onclick="vvSaveConf()" style="display:none">Save Config</button>
|
||||
<span id="vv-log-ts" class="vv-log-ts"></span>
|
||||
<button id="vv-stop-btn" class="vv-btn-sm" onclick="vvStopJob()" style="display:none" title="Stop running script and clear any stuck locks">■ Stop</button>
|
||||
<button id="vv-clear-btn" class="vv-btn-sm" onclick="vvClearRightLog()" style="display:none">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,6 +369,7 @@ function vvShowLogMode(id) {
|
||||
document.getElementById('vv-confform').style.display = 'none';
|
||||
document.getElementById('vv-auto-scroll-label').style.display = '';
|
||||
document.getElementById('vv-invert-log-label').style.display = '';
|
||||
document.getElementById('vv-stop-btn').style.display = '';
|
||||
document.getElementById('vv-auto-scroll').checked = true;
|
||||
const _pre = document.getElementById('vv-log-pre');
|
||||
_pre.removeEventListener('scroll', vvOnLogScroll);
|
||||
@@ -424,6 +431,7 @@ function vvBackToSuggestions() {
|
||||
}
|
||||
document.getElementById('vv-auto-scroll-label').style.display = 'none';
|
||||
document.getElementById('vv-invert-log-label').style.display = 'none';
|
||||
document.getElementById('vv-stop-btn').style.display = 'none';
|
||||
document.getElementById('vv-auto-scroll').checked = true;
|
||||
const _preBack = document.getElementById('vv-log-pre');
|
||||
_preBack.removeEventListener('scroll', vvOnLogScroll);
|
||||
@@ -445,6 +453,7 @@ function vvOpenRight(id) {
|
||||
if (job) job.querySelector('.vv-job-row').classList.add('vv-row-selected');
|
||||
|
||||
vvShowLogMode(id);
|
||||
vvSetStopBtn(vvRunningSet.has(id));
|
||||
requestAnimationFrame(vvFitRight);
|
||||
|
||||
vvFetchRight();
|
||||
@@ -475,16 +484,51 @@ function vvFetchRight() {
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function vvSetStopBtn(running) {
|
||||
const btn = document.getElementById('vv-stop-btn');
|
||||
if (!btn || btn.style.display === 'none') return;
|
||||
btn.disabled = !running;
|
||||
btn.style.background = running ? '#c62828' : '#444';
|
||||
btn.style.color = running ? '#fff' : '#888';
|
||||
btn.style.cursor = running ? 'pointer' : 'default';
|
||||
}
|
||||
|
||||
function vvSetDot(id) {
|
||||
const job = document.querySelector('[data-id="' + CSS.escape(id) + '"]');
|
||||
if (job) job.querySelector('.vv-job-dot').classList.add('vv-dot-running');
|
||||
if (id === vvActiveId) document.getElementById('vv-log-dot').style.display = 'inline-block';
|
||||
if (id === vvActiveId) {
|
||||
document.getElementById('vv-log-dot').style.display = 'inline-block';
|
||||
vvSetStopBtn(true);
|
||||
}
|
||||
}
|
||||
|
||||
function vvClearDot(id) {
|
||||
const job = document.querySelector('[data-id="' + CSS.escape(id) + '"]');
|
||||
if (job) job.querySelector('.vv-job-dot').classList.remove('vv-dot-running');
|
||||
if (id === vvActiveId) document.getElementById('vv-log-dot').style.display = 'none';
|
||||
if (id === vvActiveId) {
|
||||
document.getElementById('vv-log-dot').style.display = 'none';
|
||||
vvSetStopBtn(false);
|
||||
}
|
||||
}
|
||||
|
||||
function vvStopJob() {
|
||||
if (!vvActiveId || !vvRunningSet.has(vvActiveId)) return;
|
||||
const btn = document.getElementById('vv-stop-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
vvPost('/plugins/varaverk/api/stop.php', {id: vvActiveId})
|
||||
.then(d => {
|
||||
btn.textContent = '■ Stop';
|
||||
if (d.ok) {
|
||||
vvRunningSet.delete(vvActiveId);
|
||||
vvClearDot(vvActiveId);
|
||||
vvFetchRight();
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
vvSetStopBtn(true);
|
||||
}
|
||||
})
|
||||
.catch(() => { btn.textContent = '■ Stop'; btn.disabled = false; vvSetStopBtn(true); });
|
||||
}
|
||||
|
||||
function vvPollStatus() {
|
||||
@@ -663,6 +707,7 @@ function vvShowEditorMode(title) {
|
||||
document.getElementById('vv-restore-btn').style.display = 'none';
|
||||
document.getElementById('vv-save-script-btn').style.display = '';
|
||||
document.getElementById('vv-clear-btn').style.display = 'none';
|
||||
document.getElementById('vv-stop-btn').style.display = 'none';
|
||||
document.getElementById('vv-auto-scroll-label').style.display = 'none';
|
||||
document.getElementById('vv-invert-log-label').style.display = 'none';
|
||||
document.getElementById('vv-log-title').textContent = title;
|
||||
@@ -722,6 +767,7 @@ function vvShowConfMode(title) {
|
||||
document.getElementById('vv-save-conf-btn').style.display = '';
|
||||
document.getElementById('vv-delete-script-btn').style.display = 'none';
|
||||
document.getElementById('vv-clear-btn').style.display = 'none';
|
||||
document.getElementById('vv-stop-btn').style.display = 'none';
|
||||
document.getElementById('vv-auto-scroll-label').style.display = 'none';
|
||||
document.getElementById('vv-invert-log-label').style.display = 'none';
|
||||
document.getElementById('vv-log-title').textContent = title;
|
||||
|
||||
Reference in New Issue
Block a user