From 85c0967e897b4345e3d5b8510b5c3c830bf90ede Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 14 Jul 2026 15:17:38 -0500 Subject: [PATCH] feat(runbackup): cancel button with double confirmation Adds a "Cancel backup" button on the backup progress screen (visible while the backup runs). Cancelling requires TWO confirmations; on confirm it stops the stepped-backup loop, best-effort cancels the record server-side (ajax.cancelBackup), and returns to the dashboard WITHOUT continuing any pending update. The button is hidden once the backup completes or fails. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- .../language/en-GB/com_mokosuitebackup.ini | 4 ++ .../language/en-US/com_mokosuitebackup.ini | 4 ++ .../tmpl/runbackup/default.php | 54 ++++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini index 14c2d8ed..824c6f0e 100644 --- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini @@ -352,6 +352,10 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes" COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes." COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…" +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup" +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?" +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue." +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…" COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update" COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" diff --git a/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini index 1985d78e..1f2d226d 100644 --- a/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini @@ -74,6 +74,10 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes" COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes." COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…" +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup" +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?" +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue." +COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…" COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update" COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php index 9a447d5f..683f2e77 100644 --- a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php @@ -87,6 +87,9 @@ $config = [ 'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'), 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), 'continueUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE'), + 'cancelConfirm1' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1'), + 'cancelConfirm2' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2'), + 'cancelling' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING'), ], ]; @@ -141,6 +144,13 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku + + +
+ +
@@ -157,6 +167,7 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku var CFG = ; var L = CFG.labels || {}; var running = false; + var cancelled = false; var lastRecordId = 0; var el = { @@ -172,9 +183,38 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku continue: document.getElementById('msb-rb-continue'), dashboard: document.getElementById('msb-rb-dashboard'), autocontinue: document.getElementById('msb-rb-autocontinue'), - autocontinueWrap: document.getElementById('msb-rb-autocontinue-wrap') + autocontinueWrap: document.getElementById('msb-rb-autocontinue-wrap'), + cancel: document.getElementById('msb-rb-cancel'), + cancelWrap: document.getElementById('msb-rb-cancel-wrap') }; + function hideCancel() { if (el.cancelWrap) { el.cancelWrap.classList.add('d-none'); } } + + /* Cancel the running backup — requires DOUBLE confirmation. Stops the step + loop, tells the server to cancel the record, and leaves to the dashboard + WITHOUT continuing any pending update. */ + if (el.cancel) { + el.cancel.addEventListener('click', function () { + if (!running || cancelled) { return; } + if (!window.confirm(L.cancelConfirm1 || 'Cancel the backup that is in progress?')) { return; } + if (!window.confirm(L.cancelConfirm2 || 'Are you sure? The backup will be stopped and the update will NOT continue.')) { return; } + + cancelled = true; + running = false; /* also disarms the leave-warning */ + el.cancel.disabled = true; + if (el.warn) { el.warn.classList.add('d-none'); } + setStatus(L.cancelling || 'Cancelling…'); + + var go = function () { window.location.href = CFG.dashboardUrl; }; + /* Best-effort server-side cancel of the record, then leave. */ + if (lastRecordId > 0) { + post({ task: 'ajax.cancelBackup', id: lastRecordId }).then(go, go); + } else { + go(); + } + }); + } + function setBar(pct, striped) { pct = Math.max(0, Math.min(100, parseInt(pct, 10) || 0)); el.bar.style.width = pct + '%'; @@ -211,6 +251,7 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku setBar(100, false); el.title.textContent = L.complete || 'Backup complete'; if (el.warn) { el.warn.classList.add('d-none'); } + hideCancel(); /* Set the one-shot skip flag for THIS action so the following server-side onExtensionBefore(Update|Uninstall) backup is skipped once @@ -264,6 +305,7 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku if (el.warn) { el.warn.classList.add('d-none'); } el.title.textContent = L.failed || 'Backup failed'; setStatus(message || 'Backup failed'); + hideCancel(); showActions(); el.retry.textContent = L.retry || 'Retry'; @@ -285,7 +327,10 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku async function run() { if (running) { return; } running = true; + cancelled = false; el.actions.classList.add('d-none'); + if (el.cancelWrap) { el.cancelWrap.classList.remove('d-none'); } + if (el.cancel) { el.cancel.disabled = false; } setBar(0, true); setPhase(''); setStatus(L.starting || 'Starting backup…'); @@ -303,7 +348,7 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku if (init.record_id) { lastRecordId = init.record_id; } var done = false; - while (!done) { + while (!done && !cancelled) { var step = await post({ task: 'ajax.step', session_id: init.session_id }); if (!step || step.error) { @@ -317,9 +362,14 @@ $this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebacku done = step.done || false; } + /* User cancelled mid-flight — the click handler already stops the + loop and navigates away, so do not fall through to completion. */ + if (cancelled) { return; } + running = false; await onComplete(); } catch (err) { + if (cancelled) { return; } onError(err && err.message ? err.message : String(err)); } } -- 2.52.0