From 12905cb0d270b3d5f87a0d987d633e363b317d4d Mon Sep 17 00:00:00 2001 From: dshvaika Date: Mon, 25 May 2026 14:50:43 +0300 Subject: [PATCH 1/2] feat(iot-hub): single-step install dialog with rule chain profile toggle CF and Rule Chain installs now open directly into the selection form, skipping the prior "Install?" confirmation step. Rule Chain uses a single "Set as profile default rule chain" toggle that reveals the profile pickers when enabled, replacing the previous two-button design. The overwrite-confirmation step is preserved. Follow-up to #15645. --- .../iot-hub-install-dialog.component.html | 81 ++++++++++------ .../iot-hub-install-dialog.component.ts | 93 +++++++++++++------ .../assets/locale/locale.constant-en_US.json | 8 +- 3 files changed, 118 insertions(+), 64 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html index 19a43fcb30..b144c3f35d 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html @@ -19,26 +19,55 @@ @switch (state) { @case ('confirm') {

{{ 'iot-hub.install-confirm-title' | translate:{ name: item.name } }}

- @if (item.type === ItemType.RULE_CHAIN) { -

{{ 'iot-hub.rule-chain-install-desc' | translate }}

- } @else { -

{{ 'iot-hub.install-desc' | translate }}

- } +

{{ 'iot-hub.install-desc' | translate }}

} @case ('select-entity') {

{{ 'iot-hub.install-confirm-title' | translate:{ name: item.name } }}

- @if (activeSelectEntityConfig?.promptKey) { -

+ @if (item.type === ItemType.RULE_CHAIN) { +

{{ 'iot-hub.rule-chain-install-subtitle' | translate }}

+
+
+ +
+ iot-hub.rule-chain-set-as-profile-default-toggle +
+
+
+ @if (ruleChainInstallForm.controls.setAsDefault.value) { +
+ + + + +
+ } +
+ } @else { + @if (activeSelectEntityConfig?.promptKey) { +

+ } + + } - - } @case ('confirm-overwrite') {

{{ 'iot-hub.rule-chain-overwrite-title' | translate }}

@@ -59,7 +88,7 @@ @case ('error') {

{{ 'iot-hub.install-error-title' | translate }}

{{ 'iot-hub.install-error-message' | translate:{ name: item.name } }}

- @if (item?.type === ItemType.SOLUTION_TEMPLATE) { + @if (item.type === ItemType.SOLUTION_TEMPLATE) { } @else {
@@ -73,28 +102,20 @@ @switch (state) { @case ('confirm') { - @if (item.type === ItemType.RULE_CHAIN) { - - - } @else { - - } + } @case ('select-entity') { @if (item.type === ItemType.RULE_CHAIN) { - } @else { } diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts index be37e6e1bb..6484a32c4b 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts @@ -14,9 +14,11 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { Component, DestroyRef, Inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { DialogComponent } from '@shared/components/dialog.component'; @@ -70,15 +72,21 @@ export type InstallState = export class TbIotHubInstallDialogComponent extends DialogComponent { ItemType = ItemType; + EntityType = EntityType; item: MpItemVersionView; typeTranslations = itemTypeTranslations; - state: InstallState = 'confirm'; + state!: InstallState; errorMessage = ''; entityDetailsUrl: string | null = null; selectedEntityId: EntityId | null = null; pendingOverwrite: PendingOverwrite | null = null; + ruleChainInstallForm!: FormGroup<{ + setAsDefault: FormControl; + entityType: FormControl; + entityId: FormControl; + }>; private readonly selectEntityConfig: Partial> = { [ItemType.CALCULATED_FIELD]: { @@ -87,12 +95,6 @@ export class TbIotHubInstallDialogComponent extends DialogComponent(false), + entityType: this.fb.nonNullable.control(EntityType.DEVICE_PROFILE), + entityId: this.fb.control(null) + }); + this.ruleChainInstallForm.controls.setAsDefault.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(setAsDefault => { + const entityIdCtrl = this.ruleChainInstallForm.controls.entityId; + if (setAsDefault) { + entityIdCtrl.setValidators(Validators.required); + } else { + entityIdCtrl.clearValidators(); + entityIdCtrl.setValue(null); + } + entityIdCtrl.updateValueAndValidity(); + }); + this.ruleChainInstallForm.controls.entityType.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.ruleChainInstallForm.controls.entityId.setValue(null); + }); } getTypeLabel(): string { @@ -120,39 +159,33 @@ export class TbIotHubInstallDialogComponent extends DialogComponent { if (pending) { this.pendingOverwrite = pending; this.state = 'confirm-overwrite'; } else { this.pendingOverwrite = null; - this.doInstall(); + this.install(); } }, error: (err) => { @@ -164,7 +197,7 @@ export class TbIotHubInstallDialogComponent extends DialogComponent{{profile}} currently uses {{existing}} as its default rule chain. Installing will replace it with this rule chain.", "rule-chain-overwrite-replace": "Replace", From af1b888cf01a6b563fb9c100d230cb834be19270 Mon Sep 17 00:00:00 2001 From: dshvaika Date: Mon, 25 May 2026 15:03:08 +0300 Subject: [PATCH 2/2] fix(iot-hub): polish rule chain install dialog layout and copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Group the slide-toggle and the conditionally rendered profile pickers in a single bordered card via tb-form-panel.stroked, matching the 4.3 entity-aggregation 'Apply await timeout' pattern. - Set the toggle's class to 'mat-slide flex' to match the canonical TB idiom; switch the tooltip label to plain interpolation. - Restore the trailing '?' in install-confirm-title — the confirm step is still a real confirmation question for item types that don't go through the select-entity flow. --- .../iot-hub-install-dialog.component.html | 50 +++++++++---------- .../assets/locale/locale.constant-en_US.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html index b144c3f35d..102a55ad5e 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html @@ -25,34 +25,34 @@

{{ 'iot-hub.install-confirm-title' | translate:{ name: item.name } }}

@if (item.type === ItemType.RULE_CHAIN) {

{{ 'iot-hub.rule-chain-install-subtitle' | translate }}

-
-
- -
- iot-hub.rule-chain-set-as-profile-default-toggle + +
+ +
+ {{ 'iot-hub.rule-chain-set-as-profile-default-toggle' | translate }}
+ @if (ruleChainInstallForm.controls.setAsDefault.value) { +
+ + + + +
+ }
- @if (ruleChainInstallForm.controls.setAsDefault.value) { -
- - - - -
- } } @else { @if (activeSelectEntityConfig?.promptKey) { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 7444a27506..73a7dce751 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3802,7 +3802,7 @@ "changelog": "Changelog", "created-by": "Created by {{name}}", "install-confirm": "Install {{name}} v{{version}}?", - "install-confirm-title": "Install '{{name}}'", + "install-confirm-title": "Install '{{name}}'?", "install-desc": "Installing this will automatically create and configure the necessary resources within your tenant", "install-success-desc": "'{{name}}' has been installed successfully", "remove": "Remove",