From e1e9a0cf033ede096c2f67c96afe54b4e0065f3e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 2 Apr 2026 09:15:04 +0300 Subject: [PATCH] fix(iot-hub): suppress global error toast and add Back button on provisioning error - Add ignoreErrors: true to all entity creation API calls so errors are only shown in the dialog, not as global toast notifications - Add Back button on provisioning error to navigate back to the last form step, allowing users to fix parameters (e.g. device name) - Make form steps editable so stepper allows backward navigation - Reset progress step state when going back so it re-runs with updated form values --- .../device-install-dialog.component.html | 3 +- .../device-install-dialog.component.ts | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.html index 8bdce150f0..5ee5463c3f 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.html @@ -67,7 +67,7 @@ @for (ws of wizardSteps; track ws.label; let i = $index; let last = $last) { - {{ ws.label }} @@ -216,6 +216,7 @@ @case ('progress') { @if (step.progressError) { + diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts index e0e6b60f90..e0fc110045 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts @@ -191,6 +191,27 @@ export class TbDeviceInstallDialogComponent implements OnInit { this.runEntitySteps(step); } + goBackToForm(): void { + // Find the last form step before the current progress step + const currentIdx = this.stepper.selectedIndex; + for (let i = currentIdx - 1; i >= 0; i--) { + if (this.wizardSteps[i].type === 'form') { + // Reset the progress step so it re-runs when we come back + const progressStep = this.wizardSteps[currentIdx]; + progressStep.entitySteps = null; + progressStep.progressError = null; + progressStep.progressDone = false; + progressStep.completed = false; + // Allow navigation back by making intermediate steps editable + for (let j = i; j < currentIdx; j++) { + this.wizardSteps[j].completed = false; + } + this.stepper.selectedIndex = i; + return; + } + } + } + getPatternErrorMessage(field: FormFieldDefinition): string { return field.validators?.[0]?.message || 'Invalid format'; } @@ -411,16 +432,16 @@ export class TbDeviceInstallDialogComponent implements OnInit { if (existing) { return existing; } - const result = await firstValueFrom(this.deviceProfileService.saveDeviceProfile(template)); + const result = await firstValueFrom(this.deviceProfileService.saveDeviceProfile(template, {ignoreErrors: true})); return { id: result.id.id, name: result.name }; } case InstallStepType.DEVICE: { - const result = await firstValueFrom(this.deviceService.saveDevice(template)); - const creds = await firstValueFrom(this.deviceService.getDeviceCredentials(result.id.id)); + const result = await firstValueFrom(this.deviceService.saveDevice(template, {ignoreErrors: true})); + const creds = await firstValueFrom(this.deviceService.getDeviceCredentials(result.id.id, false, {ignoreErrors: true})); return { id: result.id.id, name: result.name, token: creds.credentialsId }; } case InstallStepType.DASHBOARD: { - const result = await firstValueFrom(this.dashboardService.saveDashboard(template)); + const result = await firstValueFrom(this.dashboardService.saveDashboard(template, {ignoreErrors: true})); return { id: result.id.id, name: result.title }; } case InstallStepType.RULE_CHAIN: { @@ -430,10 +451,10 @@ export class TbDeviceInstallDialogComponent implements OnInit { if (existing) { return existing; } - const saved = await firstValueFrom(this.ruleChainService.saveRuleChain(ruleChain)); + const saved = await firstValueFrom(this.ruleChainService.saveRuleChain(ruleChain, {ignoreErrors: true})); if (metadata) { metadata.ruleChainId = saved.id; - await firstValueFrom(this.ruleChainService.saveRuleChainMetadata(metadata)); + await firstValueFrom(this.ruleChainService.saveRuleChainMetadata(metadata, {ignoreErrors: true})); } return { id: saved.id.id, name: saved.name }; }