feat: lock the admin UI while a backup is actively running
Generic: Project CI / Lint & Validate (pull_request) Successful in 13s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 10s
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Project CI / Lint & Validate (pull_request) Successful in 13s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 10s
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
New "Lock admin during backup" option (on by default). While a stepped backup is running, every admin page load except the full-screen backup screen and the login page is replaced by a "backup in progress" interstitial that auto-refreshes, so no other window can navigate or change anything mid-backup. Detection uses the stepped-backup session file heartbeat (JPATH_ROOT/tmp/mokosuitebackup-sessions/mb_*.json is rewritten every step and deleted on completion) — a file with an mtime within ~45s means a backup is live. A crashed/abandoned backup goes stale and releases the lock on its own, so the admin is never permanently locked. Implemented in the system plugin's onAfterRoute (admin, HTML page loads only; AJAX/assets and the backup's own step requests are unaffected). Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
This commit is contained in:
@@ -162,6 +162,17 @@
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="lock_admin_during_backup"
|
||||
type="radio"
|
||||
label="COM_MOKOJOOMBACKUP_CONFIG_LOCK_ADMIN"
|
||||
description="COM_MOKOJOOMBACKUP_CONFIG_LOCK_ADMIN_DESC"
|
||||
default="1"
|
||||
class="btn-group"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="cleanup" label="COM_MOKOJOOMBACKUP_CONFIG_CLEANUP">
|
||||
|
||||
@@ -335,6 +335,8 @@ COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_LOCK_ADMIN="Lock admin during backup"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_LOCK_ADMIN_DESC="While a backup is actively running, show a \"backup in progress\" screen on all other admin pages until it finishes, so nothing changes mid-backup. The backup screen itself and the login page stay accessible. Auto-releases if a backup stops responding."
|
||||
|
||||
; Full-screen backup progress view (runbackup)
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE="Backing Up"
|
||||
|
||||
@@ -57,6 +57,8 @@ COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_LOCK_ADMIN="Lock admin during backup"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_LOCK_ADMIN_DESC="While a backup is actively running, show a \"backup in progress\" screen on all other admin pages until it finishes, so nothing changes mid-backup. The backup screen itself and the login page stay accessible. Auto-releases if a backup stops responding."
|
||||
|
||||
; Full-screen backup progress view (runbackup)
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE="Backing Up"
|
||||
|
||||
+2
@@ -15,3 +15,5 @@ PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update."
|
||||
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed"
|
||||
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_LOCK_TITLE="Backup in progress"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_LOCK_MESSAGE="A MokoSuiteBackup backup is currently running in another window. The administrator area is locked until it finishes — this page will refresh automatically. Please do not make changes while the backup runs."
|
||||
|
||||
+2
@@ -15,3 +15,5 @@ PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update."
|
||||
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed"
|
||||
PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_LOCK_TITLE="Backup in progress"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_LOCK_MESSAGE="A MokoSuiteBackup backup is currently running in another window. The administrator area is locked until it finishes — this page will refresh automatically. Please do not make changes while the backup runs."
|
||||
|
||||
@@ -133,6 +133,10 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
||||
return;
|
||||
}
|
||||
|
||||
// While a backup is actively running, lock the admin UI so no other
|
||||
// window/page can navigate or change anything mid-backup. May close().
|
||||
$this->maybeLockAdminDuringBackup();
|
||||
|
||||
// Pre-update: send Joomla core-update installs through the full-screen
|
||||
// backup page BEFORE the update runs (may not return from here).
|
||||
$this->maybeRedirectForUpdate();
|
||||
@@ -155,6 +159,97 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
||||
$this->cleanupOldSnapshots();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether a stepped backup is actively running right now.
|
||||
*
|
||||
* The stepped-backup engine rewrites its session JSON file
|
||||
* (JPATH_ROOT/tmp/mokosuitebackup-sessions/mb_*.json) on every step and
|
||||
* deletes it on completion, so a file whose mtime is within the last ~45s
|
||||
* is a reliable, cross-session heartbeat. A crashed/abandoned backup goes
|
||||
* stale and stops locking on its own — the admin is never permanently locked.
|
||||
*/
|
||||
private function backupIsActive(): bool
|
||||
{
|
||||
$dir = JPATH_ROOT . '/tmp/mokosuitebackup-sessions';
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cutoff = time() - 45;
|
||||
|
||||
foreach (glob($dir . '/mb_*.json') ?: [] as $file) {
|
||||
if (@filemtime($file) >= $cutoff) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* While a backup is actively running, lock the admin UI: every admin page
|
||||
* load (except the full-screen backup screen and the login/logout page)
|
||||
* gets a "backup in progress" interstitial that auto-refreshes, so no other
|
||||
* window can navigate or change anything mid-backup. Gated by the
|
||||
* lock_admin_during_backup option; auto-released when the backup ends.
|
||||
*/
|
||||
private function maybeLockAdminDuringBackup(): void
|
||||
{
|
||||
if (!(int) ComponentHelper::getParams('com_mokosuitebackup')->get('lock_admin_during_backup', 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$app = $this->getApplication();
|
||||
$input = $app->getInput();
|
||||
$option = $input->getCmd('option', '');
|
||||
$view = $input->getCmd('view', '');
|
||||
|
||||
// Never lock the full-screen backup screen (it IS the backup) nor login/logout.
|
||||
if (($option === 'com_mokosuitebackup' && $view === 'runbackup') || $option === 'com_login') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->backupIsActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderBackupLockPage();
|
||||
$app->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a self-contained "backup in progress" interstitial that refreshes
|
||||
* itself (meta refresh) until the backup finishes and the lock releases.
|
||||
*/
|
||||
private function renderBackupLockPage(): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
$title = Text::_('PLG_SYSTEM_MOKOSUITEBACKUP_LOCK_TITLE');
|
||||
$message = Text::_('PLG_SYSTEM_MOKOSUITEBACKUP_LOCK_MESSAGE');
|
||||
$t = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$m = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$html = '<!doctype html><html lang="en"><head><meta charset="utf-8">'
|
||||
. '<meta name="viewport" content="width=device-width, initial-scale=1">'
|
||||
. '<meta http-equiv="refresh" content="8">'
|
||||
. '<title>' . $t . '</title><style>'
|
||||
. 'html,body{height:100%;margin:0}'
|
||||
. 'body{background:#000;color:#fff;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;'
|
||||
. 'display:flex;align-items:center;justify-content:center;text-align:center}'
|
||||
. '.w{max-width:34rem;padding:2rem}'
|
||||
. '.s{width:3rem;height:3rem;margin:0 auto 1.5rem;border:.35rem solid rgba(255,255,255,.2);'
|
||||
. 'border-top-color:#fff;border-radius:50%;animation:sp 1s linear infinite}'
|
||||
. 'h1{font-size:1.5rem;margin:.5rem 0}p{opacity:.85;line-height:1.55}'
|
||||
. '@keyframes sp{to{transform:rotate(360deg)}}'
|
||||
. '</style></head><body><div class="w"><div class="s"></div>'
|
||||
. '<h1>' . $t . '</h1><p>' . $m . '</p></div></body></html>';
|
||||
|
||||
$app->setHeader('Content-Type', 'text/html; charset=utf-8', true);
|
||||
$app->sendHeaders();
|
||||
echo $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the client-side interceptor on the Extensions → Update and
|
||||
* Extensions → Manage pages, so an EXTENSION update / uninstall also runs
|
||||
|
||||
Reference in New Issue
Block a user