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:
Gmer4Lfe
2026-05-24 21:54:13 -04:00
parent 1540c62d3a
commit 655c520ae8
6 changed files with 126 additions and 24 deletions
@@ -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;