From a71723e51f2f7165244dd60c55b5054f61dc3dff Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Fri, 14 Aug 2026 17:24:15 -0400 Subject: [PATCH] Show the manual sync command it will actually run, and guard --delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The preview ended in the literal text "…src host:dst", so the one thing worth checking before pressing Run — the real source and destination, and which of them has the trailing slash — was the one thing never shown. --delete sat unmarked among nine checkboxes with no confirmation on a card that can remove data on the far host. Confirm fires only for --delete without --dry-run, so it stays worth reading. --- Plugin/unraid/pages/rsync.php | 68 ++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/Plugin/unraid/pages/rsync.php b/Plugin/unraid/pages/rsync.php index 1bff33f..f399163 100644 --- a/Plugin/unraid/pages/rsync.php +++ b/Plugin/unraid/pages/rsync.php @@ -186,7 +186,7 @@ require_once dirname(__DIR__) . '/include/ai_chat.php';
Local source
-
Remote path
-
+
+ @@ -1660,6 +1665,8 @@ function vvMsHostChanged() { const host = _vvMsHosts.find(h => h.slot === slot); const stat = document.getElementById('vv-ms-host-status'); + vvMsUpdatePreview(); // the destination just changed, so the command shown must too + if (!host) { stat.textContent = ''; return; } const onlineTxt = host.online === null ? 'status unknown' @@ -1720,6 +1727,14 @@ function vvMsBrowse() { }); } +// The whole command, with the real paths in it, not "…src host:dst". +// +// Two reasons it has to resolve rather than gesture. The first is the trailing slash: this card +// already warns that "/" means contents and no "/" means the folder, and a hint under a text box +// is not where anyone looks at the moment they press Run — in the command it is unmissable. The +// second is --delete, which is one checkbox among nine and is the only one that removes data at +// the far end. Seeing it sit next to the actual destination is the difference between reading a +// flag and understanding a consequence. function vvMsUpdatePreview() { const flags = Array.from(document.querySelectorAll('#vv-ms-card input[data-flag]:checked')) .map(el => el.dataset.flag).join(' '); @@ -1728,6 +1743,31 @@ function vvMsUpdatePreview() { const bwPrev= document.getElementById('vv-ms-bwlimit-preview'); if (prev) prev.textContent = flags || '(none)'; if (bwPrev) bwPrev.textContent = bw > 0 ? '--bwlimit=' + bw : ''; + + const local = document.getElementById('vv-ms-local')?.value.trim() || ''; + const rpath = document.getElementById('vv-ms-rpath')?.value.trim() || ''; + const user = document.getElementById('vv-ms-user')?.value || 'root'; + const hostEl = document.getElementById('vv-ms-host'); + const hostTxt = hostEl && hostEl.selectedIndex >= 0 + ? (hostEl.options[hostEl.selectedIndex].textContent || '').trim() : ''; + const tgt = document.getElementById('vv-ms-target-preview'); + if (tgt) { + tgt.textContent = (local || '') + ' ' + user + '@' + (hostTxt || '') + ':' + (rpath || ''); + } + + // Destructive styling follows the checkbox, so the warning cannot be left on screen after the + // flag is cleared — a stale danger marker is how a real one stops being read. + const del = document.querySelector('#vv-ms-card input[data-flag="--delete"]'); + const dry = document.getElementById('vv-ms-dryrun'); + const warn = document.getElementById('vv-ms-delete-warn'); + if (warn) { + const armed = del && del.checked && !(dry && dry.checked); + warn.style.display = armed ? '' : 'none'; + if (armed) { + warn.textContent = 'deletes anything at ' + (rpath || 'the destination') + + ' that is not in ' + (local || 'the source'); + } + } } function _vvMsRenderDirs(dirsElId, dirs, parent, navFn) { @@ -1815,7 +1855,7 @@ function vvMsBrowseLocal() { }); } -function vvMsRun() { +async function vvMsRun() { const local = document.getElementById('vv-ms-local').value.trim(); const slot = document.getElementById('vv-ms-host').value; const rpath = document.getElementById('vv-ms-rpath').value.trim(); @@ -1831,6 +1871,24 @@ function vvMsRun() { if (!slot) { vvAlert('Select a remote server.'); return; } if (!rpath) { vvAlert('Enter a remote destination path.'); return; } + // The only confirm on this card, and only for the one flag that destroys data at the far end. + // Guarding every run would train the reflex that dismisses this one — a prompt that appears on + // every safe action is not read by the time an unsafe one arrives. + // + // Skipped under --dry-run, because that is exactly the rehearsal this is asking for, and + // refusing to let someone rehearse the dangerous case cheaply is the wrong lesson to teach. + if (flags.includes('--delete') && !isDry) { + const ok = await vvConfirm( + 'This deletes anything in ' + rpath + ' on ' + slot + ' that is not in ' + local + '.\n\n' + + (local.endsWith('/') + ? 'The source ends in "/", so its CONTENTS are compared against the destination.' + : 'The source has no trailing "/", so the FOLDER ITSELF is copied into the destination — ' + + 'a common cause of deleting the wrong level.') + + '\n\nRun a --dry-run first if you have not.', + { title: 'Run with --delete?', confirmText: 'Run it', type: 'warning' }); + if (!ok) return; + } + const btn = document.getElementById('vv-ms-run-btn'); const stat = document.getElementById('vv-ms-run-status'); const out = document.getElementById('vv-ms-out');