diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 02c1c8649a..971f037282 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -16,16 +16,17 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; import { - AggregationType, calculateIntervalComparisonEndTime, - calculateIntervalEndTime, calculateIntervalStartEndTime, + AggregationType, + calculateIntervalComparisonEndTime, + calculateIntervalEndTime, + calculateIntervalStartEndTime, getCurrentTime, - getCurrentTimeForComparison, getTime, + getTime, SubscriptionTimewindow } from '@shared/models/time/time.models'; import { UtilsService } from '@core/services/utils.service'; -import { deepClone } from '@core/utils'; +import { deepClone, isNumeric } from '@core/utils'; import Timeout = NodeJS.Timeout; -import * as moment_ from 'moment'; export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; @@ -407,24 +408,11 @@ export class DataAggregator { } } - private isNumeric(val: any): boolean { - return (val - parseFloat( val ) + 1) >= 0; - } - private convertValue(val: string): any { - if (!this.noAggregation || val && this.isNumeric(val)) { + if (!this.noAggregation || val && isNumeric(val) && Number(val).toString() === val) { return Number(val); - } else { - return val; - } - } - - private getCurrentTime() { - if (this.subsTw.timeForComparison) { - return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone); - } else { - return getCurrentTime(this.subsTw.timezone); } + return val; } } diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 9c769f3777..ff828cc2ac 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -38,7 +38,7 @@ import { } from '@shared/models/telemetry/telemetry.models'; import { UtilsService } from '@core/services/utils.service'; import { EntityDataListener, EntityDataLoadResult } from '@core/api/entity-data.service'; -import { deepClone, isDefined, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils'; +import { deepClone, isDefined, isDefinedAndNotNull, isNumeric, isObject, objectHashCode } from '@core/utils'; import { PageData } from '@shared/models/page/page-data'; import { DataAggregator } from '@core/api/data-aggregator'; import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -667,8 +667,7 @@ export class EntityDataSubscription { if (prevDataCb) { dataAggregator.updateOnDataCb(prevDataCb); } - } - if (!this.history && !isUpdate) { + } else if (!this.history && !isUpdate) { this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb); } } @@ -743,16 +742,11 @@ export class EntityDataSubscription { } } - private isNumeric(val: any): boolean { - return (val - parseFloat( val ) + 1) >= 0; - } - private convertValue(val: string): any { - if (val && this.isNumeric(val) && Number(val).toString() === val) { + if (val && isNumeric(val) && Number(val).toString() === val) { return Number(val); - } else { - return val; } + return val; } private toSubscriptionData(sourceData: {[key: string]: TsValue | TsValue[]}, isTs: boolean): SubscriptionData { diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index f667fb5788..0dc143e6cb 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -231,7 +231,6 @@ export class DashboardUtilsService { private createDefaultGridSettings(): GridSettings { return { backgroundColor: '#eeeeee', - color: 'rgba(0,0,0,0.870588)', columns: 24, margin: 10, backgroundSizeMode: '100%' diff --git a/ui-ngx/src/app/core/services/mobile.service.ts b/ui-ngx/src/app/core/services/mobile.service.ts index 800651356a..cc940d6f85 100644 --- a/ui-ngx/src/app/core/services/mobile.service.ts +++ b/ui-ngx/src/app/core/services/mobile.service.ts @@ -27,6 +27,7 @@ import { AuthService } from '@core/auth/auth.service'; const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler'; const dashboardLoadedHandler = 'tbMobileDashboardLoadedHandler'; +const dashboardLayoutHandler = 'tbMobileDashboardLayoutHandler'; const navigationHandler = 'tbMobileNavigationHandler'; const mobileHandler = 'tbMobileHandler'; @@ -43,6 +44,7 @@ export class MobileService { private reloadUserObservable: Observable; private lastDashboardId: string; + private toggleLayoutFunction: () => void; constructor(@Inject(WINDOW) private window: Window, private router: Router, @@ -65,12 +67,26 @@ export class MobileService { } } - public onDashboardLoaded() { + public onDashboardLoaded(hasRightLayout: boolean, rightLayoutOpened: boolean) { if (this.mobileApp) { - this.mobileChannel.callHandler(dashboardLoadedHandler); + this.mobileChannel.callHandler(dashboardLoadedHandler, hasRightLayout, rightLayoutOpened); } } + public onDashboardRightLayoutChanged(opened: boolean) { + if (this.mobileApp) { + this.mobileChannel.callHandler(dashboardLayoutHandler, opened); + } + } + + public registerToggleLayoutFunction(toggleLayoutFunction: () => void) { + this.toggleLayoutFunction = toggleLayoutFunction; + } + + public unregisterToggleLayoutFunction() { + this.toggleLayoutFunction = null; + } + public handleWidgetMobileAction(type: WidgetMobileActionType, ...args: any[]): Observable> { if (this.mobileApp) { @@ -110,6 +126,11 @@ export class MobileService { const reloadUserMessage: ReloadUserMessage = message.data; this.reloadUser(reloadUserMessage); break; + case 'toggleDashboardLayout': + if (this.toggleLayoutFunction) { + this.toggleLayoutFunction(); + } + break; } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 89629bfe49..cc19e98a99 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -149,8 +149,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @Input() currentState: string; + private hideToolbarValue = false; + @Input() - hideToolbar: boolean; + set hideToolbar(hideToolbar: boolean) { + this.hideToolbarValue = hideToolbar; + } + + get hideToolbar(): boolean { + return (this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit; + } @Input() syncStateWithQueryParam = true; @@ -269,6 +277,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC return !this.widgetEditMode && !this.hideToolbar && (this.toolbarAlwaysOpen() || this.isToolbarOpened || this.isEdit || this.showRightLayoutSwitch()); } + set toolbarOpened(toolbarOpened: boolean) { } @@ -329,7 +338,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.dashboardCtx.aliasController.updateAliases(); setTimeout(() => { this.mobileService.handleDashboardStateName(this.dashboardCtx.stateController.getCurrentStateName()); - this.mobileService.onDashboardLoaded(); + this.mobileService.onDashboardLoaded(this.layouts.right.show, this.isRightLayoutOpened); }); } } @@ -340,6 +349,14 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.isMobile = !state.matches; } )); + if (this.isMobileApp) { + this.mobileService.registerToggleLayoutFunction(() => { + setTimeout(() => { + this.toggleLayouts(); + this.cd.detectChanges(); + }); + }); + } } private init(data: any) { @@ -430,6 +447,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } ngOnDestroy(): void { + if (this.isMobileApp) { + this.mobileService.unregisterToggleLayoutFunction(); + } this.rxSubscriptions.forEach((subscription) => { subscription.unsubscribe(); }); @@ -469,6 +489,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } } + private hideToolbarSetting(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.hideToolbar)) { + return this.dashboard.configuration.settings.hideToolbar; + } else { + return false; + } + } + public displayTitle(): boolean { if (this.dashboard.configuration.settings && isDefined(this.dashboard.configuration.settings.showTitle)) { @@ -546,15 +575,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } public showRightLayoutSwitch(): boolean { - return this.isMobile && this.layouts.right.show; + return this.isMobile && !this.isMobileApp && this.layouts.right.show; } public toggleLayouts() { this.isRightLayoutOpened = !this.isRightLayoutOpened; + this.mobileService.onDashboardRightLayoutChanged(this.isRightLayoutOpened); } public openRightLayout() { this.isRightLayoutOpened = true; + this.mobileService.onDashboardRightLayoutChanged(this.isRightLayoutOpened); } public mainLayoutWidth(): string { @@ -782,7 +813,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.updateLayouts(layoutsData); } setTimeout(() => { - this.mobileService.onDashboardLoaded(); + this.mobileService.onDashboardLoaded(this.layouts.right.show, this.isRightLayoutOpened); }); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html index f84405faed..14c2df301b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{settings ? 'dashboard.settings' : 'layout.settings'}}

@@ -28,8 +28,8 @@
-
-
+
+
dashboard.state-controller @@ -38,112 +38,116 @@ -
- - {{ 'dashboard.toolbar-always-open' | translate }} - - +
+ dashboard.title-settings + {{ 'dashboard.display-title' | translate }} - + -
-
- +
+
+ dashboard.dashboard-logo-settings + + {{ 'dashboard.display-dashboard-logo' | translate }} + + + +
+
+ dashboard.toolbar-settings + + {{ 'dashboard.hide-toolbar' | translate }} + + + {{ 'dashboard.toolbar-always-open' | translate }} + + {{ 'dashboard.display-dashboards-selection' | translate }} - - + + {{ 'dashboard.display-entities-selection' | translate }} - - + + {{ 'dashboard.display-filters' | translate }} - - + + {{ 'dashboard.display-dashboard-timewindow' | translate }} - - + + {{ 'dashboard.display-dashboard-export' | translate }} - - + + {{ 'dashboard.display-update-dashboard-image' | translate }} - -
- - {{ 'dashboard.display-dashboard-logo' | translate }} - - - + +
-
- - - - dashboard.columns-count - - - {{ 'dashboard.columns-count-required' | translate }} - - - {{ 'dashboard.min-columns-count-message' | translate }} - - - {{ 'dashboard.max-columns-count-message' | translate }} - - - - dashboard.widgets-margins - - - {{ 'dashboard.margin-required' | translate }} - - - {{ 'dashboard.min-margin-message' | translate }} - - - {{ 'dashboard.max-margin-message' | translate }} - - - - {{ 'dashboard.autofill-height' | translate }} - - - - - - - dashboard.background-size-mode - - Fit width - Fit height - Cover - Contain - Original size - - - dashboard.mobile-layout -
- +
+
+ dashboard.layout-settings + + dashboard.columns-count + + + {{ 'dashboard.columns-count-required' | translate }} + + + {{ 'dashboard.min-columns-count-message' | translate }} + + + {{ 'dashboard.max-columns-count-message' | translate }} + + + + dashboard.widgets-margins + + + {{ 'dashboard.margin-required' | translate }} + + + {{ 'dashboard.min-margin-message' | translate }} + + + {{ 'dashboard.max-margin-message' | translate }} + + + + {{ 'dashboard.autofill-height' | translate }} + + + + + + + dashboard.background-size-mode + + Fit width + Fit height + Cover + Contain + Original size + + +
+
+ dashboard.mobile-layout + {{ 'dashboard.autofill-height' | translate }} - + dashboard.mobile-row-height -
+
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.scss index f588cb8894..423c55ceea 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.scss @@ -14,13 +14,19 @@ * limitations under the License. */ :host { - small { - white-space: normal; + .fields-group { + padding: 0 8px 8px; + margin: 10px 0; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + legend { + color: rgba(0, 0, 0, .7); + } } } :host ::ng-deep { - .mat-checkbox-label { + .mat-slide-toggle-content { white-space: normal; } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts index 26ab9a3e3b..5d17d33eaa 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts @@ -71,19 +71,33 @@ export class DashboardSettingsDialogComponent extends DialogComponent { @@ -92,13 +106,52 @@ export class DashboardSettingsDialogComponent extends DialogComponent { + if (showTitleValue) { + this.settingsFormGroup.get('titleColor').enable(); + } else { + this.settingsFormGroup.get('titleColor').disable(); + } + } + ); + this.settingsFormGroup.get('showDashboardLogo').valueChanges.subscribe( + (showDashboardLogoValue: boolean) => { + if (showDashboardLogoValue) { + this.settingsFormGroup.get('dashboardLogoUrl').enable(); + } else { + this.settingsFormGroup.get('dashboardLogoUrl').disable(); + } + } + ); + this.settingsFormGroup.get('hideToolbar').valueChanges.subscribe( + (hideToolbarValue: boolean) => { + if (hideToolbarValue) { + this.settingsFormGroup.get('toolbarAlwaysOpen').disable(); + this.settingsFormGroup.get('showDashboardsSelect').disable(); + this.settingsFormGroup.get('showEntitiesSelect').disable(); + this.settingsFormGroup.get('showFilters').disable(); + this.settingsFormGroup.get('showDashboardTimewindow').disable(); + this.settingsFormGroup.get('showDashboardExport').disable(); + this.settingsFormGroup.get('showUpdateDashboardImage').disable(); + } else { + this.settingsFormGroup.get('toolbarAlwaysOpen').enable(); + this.settingsFormGroup.get('showDashboardsSelect').enable(); + this.settingsFormGroup.get('showEntitiesSelect').enable(); + this.settingsFormGroup.get('showFilters').enable(); + this.settingsFormGroup.get('showDashboardTimewindow').enable(); + this.settingsFormGroup.get('showDashboardExport').enable(); + this.settingsFormGroup.get('showUpdateDashboardImage').enable(); + } + } + ); } else { this.settingsFormGroup = this.fb.group({}); } if (this.gridSettings) { + const mobileAutoFillHeight = isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight; this.gridSettingsFormGroup = this.fb.group({ - color: [this.gridSettings.color || 'rgba(0,0,0,0.870588)', []], columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1000)]], margin: [isDefined(this.gridSettings.margin) ? this.gridSettings.margin : 10, [Validators.required, Validators.min(0), Validators.max(50)]], @@ -106,10 +159,19 @@ export class DashboardSettingsDialogComponent extends DialogComponent { + if (mobileAutoFillHeightValue) { + this.gridSettingsFormGroup.get('mobileRowHeight').disable(); + } else { + this.gridSettingsFormGroup.get('mobileRowHeight').enable(); + } + } + ); } else { this.gridSettingsFormGroup = this.fb.group({}); } @@ -133,10 +195,10 @@ export class DashboardSettingsDialogComponent extends DialogComponent
diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss deleted file mode 100644 index 1c55a86d8b..0000000000 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -:host ::ng-deep { - textarea.mat-input-element.cdk-textarea-autosize { - box-sizing: content-box; - } -} diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts index d599927834..09b7014212 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts @@ -41,7 +41,7 @@ import { Subject } from 'rxjs'; @Component({ selector: 'tb-security-config-lwm2m-server', templateUrl: './security-config-lwm2m-server.component.html', - styleUrls: ['./security-config-lwm2m-server.component.scss'], + styleUrls: [], providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss index 0188fcd559..393d2a9ef8 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss @@ -27,8 +27,4 @@ .mat-tab-body { padding: 16px 0; } - - textarea.mat-input-element.cdk-textarea-autosize { - box-sizing: content-box; - } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts index dc1da3d973..996e79d6bc 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; @@ -33,6 +33,8 @@ import { transportPayloadTypeTranslationMap, } from '@shared/models/device.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-coap-device-profile-transport-configuration', @@ -44,7 +46,7 @@ import { isDefinedAndNotNull } from '@core/utils'; multi: true }] }) -export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { coapTransportDeviceTypes = Object.keys(CoapTransportDeviceType); @@ -56,6 +58,7 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control coapDeviceProfileTransportConfigurationFormGroup: FormGroup; + private destroy$ = new Subject(); private requiredValue: boolean; private transportPayloadTypeConfiguration = this.fb.group({ @@ -99,15 +102,23 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control }) } ); - this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType') - .valueChanges.subscribe(coapDeviceType => { + this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(coapDeviceType => { this.updateCoapDeviceTypeBasedControls(coapDeviceType, true); }); - this.coapDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.coapDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + get coapDeviceTypeDefault(): boolean { const coapDeviceType = this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').value; return coapDeviceType === CoapTransportDeviceType.DEFAULT; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts index b95433d096..27dda6dc91 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts @@ -14,13 +14,15 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProfileConfiguration, DeviceProfileType } from '@shared/models/device.models'; import { deepClone } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-device-profile-configuration', @@ -32,12 +34,14 @@ import { deepClone } from '@core/utils'; multi: true }] }) -export class DeviceProfileConfigurationComponent implements ControlValueAccessor, OnInit { +export class DeviceProfileConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { deviceProfileType = DeviceProfileType; deviceProfileConfigurationFormGroup: FormGroup; + private destroy$ = new Subject(); + private requiredValue: boolean; get required(): boolean { return this.requiredValue; @@ -69,11 +73,18 @@ export class DeviceProfileConfigurationComponent implements ControlValueAccessor this.deviceProfileConfigurationFormGroup = this.fb.group({ configuration: [null, Validators.required] }); - this.deviceProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.deviceProfileConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index fdc02f910a..711b72d615 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -15,112 +15,107 @@ limitations under the License. --> -
-
-
-
- - {{ 'device-profile.lwm2m.mode' | translate }} - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} - - - - - {{ 'device-profile.lwm2m.server-host' | translate }} - - - {{ 'device-profile.lwm2m.server-host' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.server-port' | translate }} - - - {{ 'device-profile.lwm2m.server-port' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.short-id' | translate }} - - - {{ 'device-profile.lwm2m.short-id' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - -
-
-
-
- - {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} - - - {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} - - - {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.bootstrap-server' | translate }} - -
-
- - {{ 'device-profile.lwm2m.server-public-key' | translate }} - - {{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}} - - {{ 'device-profile.lwm2m.server-public-key' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - {{ 'device-profile.lwm2m.server-public-key' | translate }} - {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { - count: 0} }} - - - {{ 'device-profile.lwm2m.server-public-key' | translate }} - {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { - count: lenMaxServerPublicKey } }} - - -
-
+
+
+ + {{ 'device-profile.lwm2m.mode' | translate }} + + + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} + + + + + {{ 'device-profile.lwm2m.server-host' | translate }} + + + {{ 'device-profile.lwm2m.server-host' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.server-port' | translate }} + + + {{ 'device-profile.lwm2m.server-port' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.short-id' | translate }} + + + {{ 'device-profile.lwm2m.short-id' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + +
+
+ + {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} + + + {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} + + + {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.bootstrap-server' | translate }} + +
+
+
+ + {{ 'device-profile.lwm2m.server-public-key' | translate }} + + {{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}} + + {{ 'device-profile.lwm2m.server-public-key' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + {{ 'device-profile.lwm2m.server-public-key' | translate }} + {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { + count: 0} }} + + + {{ 'device-profile.lwm2m.server-public-key' | translate }} + {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { + count: lenMaxServerPublicKey } }} + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index 886ed12c47..7d2856e1dc 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -15,7 +15,7 @@ /// import { DeviceProfileTransportConfiguration } from '@shared/models/device.models'; -import { Component, forwardRef, Input } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { @@ -39,6 +39,8 @@ import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/util import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { Direction } from '@shared/models/page/sort-order'; import _ from 'lodash'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-profile-lwm2m-device-transport-configuration', @@ -49,11 +51,12 @@ import _ from 'lodash'; multi: true }] }) -export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators { +export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators, OnDestroy { private configurationValue: Lwm2mProfileConfigModels; private requiredValue: boolean; private disabled = false; + private destroy$ = new Subject(); bindingModeType = BINDING_MODE; bindingModeTypes = Object.keys(BINDING_MODE); @@ -86,7 +89,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro lifetime: [null, Validators.required], defaultMinPeriod: [null, Validators.required], notifIfDisabled: [true, []], - binding:[], + binding: [], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], clientStrategy: [1, []], @@ -96,10 +99,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.lwm2mDeviceConfigFormGroup = this.fb.group({ configurationJson: [null, Validators.required] }); - this.lwm2mDeviceProfileFormGroup.valueChanges.subscribe((value) => { + this.lwm2mDeviceProfileFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { this.updateDeviceProfileValue(value); }); - this.lwm2mDeviceConfigFormGroup.valueChanges.subscribe(() => { + this.lwm2mDeviceConfigFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); this.sortFunction = this.sortObjectKeyPathJson; @@ -112,6 +119,11 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro registerOnTouched(fn: any): void { } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { @@ -124,11 +136,17 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } writeValue(value: Lwm2mProfileConfigModels | null): void { - this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value; - this.lwm2mDeviceConfigFormGroup.patchValue({ - configurationJson: this.configurationValue - }, {emitEvent: false}); - this.initWriteValue(); + if (isDefinedAndNotNull(value)) { + if (Object.keys(value).length !== 0 && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { + this.configurationValue = value; + } else { + this.configurationValue = getDefaultProfileConfig(); + } + this.lwm2mDeviceConfigFormGroup.patchValue({ + configurationJson: this.configurationValue + }, {emitEvent: false}); + this.initWriteValue(); + } } private initWriteValue = (): void => { @@ -257,7 +275,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro instanceUpdate.id = instanceId; instanceUpdate.resources.forEach(resource => { resource.keyName = _.camelCase(resource.name + instanceUpdate.id); - }) + }); return instanceUpdate; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index 8f1f676769..ae7c3ccdc6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -15,10 +15,19 @@ /// import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { Observable } from 'rxjs'; -import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, mergeMap, share, tap } from 'rxjs/operators'; import { ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT } from './lwm2m-profile-config.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { Direction } from '@shared/models/page/sort-order'; @@ -33,13 +42,18 @@ import { PageLink } from '@shared/models/page/page-link'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => Lwm2mObjectListComponent), multi: true - }] + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => Lwm2mObjectListComponent), + multi: true + } + ] }) -export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validators { +export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validator { private requiredValue: boolean; private dirty = false; - private lw2mModels: Observable>; private modelValue: Array = []; lwm2mListFormGroup: FormGroup; @@ -78,8 +92,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } private updateValidators = (): void => { - this.lwm2mListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []); - this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity(); + this.lwm2mListFormGroup.get('objectsList').setValidators(this.required ? [Validators.required] : []); + this.lwm2mListFormGroup.get('objectsList').updateValueAndValidity(); } registerOnChange(fn: any): void { @@ -92,6 +106,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V ngOnInit() { this.filteredObjectsList = this.lwm2mListFormGroup.get('objectLwm2m').valueChanges .pipe( + distinctUntilChanged(), tap((value) => { if (value && typeof value !== 'string') { this.add(value); @@ -100,7 +115,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } }), filter(searchText => isString(searchText)), - mergeMap(searchText => this.fetchListObjects(searchText)) + mergeMap(searchText => this.fetchListObjects(searchText)), + share() ); } @@ -131,6 +147,12 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } } + validate(): ValidationErrors | null { + return this.lwm2mListFormGroup.valid ? null : { + lwm2mListObj: false + }; + } + private add(object: ObjectLwM2M): void { if (isDefinedAndNotNull(this.modelValue) && this.modelValue.indexOf(object.keyId) === -1) { this.modelValue.push(object.keyId); @@ -157,23 +179,13 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V return object ? object.name : undefined; } - private fetchListObjects = (searchText?: string): Observable> => { + private fetchListObjects = (searchText: string): Observable> => { this.searchText = searchText; - return this.getLwM2mModelsPage().pipe( - map(objectLwM2Ms => objectLwM2Ms) - ); - } - - private getLwM2mModelsPage(): Observable> { const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, { property: 'id', direction: Direction.ASC }); - this.lw2mModels = this.deviceProfileService.getLwm2mObjectsPage(pageLink).pipe( - publishReplay(1), - refCount() - ); - return this.lw2mModels; + return this.deviceProfileService.getLwm2mObjectsPage(pageLink); } onFocus = (): void => { @@ -183,10 +195,9 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } } - private clear = (value: string = ''): void => { - this.objectInput.nativeElement.value = value; + private clear = (): void => { this.searchText = ''; - this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value); + this.lwm2mListFormGroup.get('objectLwm2m').patchValue(null); setTimeout(() => { this.objectInput.nativeElement.blur(); this.objectInput.nativeElement.focus(); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html index e58861c1eb..3ee9bffa27 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
@@ -46,14 +46,14 @@
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html index 759e880ffa..02d594905d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html @@ -16,7 +16,7 @@ -->
- + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss index 2de6fe17d6..afed0db1ff 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss @@ -24,6 +24,9 @@ } :host{ + section { + padding: 2px; + } .instance-list { mat-expansion-panel-header { color: inherit; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 48613f58de..2a12f892e2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -64,7 +64,7 @@ export const BINDING_MODE_NAMES = new Map( [BINDING_MODE.UQ, 'UQ: UDP connection in queue mode'], [BINDING_MODE.US, 'US: both UDP and SMS connections active, both in standard mode'], [BINDING_MODE.UQS, 'UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode'], - [BINDING_MODE.T,'T: TCP connection in standard mode'], + [BINDING_MODE.T, 'T: TCP connection in standard mode'], [BINDING_MODE.TQ, 'TQ: TCP connection in queue mode'], [BINDING_MODE.TS, 'TS: both TCP and SMS connections active, both in standard mode'], [BINDING_MODE.TQS, 'TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode'], @@ -162,7 +162,6 @@ export interface Lwm2mProfileConfigModels { clientLwM2mSettings: ClientLwM2mSettings; observeAttr: ObservableAttributes; bootstrap: BootstrapSecurityConfig; - } export interface ClientLwM2mSettings { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 381e2f9e8d..0cbb50ed8e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -39,6 +39,8 @@ import { transportPayloadTypeTranslationMap } from '@shared/models/device.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-mqtt-device-profile-transport-configuration', @@ -50,7 +52,7 @@ import { isDefinedAndNotNull } from '@core/utils'; multi: true }] }) -export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { transportPayloadTypes = Object.keys(TransportPayloadType); @@ -58,6 +60,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; + private destroy$ = new Subject(); private requiredValue: boolean; get required(): boolean { @@ -98,15 +101,23 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control }) }, {validator: this.uniqueDeviceTopicValidator} ); - this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType') - .valueChanges.subscribe(payloadType => { + this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(payloadType => { this.updateTransportPayloadBasedControls(payloadType, true); }); - this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { @@ -192,8 +203,8 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null { - if (control.value) { - const formValue = control.value as MqttDeviceProfileTransportConfiguration; + if (control.getRawValue()) { + const formValue = control.getRawValue() as MqttDeviceProfileTransportConfiguration; if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) { return {unique: true}; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts index 96f7454cba..e6749a9219 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts @@ -14,17 +14,19 @@ /// limitations under the License. /// -import {Component, forwardRef, Input, OnInit} from '@angular/core'; -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; -import {Store} from '@ngrx/store'; -import {AppState} from '@app/core/core.state'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProfileTransportConfiguration, DeviceTransportType, SnmpDeviceProfileTransportConfiguration } from '@shared/models/device.models'; -import {isDefinedAndNotNull} from "@core/utils"; +import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; export interface OidMappingConfiguration { isAttribute: boolean; @@ -44,8 +46,11 @@ export interface OidMappingConfiguration { multi: true }] }) -export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { + snmpDeviceProfileTransportConfigurationFormGroup: FormGroup; + + private destroy$ = new Subject(); private requiredValue: boolean; private configuration = []; @@ -71,11 +76,18 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ configuration: [null, Validators.required] }); - this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts index 804b349cff..2c53f04986 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts @@ -32,7 +32,7 @@ import { }) export class DeviceProfileTabsComponent extends EntityTabsComponent { - deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypes = Object.values(DeviceTransportType); deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; diff --git a/ui-ngx/src/app/shared/components/color-input.component.html b/ui-ngx/src/app/shared/components/color-input.component.html index fbf8aea853..a0d226f589 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.html +++ b/ui-ngx/src/app/shared/components/color-input.component.html @@ -20,7 +20,7 @@ {{icon}} {{label}} -
+
diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index a782fe8256..684705590e 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -45,7 +45,6 @@ export interface WidgetLayouts { export interface GridSettings { backgroundColor?: string; - color?: string; columns?: number; margin?: number; backgroundSizeMode?: string; @@ -93,6 +92,7 @@ export interface DashboardSettings { showDashboardExport?: boolean; showUpdateDashboardImage?: boolean; toolbarAlwaysOpen?: boolean; + hideToolbar?: boolean; titleColor?: string; } diff --git a/ui-ngx/src/app/shared/models/window-message.model.ts b/ui-ngx/src/app/shared/models/window-message.model.ts index 078a7aefec..2073197e27 100644 --- a/ui-ngx/src/app/shared/models/window-message.model.ts +++ b/ui-ngx/src/app/shared/models/window-message.model.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage'; +export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage' | 'toggleDashboardLayout'; export interface WindowMessage { type: WindowMessageType; 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 f6d86e4e84..465aa8faf2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -721,6 +721,7 @@ "maximum-upload-file-size": "Maximum upload file size: {{ size }}", "cannot-upload-file": "Cannot upload file", "settings": "Settings", + "layout-settings": "Layout settings", "columns-count": "Columns count", "columns-count-required": "Columns count is required.", "min-columns-count-message": "Only 10 minimum column count is allowed.", @@ -743,15 +744,19 @@ "mobile-row-height-required": "Mobile row height value is required.", "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", + "title-settings": "Title settings", "display-title": "Display dashboard title", - "toolbar-always-open": "Keep toolbar opened", "title-color": "Title color", + "toolbar-settings": "Toolbar settings", + "hide-toolbar": "Hide toolbar", + "toolbar-always-open": "Keep toolbar opened", "display-dashboards-selection": "Display dashboards selection", "display-entities-selection": "Display entities selection", "display-filters": "Display filters", "display-dashboard-timewindow": "Display timewindow", "display-dashboard-export": "Display export", "display-update-dashboard-image": "Display update dashboard image", + "dashboard-logo-settings": "Dashboard logo settings", "display-dashboard-logo": "Display logo in dashboard fullscreen mode", "dashboard-logo-image": "Dashboard logo image", "import": "Import dashboard",