From 8c03c73b284381d5634410f6ff63db92c99e8a8e Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Fri, 28 Nov 2025 12:16:27 +0100 Subject: [PATCH] platform: show any container upgrade errors in the UI --- dashboard/src/components/Headerbar.vue | 10 ++++++---- src/platform.js | 21 +++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/dashboard/src/components/Headerbar.vue b/dashboard/src/components/Headerbar.vue index d253626a4..d63528845 100644 --- a/dashboard/src/components/Headerbar.vue +++ b/dashboard/src/components/Headerbar.vue @@ -85,7 +85,7 @@ function onSubscriptionRequired() { const platformStatus = ref({ message: '', - isReady: true, + state: '', }); let platformTimeoutId = 0; @@ -95,7 +95,7 @@ async function trackPlatformStatus() { platformStatus.value = result; - if (!result.isReady) platformTimeoutId = setTimeout(trackPlatformStatus, 5000); + if (result.state === 'starting') platformTimeoutId = setTimeout(trackPlatformStatus, 5000); } const description = marked.parse(t('support.help.description', { @@ -158,8 +158,10 @@ onUnmounted(() => {
-
- {{ platformStatus.message }} +
+ + + {{ platformStatus.message }}
diff --git a/src/platform.js b/src/platform.js index e72d08c56..8e71e74ce 100644 --- a/src/platform.js +++ b/src/platform.js @@ -40,10 +40,10 @@ const apps = require('./apps.js'), volumes = require('./volumes.js'), _ = require('./underscore.js'); -let gStatusMessage = 'Initializing'; +const gStatus = { message: 'Initializing', state: 'starting' }; // starting, failed, ready are the states function getStatus() { - return { message: gStatusMessage, isReady: gStatusMessage === 'Ready' }; + return gStatus; } async function pruneVolumes() { @@ -105,7 +105,8 @@ async function markApps(existingInfra, restoreOptions) { async function onInfraReady(infraChanged) { debug(`onInfraReady: platform is ready. infra changed: ${infraChanged}`); - gStatusMessage = 'Ready'; + gStatus.message = 'Ready'; + gStatus.state = 'ready'; if (infraChanged) await safe(pruneVolumes(), { debug }); // ignore error await apps.schedulePendingTasks(AuditSource.PLATFORM); @@ -137,14 +138,14 @@ async function startInfra(restoreOptions) { for (let attempt = 0; attempt < 5; attempt++) { try { await markApps(existingInfra, restoreOptions); // mark app state before we start addons. this gives the db import logic a chance to mark an app as errored - gStatusMessage = 'Updating platform, this can take a while'; + gStatus.message = 'Updating platform, this can take a while'; if (existingInfra.version !== infra.version) { - gStatusMessage = 'Removing containers for upgrade'; + gStatus.message = 'Removing containers for upgrade'; await removeAllContainers(); await createDockerNetwork(); } if (existingInfra.version === 'none') await volumes.mountAll(); // when restoring, mount all volumes - gStatusMessage = 'Starting services, this can take a while'; + gStatus.message = 'Starting services, this can take a while'; await services.startServices(existingInfra); await fs.promises.writeFile(paths.INFRA_VERSION_FILE, JSON.stringify(infra, null, 4)); break; @@ -152,8 +153,12 @@ async function startInfra(restoreOptions) { // for some reason, mysql arbitrary restarts making startup tasks fail. this makes the box update stuck // LOST is when existing connection breaks. REFUSED is when new connection cannot connect at all const retry = error.reason === BoxError.DATABASE_ERROR && (error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ECONNREFUSED'); - debug(`startInfra: Failed to start services. retry=${retry} (attempt ${attempt}): ${error.message}`); - if (!retry) throw error; // refuse to start + debug(`startInfra: Failed to start services. retry=${retry} (attempt ${attempt}): ${error}`); + if (!retry) { + gStatus.message = `Failed to start services. ${error.stdout ?? ''} ${error.stderr ?? ''}`; + gStatus.state = 'failed'; + throw error; // refuse to start + } await timers.setTimeout(10000); } }