smoother progress

This commit is contained in:
Andy Miller
2025-10-17 14:09:56 -06:00
parent 7e73ba9a61
commit 4aedf58f78
3 changed files with 77 additions and 17 deletions

View File

@@ -606,9 +606,8 @@ class SafeUpgradeManager
try { try {
$file = $this->download($package, $timeout); $file = $this->download($package, $timeout);
$this->setProgress('installing', 'Installing update...', 80);
$this->performInstall($file); $this->performInstall($file);
$this->setProgress('installing', 'Preparing promotion...', 92); $this->setProgress('installing', 'Preparing promotion...', null);
} catch (Throwable $e) { } catch (Throwable $e) {
$this->setProgress('error', $e->getMessage(), null); $this->setProgress('error', $e->getMessage(), null);
@@ -620,14 +619,14 @@ class SafeUpgradeManager
$this->tmp = null; $this->tmp = null;
} }
$this->setProgress('finalizing', 'Finalizing upgrade...', 95); $this->setProgress('finalizing', 'Finalizing upgrade...', null);
$safeUpgrade->clearRecoveryFlag(); $safeUpgrade->clearRecoveryFlag();
if ($this->recovery && method_exists($this->recovery, 'closeUpgradeWindow')) { if ($this->recovery && method_exists($this->recovery, 'closeUpgradeWindow')) {
$this->recovery->closeUpgradeWindow(); $this->recovery->closeUpgradeWindow();
} }
$this->ensureExecutablePermissions(); $this->ensureExecutablePermissions();
$this->setProgress('finalizing', 'Finalizing upgrade...', 98); $this->setProgress('finalizing', 'Finalizing upgrade...', null);
$manifest = $this->resolveLatestManifest(); $manifest = $this->resolveLatestManifest();
@@ -853,7 +852,7 @@ class SafeUpgradeManager
*/ */
protected function performInstall(string $zip): void protected function performInstall(string $zip): void
{ {
$this->setProgress('installing', 'Unpacking archive...', 82); $this->setProgress('installing', 'Unpacking update...', null);
$folder = Installer::unZip($zip, $this->tmp . '/zip'); $folder = Installer::unZip($zip, $this->tmp . '/zip');
if ($folder === false) { if ($folder === false) {
throw new RuntimeException(Installer::lastErrorMsg()); throw new RuntimeException(Installer::lastErrorMsg());
@@ -870,9 +869,9 @@ class SafeUpgradeManager
} }
try { try {
$this->setProgress('installing', 'Running installer...', 85); $this->setProgress('installing', 'Running installer...', null);
$install($zip); $install($zip);
$this->setProgress('installing', 'Verifying files...', 88); $this->setProgress('installing', 'Verifying files...', null);
} catch (Throwable $e) { } catch (Throwable $e) {
throw new RuntimeException($e->getMessage(), 0, $e); throw new RuntimeException($e->getMessage(), 0, $e);
} }

View File

@@ -56,6 +56,8 @@ export default class SafeUpgrade {
this.statusFailures = 0; this.statusFailures = 0;
this.statusContext = null; this.statusContext = null;
this.statusIdleCount = 0; this.statusIdleCount = 0;
this.currentStage = null;
this.stageEnteredAt = 0;
this.directStatusUrl = this.resolveDirectStatusUrl(); this.directStatusUrl = this.resolveDirectStatusUrl();
this.preferDirectStatus = !!this.directStatusUrl; this.preferDirectStatus = !!this.directStatusUrl;
@@ -124,6 +126,8 @@ export default class SafeUpgrade {
this.preferDirectStatus = !!this.directStatusUrl; this.preferDirectStatus = !!this.directStatusUrl;
this.statusContext = null; this.statusContext = null;
this.statusIdleCount = 0; this.statusIdleCount = 0;
this.currentStage = null;
this.stageEnteredAt = 0;
this.renderLoading(); this.renderLoading();
this.modal.open(); this.modal.open();
this.fetchPreflight(); this.fetchPreflight();
@@ -639,6 +643,11 @@ export default class SafeUpgrade {
} }
const stage = data.stage || 'initializing'; const stage = data.stage || 'initializing';
if (stage !== this.currentStage) {
this.currentStage = stage;
this.stageEnteredAt = Date.now();
}
const titleResolver = STAGE_TITLES[stage] || STAGE_TITLES.initializing; const titleResolver = STAGE_TITLES[stage] || STAGE_TITLES.initializing;
const title = titleResolver(); const title = titleResolver();
let percent = typeof data.percent === 'number' ? data.percent : null; let percent = typeof data.percent === 'number' ? data.percent : null;
@@ -648,12 +657,16 @@ export default class SafeUpgrade {
if (stage === 'initializing') { return percent !== null ? Math.min(percent, 5) : 5; } if (stage === 'initializing') { return percent !== null ? Math.min(percent, 5) : 5; }
if (stage === 'downloading') { if (stage === 'downloading') {
if (percent !== null) { if (percent !== null) {
return Math.min(60, Math.round(10 + (percent * 0.5))); return Math.min(20, Math.max(5, Math.round(percent * 0.2)));
} }
return 25; return 12;
}
if (stage === 'installing') {
return this.computeSmoothPercent(20, 90, 28, percent);
}
if (stage === 'finalizing') {
return this.computeSmoothPercent(90, 99, 6, percent);
} }
if (stage === 'installing') { return percent !== null ? Math.min(Math.max(percent, 80), 94) : 80; }
if (stage === 'finalizing') { return percent !== null ? Math.min(Math.max(percent, 95), 99) : 95; }
if (stage === 'complete') { return 100; } if (stage === 'complete') { return 100; }
if (stage === 'error') { return null; } if (stage === 'error') { return null; }
return percent; return percent;
@@ -665,11 +678,14 @@ export default class SafeUpgrade {
const statusLine = job && job.status ? `<p class="safe-upgrade-status">${t('SAFE_UPGRADE_JOB_STATUS', 'Status')}: <strong>${job.status.toUpperCase()}</strong>${job.error ? ` &mdash; ${job.error}` : ''}</p>` : ''; const statusLine = job && job.status ? `<p class="safe-upgrade-status">${t('SAFE_UPGRADE_JOB_STATUS', 'Status')}: <strong>${job.status.toUpperCase()}</strong>${job.error ? ` &mdash; ${job.error}` : ''}</p>` : '';
const animateBar = stage !== 'complete' && stage !== 'error' && percent !== null; const animateBar = stage !== 'complete' && stage !== 'error' && percent !== null;
const barClass = `safe-upgrade-progress-bar${animateBar ? ' is-active' : ''}`; const barClass = `safe-upgrade-progress-bar${animateBar ? ' is-active' : ''}`;
const detailMessage = stage === 'error'
? `<p>${data.message || ''}</p>`
: (data.message && stage !== 'installing' && stage !== 'finalizing' ? `<p>${data.message}</p>` : '');
this.steps.progress.html(` this.steps.progress.html(`
<div class="safe-upgrade-progress"> <div class="safe-upgrade-progress">
<h3>${title}</h3> <h3>${title}</h3>
<p>${data.message || ''}</p> ${detailMessage}
${statusLine} ${statusLine}
${percentLabel ? `<div class="${barClass}"><span style="width:${percent}%"></span></div><div class="progress-value">${percentLabel}</div>` : ''} ${percentLabel ? `<div class="${barClass}"><span style="width:${percent}%"></span></div><div class="progress-value">${percentLabel}</div>` : ''}
</div> </div>
@@ -746,6 +762,23 @@ export default class SafeUpgrade {
this.isPolling = false; this.isPolling = false;
this.clearPollTimer(); this.clearPollTimer();
} }
computeSmoothPercent(base, target, durationSeconds, actualPercent) {
const span = target - base;
if (span <= 0) {
return actualPercent !== null ? Math.min(Math.max(actualPercent, base), target) : base;
}
const elapsed = Math.max(0, (Date.now() - this.stageEnteredAt) / 1000);
const progressRatio = Math.min(1, elapsed / Math.max(durationSeconds, 1));
let smooth = base + (progressRatio * span);
if (actualPercent !== null && !Number.isNaN(actualPercent)) {
smooth = Math.max(smooth, Math.min(actualPercent, target));
}
return Math.min(smooth, target);
}
} }
function basename(path) { function basename(path) {

View File

@@ -4612,6 +4612,8 @@ var SafeUpgrade = /*#__PURE__*/function () {
this.statusFailures = 0; this.statusFailures = 0;
this.statusContext = null; this.statusContext = null;
this.statusIdleCount = 0; this.statusIdleCount = 0;
this.currentStage = null;
this.stageEnteredAt = 0;
this.directStatusUrl = this.resolveDirectStatusUrl(); this.directStatusUrl = this.resolveDirectStatusUrl();
this.preferDirectStatus = !!this.directStatusUrl; this.preferDirectStatus = !!this.directStatusUrl;
this.registerEvents(); this.registerEvents();
@@ -4680,6 +4682,8 @@ var SafeUpgrade = /*#__PURE__*/function () {
this.preferDirectStatus = !!this.directStatusUrl; this.preferDirectStatus = !!this.directStatusUrl;
this.statusContext = null; this.statusContext = null;
this.statusIdleCount = 0; this.statusIdleCount = 0;
this.currentStage = null;
this.stageEnteredAt = 0;
this.renderLoading(); this.renderLoading();
this.modal.open(); this.modal.open();
this.fetchPreflight(); this.fetchPreflight();
@@ -5101,9 +5105,14 @@ var SafeUpgrade = /*#__PURE__*/function () {
return; return;
} }
var stage = data.stage || 'initializing'; var stage = data.stage || 'initializing';
if (stage !== this.currentStage) {
this.currentStage = stage;
this.stageEnteredAt = Date.now();
}
var titleResolver = STAGE_TITLES[stage] || STAGE_TITLES.initializing; var titleResolver = STAGE_TITLES[stage] || STAGE_TITLES.initializing;
var title = titleResolver(); var title = titleResolver();
var percent = typeof data.percent === 'number' ? data.percent : null; var percent = typeof data.percent === 'number' ? data.percent : null;
var self = this;
var scaledPercent = function scaledPercent() { var scaledPercent = function scaledPercent() {
if (stage === 'queued') { if (stage === 'queued') {
return 0; return 0;
@@ -5113,15 +5122,15 @@ var SafeUpgrade = /*#__PURE__*/function () {
} }
if (stage === 'downloading') { if (stage === 'downloading') {
if (percent !== null) { if (percent !== null) {
return Math.min(60, Math.round(10 + percent * 0.5)); return Math.min(20, Math.max(5, Math.round(percent * 0.2)));
} }
return 25; return 12;
} }
if (stage === 'installing') { if (stage === 'installing') {
return percent !== null ? Math.min(Math.max(percent, 80), 94) : 80; return self.computeSmoothPercent(20, 90, 28, percent);
} }
if (stage === 'finalizing') { if (stage === 'finalizing') {
return percent !== null ? Math.min(Math.max(percent, 95), 99) : 95; return self.computeSmoothPercent(90, 99, 6, percent);
} }
if (stage === 'complete') { if (stage === 'complete') {
return 100; return 100;
@@ -5136,7 +5145,8 @@ var SafeUpgrade = /*#__PURE__*/function () {
var statusLine = job && job.status ? "<p class=\"safe-upgrade-status\">".concat(t('SAFE_UPGRADE_JOB_STATUS', 'Status'), ": <strong>").concat(job.status.toUpperCase(), "</strong>").concat(job.error ? " &mdash; ".concat(job.error) : '', "</p>") : ''; var statusLine = job && job.status ? "<p class=\"safe-upgrade-status\">".concat(t('SAFE_UPGRADE_JOB_STATUS', 'Status'), ": <strong>").concat(job.status.toUpperCase(), "</strong>").concat(job.error ? " &mdash; ".concat(job.error) : '', "</p>") : '';
var animateBar = stage !== 'complete' && stage !== 'error' && percent !== null; var animateBar = stage !== 'complete' && stage !== 'error' && percent !== null;
var barClass = "safe-upgrade-progress-bar".concat(animateBar ? ' is-active' : ''); var barClass = "safe-upgrade-progress-bar".concat(animateBar ? ' is-active' : '');
this.steps.progress.html("\n <div class=\"safe-upgrade-progress\">\n <h3>".concat(title, "</h3>\n <p>").concat(data.message || '', "</p>\n ").concat(statusLine, "\n ").concat(percentLabel ? "<div class=\"".concat(barClass, "\"><span style=\"width:").concat(percent, "%\"></span></div><div class=\"progress-value\">").concat(percentLabel, "</div>") : '', "\n </div>\n ")); var detailMessage = stage === 'error' ? "<p>".concat(data.message || '', "</p>") : data.message && stage !== 'installing' && stage !== 'finalizing' ? "<p>".concat(data.message, "</p>") : '';
this.steps.progress.html("\n <div class=\"safe-upgrade-progress\">\n <h3>".concat(title, "</h3>\n ").concat(detailMessage, "\n ").concat(statusLine, "\n ").concat(percentLabel ? "<div class=\"".concat(barClass, "\"><span style=\"width:").concat(percent, "%\"></span></div><div class=\"progress-value\">").concat(percentLabel, "</div>") : '', "\n </div>\n "));
this.switchStep('progress'); this.switchStep('progress');
if (stage === 'complete') { if (stage === 'complete') {
this.renderResult({ this.renderResult({
@@ -5187,6 +5197,24 @@ var SafeUpgrade = /*#__PURE__*/function () {
_this7.steps[handle].toggleClass('hidden', !isActive); _this7.steps[handle].toggleClass('hidden', !isActive);
}); });
} }
}, {
key: "computeSmoothPercent",
value: function computeSmoothPercent(base, target, durationSeconds, actualPercent) {
var span = target - base;
if (span <= 0) {
if (actualPercent !== null && !Number.isNaN(actualPercent)) {
return Math.min(Math.max(actualPercent, base), target);
}
return base;
}
var elapsed = Math.max(0, (Date.now() - this.stageEnteredAt) / 1000);
var ratio = Math.min(1, elapsed / Math.max(durationSeconds, 1));
var smooth = base + ratio * span;
if (actualPercent !== null && !Number.isNaN(actualPercent)) {
smooth = Math.max(smooth, Math.min(actualPercent, target));
}
return Math.min(smooth, target);
}
}, { }, {
key: "stopPolling", key: "stopPolling",
value: function stopPolling() { value: function stopPolling() {