diff --git a/classes/plugin/SafeUpgradeManager.php b/classes/plugin/SafeUpgradeManager.php index be9d0096..4f812678 100644 --- a/classes/plugin/SafeUpgradeManager.php +++ b/classes/plugin/SafeUpgradeManager.php @@ -696,6 +696,11 @@ class SafeUpgradeManager */ public function run(array $options = []): array { + $operation = isset($options['operation']) ? (string)$options['operation'] : 'upgrade'; + if ($operation === 'restore') { + return $this->runRestore($options); + } + $force = (bool)($options['force'] ?? false); $timeout = (int)($options['timeout'] ?? 30); $overwrite = (bool)($options['overwrite'] ?? false); @@ -852,7 +857,7 @@ class SafeUpgradeManager return $this->errorResult('Snapshot identifier is required.', ['operation' => 'restore']); } - $this->setProgress('restoring', sprintf('Restoring snapshot %s...', $snapshotId), null, [ + $this->setProgress('rollback', sprintf('Restoring snapshot %s...', $snapshotId), null, [ 'operation' => 'restore', 'snapshot' => $snapshotId, ]); diff --git a/languages/en.yaml b/languages/en.yaml index 17b57c09..7ef50b40 100644 --- a/languages/en.yaml +++ b/languages/en.yaml @@ -533,9 +533,12 @@ PLUGIN_ADMIN: SAFE_UPGRADE_BLOCKERS_DESC: "Resolve the following items to enable the upgrade." SAFE_UPGRADE_START: "Start Safe Upgrade" SAFE_UPGRADE_FINISH: "Finish" + SAFE_UPGRADE_STAGE_QUEUED: "Waiting for worker" SAFE_UPGRADE_STAGE_INITIALIZING: "Preparing upgrade" SAFE_UPGRADE_STAGE_DOWNLOADING: "Downloading update" + SAFE_UPGRADE_STAGE_SNAPSHOT: "Creating backup snapshot" SAFE_UPGRADE_STAGE_INSTALLING: "Installing update" + SAFE_UPGRADE_STAGE_ROLLBACK: "Restoring snapshot" SAFE_UPGRADE_STAGE_FINALIZING: "Finalizing changes" SAFE_UPGRADE_STAGE_COMPLETE: "Upgrade complete" SAFE_UPGRADE_STAGE_ERROR: "Upgrade encountered an error" diff --git a/themes/grav/app/updates/safe-upgrade.js b/themes/grav/app/updates/safe-upgrade.js index 94368fe4..83cc2f23 100644 --- a/themes/grav/app/updates/safe-upgrade.js +++ b/themes/grav/app/updates/safe-upgrade.js @@ -24,7 +24,9 @@ const STAGE_TITLES = { queued: () => t('SAFE_UPGRADE_STAGE_QUEUED', 'Waiting for worker'), initializing: () => t('SAFE_UPGRADE_STAGE_INITIALIZING', 'Preparing upgrade'), downloading: () => t('SAFE_UPGRADE_STAGE_DOWNLOADING', 'Downloading update'), + snapshot: () => t('SAFE_UPGRADE_STAGE_SNAPSHOT', 'Creating backup snapshot'), installing: () => t('SAFE_UPGRADE_STAGE_INSTALLING', 'Installing update'), + rollback: () => t('SAFE_UPGRADE_STAGE_ROLLBACK', 'Restoring snapshot'), finalizing: () => t('SAFE_UPGRADE_STAGE_FINALIZING', 'Finalizing changes'), complete: () => t('SAFE_UPGRADE_STAGE_COMPLETE', 'Upgrade complete'), error: () => t('SAFE_UPGRADE_STAGE_ERROR', 'Upgrade encountered an error') @@ -806,9 +808,15 @@ export default class SafeUpgrade { } return 12; } + if (stage === 'snapshot') { + return this.computeSmoothPercent(20, 45, 8, percent); + } if (stage === 'installing') { return this.computeSmoothPercent(20, 90, 28, percent); } + if (stage === 'rollback') { + return this.computeSmoothPercent(40, 95, 20, percent); + } if (stage === 'finalizing') { return this.computeSmoothPercent(90, 99, 6, percent); } @@ -821,12 +829,29 @@ export default class SafeUpgrade { const displayPercent = percent !== null ? Math.round(percent) : null; const percentLabel = displayPercent !== null ? `${displayPercent}%` : ''; + const message = typeof data.message === 'string' ? data.message : ''; + const normalize = (value) => value + .replace(/\u2026/g, '...') + .replace(/\.+$/, '') + .trim() + .toLowerCase(); + const normalizedTitle = normalize(title || ''); + const normalizedMessage = normalize(message || ''); + + const shouldShowMessage = stage === 'error' + ? message.trim().length > 0 + : ( + message && + stage !== 'installing' && + stage !== 'finalizing' && + normalizedMessage !== '' && + normalizedMessage !== normalizedTitle + ); + const statusLine = job && job.status ? `

${t('SAFE_UPGRADE_JOB_STATUS', 'Status')}: ${job.status.toUpperCase()}${job.error ? ` — ${job.error}` : ''}

` : ''; const animateBar = stage !== 'complete' && stage !== 'error' && percent !== null; const barClass = `safe-upgrade-progress-bar${animateBar ? ' is-active' : ''}`; - const detailMessage = stage === 'error' - ? `

${data.message || ''}

` - : (data.message && stage !== 'installing' && stage !== 'finalizing' ? `

${data.message}

` : ''); + const detailMessage = shouldShowMessage ? `

${message}

` : ''; this.steps.progress.html(`
diff --git a/themes/grav/js/admin.min.js b/themes/grav/js/admin.min.js index a5cf6a27..3f1dce23 100644 --- a/themes/grav/js/admin.min.js +++ b/themes/grav/js/admin.min.js @@ -2228,9 +2228,15 @@ var STAGE_TITLES = { downloading: function downloading() { return t('SAFE_UPGRADE_STAGE_DOWNLOADING', 'Downloading update'); }, + snapshot: function snapshot() { + return t('SAFE_UPGRADE_STAGE_SNAPSHOT', 'Creating backup snapshot'); + }, installing: function installing() { return t('SAFE_UPGRADE_STAGE_INSTALLING', 'Installing update'); }, + rollback: function rollback() { + return t('SAFE_UPGRADE_STAGE_ROLLBACK', 'Restoring snapshot'); + }, finalizing: function finalizing() { return t('SAFE_UPGRADE_STAGE_FINALIZING', 'Finalizing changes'); }, @@ -2861,9 +2867,15 @@ var SafeUpgrade = /*#__PURE__*/function () { } return 12; } + if (stage === 'snapshot') { + return _this7.computeSmoothPercent(20, 45, 8, percent); + } if (stage === 'installing') { return _this7.computeSmoothPercent(20, 90, 28, percent); } + if (stage === 'rollback') { + return _this7.computeSmoothPercent(40, 95, 20, percent); + } if (stage === 'finalizing') { return _this7.computeSmoothPercent(90, 99, 6, percent); } @@ -2878,10 +2890,17 @@ var SafeUpgrade = /*#__PURE__*/function () { percent = scaledPercent(); var displayPercent = percent !== null ? Math.round(percent) : null; var percentLabel = displayPercent !== null ? "".concat(displayPercent, "%") : ''; + var message = typeof data.message === 'string' ? data.message : ''; + var normalize = function normalize(value) { + return value.replace(/\u2026/g, '...').replace(/\.+$/, '').trim().toLowerCase(); + }; + var normalizedTitle = normalize(title || ''); + var normalizedMessage = normalize(message || ''); + var shouldShowMessage = stage === 'error' ? message.trim().length > 0 : message && stage !== 'installing' && stage !== 'finalizing' && normalizedMessage !== '' && normalizedMessage !== normalizedTitle; var statusLine = job && job.status ? "

".concat(t('SAFE_UPGRADE_JOB_STATUS', 'Status'), ": ").concat(job.status.toUpperCase(), "").concat(job.error ? " — ".concat(job.error) : '', "

") : ''; var animateBar = stage !== 'complete' && stage !== 'error' && percent !== null; var barClass = "safe-upgrade-progress-bar".concat(animateBar ? ' is-active' : ''); - var detailMessage = stage === 'error' ? "

".concat(data.message || '', "

") : data.message && stage !== 'installing' && stage !== 'finalizing' ? "

".concat(data.message, "

") : ''; + var detailMessage = shouldShowMessage ? "

".concat(message, "

") : ''; this.steps.progress.html("\n
\n

".concat(title, "

\n ").concat(detailMessage, "\n ").concat(statusLine, "\n ").concat(percentLabel ? "
").concat(percentLabel, "
") : '', "\n
\n ")); this.switchStep('progress'); if (stage === 'complete') {