diff --git a/controller/templates/index.html b/controller/templates/index.html
index 1ebc3d0c..f8a39178 100644
--- a/controller/templates/index.html
+++ b/controller/templates/index.html
@@ -2306,6 +2306,20 @@
} else {
upgradeHttpsBase = `https://${window.location.hostname}`;
}
+ // Two-phase upgrade detection:
+ // Phase 1: Wait for services to go DOWN (confirms upgrade actually started).
+ // Phase 2: Wait for services to come back UP (upgrade complete).
+ // State is persisted in sessionStorage so it survives page refreshes.
+ let upgradeServicesWentDown = sessionStorage.getItem('upgradeServicesWentDown') === 'true';
+
+ const markServicesDown = () => {
+ if (!upgradeServicesWentDown) {
+ upgradeServicesWentDown = true;
+ sessionStorage.setItem('upgradeServicesWentDown', 'true');
+ document.getElementById('upgrade-message').textContent = 'Upgrading... Services restarting.';
+ }
+ };
+
const checkUpgradeComplete = async () => {
try {
// WARNING: This must be a relative URL (no leading slash) so it resolves to
@@ -2318,8 +2332,14 @@
});
if (resp.ok) {
const data = await resp.json();
- if (data.api && data.app) {
- // Upgrade complete!
+ if (!upgradeServicesWentDown) {
+ // Phase 1: waiting for services to go down
+ if (!data.api || !data.app) {
+ markServicesDown();
+ }
+ } else if (data.api && data.app) {
+ // Phase 2: services are back up — upgrade complete!
+ sessionStorage.removeItem('upgradeServicesWentDown');
document.getElementById('upgrade-spinner').style.display = 'none';
document.getElementById('upgrade-success').style.display = 'inline';
document.getElementById('upgrade-message').textContent = 'Upgrade complete! Redirecting...';
@@ -2330,9 +2350,13 @@
window.location.href = `${upgradeHttpsBase}/`;
}, 2000);
}
+ } else {
+ // Non-ok response means services are going down
+ markServicesDown();
}
} catch (e) {
- // Still upgrading, continue polling
+ // Fetch failed (connection refused) — services are down
+ markServicesDown();
}
};