From d733e63b6bd14b110848b0288a055f249fe8e786 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 09:13:05 +0000 Subject: [PATCH 01/10] fix(i18n): add new error messages for ACME certificate issues --- core/ui/public/i18n/en/translation.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/ui/public/i18n/en/translation.json b/core/ui/public/i18n/en/translation.json index d0afa9188..d9c23c9eb 100644 --- a/core/ui/public/i18n/en/translation.json +++ b/core/ui/public/i18n/en/translation.json @@ -913,7 +913,9 @@ "invalid_format": "Invalid format", "cert_verification_failed_selfsigned": "Self-signed certificates are not allowed", "cert_verification_failed_chain": "Certificate chain is incomplete or invalid", - "newcert_acme_error": "Cannot obtain certificate. Check the 'Request certificate' error in the notification drawer for details." + "newcert_acme_error": "Cannot obtain certificate. Check the 'Request certificate' error in the notification drawer for details.", + "node_is_offline": "Node {nodeId}{nodeUiName} is offline", + "certificates_not_displayed": "Certificates configured on {instanceId} are not displayed" }, "settings_acme_servers": { "title": "ACME servers", From aa12ad3d1e769a8e00784f0c7ac8181c89ffa137 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 09:13:13 +0000 Subject: [PATCH 02/10] feat(ui): add notifications for offline Traefik instances in TLS certificates settings --- .../settings/SettingsTlsCertificates.vue | 76 +++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 8abb83956..031dfaef4 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -53,6 +53,16 @@ +
+ +
{ + this.listCertificatesAborted( + taskResult, + taskContext, + traefikInstance + ); + } ); this.$root.$once( @@ -930,20 +947,28 @@ export default { const err = res[0]; if (err) { - console.error(`error creating task ${taskAction}`, err); - const errMessage = this.getErrorMessage(err); - this.error.listCertificates = errMessage; - this.currentErrorAction = this.$t("action." + taskAction); - this.currentErrorDescription = errMessage; + // Add to offline instances so the error notification is displayed + if ( + traefikInstance && + !this.offlineTraefikInstances.find( + (instance) => instance.id === traefikInstance.id + ) + ) { + this.offlineTraefikInstances.push(traefikInstance); + } this.loading.listCertificatesNum--; } } }, - listCertificatesAborted(taskResult, taskContext) { - console.error(`${taskContext.action} aborted`, taskResult); - this.error.listCertificates = this.$t("error.generic_error"); - this.currentErrorAction = this.$t("action." + taskContext.action); - this.currentErrorDescription = this.$t("error.generic_error"); + listCertificatesAborted(taskResult, taskContext, traefikInstance) { + if ( + traefikInstance && + !this.offlineTraefikInstances.find( + (instance) => instance.id === traefikInstance.id + ) + ) { + this.offlineTraefikInstances.push(traefikInstance); + } this.loading.listCertificatesNum--; }, listCertificatesCompleted(taskContext, taskResult) { @@ -1032,6 +1057,29 @@ export default { return "gray"; } }, + getOfflineInstanceTitle(instance) { + let nodeUiNameDisplay = ""; + if (instance.node_ui_name && instance.node_ui_name.trim()) { + nodeUiNameDisplay = ` (${instance.node_ui_name})`; + } + + return this.$t("settings_tls_certificates.node_is_offline", { + nodeId: instance.node, + nodeUiName: nodeUiNameDisplay, + }); + }, + getOfflineInstanceDescription(instance) { + let instanceLabel = instance.id; + + // Add traefik instance ui_name in parentheses if it exists and is not empty + if (instance.ui_name && instance.ui_name.trim()) { + instanceLabel = `${instance.id} (${instance.ui_name})`; + } + + return this.$t("settings_tls_certificates.certificates_not_displayed", { + instanceId: instanceLabel, + }); + }, clearFilters() { this.q.selectedNodeId = "any"; this.filter.text = ""; From 76307ef6e485a062af65e1906cdf9d99ff2c8195 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 11:50:41 +0000 Subject: [PATCH 03/10] feat(ui): add error notifications for certificate listing failures --- .../settings/SettingsTlsCertificates.vue | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 031dfaef4..452955f14 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -63,6 +63,16 @@ :showCloseButton="false" />
+
+ +
instance.id === traefikInstance.id - ) - ) { - this.offlineTraefikInstances.push(traefikInstance); + console.error( + `${taskContext.action} aborted for instance ${traefikInstance.id} on node ${traefikInstance.node}`, + taskResult + ); + // Build module part: module_name(module_id) or module_id + let modulePart = traefikInstance.id; + if (traefikInstance.ui_name && traefikInstance.ui_name.trim()) { + modulePart = `${traefikInstance.ui_name}(${traefikInstance.id})`; } + + // Build node part: node_ui_name (node_id) or (node_id) + let nodePart = `(${traefikInstance.node})`; + if (traefikInstance.node_ui_name && traefikInstance.node_ui_name.trim()) { + nodePart = `${traefikInstance.node_ui_name} (${this.$t("common.node")} ${traefikInstance.node})`; + } + + // Add error to array + this.listCertificatesErrors.push({ + title: this.$t("action." + taskContext.action), + description: `${this.$t("error.generic_error")} (${modulePart} - ${nodePart})` + }); this.loading.listCertificatesNum--; }, listCertificatesCompleted(taskContext, taskResult) { @@ -1073,7 +1102,7 @@ export default { // Add traefik instance ui_name in parentheses if it exists and is not empty if (instance.ui_name && instance.ui_name.trim()) { - instanceLabel = `${instance.id} (${instance.ui_name})`; + instanceLabel = `${instance.ui_name} (${instance.id})`; } return this.$t("settings_tls_certificates.certificates_not_displayed", { From e9e6c68a7f4544db48be906f84e81e57b44c38f3 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 11:55:25 +0000 Subject: [PATCH 04/10] fix(i18n): improve node display format in Traefik instance settings --- core/ui/src/views/settings/SettingsTlsCertificates.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 452955f14..6ff827c34 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -988,7 +988,7 @@ export default { } // Build node part: node_ui_name (node_id) or (node_id) - let nodePart = `(${traefikInstance.node})`; + let nodePart = `${this.$t("common.node")} ${traefikInstance.node}`; if (traefikInstance.node_ui_name && traefikInstance.node_ui_name.trim()) { nodePart = `${traefikInstance.node_ui_name} (${this.$t("common.node")} ${traefikInstance.node})`; } From 512a1be378a47f7cec6e18688418cedfa0a6eac6 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 13:51:31 +0000 Subject: [PATCH 05/10] fix(i18n): update offline node message format in TLS certificates settings --- core/ui/public/i18n/en/translation.json | 2 +- .../settings/SettingsTlsCertificates.vue | 36 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/core/ui/public/i18n/en/translation.json b/core/ui/public/i18n/en/translation.json index d9c23c9eb..f1944b198 100644 --- a/core/ui/public/i18n/en/translation.json +++ b/core/ui/public/i18n/en/translation.json @@ -914,7 +914,7 @@ "cert_verification_failed_selfsigned": "Self-signed certificates are not allowed", "cert_verification_failed_chain": "Certificate chain is incomplete or invalid", "newcert_acme_error": "Cannot obtain certificate. Check the 'Request certificate' error in the notification drawer for details.", - "node_is_offline": "Node {nodeId}{nodeUiName} is offline", + "node_is_offline": "{node} is offline", "certificates_not_displayed": "Certificates configured on {instanceId} are not displayed" }, "settings_acme_servers": { diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 6ff827c34..c7257be62 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -58,7 +58,11 @@ v-for="instance in offlineTraefikInstances" :key="instance.id" kind="error" - :title="getOfflineInstanceTitle(instance)" + :title=" + $t('settings_tls_certificates.node_is_offline', { + node: getOfflineInstanceTitle(instance), + }) + " :description="getOfflineInstanceDescription(instance)" :showCloseButton="false" /> @@ -986,17 +990,21 @@ export default { if (traefikInstance.ui_name && traefikInstance.ui_name.trim()) { modulePart = `${traefikInstance.ui_name}(${traefikInstance.id})`; } - + // Build node part: node_ui_name (node_id) or (node_id) let nodePart = `${this.$t("common.node")} ${traefikInstance.node}`; if (traefikInstance.node_ui_name && traefikInstance.node_ui_name.trim()) { - nodePart = `${traefikInstance.node_ui_name} (${this.$t("common.node")} ${traefikInstance.node})`; + nodePart = `${traefikInstance.node_ui_name} (${this.$t( + "common.node" + )} ${traefikInstance.node})`; } - + // Add error to array this.listCertificatesErrors.push({ title: this.$t("action." + taskContext.action), - description: `${this.$t("error.generic_error")} (${modulePart} - ${nodePart})` + description: `${this.$t( + "error.generic_error" + )} (${modulePart} - ${nodePart})`, }); this.loading.listCertificatesNum--; }, @@ -1087,15 +1095,15 @@ export default { } }, getOfflineInstanceTitle(instance) { - let nodeUiNameDisplay = ""; - if (instance.node_ui_name && instance.node_ui_name.trim()) { - nodeUiNameDisplay = ` (${instance.node_ui_name})`; - } - - return this.$t("settings_tls_certificates.node_is_offline", { - nodeId: instance.node, - nodeUiName: nodeUiNameDisplay, - }); + const nodeLabel = instance.node_ui_name + ? instance.node_ui_name + + " (" + + this.$t("common.node") + + " " + + instance.node + + ")" + : this.$t("common.node") + " " + instance.node; + return nodeLabel; }, getOfflineInstanceDescription(instance) { let instanceLabel = instance.id; From bb6fe7335b69ad28e049704ee9cbeb7d36f2dbff Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 14:57:05 +0000 Subject: [PATCH 06/10] fix(i18n): refactor instance label functions for improved clarity in error messages --- .../settings/SettingsTlsCertificates.vue | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index c7257be62..be122681a 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -60,7 +60,7 @@ kind="error" :title=" $t('settings_tls_certificates.node_is_offline', { - node: getOfflineInstanceTitle(instance), + node: getNodeLabel(instance), }) " :description="getOfflineInstanceDescription(instance)" @@ -985,26 +985,15 @@ export default { `${taskContext.action} aborted for instance ${traefikInstance.id} on node ${traefikInstance.node}`, taskResult ); - // Build module part: module_name(module_id) or module_id - let modulePart = traefikInstance.id; - if (traefikInstance.ui_name && traefikInstance.ui_name.trim()) { - modulePart = `${traefikInstance.ui_name}(${traefikInstance.id})`; - } - - // Build node part: node_ui_name (node_id) or (node_id) - let nodePart = `${this.$t("common.node")} ${traefikInstance.node}`; - if (traefikInstance.node_ui_name && traefikInstance.node_ui_name.trim()) { - nodePart = `${traefikInstance.node_ui_name} (${this.$t( - "common.node" - )} ${traefikInstance.node})`; - } // Add error to array this.listCertificatesErrors.push({ title: this.$t("action." + taskContext.action), description: `${this.$t( "error.generic_error" - )} (${modulePart} - ${nodePart})`, + )} (${this.getTraefikInstanceLabel( + traefikInstance + )} - ${this.getNodeLabel(traefikInstance)})`, }); this.loading.listCertificatesNum--; }, @@ -1094,7 +1083,7 @@ export default { return "gray"; } }, - getOfflineInstanceTitle(instance) { + getNodeLabel(instance) { const nodeLabel = instance.node_ui_name ? instance.node_ui_name + " (" + @@ -1105,16 +1094,16 @@ export default { : this.$t("common.node") + " " + instance.node; return nodeLabel; }, - getOfflineInstanceDescription(instance) { - let instanceLabel = instance.id; - - // Add traefik instance ui_name in parentheses if it exists and is not empty + getTraefikInstanceLabel(instance) { + let label = instance.id; if (instance.ui_name && instance.ui_name.trim()) { - instanceLabel = `${instance.ui_name} (${instance.id})`; + label = `${instance.ui_name}(${instance.id})`; } - + return label; + }, + getOfflineInstanceDescription(instance) { return this.$t("settings_tls_certificates.certificates_not_displayed", { - instanceId: instanceLabel, + instanceId: this.getTraefikInstanceLabel(instance), }); }, clearFilters() { From 840de40cb53d2dc96f2f4db78f6595fd81a81b2f Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Tue, 3 Mar 2026 15:16:00 +0000 Subject: [PATCH 07/10] fix(ui): reset loading state for certificate listing and ensure error notifications display --- core/ui/src/views/settings/SettingsTlsCertificates.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index be122681a..8e62c3902 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -922,6 +922,7 @@ export default { async listCertificates() { this.offlineTraefikInstances = []; this.listCertificatesErrors = []; + this.loading.listCertificatesNum = 0; for (const traefikInstance of this.traefikInstances) { const taskAction = "list-certificates"; @@ -976,6 +977,7 @@ export default { ) { this.offlineTraefikInstances.push(traefikInstance); } + // force error notification to be displayed this.loading.listCertificatesNum--; } } From 42ddb28e320bd4b1e5ec2fd20c0ca754b8bcb5c2 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Wed, 4 Mar 2026 08:44:22 +0000 Subject: [PATCH 08/10] fix(ui): improve parallel task handling for listing certificates to prevent blocking on offline nodes --- .../settings/SettingsTlsCertificates.vue | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 8e62c3902..2a781eb89 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -924,7 +924,9 @@ export default { this.listCertificatesErrors = []; this.loading.listCertificatesNum = 0; - for (const traefikInstance of this.traefikInstances) { + // Build all task creation promises in parallel so that a slow or + // hanging HTTP request on an offline node does not block the others. + const taskPromises = this.traefikInstances.map((traefikInstance) => { const taskAction = "list-certificates"; const eventId = this.getUuid(); this.loading.listCertificatesNum++; @@ -947,7 +949,7 @@ export default { this.listCertificatesCompleted ); - const res = await to( + return to( this.createModuleTaskForApp(traefikInstance.id, { action: taskAction, data: { @@ -960,27 +962,31 @@ export default { eventId, }, }) - ); - const err = res[0]; - - if (err) { - console.error( - `error creating task ${taskAction} for instance ${traefikInstance.id} on node ${traefikInstance.node}`, - err - ); - // Add to offline instances so the error notification is displayed - if ( - traefikInstance && - !this.offlineTraefikInstances.find( - (instance) => instance.id === traefikInstance.id - ) - ) { - this.offlineTraefikInstances.push(traefikInstance); + ).then(([err]) => { + if (err) { + console.error( + `error creating task ${taskAction} for instance ${traefikInstance.id} on node ${traefikInstance.node}`, + err + ); + // Clean up orphaned event listeners + this.$root.$off(`${taskAction}-aborted-${eventId}`); + this.$root.$off(`${taskAction}-completed-${eventId}`); + // Add to offline instances so the error notification is displayed + if ( + traefikInstance && + !this.offlineTraefikInstances.find( + (instance) => instance.id === traefikInstance.id + ) + ) { + this.offlineTraefikInstances.push(traefikInstance); + } + this.loading.listCertificatesNum--; } - // force error notification to be displayed - this.loading.listCertificatesNum--; - } - } + }); + }); + // Wait for all task creations to settle (succeed or fail) before + // returning. + await Promise.allSettled(taskPromises); }, listCertificatesAborted(taskResult, taskContext, traefikInstance) { console.error( From 66f70719c0abb5a449b51f828bc16c8f0f774305 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Wed, 4 Mar 2026 10:32:08 +0000 Subject: [PATCH 09/10] Revert "fix(ui): improve parallel task handling for listing certificates to prevent blocking on offline nodes" This reverts commit 42ddb28e320bd4b1e5ec2fd20c0ca754b8bcb5c2. --- .../settings/SettingsTlsCertificates.vue | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 2a781eb89..8e62c3902 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -924,9 +924,7 @@ export default { this.listCertificatesErrors = []; this.loading.listCertificatesNum = 0; - // Build all task creation promises in parallel so that a slow or - // hanging HTTP request on an offline node does not block the others. - const taskPromises = this.traefikInstances.map((traefikInstance) => { + for (const traefikInstance of this.traefikInstances) { const taskAction = "list-certificates"; const eventId = this.getUuid(); this.loading.listCertificatesNum++; @@ -949,7 +947,7 @@ export default { this.listCertificatesCompleted ); - return to( + const res = await to( this.createModuleTaskForApp(traefikInstance.id, { action: taskAction, data: { @@ -962,31 +960,27 @@ export default { eventId, }, }) - ).then(([err]) => { - if (err) { - console.error( - `error creating task ${taskAction} for instance ${traefikInstance.id} on node ${traefikInstance.node}`, - err - ); - // Clean up orphaned event listeners - this.$root.$off(`${taskAction}-aborted-${eventId}`); - this.$root.$off(`${taskAction}-completed-${eventId}`); - // Add to offline instances so the error notification is displayed - if ( - traefikInstance && - !this.offlineTraefikInstances.find( - (instance) => instance.id === traefikInstance.id - ) - ) { - this.offlineTraefikInstances.push(traefikInstance); - } - this.loading.listCertificatesNum--; + ); + const err = res[0]; + + if (err) { + console.error( + `error creating task ${taskAction} for instance ${traefikInstance.id} on node ${traefikInstance.node}`, + err + ); + // Add to offline instances so the error notification is displayed + if ( + traefikInstance && + !this.offlineTraefikInstances.find( + (instance) => instance.id === traefikInstance.id + ) + ) { + this.offlineTraefikInstances.push(traefikInstance); } - }); - }); - // Wait for all task creations to settle (succeed or fail) before - // returning. - await Promise.allSettled(taskPromises); + // force error notification to be displayed + this.loading.listCertificatesNum--; + } + } }, listCertificatesAborted(taskResult, taskContext, traefikInstance) { console.error( From ea62bae3381175e51cef19b31bf9b513b2361e22 Mon Sep 17 00:00:00 2001 From: Stephane de Labrusse Date: Wed, 4 Mar 2026 11:47:04 +0000 Subject: [PATCH 10/10] fix(ui): enhance error handling for task creation and improve offline instance tracking --- .../settings/SettingsTlsCertificates.vue | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/core/ui/src/views/settings/SettingsTlsCertificates.vue b/core/ui/src/views/settings/SettingsTlsCertificates.vue index 8e62c3902..d61cce9bd 100644 --- a/core/ui/src/views/settings/SettingsTlsCertificates.vue +++ b/core/ui/src/views/settings/SettingsTlsCertificates.vue @@ -968,16 +968,31 @@ export default { `error creating task ${taskAction} for instance ${traefikInstance.id} on node ${traefikInstance.node}`, err ); - // Add to offline instances so the error notification is displayed - if ( - traefikInstance && - !this.offlineTraefikInstances.find( - (instance) => instance.id === traefikInstance.id - ) - ) { - this.offlineTraefikInstances.push(traefikInstance); + // Clean up orphaned event listeners since the task was never created + this.$root.$off(`${taskAction}-aborted-${eventId}`); + this.$root.$off(`${taskAction}-completed-${eventId}`); + + if (err.response && err.response.status === 404) { + // 404 means the module API is unreachable: node is offline + if ( + traefikInstance && + !this.offlineTraefikInstances.find( + (instance) => instance.id === traefikInstance.id + ) + ) { + this.offlineTraefikInstances.push(traefikInstance); + } + } else { + // Unexpected error: display a generic inline error notification + this.listCertificatesErrors.push({ + title: this.$t("action." + taskAction), + description: `${this.$t( + "error.generic_error" + )} (${this.getTraefikInstanceLabel( + traefikInstance + )} - ${this.getNodeLabel(traefikInstance)})`, + }); } - // force error notification to be displayed this.loading.listCertificatesNum--; } }