From 9b7c8595ee6cfe50e6db5fecb08003ffe2442f56 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 14 Jun 2024 19:01:39 +0300 Subject: [PATCH 01/84] UI: timewindow panel realtime mode redesign --- .../time/quick-time-interval.component.html | 2 +- .../time/quick-time-interval.component.ts | 7 + .../time/timeinterval.component.html | 10 +- .../components/time/timeinterval.component.ts | 5 +- .../time/timewindow-panel.component.html | 204 ++++++++---------- .../time/timewindow-panel.component.scss | 10 +- .../time/timewindow-panel.component.ts | 16 ++ .../time/timezone-select.component.html | 3 +- .../time/timezone-select.component.ts | 5 +- .../assets/locale/locale.constant-en_US.json | 3 +- 10 files changed, 141 insertions(+), 124 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html index 844d8fb825..52479173c1 100644 --- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html @@ -16,7 +16,7 @@ -->
- + timewindow.interval diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts index e86bf93db6..1edaad40e5 100644 --- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts @@ -17,6 +17,7 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { QuickTimeInterval, QuickTimeIntervalTranslationMap } from '@shared/models/time/time.models'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; @Component({ selector: 'tb-quick-time-interval', @@ -43,6 +44,12 @@ export class QuickTimeIntervalComponent implements OnInit, ControlValueAccessor @Input() onlyCurrentInterval = false; + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + private propagateChange = (_: any) => {}; constructor() { diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index 04292b4d93..f72704bcea 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -22,26 +22,26 @@
- + timeinterval.days - + timeinterval.hours - + timeinterval.minutes - + timeinterval.seconds
- + {{ predefinedName }} diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index 8ed6fb2034..906d938d0b 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -18,7 +18,7 @@ import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@ang import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; import { coerceNumberProperty } from '@angular/cdk/coercion'; -import { SubscriptSizing } from '@angular/material/form-field'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; import { coerceBoolean } from '@shared/decorators/coercion'; import { Interval, IntervalMath, TimeInterval } from '@shared/models/time/time.models'; import { isDefined } from '@core/utils'; @@ -85,6 +85,9 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { @Input() subscriptSizing: SubscriptSizing = 'fixed'; + @Input() + appearance: MatFormFieldAppearance = 'fill'; + days = 0; hours = 0; mins = 1; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 1ad06bab3e..b5c279df4e 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -19,73 +19,54 @@ -
-
- - -
-
-
- - -
-
- - -
-
- timewindow.last - -
-
-
- -
-
- - -
-
- timewindow.interval - -
-
-
-
- - +
+
+
{{ 'timewindow.time-range' | translate }}
+
+ + + + + + +
-
-
- - + + + + + + + + + + + + +
+ + +
@@ -110,6 +91,8 @@ formControlName="timewindowMs" predefinedName="timewindow.last" class="history-time-input" + subscriptSizing="dynamic" + appearance="outline" [fxShow]="timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL" [required]="timewindow.selectedTab === timewindowTypes.HISTORY && timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL" @@ -134,6 +117,8 @@
- + - -
-
-
- - -
-
- - aggregation.function - - - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - - -
+ +
+
+
{{ 'aggregation.aggregation' | translate }}
+ + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + +
-
-
- - -
-
-
- -
- - - - - -
+ +
+
{{ 'aggregation.limit' | translate }}
+
+
+ + + + +
-
+
@@ -217,6 +192,8 @@ (hideFlagChange)="onHideAggIntervalChanged()" [min]="minHistoryAggInterval()" [max]="maxHistoryAggInterval()" useCalendarIntervals + subscriptSizing="dynamic" + appearance="outline" predefinedName="aggregation.group-interval">
@@ -226,10 +203,11 @@ - - + + + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index 25d11f510f..f3e57fb24d 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -22,6 +22,10 @@ max-width: 100%; background-color: #fff; + .tb-flex { + gap: 16px; + } + .mat-content { overflow: hidden; } @@ -35,7 +39,7 @@ margin-right: 5px; } - tb-timeinterval[ng-reflect-fx-show="true"] { + tb-timeinterval[ng-reflect-fxe-show="true"] { margin-bottom: -16px; } @@ -78,4 +82,8 @@ .mat-mdc-tab-group:not(.tb-headless) { height: 100%; } + + .mat-mdc-tab-body-content { + padding: 16px; + } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 857d2c7cb7..3fe0a2a599 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -32,6 +32,9 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { TimeService } from '@core/services/time.service'; import { isDefined } from '@core/utils'; import { OverlayRef } from '@angular/cdk/overlay'; +import { WidgetConfigMode } from '@shared/models/widget.models'; +import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; +import { TranslateService } from '@ngx-translate/core'; export interface TimewindowPanelData { historyOnly: boolean; @@ -82,11 +85,23 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { result: Timewindow; + realtimeTimewindowOptions: ToggleHeaderOption[] = [ + { + name: this.translate.instant('timewindow.last'), + value: this.realtimeTypes.LAST_INTERVAL + }, + { + name: this.translate.instant('timewindow.interval'), + value: this.realtimeTypes.INTERVAL + } + ]; + constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, public overlayRef: OverlayRef, protected store: Store, public fb: UntypedFormBuilder, private timeService: TimeService, + private translate: TranslateService, public viewContainerRef: ViewContainerRef) { super(store); this.historyOnly = data.historyOnly; @@ -380,4 +395,5 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindowForm.markAsDirty(); } + protected readonly WidgetConfigMode = WidgetConfigMode; } diff --git a/ui-ngx/src/app/shared/components/time/timezone-select.component.html b/ui-ngx/src/app/shared/components/time/timezone-select.component.html index 1ed05d820d..280a58e4b8 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-select.component.html +++ b/ui-ngx/src/app/shared/components/time/timezone-select.component.html @@ -15,7 +15,8 @@ limitations under the License. --> - + timezone.timezone Date: Fri, 28 Jun 2024 18:29:47 +0300 Subject: [PATCH 02/84] UI: timewindow panel history mode redesign --- .../time/quick-time-interval.component.html | 2 +- .../time/quick-time-interval.component.ts | 5 + .../time/timeinterval.component.html | 8 +- .../time/timewindow-panel.component.html | 270 ++++++++---------- .../time/timewindow-panel.component.scss | 7 +- .../time/timewindow-panel.component.ts | 28 +- .../assets/locale/locale.constant-en_US.json | 4 +- 7 files changed, 172 insertions(+), 152 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html index 52479173c1..2a68d672c9 100644 --- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html @@ -17,7 +17,7 @@ -->
- timewindow.interval + timewindow.interval {{ timeIntervalTranslationMap.get(interval) | translate}} diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts index 1edaad40e5..87b3687d0e 100644 --- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts @@ -18,6 +18,7 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { QuickTimeInterval, QuickTimeIntervalTranslationMap } from '@shared/models/time/time.models'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-quick-time-interval', @@ -40,6 +41,10 @@ export class QuickTimeIntervalComponent implements OnInit, ControlValueAccessor rendered = false; + @Input() + @coerceBoolean() + displayLabel = true; + @Input() disabled: boolean; @Input() onlyCurrentInterval = false; diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index f72704bcea..3cf001e426 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -22,19 +22,19 @@
- + timeinterval.days - + timeinterval.hours - + timeinterval.minutes - + timeinterval.seconds diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index b5c279df4e..42daf5f6ff 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -16,7 +16,7 @@ -->
-
@@ -24,24 +24,18 @@
{{ 'timewindow.time-range' | translate }}
- - - + +
- + - + -
-
- - -
-
-
- - -
- timewindow.for-all-time -
-
- -
- timewindow.last - -
-
- -
- timewindow.time-period - -
-
- -
- timewindow.interval - -
-
-
+
+
+
{{ 'timewindow.time-range' | translate }}
+
+ + + + + + +
+ + + + + + + + + + + + + + + + +
-
- - + + +
-
-
-
{{ 'aggregation.aggregation' | translate }}
- - - - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - - -
+
+ +
+
{{ 'aggregation.aggregation' | translate }}
+ + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + + +
-
-
{{ 'aggregation.limit' | translate }}
-
-
- - - - - +
+
{{ 'aggregation.limit' | translate }}
+
+
+ + + + + + +
-
-
-
-
- - -
-
- - -
-
-
- - +
+ + +
+
{{ 'aggregation.group-interval' | translate }}
+ + + + + + + +
- - - - -
+ + + + + + +
-
{{ 'aggregation.group-interval' | translate }}
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 9f5718a018..b3ce04cef4 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -85,16 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { result: Timewindow; - realtimeTimewindowOptions: ToggleHeaderOption[] = [ - { - name: this.translate.instant('timewindow.last'), - value: this.realtimeTypes.LAST_INTERVAL - }, - { - name: this.translate.instant('timewindow.relative'), - value: this.realtimeTypes.INTERVAL - } - ]; + realtimeTimewindowOptions: ToggleHeaderOption[] = []; historyTimewindowOptions: ToggleHeaderOption[] = [ { @@ -121,17 +112,32 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { super(store); this.historyOnly = data.historyOnly; this.forAllTimeEnabled = data.forAllTimeEnabled; + this.quickIntervalOnly = data.quickIntervalOnly; + this.timewindow = data.timewindow; + this.aggregation = data.aggregation; + this.timezone = data.timezone; + this.isEdit = data.isEdit; + if (this.forAllTimeEnabled) { this.historyTimewindowOptions.unshift({ name: this.translate.instant('timewindow.for-all-time'), value: this.historyTypes.FOR_ALL_TIME }); } - this.quickIntervalOnly = data.quickIntervalOnly; - this.timewindow = data.timewindow; - this.aggregation = data.aggregation; - this.timezone = data.timezone; - this.isEdit = data.isEdit; + + if (this.isEdit || (!this.timewindow.hideLastInterval && !this.quickIntervalOnly)) { + this.realtimeTimewindowOptions.push({ + name: this.translate.instant('timewindow.last'), + value: this.realtimeTypes.LAST_INTERVAL + }); + } + + if (this.isEdit || (!this.timewindow.hideQuickInterval && this.quickIntervalOnly)) { + this.realtimeTimewindowOptions.push({ + name: this.translate.instant('timewindow.relative'), + value: this.realtimeTypes.INTERVAL + }); + } } ngOnInit(): void { From 8c2814e4168f76142489a3f0d94f20ef991564d5 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 18 Jul 2024 17:59:04 +0300 Subject: [PATCH 08/84] UI: timezone selection draft --- .../components/event/event-table-config.ts | 4 +- .../time/timewindow-panel.component.html | 11 +- .../time/timewindow-panel.component.ts | 2 +- .../time/timezone-panel.component.html | 25 ++ .../time/timezone-panel.component.scss | 47 +++ .../time/timezone-panel.component.ts | 71 ++++ .../time/timezone-select.component.ts | 12 +- .../components/time/timezone.component.html | 56 +++ .../components/time/timezone.component.scss | 64 ++++ .../components/time/timezone.component.ts | 330 ++++++++++++++++++ .../shared/models/widget-settings.models.ts | 13 + ui-ngx/src/app/shared/shared.module.ts | 6 + ui-ngx/src/styles.scss | 2 +- 13 files changed, 626 insertions(+), 17 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/time/timezone-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/time/timezone-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/timezone-panel.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/timezone.component.html create mode 100644 ui-ngx/src/app/shared/components/time/timezone.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/timezone.component.ts diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index c8947ed55d..107d08c2aa 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -40,7 +40,7 @@ import { EventContentDialogData } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; -import { historyInterval, MINUTE } from '@shared/models/time/time.models'; +import { historyInterval, HOUR, MINUTE } from '@shared/models/time/time.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ChangeDetectorRef, EventEmitter, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; @@ -94,7 +94,7 @@ export class EventTableConfig extends EntityTableConfig { this.loadDataOnInit = false; this.tableTitle = ''; this.useTimePageLink = true; - this.defaultTimewindowInterval = historyInterval(MINUTE * 15); + this.defaultTimewindowInterval = historyInterval(HOUR); this.detailsPanelEnabled = false; this.selectionEnabled = false; this.searchEnabled = false; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 20ae8c02ff..7d8546c021 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -20,7 +20,8 @@ (selectedTabChange)="onTimewindowTypeChange()" [(selectedIndex)]="timewindow.selectedTab">
-
+
{{ 'timewindow.time-range' | translate }}
@@ -58,7 +59,7 @@
- + @@ -188,11 +189,7 @@ - - + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index b3ce04cef4..ac29d4aaa5 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -132,7 +132,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { }); } - if (this.isEdit || (!this.timewindow.hideQuickInterval && this.quickIntervalOnly)) { + if (this.isEdit || !this.timewindow.hideQuickInterval || this.quickIntervalOnly) { this.realtimeTimewindowOptions.push({ name: this.translate.instant('timewindow.relative'), value: this.realtimeTypes.INTERVAL diff --git a/ui-ngx/src/app/shared/components/time/timezone-panel.component.html b/ui-ngx/src/app/shared/components/time/timezone-panel.component.html new file mode 100644 index 0000000000..0dae7823ec --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timezone-panel.component.html @@ -0,0 +1,25 @@ +
+
+
{{ 'timezone.timezone' | translate }}
+ + +
+
+
+ + +
diff --git a/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss b/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss new file mode 100644 index 0000000000..031d2bb5da --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2024 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. + */ +@import "../../../../scss/constants"; + +:host { + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 100%; + background-color: #fff; + + .tb-form-panel { + padding: 12px; + gap: 12px; + } + + .tb-flex { + gap: 16px; + } + + .mat-content { + overflow: hidden; + } + + .mat-padding { + padding: 0 16px; + } + + .hide-label { + margin-bottom: 5px; + margin-right: 5px; + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts b/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts new file mode 100644 index 0000000000..4b6eb468aa --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts @@ -0,0 +1,71 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { TimeService } from '@core/services/time.service'; +import { TranslateService } from '@ngx-translate/core'; + +export interface TimezonePanelData { + timezone: string; + isEdit: boolean; +} + +export const TIMEZONE_PANEL_DATA = new InjectionToken('TimezonePanelData'); + +@Component({ + selector: 'tb-timezone-panel', + templateUrl: './timezone-panel.component.html', + styleUrls: ['./timezone-panel.component.scss'] +}) +export class TimezonePanelComponent extends PageComponent implements OnInit { + + timezone: string; + result: string; + + timezoneForm: FormGroup; + + constructor(@Inject(TIMEZONE_PANEL_DATA) public data: TimezonePanelData, + public overlayRef: OverlayRef, + protected store: Store, + public fb: FormBuilder, + private timeService: TimeService, + private translate: TranslateService, + public viewContainerRef: ViewContainerRef) { + super(store); + this.timezone = this.data.timezone; + this.timezoneForm = this.fb.group({ + timezone: [this.timezone] + }); + } + + ngOnInit(): void { + } + + update() { + this.result = this.timezoneForm.get('timezone').value; + this.overlayRef.dispose(); + } + + cancel() { + this.overlayRef.dispose(); + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timezone-select.component.ts b/ui-ngx/src/app/shared/components/time/timezone-select.component.ts index bc9cc56cfa..976aa8436b 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-select.component.ts +++ b/ui-ngx/src/app/shared/components/time/timezone-select.component.ts @@ -45,12 +45,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af defaultTimezoneId: string = null; - @Input() - subscriptSizing: SubscriptSizing = 'fixed'; - - @Input() - appearance: MatFormFieldAppearance = 'fill'; - @Input() set defaultTimezone(timezone: string) { if (this.defaultTimezoneId !== timezone) { @@ -58,6 +52,12 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af } } + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.html b/ui-ngx/src/app/shared/components/time/timezone.component.html new file mode 100644 index 0000000000..5aff9da59c --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timezone.component.html @@ -0,0 +1,56 @@ + + + + +
+ {{ computedTimezoneStyle.icon }} +
+ {{innerValue}} +
+ {{ computedTimezoneStyle.icon }} +
diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.scss b/ui-ngx/src/app/shared/components/time/timezone.component.scss new file mode 100644 index 0000000000..99f3d8367d --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timezone.component.scss @@ -0,0 +1,64 @@ +/** + * Copyright © 2016-2024 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 { + min-width: 48px; + margin: 8px 0; + max-width: 100%; + &.no-margin { + margin: 0; + } + .mdc-button { + max-width: 100%; + } + section.tb-timewindow { + padding: 0 8px; + &.no-padding { + padding: 0; + } + line-height: 32px; + pointer-events: all; + cursor: pointer; + display: flex; + flex-direction: row; + place-content: center flex-start; + align-items: center; + gap: 4px; + width: 100%; + + .tb-timewindow-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .timezone-abbr { + font-weight: 500; + } + } +} + +:host ::ng-deep { + .mdc-button { + .mat-icon { + min-width: 24px; + } + .mdc-button__label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.ts b/ui-ngx/src/app/shared/components/time/timezone.component.ts new file mode 100644 index 0000000000..79fe0b870f --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timezone.component.ts @@ -0,0 +1,330 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + HostBinding, + Injector, + Input, + OnChanges, + OnInit, Renderer2, + SimpleChanges, + StaticProvider, + ViewContainerRef +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { DatePipe } from '@angular/common'; +import { TimeService } from '@core/services/time.service'; +import { TooltipPosition } from '@angular/material/tooltip'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { + ComponentStyle, + defaultTimezoneStyle, + iconStyle, + textStyle, + TimezoneStyle +} from '@shared/models/widget-settings.models'; +import { DEFAULT_OVERLAY_POSITIONS } from '@shared/models/overlay.models'; +import { fromEvent } from 'rxjs'; +import { + TIMEZONE_PANEL_DATA, + TimezonePanelComponent, + TimezonePanelData +} from '@shared/components/time/timezone-panel.component'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; + +// @dynamic +@Component({ + selector: 'tb-timezone', + templateUrl: './timezone.component.html', + styleUrls: ['./timezone.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimezoneComponent), + multi: true + } + ] +}) +export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChanges { + + @HostBinding('class.no-margin') + @Input() + @coerceBoolean() + noMargin = false; + + @Input() + @coerceBoolean() + noPadding = false; + + @Input() + @coerceBoolean() + disablePanel = false; + + @Input() + @coerceBoolean() + isToolbar = false; + + @Input() + @coerceBoolean() + asButton = false; + + @Input() + @coerceBoolean() + strokedButton = false; + + @Input() + @coerceBoolean() + flatButton = false; + + @Input() + @coerceBoolean() + displayTimezoneValue = true; + + @Input() + @coerceBoolean() + hideLabel = false; + + isEditValue = false; + + @Input() + set isEdit(val) { + this.isEditValue = coerceBooleanProperty(val); + this.timezoneDisabled = this.isTimezoneDisabled(); + } + + get isEdit() { + return this.isEditValue; + } + + @Input() + tooltipPosition: TooltipPosition = 'above'; + + @Input() + timezoneStyle: TimezoneStyle; + + @Input() + @coerceBoolean() + disabled: boolean; + + private userTimezoneByDefaultValue: boolean; + get userTimezoneByDefault(): boolean { + return this.userTimezoneByDefaultValue; + } + @Input() + set userTimezoneByDefault(value: boolean) { + this.userTimezoneByDefaultValue = coerceBooleanProperty(value); + } + + private localBrowserTimezonePlaceholderOnEmptyValue: boolean; + get localBrowserTimezonePlaceholderOnEmpty(): boolean { + return this.localBrowserTimezonePlaceholderOnEmptyValue; + } + @Input() + set localBrowserTimezonePlaceholderOnEmpty(value: boolean) { + this.localBrowserTimezonePlaceholderOnEmptyValue = coerceBooleanProperty(value); + }defaultTimezoneId: string = null; + + @Input() + set defaultTimezone(timezone: string) { + if (this.defaultTimezoneId !== timezone) { + this.defaultTimezoneId = timezone; + } + } + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + innerValue: string; + + timezoneDisabled: boolean; + + computedTimezoneStyle: TimezoneStyle; + timezoneComponentStyle: ComponentStyle; + timezoneIconStyle: ComponentStyle; + + private propagateChange = (_: any) => {}; + + constructor(private overlay: Overlay, + private translate: TranslateService, + private timeService: TimeService, + private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe, + private datePipe: DatePipe, + private cd: ChangeDetectorRef, + private nativeElement: ElementRef, + public viewContainerRef: ViewContainerRef, + private popoverService: TbPopoverService, + private renderer: Renderer2) { + } + + ngOnInit() { + this.updateTimezoneStyle(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'timezoneStyle') { + this.updateTimezoneStyle(); + this.updateDisplayValue(); + } + } + } + } + + toggleTimezone($event: Event) { + console.log($event); + if ($event) { + $event.stopPropagation(); + } + if (this.disablePanel) { + return; + } + // const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element; + // if (this.popoverService.hasPopover(trigger)) { + // this.popoverService.hidePopover(trigger); + // } else { + // const timezoneSelectionPopover = this.popoverService.displayPopover(trigger, this.renderer, + // this.viewContainerRef, TimewindowStylePanelComponent, 'left', true, null, + // ctx, + // {}, + // {}, {}, true); + // timewindowStylePanelPopover.tbComponentRef.instance.popover = timewindowStylePanelPopover; + // timewindowStylePanelPopover.tbComponentRef.instance.timewindowStyleApplied.subscribe((timewindowStyle) => { + // timewindowStylePanelPopover.hide(); + // this.modelValue = timewindowStyle; + // this.propagateChange(this.modelValue); + // }); + // + // // if (componentRef.instance.result) { + // // this.innerValue = componentRef.instance.result; + // // this.timezoneDisabled = this.isTimezoneDisabled(); + // // this.updateDisplayValue(); + // // this.notifyChanged(); + // // } + // } + const config = new OverlayConfig({ + panelClass: 'tb-timezone-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + maxHeight: '30vh', + height: 'min-content' + }); + + config.positionStrategy = this.overlay.position() + .flexibleConnectedTo(this.nativeElement) + .withPositions(DEFAULT_OVERLAY_POSITIONS); + + // TODO: change panel to popover + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: TIMEZONE_PANEL_DATA, + useValue: { + timezone: this.innerValue, + isEdit: this.isEdit + } as TimezonePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + const componentRef = overlayRef.attach(new ComponentPortal(TimezonePanelComponent, + this.viewContainerRef, injector)); + const resizeWindows$ = fromEvent(window, 'resize').subscribe(() => { + overlayRef.updatePosition(); + }); + componentRef.onDestroy(() => { + resizeWindows$.unsubscribe(); + // TODO: if value check makes impossible to select browser timezone (null) + if (componentRef.instance.result) { + this.innerValue = componentRef.instance.result; + this.timezoneDisabled = this.isTimezoneDisabled(); + this.updateDisplayValue(); + this.notifyChanged(); + } + }); + this.cd.detectChanges(); + } + + private updateTimezoneStyle() { + if (!this.asButton) { + this.computedTimezoneStyle = {...defaultTimezoneStyle, ...(this.timezoneStyle || {})}; + this.timezoneComponentStyle = textStyle(this.computedTimezoneStyle.font); + if (this.computedTimezoneStyle.color) { + this.timezoneComponentStyle.color = this.computedTimezoneStyle.color; + } + this.timezoneIconStyle = this.computedTimezoneStyle.iconSize ? iconStyle(this.computedTimezoneStyle.iconSize) : {}; + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this.timezoneDisabled = this.isTimezoneDisabled(); + } + + writeValue(value: string): void { + this.innerValue = value; + this.timezoneDisabled = this.isTimezoneDisabled(); + this.updateDisplayValue(); + } + + notifyChanged() { + this.propagateChange(this.innerValue); + } + + displayValue(): string { + // TODO: only offset should be displayed, timezone name shouldn't be present + return this.displayTimezoneValue ? this.innerValue : this.translate.instant('timezone.timezone'); + } + + updateDisplayValue() { + this.cd.detectChanges(); + } + + private isTimezoneDisabled(): boolean { + return this.disabled; + } + +} diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 5124458ff0..dbe75a5b36 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -262,6 +262,19 @@ export const defaultTimewindowStyle: TimewindowStyle = { displayTypePrefix: true }; +export interface TimezoneStyle { + showIcon: boolean; + icon?: string; + iconSize?: string; + iconPosition?: 'left' | 'right'; + font?: Font; + color?: string; +} + +export const defaultTimezoneStyle: TimezoneStyle = { + showIcon: false +}; + export const constantColor = (color: string): ColorSettings => ({ type: ColorType.constant, color, diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 567b80ed83..ac15cfc26e 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -219,6 +219,8 @@ import { ImageGalleryDialogComponent } from '@shared/components/image/image-gall import { RuleChainSelectPanelComponent } from '@shared/components/rule-chain/rule-chain-select-panel.component'; import { WidgetButtonComponent } from '@shared/components/button/widget-button.component'; import { HexInputComponent } from '@shared/components/color-picker/hex-input.component'; +import { TimezoneComponent } from '@shared/components/time/timezone.component'; +import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -302,6 +304,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + TimezoneComponent, + TimezonePanelComponent, QuickTimeIntervalComponent, DashboardSelectComponent, DashboardSelectPanelComponent, @@ -506,6 +510,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + TimezoneComponent, + TimezonePanelComponent, QuickTimeIntervalComponent, DashboardSelectComponent, DatetimePeriodComponent, diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 4d5ae09a15..08850ddbe8 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -268,7 +268,7 @@ pre.tb-highlight { letter-spacing: normal; } -.tb-timewindow-panel, .tb-legend-config-panel, .tb-filter-panel, .tb-panel-container { +.tb-timewindow-panel, .tb-legend-config-panel, .tb-filter-panel, .tb-panel-container, tb-timezone-panel { overflow: hidden; background: #fff; border-radius: 4px; From 7720096837773b1581f0c1eaa0b9d396cd813a18 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 29 Jul 2024 18:47:48 +0300 Subject: [PATCH 09/84] UI: display offset in timezone component --- ui-ngx/src/app/core/services/time.service.ts | 25 ++- .../time/timewindow-panel.component.html | 17 +- .../time/timezone-panel.component.html | 5 +- .../time/timezone-panel.component.scss | 3 +- .../time/timezone-panel.component.ts | 59 ++++--- .../time/timezone-select.component.html | 2 +- .../time/timezone-select.component.ts | 27 ++- .../components/time/timezone.component.html | 14 +- .../components/time/timezone.component.scss | 4 +- .../components/time/timezone.component.ts | 167 +++++++----------- ui-ngx/src/styles.scss | 2 +- 11 files changed, 161 insertions(+), 164 deletions(-) diff --git a/ui-ngx/src/app/core/services/time.service.ts b/ui-ngx/src/app/core/services/time.service.ts index 62d5a3507c..c60ee89222 100644 --- a/ui-ngx/src/app/core/services/time.service.ts +++ b/ui-ngx/src/app/core/services/time.service.ts @@ -19,13 +19,18 @@ import { AggregationType, DAY, defaultTimeIntervals, - defaultTimewindow, Interval, IntervalMath, + defaultTimewindow, + getDefaultTimezoneInfo, + Interval, + IntervalMath, SECOND, TimeInterval, - Timewindow + Timewindow, + TimezoneInfo } from '@shared/models/time/time.models'; import { HttpClient } from '@angular/common/http'; -import { isDefined } from '@core/utils'; +import { deepClone, isDefined } from '@core/utils'; +import { TranslateService } from '@ngx-translate/core'; const MIN_INTERVAL = SECOND; const MAX_INTERVAL = 365 * 20 * DAY; @@ -41,8 +46,11 @@ export class TimeService { private maxDatapointsLimit = MAX_DATAPOINTS_LIMIT; + private localBrowserTimezoneInfoPlaceholder: TimezoneInfo; + constructor( - private http: HttpClient + private http: HttpClient, + private translate: TranslateService ) {} public setMaxDatapointsLimit(limit: number) { @@ -161,4 +169,13 @@ export class TimeService { return defValue; } } + + public getLocalBrowserTimezoneInfoPlaceholder(): TimezoneInfo { + if (!this.localBrowserTimezoneInfoPlaceholder) { + this.localBrowserTimezoneInfoPlaceholder = deepClone(getDefaultTimezoneInfo()); + this.localBrowserTimezoneInfoPlaceholder.id = null; + this.localBrowserTimezoneInfoPlaceholder.name = this.translate.instant('timezone.browser-time'); + } + return this.localBrowserTimezoneInfoPlaceholder; + } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 7d8546c021..39c8de97e1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -59,10 +59,12 @@
- +
+
{{ 'timezone.timezone' | translate }}
- +
@@ -115,10 +117,12 @@
- +
+
{{ 'timezone.timezone' | translate }}
- +
@@ -189,7 +193,10 @@ - + + diff --git a/ui-ngx/src/app/shared/components/time/timezone-panel.component.html b/ui-ngx/src/app/shared/components/time/timezone-panel.component.html index 0dae7823ec..3e4b128226 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timezone-panel.component.html @@ -1,10 +1,11 @@
{{ 'timezone.timezone' | translate }}
- + appearance="outline" + displayLabel="false">
diff --git a/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss b/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss index 031d2bb5da..c25c4cdf50 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timezone-panel.component.scss @@ -18,13 +18,14 @@ :host { display: flex; flex-direction: column; + width: 380px; max-height: 100%; max-width: 100%; background-color: #fff; .tb-form-panel { padding: 12px; - gap: 12px; + gap: 24px; } .tb-flex { diff --git a/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts b/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts index 4b6eb468aa..361902f51d 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timezone-panel.component.ts @@ -14,22 +14,17 @@ /// limitations under the License. /// -import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { OverlayRef } from '@angular/cdk/overlay'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { TimeService } from '@core/services/time.service'; -import { TranslateService } from '@ngx-translate/core'; +import { TbPopoverComponent } from '@shared/components/popover.component'; -export interface TimezonePanelData { - timezone: string; - isEdit: boolean; +export interface TimezoneSelectionResult { + timezone: string | null; } -export const TIMEZONE_PANEL_DATA = new InjectionToken('TimezonePanelData'); - @Component({ selector: 'tb-timezone-panel', templateUrl: './timezone-panel.component.html', @@ -37,35 +32,49 @@ export const TIMEZONE_PANEL_DATA = new InjectionToken('TimezonePanelData'); }) export class TimezonePanelComponent extends PageComponent implements OnInit { - timezone: string; - result: string; + @Input() + timezone: string | null; + + @Input() + userTimezoneByDefault: boolean; + + @Input() + localBrowserTimezonePlaceholderOnEmpty: boolean; + + @Input() + defaultTimezone: string; + + @Input() + onClose: (result: TimezoneSelectionResult | null) => void; + + @Input() + popoverComponent: TbPopoverComponent; timezoneForm: FormGroup; - constructor(@Inject(TIMEZONE_PANEL_DATA) public data: TimezonePanelData, - public overlayRef: OverlayRef, - protected store: Store, - public fb: FormBuilder, - private timeService: TimeService, - private translate: TranslateService, - public viewContainerRef: ViewContainerRef) { + constructor(protected store: Store, + public fb: FormBuilder) { super(store); - this.timezone = this.data.timezone; - this.timezoneForm = this.fb.group({ - timezone: [this.timezone] - }); } ngOnInit(): void { + this.timezoneForm = this.fb.group({ + timezone: [this.timezone] + }); } update() { - this.result = this.timezoneForm.get('timezone').value; - this.overlayRef.dispose(); + if (this.onClose) { + this.onClose({ + timezone: this.timezoneForm.get('timezone').value + }); + } } cancel() { - this.overlayRef.dispose(); + if (this.onClose) { + this.onClose(null); + } } } diff --git a/ui-ngx/src/app/shared/components/time/timezone-select.component.html b/ui-ngx/src/app/shared/components/time/timezone-select.component.html index 280a58e4b8..c9a9724aaa 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-select.component.html +++ b/ui-ngx/src/app/shared/components/time/timezone-select.component.html @@ -17,7 +17,7 @@ --> - timezone.timezone + timezone.timezone ; @@ -107,7 +113,8 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af constructor(private store: Store, public translate: TranslateService, private ngZone: NgZone, - private fb: UntypedFormBuilder) { + private fb: UntypedFormBuilder, + private timeService: TimeService) { this.selectTimezoneFormGroup = this.fb.group({ timezone: [null] }); @@ -168,7 +175,7 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af } else { this.modelValue = null; if (this.localBrowserTimezonePlaceholderOnEmptyValue) { - this.selectTimezoneFormGroup.get('timezone').patchValue(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: false}); + this.selectTimezoneFormGroup.get('timezone').patchValue(this.localBrowserTimezoneInfoPlaceholder, {emitEvent: false}); } else { this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); } @@ -197,7 +204,7 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af } } else if (this.localBrowserTimezonePlaceholderOnEmptyValue) { this.ngZone.run(() => { - this.selectTimezoneFormGroup.get('timezone').reset(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: true}); + this.selectTimezoneFormGroup.get('timezone').reset(this.localBrowserTimezoneInfoPlaceholder, {emitEvent: true}); }); } } @@ -235,19 +242,11 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af if (!this.timezones) { this.timezones = []; if (this.localBrowserTimezonePlaceholderOnEmptyValue) { - this.timezones.push(this.getLocalBrowserTimezoneInfoPlaceholder()); + this.timezones.push(this.localBrowserTimezoneInfoPlaceholder); } this.timezones.push(...getTimezones()); } return this.timezones; } - private getLocalBrowserTimezoneInfoPlaceholder(): TimezoneInfo { - if (!this.localBrowserTimezoneInfoPlaceholder) { - this.localBrowserTimezoneInfoPlaceholder = deepClone(getDefaultTimezoneInfo()); - this.localBrowserTimezoneInfoPlaceholder.id = null; - this.localBrowserTimezoneInfoPlaceholder.name = this.translate.instant('timezone.browser-time'); - } - return this.localBrowserTimezoneInfoPlaceholder; - } } diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.html b/ui-ngx/src/app/shared/components/time/timezone.component.html index 5aff9da59c..7977f24081 100644 --- a/ui-ngx/src/app/shared/components/time/timezone.component.html +++ b/ui-ngx/src/app/shared/components/time/timezone.component.html @@ -17,6 +17,8 @@ -->
{{ computedTimezoneStyle.icon }} -
- {{innerValue}} +
+ {{timezoneInfo}}
{{ computedTimezoneStyle.icon }} diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.scss b/ui-ngx/src/app/shared/components/time/timezone.component.scss index 99f3d8367d..db4f492985 100644 --- a/ui-ngx/src/app/shared/components/time/timezone.component.scss +++ b/ui-ngx/src/app/shared/components/time/timezone.component.scss @@ -23,7 +23,7 @@ .mdc-button { max-width: 100%; } - section.tb-timewindow { + section.tb-timezone { padding: 0 8px; &.no-padding { padding: 0; @@ -38,7 +38,7 @@ gap: 4px; width: 100%; - .tb-timewindow-label { + .tb-timezone-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.ts b/ui-ngx/src/app/shared/components/time/timezone.component.ts index 79fe0b870f..8f0f2eee40 100644 --- a/ui-ngx/src/app/shared/components/time/timezone.component.ts +++ b/ui-ngx/src/app/shared/components/time/timezone.component.ts @@ -17,26 +17,19 @@ import { ChangeDetectorRef, Component, - ElementRef, forwardRef, HostBinding, - Injector, Input, OnChanges, - OnInit, Renderer2, + OnInit, + Renderer2, SimpleChanges, - StaticProvider, ViewContainerRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; -import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; -import { DatePipe } from '@angular/common'; -import { TimeService } from '@core/services/time.service'; import { TooltipPosition } from '@angular/material/tooltip'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal } from '@angular/cdk/portal'; import { coerceBoolean } from '@shared/decorators/coercion'; import { ComponentStyle, @@ -45,15 +38,10 @@ import { textStyle, TimezoneStyle } from '@shared/models/widget-settings.models'; -import { DEFAULT_OVERLAY_POSITIONS } from '@shared/models/overlay.models'; -import { fromEvent } from 'rxjs'; -import { - TIMEZONE_PANEL_DATA, - TimezonePanelComponent, - TimezonePanelData -} from '@shared/components/time/timezone-panel.component'; +import { TimezonePanelComponent, TimezoneSelectionResult } from '@shared/components/time/timezone-panel.component'; import { TbPopoverService } from '@shared/components/popover.service'; -import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; +import { getTimezoneInfo, TimezoneInfo } from '@shared/models/time/time.models'; +import { TimeService } from '@core/services/time.service'; // @dynamic @Component({ @@ -83,10 +71,6 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange @coerceBoolean() disablePanel = false; - @Input() - @coerceBoolean() - isToolbar = false; - @Input() @coerceBoolean() asButton = false; @@ -145,7 +129,8 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange @Input() set localBrowserTimezonePlaceholderOnEmpty(value: boolean) { this.localBrowserTimezonePlaceholderOnEmptyValue = coerceBooleanProperty(value); - }defaultTimezoneId: string = null; + } + defaultTimezoneId: string = null; @Input() set defaultTimezone(timezone: string) { @@ -163,7 +148,10 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange this.requiredValue = coerceBooleanProperty(value); } - innerValue: string; + modelValue: string | null; + timezoneInfo: TimezoneInfo; + + private localBrowserTimezoneInfoPlaceholder: TimezoneInfo = this.timeService.getLocalBrowserTimezoneInfoPlaceholder(); timezoneDisabled: boolean; @@ -173,16 +161,12 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange private propagateChange = (_: any) => {}; - constructor(private overlay: Overlay, - private translate: TranslateService, - private timeService: TimeService, - private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe, - private datePipe: DatePipe, + constructor(private translate: TranslateService, private cd: ChangeDetectorRef, - private nativeElement: ElementRef, public viewContainerRef: ViewContainerRef, private popoverService: TbPopoverService, - private renderer: Renderer2) { + private renderer: Renderer2, + private timeService: TimeService) { } ngOnInit() { @@ -202,82 +186,38 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange } toggleTimezone($event: Event) { - console.log($event); if ($event) { $event.stopPropagation(); } if (this.disablePanel) { return; } - // const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element; - // if (this.popoverService.hasPopover(trigger)) { - // this.popoverService.hidePopover(trigger); - // } else { - // const timezoneSelectionPopover = this.popoverService.displayPopover(trigger, this.renderer, - // this.viewContainerRef, TimewindowStylePanelComponent, 'left', true, null, - // ctx, - // {}, - // {}, {}, true); - // timewindowStylePanelPopover.tbComponentRef.instance.popover = timewindowStylePanelPopover; - // timewindowStylePanelPopover.tbComponentRef.instance.timewindowStyleApplied.subscribe((timewindowStyle) => { - // timewindowStylePanelPopover.hide(); - // this.modelValue = timewindowStyle; - // this.propagateChange(this.modelValue); - // }); - // - // // if (componentRef.instance.result) { - // // this.innerValue = componentRef.instance.result; - // // this.timezoneDisabled = this.isTimezoneDisabled(); - // // this.updateDisplayValue(); - // // this.notifyChanged(); - // // } - // } - const config = new OverlayConfig({ - panelClass: 'tb-timezone-panel', - backdropClass: 'cdk-overlay-transparent-backdrop', - hasBackdrop: true, - maxHeight: '30vh', - height: 'min-content' - }); - - config.positionStrategy = this.overlay.position() - .flexibleConnectedTo(this.nativeElement) - .withPositions(DEFAULT_OVERLAY_POSITIONS); - - // TODO: change panel to popover - const overlayRef = this.overlay.create(config); - overlayRef.backdropClick().subscribe(() => { - overlayRef.dispose(); - }); - const providers: StaticProvider[] = [ - { - provide: TIMEZONE_PANEL_DATA, - useValue: { - timezone: this.innerValue, - isEdit: this.isEdit - } as TimezonePanelData - }, - { - provide: OverlayRef, - useValue: overlayRef - } - ]; - const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); - const componentRef = overlayRef.attach(new ComponentPortal(TimezonePanelComponent, - this.viewContainerRef, injector)); - const resizeWindows$ = fromEvent(window, 'resize').subscribe(() => { - overlayRef.updatePosition(); - }); - componentRef.onDestroy(() => { - resizeWindows$.unsubscribe(); - // TODO: if value check makes impossible to select browser timezone (null) - if (componentRef.instance.result) { - this.innerValue = componentRef.instance.result; - this.timezoneDisabled = this.isTimezoneDisabled(); - this.updateDisplayValue(); - this.notifyChanged(); - } - }); + const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const timezoneSelectionPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, TimezonePanelComponent, ['bottomRight', 'leftBottom'], true, null, + { + timezone: this.modelValue, + userTimezoneByDefault: this.userTimezoneByDefaultValue, + localBrowserTimezonePlaceholderOnEmpty: this.localBrowserTimezonePlaceholderOnEmptyValue, + defaultTimezone: this.defaultTimezoneId, + onClose: (result: TimezoneSelectionResult | null) => { + timezoneSelectionPopover.hide(); + if (result) { + this.modelValue = result.timezone; + this.setTimezoneInfo(); + this.timezoneDisabled = this.isTimezoneDisabled(); + this.updateDisplayValue(); + this.notifyChanged(); + } + } + }, + {}, + {}, {}, true); + timezoneSelectionPopover.tbComponentRef.instance.popoverComponent = timezoneSelectionPopover; + } this.cd.detectChanges(); } @@ -304,19 +244,23 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange this.timezoneDisabled = this.isTimezoneDisabled(); } - writeValue(value: string): void { - this.innerValue = value; + writeValue(value: string | null): void { + this.modelValue = value; + this.setTimezoneInfo(); this.timezoneDisabled = this.isTimezoneDisabled(); this.updateDisplayValue(); } notifyChanged() { - this.propagateChange(this.innerValue); + this.propagateChange(this.modelValue); } displayValue(): string { - // TODO: only offset should be displayed, timezone name shouldn't be present - return this.displayTimezoneValue ? this.innerValue : this.translate.instant('timezone.timezone'); + return this.displayTimezoneValue && this.timezoneInfo ? this.timezoneInfo.offset : this.translate.instant('timezone.timezone'); + } + + tooltipValue(): string { + return this.timezoneInfo ? `${this.timezoneInfo.name} (${this.timezoneInfo.offset})` : undefined; } updateDisplayValue() { @@ -327,4 +271,17 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange return this.disabled; } + private setTimezoneInfo() { + const foundTimezone = getTimezoneInfo(this.modelValue, this.defaultTimezoneId, this.userTimezoneByDefaultValue); + if (foundTimezone !== null) { + this.timezoneInfo = foundTimezone; + } else { + if (this.localBrowserTimezonePlaceholderOnEmptyValue) { + this.timezoneInfo = this.localBrowserTimezoneInfoPlaceholder; + } else { + this.timezoneInfo = null; + } + } + } + } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 08850ddbe8..4d5ae09a15 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -268,7 +268,7 @@ pre.tb-highlight { letter-spacing: normal; } -.tb-timewindow-panel, .tb-legend-config-panel, .tb-filter-panel, .tb-panel-container, tb-timezone-panel { +.tb-timewindow-panel, .tb-legend-config-panel, .tb-filter-panel, .tb-panel-container { overflow: hidden; background: #fff; border-radius: 4px; From 91d0f1dd1d7ca485cebd35b98b9c9f6e305ebf8b Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 1 Aug 2024 15:44:48 +0300 Subject: [PATCH 10/84] Added ability to provision gateway device on device creating using device provisioning feature --- .../transport/DefaultTransportApiService.java | 5 ++- .../MqttProvisionProtoDeviceTest.java | 39 +++++++++++++++++++ .../device/provision/ProvisionRequest.java | 1 + .../server/common/adaptor/JsonConverter.java | 1 + common/proto/src/main/proto/queue.proto | 1 + .../server/dao/device/DeviceServiceImpl.java | 6 +++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 8b29af34ed..32f7d48e29 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -417,7 +417,8 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()), + requestMsg.getGateway())); } catch (ProvisionFailedException e) { return getTransportApiResponseMsg(new DeviceCredentials(), TransportProtos.ResponseStatus.valueOf(e.getMessage())); } @@ -665,7 +666,7 @@ public class DefaultTransportApiService implements TransportApiService { private ProvisionRequest createProvisionRequest(String certificateValue) { return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), - null); + null, null); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java index 0926459513..16b3f8a233 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.mqttv3.provision; +import com.fasterxml.jackson.databind.JsonNode; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; @@ -76,6 +77,11 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -130,6 +136,35 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); } + protected void processTestProvisioningCreateNewGatewayDevice() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision gateway device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); + + byte[] provisionRequestMsg = createTestsProvisionMessage(null, null, true); + byte[] responseBytesMsg = createMqttClientAndPublish(provisionRequestMsg); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(responseBytesMsg); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision gateway device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has("gateway")); + Assert.assertTrue(additionalInfo.get("gateway").asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); + } + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -280,6 +315,10 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { } protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return createTestsProvisionMessage(credentialsType, credentialsData, false); + } + + protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData, boolean isGateway) throws Exception { return ProvisionDeviceRequestMsg.newBuilder() .setDeviceName("Test Provision device") .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java index d01d7be3c1..c0643f96da 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -28,4 +28,5 @@ public class ProvisionRequest { private DeviceCredentialsType credentialsType; private ProvisionDeviceCredentialsData credentialsData; private ProvisionDeviceProfileCredentials credentials; + private Boolean gateway; } diff --git a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java index f5c7bbb901..d1283d3ba0 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java @@ -635,6 +635,7 @@ public class JsonConverter { .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), getStrValue(jo, DataConstants.PROVISION_SECRET, true))) + .setGateway(jo.has(DataConstants.GATEWAY_PARAMETER) && jo.get(DataConstants.GATEWAY_PARAMETER).getAsBoolean()) .build(); } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 6a8fb543f6..7ede3bdf1b 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -687,6 +687,7 @@ message ProvisionDeviceRequestMsg { CredentialsType credentialsType = 2; ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3; CredentialsDataProto credentialsDataProto = 4; + bool gateway = 5; } message ProvisionDeviceCredentialsMsg { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 5b770845c1..e7f738a90d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -564,6 +565,11 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Fri, 2 Aug 2024 09:30:25 +0300 Subject: [PATCH 11/84] Added additional tests --- .../provision/DeviceProvisionServiceTest.java | 2 +- .../CoapProvisionJsonDeviceTest.java | 59 +++++++++++++++++-- .../CoapProvisionProtoDeviceTest.java | 44 +++++++++++++- .../MqttProvisionJsonDeviceTest.java | 58 ++++++++++++++++-- .../MqttProvisionProtoDeviceTest.java | 10 ++-- .../msa/connectivity/HttpClientTest.java | 41 +++++++++++++ 6 files changed, 200 insertions(+), 14 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java index 8d5d66f6d2..75b84f50be 100644 --- a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java @@ -236,7 +236,7 @@ public class DeviceProvisionServiceTest { private ProvisionRequest createProvisionRequest(String certificateValue) { return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), - null); + null, null); } public static String certTrimNewLinesForChainInDeviceProfile(String input) { diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java index 22d1e9e349..b2187ba7b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -67,6 +68,11 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -123,6 +129,37 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { } + private void processTestProvisioningCreateNewGatewayDevice() throws Exception { + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish(true)); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); + } + + private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -222,16 +259,30 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { } private byte[] createCoapClientAndPublish() throws Exception { - return createCoapClientAndPublish(""); + return createCoapClientAndPublish(false); + } + + private byte[] createCoapClientAndPublish(boolean isGateway) throws Exception { + return createCoapClientAndPublish("", isGateway); } private byte[] createCoapClientAndPublish(String deviceCredentials) throws Exception { - String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); + return createCoapClientAndPublish(deviceCredentials, false); + } + + private byte[] createCoapClientAndPublish(String deviceCredentials, boolean isGateway) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials, isGateway); client = new CoapTestClient(accessToken, FeatureType.PROVISION); return client.postMethod(provisionRequestMsg.getBytes()).getPayload(); } - private String createTestProvisionMessage(String deviceCredentials) { - return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; + protected String createTestProvisionMessage(String deviceCredentials, boolean isGateway) { + String request = "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + + deviceCredentials; + if (isGateway) { + request += ",\"gateway\":true"; + } + request += "}"; + return request; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java index 5a7c880d44..4b1f806f74 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.coap.provision; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapResponse; import org.junit.After; @@ -22,6 +23,7 @@ import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -74,6 +76,11 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -124,6 +131,35 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); } + private void processTestProvisioningCreateNewGatewayDevice() throws Exception { + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); + byte[] testsProvisionMessage = createTestsProvisionMessage(null, null, true); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(testsProvisionMessage)); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); + } + private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -233,6 +269,10 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { } private byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return createTestsProvisionMessage(credentialsType, credentialsData, false); + } + + private byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData, boolean isGateway) throws Exception { return ProvisionDeviceRequestMsg.newBuilder() .setDeviceName("Test Provision device") .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) @@ -241,7 +281,9 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey("testProvisionKey") .setProvisionDeviceSecret("testProvisionSecret") - ).build() + ) + .setGateway(isGateway) + .build() .toByteArray(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java index 27ed65f54a..d32ca58aa4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -68,6 +69,11 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -130,6 +136,37 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { } + protected void processTestProvisioningCreateNewGatewayDevice() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); + byte[] result = createMqttClientAndPublish(true); + JsonNode response = JacksonUtil.fromBytes(result); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); + } + + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -264,11 +301,19 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { } protected byte[] createMqttClientAndPublish() throws Exception { - return createMqttClientAndPublish(""); + return createMqttClientAndPublish(false); + } + + protected byte[] createMqttClientAndPublish(boolean isGateway) throws Exception { + return createMqttClientAndPublish("", isGateway); } protected byte[] createMqttClientAndPublish(String deviceCredentials) throws Exception { - String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); + return createMqttClientAndPublish(deviceCredentials, false); + } + + protected byte[] createMqttClientAndPublish(String deviceCredentials, boolean isGateway) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials, isGateway); MqttTestClient client = new MqttTestClient(); client.connectAndWait("provision"); MqttTestCallback onProvisionCallback = new MqttTestSubscribeOnTopicCallback(DEVICE_PROVISION_RESPONSE_TOPIC); @@ -280,7 +325,12 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { return onProvisionCallback.getPayloadBytes(); } - protected String createTestProvisionMessage(String deviceCredentials) { - return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; + protected String createTestProvisionMessage(String deviceCredentials, boolean isGateway) { + String request = "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials; + if (isGateway) { + request += ",\"gateway\":true"; + } + request += "}"; + return request; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java index 16b3f8a233..f05a4f8953 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -138,7 +139,7 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { protected void processTestProvisioningCreateNewGatewayDevice() throws Exception { MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() - .deviceName("Test Provision gateway device") + .deviceName("Test Provision device3") .transportPayloadType(TransportPayloadType.PROTOBUF) .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) .provisionKey("testProvisionKey") @@ -150,14 +151,15 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { byte[] responseBytesMsg = createMqttClientAndPublish(provisionRequestMsg); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(responseBytesMsg); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision gateway device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); JsonNode additionalInfo = createdDevice.getAdditionalInfo(); Assert.assertNotNull(additionalInfo); - Assert.assertTrue(additionalInfo.has("gateway")); - Assert.assertTrue(additionalInfo.get("gateway").asBoolean()); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java index 5ec80ca8be..9c33a04e64 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -21,6 +21,7 @@ import io.restassured.path.json.JsonPath; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -156,6 +157,46 @@ public class HttpClientTest extends AbstractContainerTest { updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); } + @Test + public void provisionRequestForGatewayDeviceWithAllowToCreateNewDevicesStrategy() throws Exception { + + String testDeviceName = "test_provision_device"; + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + provisionRequest.addProperty("deviceName", testDeviceName); + provisionRequest.addProperty("gateway", true); + + JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString()); + + String credentialsType = provisionResponse.get("credentialsType"); + String credentialsValue = provisionResponse.get("credentialsValue"); + String status = provisionResponse.get("status"); + + testRestClient.deleteDeviceIfExists(device.getId()); + device = testRestClient.getDeviceByName(testDeviceName); + + JsonNode additionalInfo = device.getAdditionalInfo(); + + assertThat(additionalInfo).isNotNull(); + assertThat(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()).isTrue(); + assertThat(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()).isTrue(); + + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + + assertThat(credentialsType).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(credentialsValue).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(status).isEqualTo("SUCCESS"); + + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + @Test public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception { From 599237d08d2d540d24ff99dc85ba843906681405 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Fri, 2 Aug 2024 09:39:23 +0300 Subject: [PATCH 12/84] Refactoring --- .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index e7f738a90d..73633d041f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -28,6 +28,7 @@ import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.device.DeviceCacheEvictEvent; import org.thingsboard.server.cache.device.DeviceCacheKey; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; @@ -567,7 +568,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Fri, 2 Aug 2024 10:41:32 +0300 Subject: [PATCH 13/84] Test fix --- .../mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java index f05a4f8953..bc4f1cdd86 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java @@ -329,7 +329,9 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey("testProvisionKey") .setProvisionDeviceSecret("testProvisionSecret") - ).build() + ) + .setGateway(isGateway) + .build() .toByteArray(); } From 7be52df6f51ea0d66be6582c5002cd7759d64a36 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 2 Aug 2024 13:23:44 +0300 Subject: [PATCH 14/84] UI: fix timezone displaying on quick-interval-only --- .../time/timewindow-panel.component.html | 37 +++++++++++-------- .../time/timewindow-panel.component.ts | 2 +- ui-ngx/src/form.scss | 6 +++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 39c8de97e1..6a596807a3 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -22,14 +22,16 @@
-
{{ 'timewindow.time-range' | translate }}
-
- - - - +
+
+
{{ 'timewindow.time-range' | translate }}
+ + + + +
@@ -58,7 +60,7 @@ -
+
{{ 'timezone.timezone' | translate }}
@@ -72,13 +74,16 @@
-
{{ 'timewindow.time-range' | translate }}
-
- - - - +
+
+
{{ 'timewindow.time-range' | translate }}
+ + + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index ac29d4aaa5..d3cf61ba88 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -125,7 +125,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { }); } - if (this.isEdit || (!this.timewindow.hideLastInterval && !this.quickIntervalOnly)) { + if ((this.isEdit || !this.timewindow.hideLastInterval) && !this.quickIntervalOnly) { this.realtimeTimewindowOptions.push({ name: this.translate.instant('timewindow.last'), value: this.realtimeTypes.LAST_INTERVAL diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index c9580bcf74..661f4bccc7 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -293,6 +293,12 @@ &.align-center { align-items: center; } + &.align-start { + align-items: start; + } + &.align-end { + align-items: end; + } &.no-gap { gap: 0; } From c8d48521fbd63c5f275a40196fcd0d4572900c60 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 2 Aug 2024 13:47:50 +0300 Subject: [PATCH 15/84] UI: timezone displaying refactoring --- .../time/timewindow-panel.component.html | 32 ++++++++----------- .../time/timewindow-panel.component.ts | 11 +++++++ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 6a596807a3..7aa8cf9db8 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -20,13 +20,12 @@ (selectedTabChange)="onTimewindowTypeChange()" [(selectedIndex)]="timewindow.selectedTab">
-
+
+ [ngClass]="{'align-end': realtimeTypeSelectionAvailable, 'align-center': !realtimeTypeSelectionAvailable }">
{{ 'timewindow.time-range' | translate }}
- + @@ -61,24 +60,18 @@
-
-
{{ 'timezone.timezone' | translate }}
- - -
-
+
+ [ngClass]="{'align-end': historyTypeSelectionAvailable, 'align-center': !historyTypeSelectionAvailable }">
{{ 'timewindow.time-range' | translate }}
- + @@ -122,12 +115,6 @@
-
-
{{ 'timezone.timezone' | translate }}
- - -
@@ -196,6 +183,13 @@
+ +
+
{{ 'timezone.timezone' | translate }}
+ + +
+
, @@ -138,6 +143,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { value: this.realtimeTypes.INTERVAL }); } + + this.realtimeTypeSelectionAvailable = this.realtimeTimewindowOptions.length > 1; + this.historyTypeSelectionAvailable = this.historyTimewindowOptions.length > 1; + this.realtimeIntervalSelectionAvailable = this.isEdit || !(this.timewindow.hideInterval || + (this.timewindow.hideLastInterval && this.timewindow.hideQuickInterval)); + this.historyIntervalSelectionAvailable = this.isEdit || !this.timewindow.hideInterval; } ngOnInit(): void { From a9b350ff7629e124d0a14f84632f52ee7bfba6a1 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 2 Aug 2024 17:54:56 +0300 Subject: [PATCH 16/84] UI: date-time period component redesign --- .../time/datetime-period.component.html | 36 +++++-------------- .../time/datetime-period.component.scss | 11 ++---- .../time/datetime-period.component.ts | 7 ++++ .../time/timewindow-panel.component.html | 14 ++++---- .../time/timewindow-panel.component.scss | 25 ------------- .../assets/locale/locale.constant-en_US.json | 4 ++- ui-ngx/src/form.scss | 6 ++++ 7 files changed, 35 insertions(+), 68 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.html b/ui-ngx/src/app/shared/components/time/datetime-period.component.html index fd6c1cf853..b846e58914 100644 --- a/ui-ngx/src/app/shared/components/time/datetime-period.component.html +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.html @@ -15,35 +15,17 @@ limitations under the License. --> -
-
- - datetime.date-from - - +
+ + datetime.from + + - - datetime.time-from - - - - -
-
- - datetime.date-to - - + + datetime.to + + - - datetime.time-to - - - - -
diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.scss b/ui-ngx/src/app/shared/components/time/datetime-period.component.scss index 7f2c84ee0a..6fa2806335 100644 --- a/ui-ngx/src/app/shared/components/time/datetime-period.component.scss +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.scss @@ -13,14 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../../scss/constants'; -:host ::ng-deep { - .mat-mdc-form-field-infix { - width: 100px; - - @media #{$mat-xs} { - width: 100%; - } +:host { + .tb-form-row { + gap: 8px; } } diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts index 69c3b55bae..c0fbaea21e 100644 --- a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts @@ -17,6 +17,7 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FixedWindow } from '@shared/models/time/time.models'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; @Component({ selector: 'tb-datetime-period', @@ -34,6 +35,12 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { @Input() disabled: boolean; + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + modelValue: FixedWindow; startDate: Date; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 7aa8cf9db8..03be25a760 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -23,11 +23,10 @@
-
+
{{ 'timewindow.time-range' | translate }}
- +
@@ -69,11 +68,10 @@
-
+
{{ 'timewindow.time-range' | translate }}
- +
@@ -97,6 +95,8 @@ @@ -126,7 +126,7 @@
{{ 'aggregation.aggregation' | translate }}
- + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index aaa861c131..64b35e9592 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -35,19 +35,6 @@ overflow: hidden; } - .mat-padding { - padding: 0 16px; - } - - .hide-label { - margin-bottom: 5px; - margin-right: 5px; - } - - tb-timeinterval[ng-reflect-fxe-show="true"] { - margin-bottom: -16px; - } - .limit-slider-container { .limit-slider-value { margin-left: 16px; @@ -60,9 +47,6 @@ } @media #{$mat-gt-sm} { - .history-time-input { - min-width: 364px; - } .limit-slider-container { > label { margin-right: 16px; @@ -75,15 +59,6 @@ } :host ::ng-deep { - .mat-mdc-radio-button { - display: block; - .mdc-form-field { - align-items: start; - > label { - padding-top: 10px; - } - } - } .mat-mdc-tab-group:not(.tb-headless) { height: 100%; } 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 6347ea381d..cbd9be152b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1078,7 +1078,9 @@ "date-from": "Date from", "time-from": "Time from", "date-to": "Date to", - "time-to": "Time to" + "time-to": "Time to", + "from": "From", + "to": "To" }, "dashboard": { "dashboard": "Dashboard", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 661f4bccc7..c91f575e40 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -265,6 +265,12 @@ gap: 8px; &.no-flex { flex: none; + &.flex-xs { + @media #{$mat-xs} { + width: auto; + flex: 1; + } + } } &.row { flex-direction: row; From d464b317a4e905e03a5e6d7ef8c46eb511cc8dc4 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 2 Aug 2024 18:31:07 +0300 Subject: [PATCH 17/84] UI: start introducing custom interval option instead of advanced option --- .../time/timeinterval.component.html | 24 +++++++++---------- .../components/time/timeinterval.component.ts | 21 ++++++++++++---- .../models/telemetry/telemetry.models.ts | 3 ++- .../assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index 3cf001e426..a67cbdd9be 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -20,8 +20,18 @@
-
-
+
+
+ + {{ predefinedName }} + + + {{ interval.name | translate:interval.translateParams }} + + + +
+
timeinterval.days @@ -40,16 +50,6 @@
-
- - {{ predefinedName }} - - - {{ interval.name | translate:interval.translateParams }} - - - -
diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index 906d938d0b..4ffb18d624 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -22,6 +22,7 @@ import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form- import { coerceBoolean } from '@shared/decorators/coercion'; import { Interval, IntervalMath, TimeInterval } from '@shared/models/time/time.models'; import { isDefined } from '@core/utils'; +import { IntervalType } from '@shared/models/telemetry/telemetry.models'; @Component({ selector: 'tb-timeinterval', @@ -98,6 +99,12 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { advanced = false; + customTimeInterval: TimeInterval = { + name: 'timeinterval.custom', + translateParams: {}, + value: IntervalType.CUSTOM + }; + private modelValue: Interval; private rendered = false; private propagateChangeValue: any; @@ -134,8 +141,14 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { const min = this.timeService.boundMinInterval(this.minValue); const max = this.timeService.boundMaxInterval(this.maxValue); if (IntervalMath.numberValue(this.modelValue) >= min && IntervalMath.numberValue(this.modelValue) <= max) { - this.advanced = !this.timeService.matchesExistingInterval(this.minValue, this.maxValue, this.modelValue, this.useCalendarIntervals); - this.setInterval(this.modelValue); + const advanced = !this.timeService.matchesExistingInterval(this.minValue, this.maxValue, this.modelValue, + this.useCalendarIntervals); + if (advanced && this.disabledAdvanced) { + this.boundInterval(); + } else { + this.advanced = advanced; + this.setInterval(this.modelValue); + } } else { this.boundInterval(); } @@ -143,9 +156,9 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { } private setInterval(interval: Interval) { - if (!this.advanced) { + // if (!this.advanced) { this.interval = interval; - } + // } const intervalSeconds = Math.floor(IntervalMath.numberValue(interval) / 1000); this.days = Math.floor(intervalSeconds / 86400); this.hours = Math.floor((intervalSeconds % 86400) / 3600); diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index e1c6af620b..54ebbf3f14 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -186,7 +186,8 @@ export enum IntervalType { WEEK = 'WEEK', WEEK_ISO = 'WEEK_ISO', MONTH = 'MONTH', - QUARTER = 'QUARTER' + QUARTER = 'QUARTER', + CUSTOM = 'CUSTOM' } export class TimeseriesSubscriptionCmd extends SubscriptionCmd { 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 cbd9be152b..1509a434c7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4609,6 +4609,7 @@ "minutes": "Minutes", "seconds": "Seconds", "advanced": "Advanced", + "custom": "Custom", "predefined": { "yesterday": "Yesterday", "day-before-yesterday": "Day before yesterday", From 399bfb8dbda5439367310d71d320c05501ae599d Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 5 Aug 2024 18:48:52 +0300 Subject: [PATCH 18/84] UI: timeinterval component redesign --- .../time/timeinterval.component.html | 54 ++--- .../time/timeinterval.component.scss | 17 +- .../components/time/timeinterval.component.ts | 224 ++++++++++-------- .../time/timewindow-panel.component.html | 2 +- 4 files changed, 153 insertions(+), 144 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index a67cbdd9be..dc81688418 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -15,43 +15,37 @@ limitations under the License. --> -
-
- - -
-
-
- - {{ predefinedName }} - - - {{ interval.name | translate:interval.translateParams }} - - - -
-
- +
+ + {{ predefinedName }} + + + {{ interval.name | translate:interval.translateParams }} + + + +
+
+ timeinterval.days - + - + timeinterval.hours - + - +
+
+ timeinterval.minutes - + - + timeinterval.seconds - + -
-
-
- - +
diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss index ba1c7b8d5a..c91f37bb2d 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss @@ -18,22 +18,21 @@ :host { min-width: 355px; - .advanced-switch { - margin-bottom: 16px; + .tb-form-panel { + gap: 12px; } - .advanced-label { - margin: 5px 0; - } - - .hide-label { - margin-bottom: 5px; - margin-right: 5px; + .advanced-input { + gap: 8px; } @media #{$mat-xs} { min-width: 0; width: 100%; + + .advanced-input { + gap: 12px; + } } } diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index 4ffb18d624..9e533036d7 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; import { coerceNumberProperty } from '@angular/cdk/coercion'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; @@ -23,6 +23,8 @@ import { coerceBoolean } from '@shared/decorators/coercion'; import { Interval, IntervalMath, TimeInterval } from '@shared/models/time/time.models'; import { isDefined } from '@core/utils'; import { IntervalType } from '@shared/models/telemetry/telemetry.models'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; @Component({ selector: 'tb-timeinterval', @@ -36,7 +38,7 @@ import { IntervalType } from '@shared/models/telemetry/telemetry.models'; } ] }) -export class TimeintervalComponent implements OnInit, ControlValueAccessor { +export class TimeintervalComponent implements OnInit, ControlValueAccessor, OnDestroy { minValue: number; maxValue: number; @@ -67,10 +69,6 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { @coerceBoolean() isEdit = false; - @Input() - @coerceBoolean() - hideFlag = false; - @Input() @coerceBoolean() disabledAdvanced = false; @@ -79,8 +77,6 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { @coerceBoolean() useCalendarIntervals = false; - @Output() hideFlagChange = new EventEmitter(); - @Input() disabled: boolean; @Input() @@ -89,16 +85,12 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { @Input() appearance: MatFormFieldAppearance = 'fill'; - days = 0; - hours = 0; - mins = 1; - secs = 0; - - interval: Interval = 0; intervals: Array; advanced = false; + timeintervalFormGroup: FormGroup; + customTimeInterval: TimeInterval = { name: 'timeinterval.custom', translateParams: {}, @@ -113,7 +105,43 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { this.propagateChangeValue = value; }; - constructor(private timeService: TimeService) { + private destroy$ = new Subject(); + + constructor(private timeService: TimeService, + private fb: FormBuilder) { + this.timeintervalFormGroup = this.fb.group({ + interval: [ 1 ], + customInterval: this.fb.group({ + days: [ 0 ], + hours: [ 0 ], + mins: [ 1 ], + secs: [ 0 ] + }) + }); + + this.timeintervalFormGroup.get('interval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.onIntervalChange()); + + this.timeintervalFormGroup.get('customInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateView()); + + this.timeintervalFormGroup.get('customInterval.secs').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((secs) => this.onSecsChange(secs)); + + this.timeintervalFormGroup.get('customInterval.mins').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((mins) => this.onMinsChange(mins)); + + this.timeintervalFormGroup.get('customInterval.hours').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((hours) => this.onHoursChange(hours)); + + this.timeintervalFormGroup.get('customInterval.days').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((days) => this.onDaysChange(days)); } ngOnInit(): void { @@ -132,6 +160,11 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.timeintervalFormGroup.disable({emitEvent: false}); + } else { + this.timeintervalFormGroup.enable({emitEvent: false}); + } } writeValue(interval: Interval): void { @@ -156,20 +189,31 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { } private setInterval(interval: Interval) { - // if (!this.advanced) { - this.interval = interval; - // } + if (!this.advanced) { + this.timeintervalFormGroup.get('interval').patchValue(interval); + } else { + this.timeintervalFormGroup.get('interval').patchValue(IntervalType.CUSTOM); + this.setCustomInterval(interval); + } + } + + private setCustomInterval(interval: Interval) { const intervalSeconds = Math.floor(IntervalMath.numberValue(interval) / 1000); - this.days = Math.floor(intervalSeconds / 86400); - this.hours = Math.floor((intervalSeconds % 86400) / 3600); - this.mins = Math.floor(((intervalSeconds % 86400) % 3600) / 60); - this.secs = intervalSeconds % 60; + this.timeintervalFormGroup.get('customInterval').patchValue({ + days: Math.floor(intervalSeconds / 86400), + hours: Math.floor((intervalSeconds % 86400) / 3600), + mins: Math.floor(((intervalSeconds % 86400) % 3600) / 60), + secs: intervalSeconds % 60 + }); } private boundInterval(updateToPreferred = false) { const min = this.timeService.boundMinInterval(this.minValue); const max = this.timeService.boundMaxInterval(this.maxValue); this.intervals = this.timeService.getIntervals(this.minValue, this.maxValue, this.useCalendarIntervals); + if (!this.disabledAdvanced) { + this.intervals.push(this.customTimeInterval); + } if (this.rendered) { let newInterval = this.modelValue; const newIntervalMs = IntervalMath.numberValue(newInterval); @@ -195,7 +239,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { let value: Interval = null; let interval: Interval; if (!this.advanced) { - interval = this.interval; + interval = this.timeintervalFormGroup.get('interval').value; if (!interval || typeof interval === 'number' && isNaN(interval)) { interval = this.calculateIntervalMs(); } @@ -211,118 +255,90 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { } private calculateIntervalMs(): number { - return (this.days * 86400 + - this.hours * 3600 + - this.mins * 60 + - this.secs) * 1000; + const customInterval = this.timeintervalFormGroup.get('customInterval').value; + return (customInterval.days * 86400 + + customInterval.hours * 3600 + + customInterval.mins * 60 + + customInterval.secs) * 1000; } onIntervalChange() { - this.updateView(); - } - - onAdvancedChange() { - if (!this.advanced) { - this.interval = this.calculateIntervalMs(); - } else { - let interval = this.interval; - if (!interval || typeof interval === 'number' && isNaN(interval)) { - interval = this.calculateIntervalMs(); + const customIntervalSelected = this.timeintervalFormGroup.get('interval').value === IntervalType.CUSTOM; + if (customIntervalSelected !== this.advanced) { + this.advanced = customIntervalSelected; + if (this.advanced) { + this.setCustomInterval(this.modelValue); } - this.setInterval(interval); } this.updateView(); } - onHideFlagChange() { - this.hideFlagChange.emit(this.hideFlag); - } - - onTimeInputChange(type: string) { - switch (type) { - case 'secs': - setTimeout(() => this.onSecsChange(), 0); - break; - case 'mins': - setTimeout(() => this.onMinsChange(), 0); - break; - case 'hours': - setTimeout(() => this.onHoursChange(), 0); - break; - case 'days': - setTimeout(() => this.onDaysChange(), 0); - break; - } - } - - private onSecsChange() { - if (typeof this.secs === 'undefined') { + private onSecsChange(secs: number) { + const customInterval = this.timeintervalFormGroup.get('customInterval').value; + if (typeof secs === 'undefined') { return; } - if (this.secs < 0) { - if ((this.days + this.hours + this.mins) > 0) { - this.secs = this.secs + 60; - this.mins--; - this.onMinsChange(); + if (secs < 0) { + if ((customInterval.days + customInterval.hours + customInterval.mins) > 0) { + this.timeintervalFormGroup.get('customInterval.secs').patchValue(secs + 60, {emitEvent: false}); + this.timeintervalFormGroup.get('customInterval.mins').patchValue(customInterval.mins - 1, {emitEvent: true}); } else { - this.secs = 0; + this.timeintervalFormGroup.get('customInterval.secs').patchValue(0, {emitEvent: false}); } - } else if (this.secs >= 60) { - this.secs = this.secs - 60; - this.mins++; - this.onMinsChange(); + } else if (secs >= 60) { + this.timeintervalFormGroup.get('customInterval.secs').patchValue(secs - 60, {emitEvent: false}); + this.timeintervalFormGroup.get('customInterval.mins').patchValue(customInterval.mins + 1, {emitEvent: true}); } - this.updateView(); } - private onMinsChange() { - if (typeof this.mins === 'undefined') { + private onMinsChange(mins: number) { + const customInterval = this.timeintervalFormGroup.get('customInterval').value; + if (typeof mins === 'undefined') { return; } - if (this.mins < 0) { - if ((this.days + this.hours) > 0) { - this.mins = this.mins + 60; - this.hours--; - this.onHoursChange(); + if (mins < 0) { + if ((customInterval.days + customInterval.hours) > 0) { + this.timeintervalFormGroup.get('customInterval.mins').patchValue(mins + 60, {emitEvent: false}); + this.timeintervalFormGroup.get('customInterval.hours').patchValue(customInterval.hours - 1, {emitEvent: true}); } else { - this.mins = 0; + this.timeintervalFormGroup.get('customInterval.mins').patchValue(0, {emitEvent: false}); } - } else if (this.mins >= 60) { - this.mins = this.mins - 60; - this.hours++; - this.onHoursChange(); + } else if (mins >= 60) { + this.timeintervalFormGroup.get('customInterval.mins').patchValue(mins - 60, {emitEvent: false}); + this.timeintervalFormGroup.get('customInterval.hours').patchValue(customInterval.hours + 1, {emitEvent: true}); } - this.updateView(); } - private onHoursChange() { - if (typeof this.hours === 'undefined') { + private onHoursChange(hours: number) { + const customInterval = this.timeintervalFormGroup.get('customInterval').value; + if (typeof hours === 'undefined') { return; } - if (this.hours < 0) { - if (this.days > 0) { - this.hours = this.hours + 24; - this.days--; - this.onDaysChange(); + if (hours < 0) { + if (customInterval.days > 0) { + this.timeintervalFormGroup.get('customInterval.hours').patchValue(hours + 24, {emitEvent: false}); + this.timeintervalFormGroup.get('customInterval.days').patchValue(customInterval.days - 1, {emitEvent: true}); } else { - this.hours = 0; + this.timeintervalFormGroup.get('customInterval.hours').patchValue(0, {emitEvent: false}); } - } else if (this.hours >= 24) { - this.hours = this.hours - 24; - this.days++; - this.onDaysChange(); + } else if (hours >= 24) { + this.timeintervalFormGroup.get('customInterval.hours').patchValue(hours - 24, {emitEvent: false}); + this.timeintervalFormGroup.get('customInterval.days').patchValue(customInterval.days + 1, {emitEvent: true}); } - this.updateView(); } - private onDaysChange() { - if (typeof this.days === 'undefined') { + private onDaysChange(days: number) { + if (typeof days === 'undefined') { return; } - if (this.days < 0) { - this.days = 0; + if (days < 0) { + this.timeintervalFormGroup.get('customInterval.days').patchValue(0, {emitEvent: false}); } - this.updateView(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 03be25a760..dbb36b84a6 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -159,7 +159,7 @@
-
{{ 'aggregation.group-interval' | translate }}
From 9ca85d5cc713a5bb30eba838fb73235072ab237c Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 9 Aug 2024 18:41:03 +0300 Subject: [PATCH 19/84] UI: timewindow configuration dialog introduction --- .../components/time/timeinterval.component.ts | 6 +- .../timewindow-config-dialog.component.html | 211 ++++++++++++ .../timewindow-config-dialog.component.scss | 70 ++++ .../timewindow-config-dialog.component.ts | 309 ++++++++++++++++++ .../time/timewindow-panel.component.html | 15 +- .../time/timewindow-panel.component.scss | 16 +- .../time/timewindow-panel.component.ts | 45 ++- ui-ngx/src/app/shared/shared.module.ts | 3 + .../assets/locale/locale.constant-en_US.json | 10 +- 9 files changed, 662 insertions(+), 23 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index 9e533036d7..61c0126267 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -190,9 +190,9 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor, OnDe private setInterval(interval: Interval) { if (!this.advanced) { - this.timeintervalFormGroup.get('interval').patchValue(interval); + this.timeintervalFormGroup.get('interval').patchValue(interval, {emitEvent: false}); } else { - this.timeintervalFormGroup.get('interval').patchValue(IntervalType.CUSTOM); + this.timeintervalFormGroup.get('interval').patchValue(IntervalType.CUSTOM, {emitEvent: false}); this.setCustomInterval(interval); } } @@ -204,7 +204,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor, OnDe hours: Math.floor((intervalSeconds % 86400) / 3600), mins: Math.floor(((intervalSeconds % 86400) % 3600) / 60), secs: intervalSeconds % 60 - }); + }, {emitEvent: false}); } private boundInterval(updateToPreferred = false) { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html new file mode 100644 index 0000000000..e6bb44a423 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -0,0 +1,211 @@ + + +

{{ 'timewindow.timewindow-settings' | translate }}

+ + + +
+
+ + +
+
+
+
+
{{ 'timewindow.time-range' | translate }}
+ + + + +
+ + + +
+ + + + + + + + + + + + +
+ + +
+
+ +
+
+
+
+
{{ 'timewindow.time-range' | translate }}
+ + + + +
+ + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+
+
+ +
+ +
+
{{ 'aggregation.aggregation' | translate }}
+ + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + + +
+ +
+
{{ 'aggregation.limit' | translate }}
+
+
+ + + + + + +
+
+
+
+ +
+
{{ 'aggregation.group-interval' | translate }}
+ + + + + + + + +
+
+
+ + + + +
+ +
+ + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss new file mode 100644 index 0000000000..0c5ad7b927 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2024 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. + */ +@import "../../../../scss/constants"; + +:host { + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 100%; + background-color: #fff; + + .tb-form-panel { + padding: 12px; + gap: 12px; + } + + .mat-content { + overflow: hidden; + + .tb-flex { + gap: 16px; + } + } + + .limit-slider-container { + .limit-slider-value { + margin-left: 16px; + min-width: 25px; + max-width: 100px; + } + mat-form-field input[type=number] { + text-align: center; + } + } + + @media #{$mat-gt-sm} { + .limit-slider-container { + > label { + margin-right: 16px; + width: min-content; + max-width: 40%; + } + } + } + +} + +:host ::ng-deep { + .mat-mdc-tab-group:not(.tb-headless) { + height: 100%; + } + + .mat-mdc-tab-body-content { + padding: 12px 16px; + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts new file mode 100644 index 0000000000..e1cabedda0 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -0,0 +1,309 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Component, Inject, OnInit } from '@angular/core'; +import { + aggregationTranslations, + AggregationType, + DAY, + HistoryWindowType, + quickTimeIntervalPeriod, + RealtimeWindowType, + Timewindow, + TimewindowType +} from '@shared/models/time/time.models'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TimeService } from '@core/services/time.service'; +import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; +import { TranslateService } from '@ngx-translate/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +export interface TimewindowConfigDialogData { + quickIntervalOnly: boolean; + timewindow: Timewindow; +} + +@Component({ + selector: 'tb-timewindow-config-dialog', + templateUrl: './timewindow-config-dialog.component.html', + styleUrls: ['./timewindow-config-dialog.component.scss'] +}) +export class TimewindowConfigDialogComponent extends PageComponent implements OnInit { + + quickIntervalOnly = false; + + timewindow: Timewindow; + + timewindowForm: UntypedFormGroup; + + historyTypes = HistoryWindowType; + + realtimeTypes = RealtimeWindowType; + + timewindowTypes = TimewindowType; + + aggregationTypes = AggregationType; + + aggregations = Object.keys(AggregationType); + + aggregationTypesTranslations = aggregationTranslations; + + result: Timewindow; + + realtimeTimewindowOptions: ToggleHeaderOption[] = [ + { + name: this.translate.instant('timewindow.relative'), + value: this.realtimeTypes.INTERVAL + } + ]; + + historyTimewindowOptions: ToggleHeaderOption[] = [ + { + name: this.translate.instant('timewindow.last'), + value: this.historyTypes.LAST_INTERVAL + }, + { + name: this.translate.instant('timewindow.fixed'), + value: this.historyTypes.FIXED + }, + { + name: this.translate.instant('timewindow.relative'), + value: this.historyTypes.INTERVAL + } + ]; + + realtimeTypeSelectionAvailable: boolean; + + constructor(@Inject(MAT_DIALOG_DATA) public data: TimewindowConfigDialogData, + public dialogRef: MatDialogRef, + protected store: Store, + public fb: UntypedFormBuilder, + private timeService: TimeService, + private translate: TranslateService) { + super(store); + this.quickIntervalOnly = data.quickIntervalOnly; + this.timewindow = data.timewindow; + + if (!this.quickIntervalOnly) { + this.realtimeTimewindowOptions.unshift({ + name: this.translate.instant('timewindow.last'), + value: this.realtimeTypes.LAST_INTERVAL + }); + } + + this.realtimeTypeSelectionAvailable = this.realtimeTimewindowOptions.length > 1; + } + + ngOnInit(): void { + const hideInterval = this.timewindow.hideInterval || false; + const hideLastInterval = this.timewindow.hideLastInterval || false; + const hideQuickInterval = this.timewindow.hideQuickInterval || false; + const hideAggregation = this.timewindow.hideAggregation || false; + const hideAggInterval = this.timewindow.hideAggInterval || false; + const hideTimezone = this.timewindow.hideTimezone || false; + + const realtime = this.timewindow.realtime; + const history = this.timewindow.history; + const aggregation = this.timewindow.aggregation; + + this.timewindowForm = this.fb.group({ + realtime: this.fb.group({ + realtimeType: [ isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL ], + timewindowMs: [ isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null ], + interval: [ isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null ], + quickInterval: [ isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null ] + }), + history: this.fb.group({ + historyType: [ isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL ], + timewindowMs: [ isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null ], + interval: [ isDefined(history?.interval) ? this.timewindow.history.interval : null ], + fixedTimewindow: [ isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null ], + quickInterval: [ isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null ] + }), + aggregation: this.fb.group({ + type: [ isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null ], + limit: [ isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null ] + }), + timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ], + hideInterval: [ isDefinedAndNotNull(this.timewindow.hideInterval) + ? this.timewindow.hideInterval : false ], + hideLastInterval: [{ + value: isDefinedAndNotNull(this.timewindow.hideLastInterval) + ? this.timewindow.hideLastInterval : false, + disabled: this.timewindow.hideInterval + }], + hideQuickInterval: [{ + value: isDefinedAndNotNull(this.timewindow.hideQuickInterval) + ? this.timewindow.hideQuickInterval : false, + disabled: this.timewindow.hideInterval + }], + hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation) + ? this.timewindow.hideAggregation : false ], + hideAggInterval: [ isDefinedAndNotNull(this.timewindow.hideAggInterval) + ? this.timewindow.hideAggInterval : false ], + hideTimezone: [ isDefinedAndNotNull(this.timewindow.hideTimezone) + ? this.timewindow.hideTimezone : false ] + }); + + this.updateValidators(this.timewindowForm.get('aggregation.type').value); + this.timewindowForm.get('aggregation.type').valueChanges.subscribe((aggregationType: AggregationType) => { + this.updateValidators(aggregationType); + }); + this.timewindowForm.get('hideInterval').valueChanges.subscribe((value: boolean) => { + if (value) { + this.timewindowForm.get('hideLastInterval').disable(); + this.timewindowForm.get('hideQuickInterval').disable(); + } else { + this.timewindowForm.get('hideLastInterval').enable(); + this.timewindowForm.get('hideQuickInterval').enable(); + } + }); + } + + private checkLimit(limit?: number): number { + if (!limit || limit < this.minDatapointsLimit()) { + return this.minDatapointsLimit(); + } else if (limit > this.maxDatapointsLimit()) { + return this.maxDatapointsLimit(); + } + return limit; + } + + private updateValidators(aggType: AggregationType) { + if (aggType !== AggregationType.NONE) { + this.timewindowForm.get('aggregation.limit').clearValidators(); + } else { + this.timewindowForm.get('aggregation.limit').setValidators([Validators.min(this.minDatapointsLimit()), + Validators.max(this.maxDatapointsLimit())]); + } + this.timewindowForm.get('aggregation.limit').updateValueAndValidity({emitEvent: false}); + } + + onTimewindowTypeChange() { + this.timewindowForm.markAsDirty(); + const timewindowFormValue = this.timewindowForm.getRawValue(); + if (this.timewindow.selectedTab === TimewindowType.REALTIME) { + if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { + this.timewindowForm.get('realtime').patchValue({ + realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? + RealtimeWindowType[HistoryWindowType[timewindowFormValue.history.historyType]] : + timewindowFormValue.realtime.realtimeType, + timewindowMs: timewindowFormValue.history.timewindowMs, + quickInterval: timewindowFormValue.history.quickInterval.startsWith('CURRENT') ? + timewindowFormValue.history.quickInterval : timewindowFormValue.realtime.quickInterval + }); + setTimeout(() => this.timewindowForm.get('realtime.interval').patchValue(timewindowFormValue.history.interval)); + } + } else { + this.timewindowForm.get('history').patchValue({ + historyType: HistoryWindowType[RealtimeWindowType[timewindowFormValue.realtime.realtimeType]], + timewindowMs: timewindowFormValue.realtime.timewindowMs, + quickInterval: timewindowFormValue.realtime.quickInterval + }); + setTimeout(() => this.timewindowForm.get('history.interval').patchValue(timewindowFormValue.realtime.interval)); + } + this.timewindowForm.patchValue({ + aggregation: { + type: timewindowFormValue.aggregation.type, + limit: timewindowFormValue.aggregation.limit + }, + timezone: timewindowFormValue.timezone + }); + } + + update() { + const timewindowFormValue = this.timewindowForm.getRawValue(); + this.timewindow.realtime = { + realtimeType: timewindowFormValue.realtime.realtimeType, + timewindowMs: timewindowFormValue.realtime.timewindowMs, + quickInterval: timewindowFormValue.realtime.quickInterval, + interval: timewindowFormValue.realtime.interval + }; + this.timewindow.history = { + historyType: timewindowFormValue.history.historyType, + timewindowMs: timewindowFormValue.history.timewindowMs, + interval: timewindowFormValue.history.interval, + fixedTimewindow: timewindowFormValue.history.fixedTimewindow, + quickInterval: timewindowFormValue.history.quickInterval, + }; + this.timewindow.aggregation = { + type: timewindowFormValue.aggregation.type, + limit: timewindowFormValue.aggregation.limit + }; + this.timewindow.timezone = timewindowFormValue.timezone; + this.result = this.timewindow; + this.dialogRef.close(this.result); + } + + cancel() { + this.dialogRef.close(); + } + + minDatapointsLimit() { + return this.timeService.getMinDatapointsLimit(); + } + + maxDatapointsLimit() { + return this.timeService.getMaxDatapointsLimit(); + } + + minRealtimeAggInterval() { + return this.timeService.minIntervalLimit(this.currentRealtimeTimewindow()); + } + + maxRealtimeAggInterval() { + return this.timeService.maxIntervalLimit(this.currentRealtimeTimewindow()); + } + + currentRealtimeTimewindow(): number { + const timeWindowFormValue = this.timewindowForm.getRawValue(); + switch (timeWindowFormValue.realtime.realtimeType) { + case RealtimeWindowType.LAST_INTERVAL: + return timeWindowFormValue.realtime.timewindowMs; + case RealtimeWindowType.INTERVAL: + return quickTimeIntervalPeriod(timeWindowFormValue.realtime.quickInterval); + default: + return DAY; + } + } + + minHistoryAggInterval() { + return this.timeService.minIntervalLimit(this.currentHistoryTimewindow()); + } + + maxHistoryAggInterval() { + return this.timeService.maxIntervalLimit(this.currentHistoryTimewindow()); + } + + currentHistoryTimewindow() { + const timewindowFormValue = this.timewindowForm.getRawValue(); + if (timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { + return timewindowFormValue.history.timewindowMs; + } else if (timewindowFormValue.history.historyType === HistoryWindowType.INTERVAL) { + return quickTimeIntervalPeriod(timewindowFormValue.history.quickInterval); + } else if (timewindowFormValue.history.fixedTimewindow) { + return timewindowFormValue.history.fixedTimewindow.endTimeMs - + timewindowFormValue.history.fixedTimewindow.startTimeMs; + } else { + return DAY; + } + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index dbb36b84a6..b56d46ee65 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -80,7 +80,7 @@
- + + + + + +
@@ -141,9 +149,6 @@ fxLayout.xs="column" fxLayoutAlign.xs="stretch">
@@ -200,7 +205,7 @@ -
+
-
+
-
-
-
{{ 'timewindow.time-range' | translate }}
- - - - -
+
{{ 'timewindow.time-range' | translate }}
+ + {{ 'timewindow.hide-time-range' | translate }} + + + + + - - -
+ + {{ 'timewindow.hide-last-interval' | translate }} + + + {{ 'timewindow.hide-relative-interval' | translate }} + + + {{ 'timewindow.disable-custom-interval' | translate }} + @@ -70,30 +78,36 @@
+ +
-
-
-
-
{{ 'timewindow.time-range' | translate }}
- - - - -
- - - -
- +
{{ 'timewindow.time-range' | translate }}
+ + {{ 'timewindow.hide-time-range' | translate }} + + + + + + + + + + + + + {{ 'timewindow.disable-custom-interval' | translate }} + @@ -124,14 +138,19 @@
+ +
-
- -
-
{{ 'aggregation.aggregation' | translate }}
+
+
+
{{ 'aggregation.aggregation' | translate }}
+ + {{ 'timewindow.hide-aggregation' | translate }} + + @@ -139,58 +158,80 @@ -
+ +
-
-
{{ 'aggregation.limit' | translate }}
-
-
- - - - - - -
+
+
{{ 'aggregation.limit' | translate }}
+ + {{ 'timewindow.hide-max-values' | translate }} + +
+
+ + + + + +
-
- +
+
{{ 'aggregation.group-interval' | translate }}
+ + {{ 'timewindow.hide-group-interval' | translate }} + + + {{ 'timewindow.disable-custom-interval' | translate }} + + appearance="outline" + [disabledAdvanced]="timewindowForm.get('realtime.disableCustomGroupInterval').value"> + + {{ 'timewindow.disable-custom-interval' | translate }} + + appearance="outline" + [disabledAdvanced]="timewindowForm.get('history.disableCustomGroupInterval').value">
- - - + +
+
{{ 'timezone.timezone' | translate }}
+ + {{ 'timewindow.hide-timezone' | translate }} + + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss index 0c5ad7b927..9245856bb3 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss @@ -22,17 +22,17 @@ max-width: 100%; background-color: #fff; - .tb-form-panel { - padding: 12px; - gap: 12px; - } - - .mat-content { - overflow: hidden; + .timewindow-settings-form { + width: 600px; .tb-flex { gap: 16px; } + + &.mat-mdc-dialog-content { + overflow: hidden; + padding: 0; + } } .limit-slider-container { @@ -59,7 +59,8 @@ } :host ::ng-deep { - .mat-mdc-tab-group:not(.tb-headless) { + .mat-mdc-tab-group { + min-height: 100%; height: 100%; } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index e1cabedda0..41efc05ea6 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -28,15 +28,16 @@ import { import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; -import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { isDefined, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { TranslateService } from '@ngx-translate/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; export interface TimewindowConfigDialogData { quickIntervalOnly: boolean; + aggregation: boolean; timewindow: Timewindow; } @@ -49,9 +50,11 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On quickIntervalOnly = false; + aggregation = false; + timewindow: Timewindow; - timewindowForm: UntypedFormGroup; + timewindowForm: FormGroup; historyTypes = HistoryWindowType; @@ -94,11 +97,12 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On constructor(@Inject(MAT_DIALOG_DATA) public data: TimewindowConfigDialogData, public dialogRef: MatDialogRef, protected store: Store, - public fb: UntypedFormBuilder, + public fb: FormBuilder, private timeService: TimeService, private translate: TranslateService) { super(store); this.quickIntervalOnly = data.quickIntervalOnly; + this.aggregation = data.aggregation; this.timewindow = data.timewindow; if (!this.quickIntervalOnly) { @@ -112,13 +116,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } ngOnInit(): void { - const hideInterval = this.timewindow.hideInterval || false; - const hideLastInterval = this.timewindow.hideLastInterval || false; - const hideQuickInterval = this.timewindow.hideQuickInterval || false; - const hideAggregation = this.timewindow.hideAggregation || false; - const hideAggInterval = this.timewindow.hideAggInterval || false; - const hideTimezone = this.timewindow.hideTimezone || false; - const realtime = this.timewindow.realtime; const history = this.timewindow.history; const aggregation = this.timewindow.aggregation; @@ -128,14 +125,22 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On realtimeType: [ isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL ], timewindowMs: [ isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null ], interval: [ isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null ], - quickInterval: [ isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null ] + quickInterval: [ isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null ], + disableCustomInterval: [ isDefinedAndNotNull(this.timewindow.realtime?.disableCustomInterval) + ? this.timewindow.realtime?.disableCustomInterval : false ], + disableCustomGroupInterval: [ isDefinedAndNotNull(this.timewindow.realtime?.disableCustomGroupInterval) + ? this.timewindow.realtime?.disableCustomGroupInterval : false ], }), history: this.fb.group({ historyType: [ isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL ], timewindowMs: [ isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null ], interval: [ isDefined(history?.interval) ? this.timewindow.history.interval : null ], fixedTimewindow: [ isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null ], - quickInterval: [ isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null ] + quickInterval: [ isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null ], + disableCustomInterval: [ isDefinedAndNotNull(this.timewindow.history?.disableCustomInterval) + ? this.timewindow.history?.disableCustomInterval : false ], + disableCustomGroupInterval: [ isDefinedAndNotNull(this.timewindow.history?.disableCustomGroupInterval) + ? this.timewindow.history?.disableCustomGroupInterval : false ], }), aggregation: this.fb.group({ type: [ isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null ], @@ -224,32 +229,21 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On type: timewindowFormValue.aggregation.type, limit: timewindowFormValue.aggregation.limit }, - timezone: timewindowFormValue.timezone + timezone: timewindowFormValue.timezone, + hideInterval: timewindowFormValue.hideInterval, + hideAggregation: timewindowFormValue.hideAggregation, + hideAggInterval: timewindowFormValue.hideAggInterval, + hideTimezone: timewindowFormValue.hideTimezone }); } update() { const timewindowFormValue = this.timewindowForm.getRawValue(); - this.timewindow.realtime = { - realtimeType: timewindowFormValue.realtime.realtimeType, - timewindowMs: timewindowFormValue.realtime.timewindowMs, - quickInterval: timewindowFormValue.realtime.quickInterval, - interval: timewindowFormValue.realtime.interval - }; - this.timewindow.history = { - historyType: timewindowFormValue.history.historyType, - timewindowMs: timewindowFormValue.history.timewindowMs, - interval: timewindowFormValue.history.interval, - fixedTimewindow: timewindowFormValue.history.fixedTimewindow, - quickInterval: timewindowFormValue.history.quickInterval, - }; - this.timewindow.aggregation = { - type: timewindowFormValue.aggregation.type, - limit: timewindowFormValue.aggregation.limit - }; - this.timewindow.timezone = timewindowFormValue.timezone; - this.result = this.timewindow; - this.dialogRef.close(this.result); + this.timewindow = mergeDeep(this.timewindow, timewindowFormValue); + if (!this.aggregation) { + delete this.timewindow.aggregation; + } + this.dialogRef.close(this.timewindow); } cancel() { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index b56d46ee65..9c6ed91d05 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -41,6 +41,7 @@ formControlName="timewindowMs" subscriptSizing="dynamic" appearance="outline" + [disabledAdvanced]="timewindow.realtime.disableCustomInterval" [required]="timewindow.selectedTab === timewindowTypes.REALTIME && timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"> @@ -87,6 +88,7 @@ formControlName="timewindowMs" subscriptSizing="dynamic" appearance="outline" + [disabledAdvanced]="timewindow.history.disableCustomInterval" [required]="timewindow.selectedTab === timewindowTypes.HISTORY && timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL"> @@ -173,7 +175,8 @@ [min]="minRealtimeAggInterval()" [max]="maxRealtimeAggInterval()" useCalendarIntervals subscriptSizing="dynamic" - appearance="outline"> + appearance="outline" + [disabledAdvanced]="timewindow.realtime.disableCustomGroupInterval"> @@ -182,7 +185,8 @@ [min]="minHistoryAggInterval()" [max]="maxHistoryAggInterval()" useCalendarIntervals subscriptSizing="dynamic" - appearance="outline"> + appearance="outline" + [disabledAdvanced]="timewindow.history.disableCustomGroupInterval">
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index ec1dc89ee9..ab080667c3 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -18,6 +18,7 @@ :host { display: flex; flex-direction: column; + width: 500px; max-height: 100%; max-width: 100%; background-color: #fff; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 903ab78495..841f0b2eb4 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -313,6 +313,16 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { } } + private updateTimewindowForm() { + this.timewindowForm.patchValue(this.timewindow); + this.onHideIntervalChanged(); + this.onHideLastIntervalChanged(); + this.onHideQuickIntervalChanged(); + this.onHideAggregationChanged(); + this.onHideAggIntervalChanged(); + this.onHideTimezoneChanged(); + } + cancel() { this.overlayRef.dispose(); } @@ -453,23 +463,21 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { openTimewindowConfig() { this.prepareTimewindowConfig(); - console.log(this.timewindow); this.dialog.open( TimewindowConfigDialogComponent, { autoFocus: false, - disableClose: false, + disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { quickIntervalOnly: this.quickIntervalOnly, + aggregation: this.aggregation, timewindow: this.timewindow } }).afterClosed() .subscribe((res) => { if (res) { - // update timewindow - console.log(res); - // TODO: propagate changes to form this.timewindow = res; + this.updateTimewindowForm(); } }); } diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 9336f47d90..e5888fbf52 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -81,6 +81,8 @@ export interface IntervalWindow { export interface RealtimeWindow extends IntervalWindow{ realtimeType?: RealtimeWindowType; + disableCustomInterval?: boolean; + disableCustomGroupInterval?: boolean; } export interface FixedWindow { @@ -91,6 +93,8 @@ export interface FixedWindow { export interface HistoryWindow extends IntervalWindow { historyType?: HistoryWindowType; fixedTimewindow?: FixedWindow; + disableCustomInterval?: boolean; + disableCustomGroupInterval?: boolean; } export enum AggregationType { 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 8fd3c59f11..31a64f960a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4707,6 +4707,7 @@ "hide-relative-interval": "Hide relative interval from end-users", "hide-aggregation": "Hide aggregation from end-users", "hide-group-interval": "Hide grouping interval from end-users", + "hide-max-values": "Hide max values from end-users", "hide-timezone": "Hide timezone from end-users", "disable-custom-interval": "Disable custom interval selection" }, From a34088fcfbece13baccba1871e67e648988f2fc1 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Thu, 15 Aug 2024 10:17:47 +0300 Subject: [PATCH 21/84] added verification on init --- .../rule/engine/delay/TbMsgDelayNode.java | 66 +++-- .../rule/engine/delay/TbMsgDelayNodeTest.java | 249 ++++++++++-------- 2 files changed, 179 insertions(+), 136 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 3f1cd88571..be569162c4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -31,10 +31,10 @@ import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; @Slf4j @@ -54,13 +54,17 @@ import java.util.concurrent.TimeUnit; ) public class TbMsgDelayNode implements TbNode { + private final List supportedTimeUnits = List.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); + private final String supportedTimeUnitsStr = String.join(",", TimeUnit.SECONDS.name(), TimeUnit.MINUTES.name(), TimeUnit.HOURS.name()); + private TbMsgDelayNodeConfiguration config; - private Map pendingMsgs; + private ConcurrentMap pendingMsgs; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgDelayNodeConfiguration.class); - this.pendingMsgs = new HashMap<>(); + validateConfig(); + this.pendingMsgs = new ConcurrentHashMap<>(); } @Override @@ -71,7 +75,7 @@ public class TbMsgDelayNode implements TbNode { ctx.enqueueForTellNext( TbMsg.newMsg( pendingMsg.getQueueName(), - pendingMsg.getType(), + pendingMsg.getInternalType(), pendingMsg.getOriginator(), pendingMsg.getCustomerId(), pendingMsg.getMetaData(), @@ -97,12 +101,28 @@ public class TbMsgDelayNode implements TbNode { String periodPattern = TbNodeUtils.processPattern(config.getPeriod(), msg); try { TimeUnit timeUnit = TimeUnit.valueOf(timeUnitPattern.toUpperCase()); + if (!supportedTimeUnits.contains(timeUnit)) { + throw new RuntimeException("Time unit '" + timeUnit + "' is not supported! " + + "Only " + supportedTimeUnitsStr + " are supported."); + } int period = Integer.parseInt(periodPattern); return timeUnit.toMillis(period); } catch (NumberFormatException e) { - throw new RuntimeException("Can't parse period value : " + periodPattern); + throw new NumberFormatException("Can't parse period value : " + periodPattern); } catch (IllegalArgumentException e) { - throw new RuntimeException("Invalid value for period time unit : " + timeUnitPattern); + throw new IllegalArgumentException("Invalid value for period time unit : " + timeUnitPattern); + } + } + + private void validateConfig() throws TbNodeException { + if (config.getMaxPendingMsgs() < 1 || config.getMaxPendingMsgs() > 100000) { + throw new TbNodeException("Maximum pending messages should be in a range from 1 to 100000.", true); + } + if (config.getPeriod() == null) { + throw new TbNodeException("Period should be specified.", true); + } + if (config.getTimeUnit() == null) { + throw new TbNodeException("Time unit should be specified.", true); } } @@ -121,26 +141,30 @@ public class TbMsgDelayNode implements TbNode { var useMetadataPeriodInSecondsPatterns = "useMetadataPeriodInSecondsPatterns"; var period = "period"; if (oldConfiguration.has(useMetadataPeriodInSecondsPatterns)) { - var isUsedPattern = oldConfiguration.get(useMetadataPeriodInSecondsPatterns).asBoolean(); - if (isUsedPattern) { - if (!oldConfiguration.has(periodInSecondsPattern)) { - throw new TbNodeException("Property to update: '" + periodInSecondsPattern + "' does not exist in configuration."); - } - ((ObjectNode) oldConfiguration).set(period, oldConfiguration.get(periodInSecondsPattern)); - } else { - if (!oldConfiguration.has(periodInSeconds)) { - throw new TbNodeException("Property to update: '" + periodInSeconds + "' does not exist in configuration."); - } - ((ObjectNode) oldConfiguration).set(period, oldConfiguration.get(periodInSeconds)); - } - ((ObjectNode) oldConfiguration).remove(List.of(periodInSeconds, periodInSecondsPattern, useMetadataPeriodInSecondsPatterns)); - hasChanges = true; + var isUsedPattern = oldConfiguration.get(useMetadataPeriodInSecondsPatterns).booleanValue(); + if (isUsedPattern) { + if (!oldConfiguration.has(periodInSecondsPattern)) { + throw new TbNodeException("Property to update: '" + periodInSecondsPattern + "' does not exist in configuration."); + } + ((ObjectNode) oldConfiguration).set(period, oldConfiguration.get(periodInSecondsPattern)); + } else { + if (!oldConfiguration.has(periodInSeconds)) { + throw new TbNodeException("Property to update: '" + periodInSeconds + "' does not exist in configuration."); + } + ((ObjectNode) oldConfiguration).put(period, oldConfiguration.get(periodInSeconds).asText()); + } + hasChanges = true; + } + if (!oldConfiguration.has(period)) { + ((ObjectNode) oldConfiguration).put(period, "60"); + hasChanges = true; } var timeUnit = "timeUnit"; if (!oldConfiguration.has(timeUnit)) { ((ObjectNode) oldConfiguration).put(timeUnit, TimeUnit.SECONDS.name()); hasChanges = true; } + ((ObjectNode) oldConfiguration).remove(List.of(periodInSeconds, periodInSecondsPattern, useMetadataPeriodInSecondsPatterns)); break; default: break; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java index 751faee18b..63de8b505b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java @@ -20,7 +20,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -38,11 +40,10 @@ import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -52,16 +53,18 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.never; import static org.mockito.BDDMockito.spy; import static org.mockito.BDDMockito.then; -import static org.mockito.BDDMockito.times; import static org.mockito.BDDMockito.willAnswer; @ExtendWith(MockitoExtension.class) public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("20107cf0-1c5e-4ac4-8131-7c466c955a7c")); + private final RuleNodeId RULE_NODE_ID = new RuleNodeId(UUID.fromString("1be24225-b669-4b26-ab7e-083aaa82d0a0")); + + private final List supportedTimeUnits = List.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); + private final String supportedTimeUnitsStr = String.join(",", TimeUnit.SECONDS.name(), TimeUnit.MINUTES.name(), TimeUnit.HOURS.name()); private TbMsgDelayNode node; private TbMsgDelayNodeConfiguration config; @@ -87,6 +90,40 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { assertThatNoException().isThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))); } + @ParameterizedTest + @ValueSource(ints = {-1, 0, 5000000}) + public void givenInvalidMaxPendingMsgsValue_whenInit_thenThrowsException() { + config.setMaxPendingMsgs(-1); + + assertThatThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) + .isInstanceOf(TbNodeException.class) + .hasMessage("Maximum pending messages should be in a range from 1 to 100000.") + .extracting(e -> ((TbNodeException) e).isUnrecoverable()) + .isEqualTo(true); + } + + @Test + public void givenPeriodIsNull_whenInit_thenThrowsException() { + config.setPeriod(null); + + assertThatThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) + .isInstanceOf(TbNodeException.class) + .hasMessage("Period should be specified.") + .extracting(e -> ((TbNodeException) e).isUnrecoverable()) + .isEqualTo(true); + } + + @Test + public void givenTimeUnitIsNull_whenInit_thenThrowsException() { + config.setTimeUnit(null); + + assertThatThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) + .isInstanceOf(TbNodeException.class) + .hasMessage("Time unit should be specified.") + .extracting(e -> ((TbNodeException) e).isUnrecoverable()) + .isEqualTo(true); + } + @ParameterizedTest @MethodSource public void givenPeriodValueAndPeriodTimeUnitPatterns_whenOnMsg_thenTellSelfTickMsgAndEnqueueForTellNext( @@ -96,41 +133,23 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); - var ruleNodeId = new RuleNodeId(UUID.fromString("e8172ef8-bf91-4821-b9f5-ccd7b865e418")); - given(ctxMock.getSelfId()).willReturn(ruleNodeId); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metaData, data); + var tickMsg = TbMsg.newMsg(TbMsgType.DELAY_TIMEOUT_SELF_MSG, RULE_NODE_ID, TbMsgMetaData.EMPTY, msg.getId().toString()); + + given(ctxMock.newMsg(any(), any(TbMsgType.class), any(), any(), any(), any())).willReturn(tickMsg); + given(ctxMock.getSelfId()).willReturn(RULE_NODE_ID); willAnswer(invocation -> { node.onMsg(ctxMock, invocation.getArgument(0)); return null; }).given(ctxMock).tellSelf(any(TbMsg.class), any(Long.class)); - List incomingMsgs = new ArrayList<>(); - List tickMsgs = new ArrayList<>(); - for (int i = 0; i < 9; i++) { - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metaData, data); - incomingMsgs.add(msg); - var tickMsg = TbMsg.newMsg(TbMsgType.DELAY_TIMEOUT_SELF_MSG, ruleNodeId, TbMsgMetaData.EMPTY, msg.getId().toString()); - tickMsgs.add(tickMsg); - given(ctxMock.newMsg(any(), any(TbMsgType.class), any(), any(), any(), eq(msg.getId().toString()))).willReturn(tickMsg); - } + node.onMsg(ctxMock, msg); - incomingMsgs.forEach(msg -> node.onMsg(ctxMock, msg)); - - incomingMsgs.forEach(incomingMsg -> { - then(ctxMock).should().newMsg(incomingMsg.getQueueName(), TbMsgType.DELAY_TIMEOUT_SELF_MSG, ruleNodeId, incomingMsg.getCustomerId(), TbMsgMetaData.EMPTY, incomingMsg.getId().toString()); - then(ctxMock).should().ack(incomingMsg); - }); - tickMsgs.forEach(tickMsg -> { - then(ctxMock).should().tellSelf(tickMsg, expectedDelay); - then(node).should().onMsg(ctxMock, tickMsg); - }); - var actualMsgsCaptor = ArgumentCaptor.forClass(TbMsg.class); - then(ctxMock).should(times(9)).enqueueForTellNext(actualMsgsCaptor.capture(), eq(TbNodeConnectionType.SUCCESS)); - var actualMsgs = actualMsgsCaptor.getAllValues(); - for (int i = 0; i < 9; i++) { - var actualMsg = actualMsgs.get(i); - then(ctxMock).should().enqueueForTellNext(actualMsg, TbNodeConnectionType.SUCCESS); - assertThat(actualMsg).usingRecursiveComparison().ignoringFields("id", "ts").isEqualTo(incomingMsgs.get(i)); - } + then(ctxMock).should().tellSelf(tickMsg, expectedDelay); + then(ctxMock).should().ack(msg); + ArgumentCaptor actualMsg = ArgumentCaptor.forClass(TbMsg.class); + then(ctxMock).should().enqueueForTellNext(actualMsg.capture(), eq(TbNodeConnectionType.SUCCESS)); + assertThat(actualMsg.getValue()).usingRecursiveComparison().ignoringFields("id", "ts").isEqualTo(msg); } private static Stream givenPeriodValueAndPeriodTimeUnitPatterns_whenOnMsg_thenTellSelfTickMsgAndEnqueueForTellNext() { @@ -138,28 +157,38 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { Arguments.of("1", "HOURS", TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, TimeUnit.HOURS.toMillis(1L)), Arguments.of("${md-period}", "${md-time-unit}", new TbMsgMetaData(Map.of( - "md-period", "5", - "md-time-unit", "MINUTES" + "md-period", "5", + "md-time-unit", "MINUTES" )), TbMsg.EMPTY_JSON_OBJECT, TimeUnit.MINUTES.toMillis(5L)), Arguments.of("$[msg-period]", "$[msg-time-unit]", TbMsgMetaData.EMPTY, "{\"msg-period\":10,\"msg-time-unit\":\"SECONDS\"}", TimeUnit.SECONDS.toMillis(10L)) ); } + @ParameterizedTest + @EnumSource(TimeUnit.class) + public void givenTimeUnit_whenOnMsg_thenVerify(TimeUnit timeUnit) throws TbNodeException { + config.setTimeUnit(timeUnit.name()); + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + if (supportedTimeUnits.contains(timeUnit)) { + assertThatNoException().isThrownBy(() -> node.onMsg(ctxMock, msg)); + } else { + assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) + .isInstanceOf(RuntimeException.class) + .hasMessage("Time unit '" + timeUnit + "' is not supported! Only " + supportedTimeUnitsStr + " are supported."); + } + } + @Test public void givenPeriodIsUnparsable_whenOnMsg_thenThrowsException() throws TbNodeException { config.setPeriod("five"); node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); - var ruleNodeId = new RuleNodeId(UUID.fromString("5236e9b9-1e29-4b95-b219-7043ff8f0414")); - var tickMsg = TbMsg.newMsg(TbMsgType.DELAY_TIMEOUT_SELF_MSG, ruleNodeId, TbMsgMetaData.EMPTY, msg.getId().toString()); - - given(ctxMock.getSelfId()).willReturn(ruleNodeId); - given(ctxMock.newMsg(any(), any(TbMsgType.class), any(), any(), any(), any())).willReturn(tickMsg); - assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) - .isInstanceOf(RuntimeException.class) + .isInstanceOf(NumberFormatException.class) .hasMessage("Can't parse period value : five"); } @@ -169,58 +198,30 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); - var ruleNodeId = new RuleNodeId(UUID.fromString("0210d69a-247f-4488-a242-dd3244b55088")); - var tickMsg = TbMsg.newMsg(TbMsgType.DELAY_TIMEOUT_SELF_MSG, ruleNodeId, TbMsgMetaData.EMPTY, msg.getId().toString()); - - given(ctxMock.getSelfId()).willReturn(ruleNodeId); - given(ctxMock.newMsg(any(), any(TbMsgType.class), any(), any(), any(), any())).willReturn(tickMsg); - assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) - .isInstanceOf(RuntimeException.class) + .isInstanceOf(IllegalArgumentException.class) .hasMessage("Invalid value for period time unit : sec"); } @Test public void givenMaxLimitOfPendingMsgsReached_whenOnMsg_thenTellFailure() throws TbNodeException { - int maxPendingMsgs = 5; - config.setMaxPendingMsgs(maxPendingMsgs); + config.setMaxPendingMsgs(1); node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); - - RuleNodeId ruleNodeId = new RuleNodeId(UUID.fromString("d1440f09-ca81-41f3-b67e-1495aee87dc6")); - given(ctxMock.getSelfId()).willReturn(ruleNodeId); - - List incomingMsgs = new ArrayList<>(); - List tickMsgs = new ArrayList<>(); - for (int i = 0; i < 6; i++) { - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); - incomingMsgs.add(msg); - var tickMsg = TbMsg.newMsg(TbMsgType.DELAY_TIMEOUT_SELF_MSG, ruleNodeId, TbMsgMetaData.EMPTY, msg.getId().toString()); - tickMsgs.add(tickMsg); - } - for (int i = 0; i < maxPendingMsgs; i++) { - given(ctxMock.newMsg(any(), any(TbMsgType.class), any(), any(), any(), eq(incomingMsgs.get(i).getId().toString()))).willReturn(tickMsgs.get(i)); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + for (int i = 0; i < 2; i++) { + node.onMsg(ctxMock, msg); } - incomingMsgs.forEach(msg -> node.onMsg(ctxMock, msg)); - - var lastMsg = incomingMsgs.remove(maxPendingMsgs); - incomingMsgs.forEach(incomingMsg -> { - then(ctxMock).should().newMsg(incomingMsg.getQueueName(), TbMsgType.DELAY_TIMEOUT_SELF_MSG, ruleNodeId, incomingMsg.getCustomerId(), TbMsgMetaData.EMPTY, incomingMsg.getId().toString()); - then(ctxMock).should().ack(incomingMsg); - }); - var tickForLastMsg = tickMsgs.remove(maxPendingMsgs); - tickMsgs.forEach(tickMsg -> then(ctxMock).should().tellSelf(tickMsg, TimeUnit.SECONDS.toMillis(60L))); - then(ctxMock).should(never()).tellSelf(eq(tickForLastMsg), any(Long.class)); ArgumentCaptor throwable = ArgumentCaptor.forClass(Throwable.class); - then(ctxMock).should().tellFailure(eq(lastMsg), throwable.capture()); + then(ctxMock).should().tellFailure(eq(msg), throwable.capture()); assertThat(throwable.getValue()).isInstanceOf(RuntimeException.class).hasMessage("Max limit of pending messages reached!"); } @Test public void verifyDestroyMethod() { var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); - var pendingMsgs = new HashMap(); + var pendingMsgs = new ConcurrentHashMap<>(); pendingMsgs.put(UUID.fromString("321f0301-9bed-4e7d-b92f-a978f53ec5d6"), msg); ReflectionTestUtils.setField(node, "pendingMsgs", pendingMsgs); var actualPendingMsgs = (Map) ReflectionTestUtils.getField(node, "pendingMsgs"); @@ -233,61 +234,79 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { return Stream.of( - // config for version 1 with upgrade from version 0 + // config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns does not exist) Arguments.of(0, """ - { - "periodInSeconds": 60, - "maxPendingMsgs": 1000, - "periodInSecondsPattern": null, - "useMetadataPeriodInSecondsPatterns": false - } - """, + { + "periodInSeconds": 13, + "maxPendingMsgs": 1000, + "periodInSecondsPattern": "17" + } + """, true, """ - { - "period": 60, - "timeUnit": "SECONDS", - "maxPendingMsgs": 1000 - } + { + "period": "60", + "timeUnit": "SECONDS", + "maxPendingMsgs": 1000 + } + """ + ), + // config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns is false) + Arguments.of(0, """ + { + "periodInSeconds": 60, + "maxPendingMsgs": 1000, + "periodInSecondsPattern": null, + "useMetadataPeriodInSecondsPatterns": false + } + """, + true, + """ + { + "period": "60", + "timeUnit": "SECONDS", + "maxPendingMsgs": 1000 + } + """ ), // config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPattern is true) Arguments.of(0, """ - { - "periodInSeconds": 60, - "maxPendingMsgs": 1000, - "periodInSecondsPattern": "${period-pattern}", - "useMetadataPeriodInSecondsPatterns": true - } - """, + { + "periodInSeconds": 60, + "maxPendingMsgs": 1000, + "periodInSecondsPattern": "${period-pattern}", + "useMetadataPeriodInSecondsPatterns": true + } + """, true, """ - { - "period": "${period-pattern}", - "timeUnit": "SECONDS", - "maxPendingMsgs": 1000 - } - """ + { + "period": "${period-pattern}", + "timeUnit": "SECONDS", + "maxPendingMsgs": 1000 + } + """ ), // config for version 1 with upgrade from version 0 (hasChanges is false) Arguments.of(0, """ - { - "period": "${period-pattern}", - "timeUnit": "SECONDS", - "maxPendingMsgs": 1000 - } - """, + { + "period": "${period-pattern}", + "timeUnit": "SECONDS", + "maxPendingMsgs": 1000 + } + """, false, """ - { - "period": "${period-pattern}", - "timeUnit": "SECONDS", - "maxPendingMsgs": 1000 - } - """ + { + "period": "${period-pattern}", + "timeUnit": "SECONDS", + "maxPendingMsgs": 1000 + } + """ ) ); } From 1284ad0aadcb31e625a9266fea9b34339fff0e18 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 16 Aug 2024 18:38:35 +0300 Subject: [PATCH 22/84] UI: start introducing new hide options --- .../timewindow-config-dialog.component.html | 163 +++++++++--------- .../timewindow-config-dialog.component.ts | 71 +++++--- .../time/timewindow-panel.component.scss | 2 +- .../time/timewindow-panel.component.ts | 67 ++++--- .../src/app/shared/models/time/time.models.ts | 10 +- .../assets/locale/locale.constant-en_US.json | 1 + 6 files changed, 175 insertions(+), 139 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index fe10fb1a52..74a0204c26 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -29,51 +29,48 @@
-
+
{{ 'timewindow.time-range' | translate }}
{{ 'timewindow.hide-time-range' | translate }} - - - - - - {{ 'timewindow.hide-last-interval' | translate }} - - - {{ 'timewindow.hide-relative-interval' | translate }} - + + - - - - {{ 'timewindow.disable-custom-interval' | translate }} - - - - + + + {{ 'timewindow.hide-last-interval' | translate }} + + + {{ 'timewindow.disable-custom-interval' | translate }} + + + + - - - - + + + {{ 'timewindow.hide-relative-interval' | translate }} + + +
@@ -84,56 +81,58 @@
-
{{ 'timewindow.time-range' | translate }}
+
+
{{ 'timewindow.time-range' | translate }}
{{ 'timewindow.hide-time-range' | translate }} - - - + + + + + + + {{ 'timewindow.hide-last-interval' | translate }} + + + {{ 'timewindow.disable-custom-interval' | translate }} + + + - - - - - - - - - - {{ 'timewindow.disable-custom-interval' | translate }} - - - - - - - - + + + {{ 'timewindow.hide-fixed-interval' | translate }} + + + + - - - - + + + {{ 'timewindow.hide-relative-interval' | translate }} + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 41efc05ea6..5f8fa10f94 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -130,6 +130,18 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On ? this.timewindow.realtime?.disableCustomInterval : false ], disableCustomGroupInterval: [ isDefinedAndNotNull(this.timewindow.realtime?.disableCustomGroupInterval) ? this.timewindow.realtime?.disableCustomGroupInterval : false ], + hideInterval: [ isDefinedAndNotNull(this.timewindow.realtime.hideInterval) + ? this.timewindow.realtime.hideInterval : false ], + hideLastInterval: [{ + value: isDefinedAndNotNull(this.timewindow.realtime.hideLastInterval) + ? this.timewindow.realtime.hideLastInterval : false, + disabled: this.timewindow.realtime.hideInterval + }], + hideQuickInterval: [{ + value: isDefinedAndNotNull(this.timewindow.realtime.hideQuickInterval) + ? this.timewindow.realtime.hideQuickInterval : false, + disabled: this.timewindow.realtime.hideInterval + }] }), history: this.fb.group({ historyType: [ isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL ], @@ -141,6 +153,23 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On ? this.timewindow.history?.disableCustomInterval : false ], disableCustomGroupInterval: [ isDefinedAndNotNull(this.timewindow.history?.disableCustomGroupInterval) ? this.timewindow.history?.disableCustomGroupInterval : false ], + hideInterval: [ isDefinedAndNotNull(this.timewindow.history.hideInterval) + ? this.timewindow.history.hideInterval : false ], + hideLastInterval: [{ + value: isDefinedAndNotNull(this.timewindow.history.hideLastInterval) + ? this.timewindow.history.hideLastInterval : false, + disabled: this.timewindow.history.hideInterval + }], + hideQuickInterval: [{ + value: isDefinedAndNotNull(this.timewindow.history.hideQuickInterval) + ? this.timewindow.history.hideQuickInterval : false, + disabled: this.timewindow.history.hideInterval + }], + hideFixedInterval: [{ + value: isDefinedAndNotNull(this.timewindow.history.hideFixedInterval) + ? this.timewindow.history.hideFixedInterval : false, + disabled: this.timewindow.history.hideInterval + }] }), aggregation: this.fb.group({ type: [ isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null ], @@ -171,13 +200,24 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('aggregation.type').valueChanges.subscribe((aggregationType: AggregationType) => { this.updateValidators(aggregationType); }); - this.timewindowForm.get('hideInterval').valueChanges.subscribe((value: boolean) => { + this.timewindowForm.get('realtime.hideInterval').valueChanges.subscribe((value: boolean) => { if (value) { - this.timewindowForm.get('hideLastInterval').disable(); - this.timewindowForm.get('hideQuickInterval').disable(); + this.timewindowForm.get('realtime.hideLastInterval').disable(); + this.timewindowForm.get('realtime.hideQuickInterval').disable(); } else { - this.timewindowForm.get('hideLastInterval').enable(); - this.timewindowForm.get('hideQuickInterval').enable(); + this.timewindowForm.get('realtime.hideLastInterval').enable(); + this.timewindowForm.get('realtime.hideQuickInterval').enable(); + } + }); + this.timewindowForm.get('history.hideInterval').valueChanges.subscribe((value: boolean) => { + if (value) { + this.timewindowForm.get('history.hideLastInterval').disable(); + this.timewindowForm.get('history.hideQuickInterval').disable(); + this.timewindowForm.get('history.hideFixedInterval').disable(); + } else { + this.timewindowForm.get('history.hideLastInterval').enable(); + this.timewindowForm.get('history.hideQuickInterval').enable(); + this.timewindowForm.get('history.hideFixedInterval').enable(); } }); } @@ -204,33 +244,12 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On onTimewindowTypeChange() { this.timewindowForm.markAsDirty(); const timewindowFormValue = this.timewindowForm.getRawValue(); - if (this.timewindow.selectedTab === TimewindowType.REALTIME) { - if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { - this.timewindowForm.get('realtime').patchValue({ - realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? - RealtimeWindowType[HistoryWindowType[timewindowFormValue.history.historyType]] : - timewindowFormValue.realtime.realtimeType, - timewindowMs: timewindowFormValue.history.timewindowMs, - quickInterval: timewindowFormValue.history.quickInterval.startsWith('CURRENT') ? - timewindowFormValue.history.quickInterval : timewindowFormValue.realtime.quickInterval - }); - setTimeout(() => this.timewindowForm.get('realtime.interval').patchValue(timewindowFormValue.history.interval)); - } - } else { - this.timewindowForm.get('history').patchValue({ - historyType: HistoryWindowType[RealtimeWindowType[timewindowFormValue.realtime.realtimeType]], - timewindowMs: timewindowFormValue.realtime.timewindowMs, - quickInterval: timewindowFormValue.realtime.quickInterval - }); - setTimeout(() => this.timewindowForm.get('history.interval').patchValue(timewindowFormValue.realtime.interval)); - } this.timewindowForm.patchValue({ aggregation: { type: timewindowFormValue.aggregation.type, limit: timewindowFormValue.aggregation.limit }, timezone: timewindowFormValue.timezone, - hideInterval: timewindowFormValue.hideInterval, hideAggregation: timewindowFormValue.hideAggregation, hideAggInterval: timewindowFormValue.hideAggInterval, hideTimezone: timewindowFormValue.hideTimezone diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index ab080667c3..1416d95691 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -68,7 +68,7 @@ padding: 12px 16px; } - .mat-mdc-tab-header .mat-mdc-tab.timewindow-config-btn { + .mat-mdc-tab-group>.mat-mdc-tab-header .mat-mdc-tab.timewindow-config-btn { flex-grow: unset; min-width: 0; padding: 0 8px; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 841f0b2eb4..d60cf1b6ce 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -30,7 +30,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; -import { isDefined } from '@core/utils'; +import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { OverlayRef } from '@angular/cdk/overlay'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { TranslateService } from '@ngx-translate/core'; @@ -128,6 +128,21 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timezone = data.timezone; this.isEdit = data.isEdit; + // for backward compatibility + if (isDefinedAndNotNull(this.timewindow.hideInterval)) { + this.timewindow.realtime.hideInterval = this.timewindow.hideInterval; + this.timewindow.history.hideInterval = this.timewindow.hideInterval; + // delete this.timewindow.hideInterval; + } + if (isDefinedAndNotNull(this.timewindow.hideLastInterval)) { + this.timewindow.realtime.hideLastInterval = this.timewindow.hideLastInterval; + // delete this.timewindow.hideLastInterval; + } + if (isDefinedAndNotNull(this.timewindow.hideQuickInterval)) { + this.timewindow.realtime.hideQuickInterval = this.timewindow.hideQuickInterval; + // delete this.timewindow.hideQuickInterval; + } + if (this.forAllTimeEnabled) { this.historyTimewindowOptions.unshift({ name: this.translate.instant('timewindow.for-all-time'), @@ -135,14 +150,14 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { }); } - if ((this.isEdit || !this.timewindow.hideLastInterval) && !this.quickIntervalOnly) { + if ((this.isEdit || !this.timewindow.realtime.hideLastInterval) && !this.quickIntervalOnly) { this.realtimeTimewindowOptions.push({ name: this.translate.instant('timewindow.last'), value: this.realtimeTypes.LAST_INTERVAL }); } - if (this.isEdit || !this.timewindow.hideQuickInterval || this.quickIntervalOnly) { + if (this.isEdit || !this.timewindow.realtime.hideQuickInterval || this.quickIntervalOnly) { this.realtimeTimewindowOptions.push({ name: this.translate.instant('timewindow.relative'), value: this.realtimeTypes.INTERVAL @@ -151,15 +166,15 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.realtimeTypeSelectionAvailable = this.realtimeTimewindowOptions.length > 1; this.historyTypeSelectionAvailable = this.historyTimewindowOptions.length > 1; - this.realtimeIntervalSelectionAvailable = this.isEdit || !(this.timewindow.hideInterval || - (this.timewindow.hideLastInterval && this.timewindow.hideQuickInterval)); + this.realtimeIntervalSelectionAvailable = this.isEdit || !(this.timewindow.realtime.hideInterval || + (this.timewindow.realtime.hideLastInterval && this.timewindow.realtime.hideQuickInterval)); this.historyIntervalSelectionAvailable = this.isEdit || !this.timewindow.hideInterval; } ngOnInit(): void { const hideInterval = this.timewindow.hideInterval || false; - const hideLastInterval = this.timewindow.hideLastInterval || false; - const hideQuickInterval = this.timewindow.hideQuickInterval || false; + const hideLastInterval = this.timewindow.realtime.hideLastInterval || false; + const hideQuickInterval = this.timewindow.realtime.hideQuickInterval || false; const hideAggregation = this.timewindow.hideAggregation || false; const hideAggInterval = this.timewindow.hideAggInterval || false; const hideTimezone = this.timewindow.hideTimezone || false; @@ -289,19 +304,19 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { private prepareTimewindowConfig() { const timewindowFormValue = this.timewindowForm.getRawValue(); - this.timewindow.realtime = { - realtimeType: timewindowFormValue.realtime.realtimeType, - timewindowMs: timewindowFormValue.realtime.timewindowMs, - quickInterval: timewindowFormValue.realtime.quickInterval, - interval: timewindowFormValue.realtime.interval - }; - this.timewindow.history = { - historyType: timewindowFormValue.history.historyType, - timewindowMs: timewindowFormValue.history.timewindowMs, - interval: timewindowFormValue.history.interval, - fixedTimewindow: timewindowFormValue.history.fixedTimewindow, - quickInterval: timewindowFormValue.history.quickInterval, - }; + this.timewindow.realtime = {...this.timewindow.realtime, ...{ + realtimeType: timewindowFormValue.realtime.realtimeType, + timewindowMs: timewindowFormValue.realtime.timewindowMs, + quickInterval: timewindowFormValue.realtime.quickInterval, + interval: timewindowFormValue.realtime.interval + }}; + this.timewindow.history = {...this.timewindow.history, ...{ + historyType: timewindowFormValue.history.historyType, + timewindowMs: timewindowFormValue.history.timewindowMs, + interval: timewindowFormValue.history.interval, + fixedTimewindow: timewindowFormValue.history.fixedTimewindow, + quickInterval: timewindowFormValue.history.quickInterval, + }}; if (this.aggregation) { this.timewindow.aggregation = { type: timewindowFormValue.aggregation.type, @@ -392,10 +407,10 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); this.timewindowForm.get('history.quickInterval').enable({emitEvent: false}); this.timewindowForm.get('realtime.realtimeType').enable({emitEvent: false}); - if (!this.timewindow.hideLastInterval) { + if (!this.timewindow.realtime.hideLastInterval) { this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); } - if (!this.timewindow.hideQuickInterval) { + if (!this.timewindow.realtime.hideQuickInterval) { this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); } } @@ -403,9 +418,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { } onHideLastIntervalChanged() { - if (this.timewindow.hideLastInterval) { + if (this.timewindow.realtime.hideLastInterval) { this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); - if (!this.timewindow.hideQuickInterval) { + if (!this.timewindow.realtime.hideQuickInterval) { this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); } } else { @@ -417,9 +432,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { } onHideQuickIntervalChanged() { - if (this.timewindow.hideQuickInterval) { + if (this.timewindow.realtime.hideQuickInterval) { this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); - if (!this.timewindow.hideLastInterval) { + if (!this.timewindow.realtime.hideLastInterval) { this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL); } } else { diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index e5888fbf52..83f2694388 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -77,12 +77,16 @@ export interface IntervalWindow { interval?: Interval; timewindowMs?: number; quickInterval?: QuickTimeInterval; + disableCustomInterval?: boolean; + disableCustomGroupInterval?: boolean; + hideInterval?: boolean; + hideLastInterval?: boolean; + hideQuickInterval?: boolean; + hideFixedInterval?: boolean; } export interface RealtimeWindow extends IntervalWindow{ realtimeType?: RealtimeWindowType; - disableCustomInterval?: boolean; - disableCustomGroupInterval?: boolean; } export interface FixedWindow { @@ -93,8 +97,6 @@ export interface FixedWindow { export interface HistoryWindow extends IntervalWindow { historyType?: HistoryWindowType; fixedTimewindow?: FixedWindow; - disableCustomInterval?: boolean; - disableCustomGroupInterval?: boolean; } export enum AggregationType { 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 31a64f960a..bef7474679 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4705,6 +4705,7 @@ "hide-time-range": "Hide time range from end-users", "hide-last-interval": "Hide last interval from end-users", "hide-relative-interval": "Hide relative interval from end-users", + "hide-fixed-interval": "Hide fixed interval from end-users", "hide-aggregation": "Hide aggregation from end-users", "hide-group-interval": "Hide grouping interval from end-users", "hide-max-values": "Hide max values from end-users", From d06d31c09a0e27ba87757205cfddceedfe0e24bc Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 19 Aug 2024 18:35:37 +0300 Subject: [PATCH 23/84] UI: new hide options implementation --- .../timewindow-config-dialog.component.ts | 24 +- .../time/timewindow-panel.component.html | 2 +- .../time/timewindow-panel.component.ts | 297 +++++++++--------- .../components/time/timewindow.component.html | 1 + .../components/time/timewindow.component.ts | 12 +- .../src/app/shared/models/time/time.models.ts | 93 ++++-- 6 files changed, 239 insertions(+), 190 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 5f8fa10f94..b154b4140d 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -20,8 +20,10 @@ import { AggregationType, DAY, HistoryWindowType, + historyWindowTypeTranslations, quickTimeIntervalPeriod, RealtimeWindowType, + realtimeWindowTypeTranslations, Timewindow, TimewindowType } from '@shared/models/time/time.models'; @@ -72,22 +74,22 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On realtimeTimewindowOptions: ToggleHeaderOption[] = [ { - name: this.translate.instant('timewindow.relative'), + name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.INTERVAL)), value: this.realtimeTypes.INTERVAL } ]; historyTimewindowOptions: ToggleHeaderOption[] = [ { - name: this.translate.instant('timewindow.last'), + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.LAST_INTERVAL)), value: this.historyTypes.LAST_INTERVAL }, { - name: this.translate.instant('timewindow.fixed'), + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.FIXED)), value: this.historyTypes.FIXED }, { - name: this.translate.instant('timewindow.relative'), + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.INTERVAL)), value: this.historyTypes.INTERVAL } ]; @@ -107,7 +109,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On if (!this.quickIntervalOnly) { this.realtimeTimewindowOptions.unshift({ - name: this.translate.instant('timewindow.last'), + name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.LAST_INTERVAL)), value: this.realtimeTypes.LAST_INTERVAL }); } @@ -176,18 +178,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On limit: [ isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null ] }), timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ], - hideInterval: [ isDefinedAndNotNull(this.timewindow.hideInterval) - ? this.timewindow.hideInterval : false ], - hideLastInterval: [{ - value: isDefinedAndNotNull(this.timewindow.hideLastInterval) - ? this.timewindow.hideLastInterval : false, - disabled: this.timewindow.hideInterval - }], - hideQuickInterval: [{ - value: isDefinedAndNotNull(this.timewindow.hideQuickInterval) - ? this.timewindow.hideQuickInterval : false, - disabled: this.timewindow.hideInterval - }], hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation) ? this.timewindow.hideAggregation : false ], hideAggInterval: [ isDefinedAndNotNull(this.timewindow.hideAggInterval) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 9c6ed91d05..45a735187c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -131,7 +131,7 @@ -
+
{{ 'aggregation.aggregation' | translate }}
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index d60cf1b6ce..cc75725c74 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -20,8 +20,10 @@ import { AggregationType, DAY, HistoryWindowType, + historyWindowTypeTranslations, quickTimeIntervalPeriod, RealtimeWindowType, + realtimeWindowTypeTranslations, Timewindow, TimewindowType } from '@shared/models/time/time.models'; @@ -30,7 +32,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; -import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { isDefined } from '@core/utils'; import { OverlayRef } from '@angular/cdk/overlay'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { TranslateService } from '@ngx-translate/core'; @@ -91,25 +93,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { realtimeTimewindowOptions: ToggleHeaderOption[] = []; - historyTimewindowOptions: ToggleHeaderOption[] = [ - { - name: this.translate.instant('timewindow.last'), - value: this.historyTypes.LAST_INTERVAL - }, - { - name: this.translate.instant('timewindow.fixed'), - value: this.historyTypes.FIXED - }, - { - name: this.translate.instant('timewindow.relative'), - value: this.historyTypes.INTERVAL - } - ]; + historyTimewindowOptions: ToggleHeaderOption[] = []; realtimeTypeSelectionAvailable: boolean; realtimeIntervalSelectionAvailable: boolean; historyTypeSelectionAvailable: boolean; historyIntervalSelectionAvailable: boolean; + aggregationOptionsAvailable: boolean; constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, public overlayRef: OverlayRef, @@ -128,39 +118,45 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timezone = data.timezone; this.isEdit = data.isEdit; - // for backward compatibility - if (isDefinedAndNotNull(this.timewindow.hideInterval)) { - this.timewindow.realtime.hideInterval = this.timewindow.hideInterval; - this.timewindow.history.hideInterval = this.timewindow.hideInterval; - // delete this.timewindow.hideInterval; - } - if (isDefinedAndNotNull(this.timewindow.hideLastInterval)) { - this.timewindow.realtime.hideLastInterval = this.timewindow.hideLastInterval; - // delete this.timewindow.hideLastInterval; + if ((this.isEdit || !this.timewindow.realtime.hideLastInterval) && !this.quickIntervalOnly) { + this.realtimeTimewindowOptions.push({ + name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.LAST_INTERVAL)), + value: this.realtimeTypes.LAST_INTERVAL + }); } - if (isDefinedAndNotNull(this.timewindow.hideQuickInterval)) { - this.timewindow.realtime.hideQuickInterval = this.timewindow.hideQuickInterval; - // delete this.timewindow.hideQuickInterval; + + if (this.isEdit || !this.timewindow.realtime.hideQuickInterval || this.quickIntervalOnly) { + this.realtimeTimewindowOptions.push({ + name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.INTERVAL)), + value: this.realtimeTypes.INTERVAL + }); } if (this.forAllTimeEnabled) { - this.historyTimewindowOptions.unshift({ - name: this.translate.instant('timewindow.for-all-time'), + this.historyTimewindowOptions.push({ + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.FOR_ALL_TIME)), value: this.historyTypes.FOR_ALL_TIME }); } - if ((this.isEdit || !this.timewindow.realtime.hideLastInterval) && !this.quickIntervalOnly) { - this.realtimeTimewindowOptions.push({ - name: this.translate.instant('timewindow.last'), - value: this.realtimeTypes.LAST_INTERVAL + if (this.isEdit || !this.timewindow.history.hideLastInterval) { + this.historyTimewindowOptions.push({ + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.LAST_INTERVAL)), + value: this.historyTypes.LAST_INTERVAL }); } - if (this.isEdit || !this.timewindow.realtime.hideQuickInterval || this.quickIntervalOnly) { - this.realtimeTimewindowOptions.push({ - name: this.translate.instant('timewindow.relative'), - value: this.realtimeTypes.INTERVAL + if (this.isEdit || !this.timewindow.history.hideFixedInterval) { + this.historyTimewindowOptions.push({ + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.FIXED)), + value: this.historyTypes.FIXED + }); + } + + if (this.isEdit || !this.timewindow.history.hideQuickInterval) { + this.historyTimewindowOptions.push({ + name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.INTERVAL)), + value: this.historyTypes.INTERVAL }); } @@ -168,13 +164,14 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.historyTypeSelectionAvailable = this.historyTimewindowOptions.length > 1; this.realtimeIntervalSelectionAvailable = this.isEdit || !(this.timewindow.realtime.hideInterval || (this.timewindow.realtime.hideLastInterval && this.timewindow.realtime.hideQuickInterval)); - this.historyIntervalSelectionAvailable = this.isEdit || !this.timewindow.hideInterval; + this.historyIntervalSelectionAvailable = this.isEdit || !(this.timewindow.history.hideInterval || + (this.timewindow.history.hideLastInterval && this.timewindow.history.hideQuickInterval && this.timewindow.history.hideFixedInterval)); + + this.aggregationOptionsAvailable = this.aggregation && (this.isEdit || + !(this.timewindow.hideAggregation && this.timewindow.hideAggInterval)); } ngOnInit(): void { - const hideInterval = this.timewindow.hideInterval || false; - const hideLastInterval = this.timewindow.realtime.hideLastInterval || false; - const hideQuickInterval = this.timewindow.realtime.hideQuickInterval || false; const hideAggregation = this.timewindow.hideAggregation || false; const hideAggInterval = this.timewindow.hideAggInterval || false; const hideTimezone = this.timewindow.hideTimezone || false; @@ -186,51 +183,51 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindowForm = this.fb.group({ realtime: this.fb.group({ realtimeType: [{ - value: isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, - disabled: hideInterval + value: isDefined(realtime?.realtimeType) ? realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, + disabled: realtime.hideInterval }], timewindowMs: [{ - value: isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null, - disabled: hideInterval || hideLastInterval + value: isDefined(realtime?.timewindowMs) ? realtime.timewindowMs : null, + disabled: realtime.hideInterval || realtime.hideLastInterval }], interval: [{ - value:isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null, + value:isDefined(realtime?.interval) ? realtime.interval : null, disabled: hideAggInterval }], quickInterval: [{ - value: isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null, - disabled: hideInterval || hideQuickInterval + value: isDefined(realtime?.quickInterval) ? realtime.quickInterval : null, + disabled: realtime.hideInterval || realtime.hideQuickInterval }] }), history: this.fb.group({ historyType: [{ - value: isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL, - disabled: hideInterval + value: isDefined(history?.historyType) ? history.historyType : HistoryWindowType.LAST_INTERVAL, + disabled: history.hideInterval }], timewindowMs: [{ - value: isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null, - disabled: hideInterval + value: isDefined(history?.timewindowMs) ? history.timewindowMs : null, + disabled: history.hideInterval || history.hideLastInterval }], interval: [{ - value:isDefined(history?.interval) ? this.timewindow.history.interval : null, + value:isDefined(history?.interval) ? history.interval : null, disabled: hideAggInterval }], fixedTimewindow: [{ - value: isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null, - disabled: hideInterval + value: isDefined(history?.fixedTimewindow) ? history.fixedTimewindow : null, + disabled: history.hideInterval || history.hideFixedInterval }], quickInterval: [{ - value: isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null, - disabled: hideInterval + value: isDefined(history?.quickInterval) ? history.quickInterval : null, + disabled: history.hideInterval || history.hideQuickInterval }] }), aggregation: this.fb.group({ type: [{ - value: isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null, + value: isDefined(aggregation?.type) ? aggregation.type : null, disabled: hideAggregation }], limit: [{ - value: isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null, + value: isDefined(aggregation?.limit) ? this.checkLimit(aggregation.limit) : null, disabled: hideAggInterval }, []] }), @@ -330,12 +327,71 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { private updateTimewindowForm() { this.timewindowForm.patchValue(this.timewindow); - this.onHideIntervalChanged(); - this.onHideLastIntervalChanged(); - this.onHideQuickIntervalChanged(); - this.onHideAggregationChanged(); - this.onHideAggIntervalChanged(); - this.onHideTimezoneChanged(); + + if (this.timewindow.realtime.hideInterval) { + this.timewindowForm.get('realtime.realtimeType').disable({emitEvent: false}); + this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); + this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); + } else { + this.timewindowForm.get('realtime.realtimeType').enable({emitEvent: false}); + if (this.timewindow.realtime.hideLastInterval) { + this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); + } else { + this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); + } + if (this.timewindow.realtime.hideQuickInterval) { + this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); + } else { + this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); + } + } + + if (this.timewindow.history.hideInterval) { + this.timewindowForm.get('history.historyType').disable({emitEvent: false}); + this.timewindowForm.get('history.timewindowMs').disable({emitEvent: false}); + this.timewindowForm.get('history.fixedTimewindow').disable({emitEvent: false}); + this.timewindowForm.get('history.quickInterval').disable({emitEvent: false}); + } else { + this.timewindowForm.get('history.historyType').enable({emitEvent: false}); + if (this.timewindow.history.hideLastInterval) { + this.timewindowForm.get('history.timewindowMs').disable({emitEvent: false}); + } else { + this.timewindowForm.get('history.timewindowMs').enable({emitEvent: false}); + } + if (this.timewindow.history.hideFixedInterval) { + this.timewindowForm.get('history.fixedTimewindow').disable({emitEvent: false}); + } else { + this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); + } + if (this.timewindow.history.hideQuickInterval) { + this.timewindowForm.get('history.quickInterval').disable({emitEvent: false}); + } else { + this.timewindowForm.get('history.quickInterval').enable({emitEvent: false}); + } + } + + if (this.timewindow.hideAggregation) { + this.timewindowForm.get('aggregation.type').disable({emitEvent: false}); + } else { + this.timewindowForm.get('aggregation.type').enable({emitEvent: false}); + } + if (this.timewindow.hideAggInterval) { + this.timewindowForm.get('aggregation.limit').disable({emitEvent: false}); + this.timewindowForm.get('realtime.interval').disable({emitEvent: false}); + this.timewindowForm.get('history.interval').disable({emitEvent: false}); + } else { + this.timewindowForm.get('aggregation.limit').enable({emitEvent: false}); + this.timewindowForm.get('realtime.interval').enable({emitEvent: false}); + this.timewindowForm.get('history.interval').enable({emitEvent: false}); + } + + if (this.timewindow.hideTimezone) { + this.timewindowForm.get('timezone').disable({emitEvent: false}); + } else { + this.timewindowForm.get('timezone').enable({emitEvent: false}); + } + + this.timewindowForm.markAsDirty(); } cancel() { @@ -392,89 +448,34 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { } } - onHideIntervalChanged() { - if (this.timewindow.hideInterval) { - this.timewindowForm.get('history.historyType').disable({emitEvent: false}); - this.timewindowForm.get('history.timewindowMs').disable({emitEvent: false}); - this.timewindowForm.get('history.fixedTimewindow').disable({emitEvent: false}); - this.timewindowForm.get('history.quickInterval').disable({emitEvent: false}); - this.timewindowForm.get('realtime.realtimeType').disable({emitEvent: false}); - this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); - this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); - } else { - this.timewindowForm.get('history.historyType').enable({emitEvent: false}); - this.timewindowForm.get('history.timewindowMs').enable({emitEvent: false}); - this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); - this.timewindowForm.get('history.quickInterval').enable({emitEvent: false}); - this.timewindowForm.get('realtime.realtimeType').enable({emitEvent: false}); - if (!this.timewindow.realtime.hideLastInterval) { - this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); - } - if (!this.timewindow.realtime.hideQuickInterval) { - this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); - } - } - this.timewindowForm.markAsDirty(); - } - - onHideLastIntervalChanged() { - if (this.timewindow.realtime.hideLastInterval) { - this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); - if (!this.timewindow.realtime.hideQuickInterval) { - this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); - } - } else { - if (!this.timewindow.hideInterval) { - this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); - } - } - this.timewindowForm.markAsDirty(); - } - - onHideQuickIntervalChanged() { - if (this.timewindow.realtime.hideQuickInterval) { - this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); - if (!this.timewindow.realtime.hideLastInterval) { - this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL); - } - } else { - if (!this.timewindow.hideInterval) { - this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); - } - } - this.timewindowForm.markAsDirty(); - } - - onHideAggregationChanged() { - if (this.timewindow.hideAggregation) { - this.timewindowForm.get('aggregation.type').disable({emitEvent: false}); - } else { - this.timewindowForm.get('aggregation.type').enable({emitEvent: false}); - } - this.timewindowForm.markAsDirty(); - } - - onHideAggIntervalChanged() { - if (this.timewindow.hideAggInterval) { - this.timewindowForm.get('aggregation.limit').disable({emitEvent: false}); - this.timewindowForm.get('realtime.interval').disable({emitEvent: false}); - this.timewindowForm.get('history.interval').disable({emitEvent: false}); - } else { - this.timewindowForm.get('aggregation.limit').enable({emitEvent: false}); - this.timewindowForm.get('realtime.interval').enable({emitEvent: false}); - this.timewindowForm.get('history.interval').enable({emitEvent: false}); - } - this.timewindowForm.markAsDirty(); - } - - onHideTimezoneChanged() { - if (this.timewindow.hideTimezone) { - this.timewindowForm.get('timezone').disable({emitEvent: false}); - } else { - this.timewindowForm.get('timezone').enable({emitEvent: false}); - } - this.timewindowForm.markAsDirty(); - } + // TODO: pay attention to selecting another option on hiding interval in config + // onHideLastIntervalChanged() { + // if (this.timewindow.realtime.hideLastInterval) { + // this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); + // if (!this.timewindow.realtime.hideQuickInterval) { + // this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); + // } + // } else { + // if (!this.timewindow.hideInterval) { + // this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); + // } + // } + // this.timewindowForm.markAsDirty(); + // } + + // onHideQuickIntervalChanged() { + // if (this.timewindow.realtime.hideQuickInterval) { + // this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); + // if (!this.timewindow.realtime.hideLastInterval) { + // this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL); + // } + // } else { + // if (!this.timewindow.hideInterval) { + // this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); + // } + // } + // this.timewindowForm.markAsDirty(); + // } openTimewindowConfig() { this.prepareTimewindowConfig(); diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index 622f5654d9..9896d65bc0 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -44,6 +44,7 @@ [class]="{'no-padding': noPadding}" matTooltip="{{ 'timewindow.edit' | translate }}" [matTooltipPosition]="tooltipPosition" + [matTooltipDisabled]="timewindowDisabled" [style]="timewindowComponentStyle" (click)="toggleTimewindow($event)"> ([ + [RealtimeWindowType.LAST_INTERVAL, 'timewindow.last'], + [RealtimeWindowType.INTERVAL, 'timewindow.relative'] +]); +export const historyWindowTypeTranslations = new Map([ + [HistoryWindowType.LAST_INTERVAL, 'timewindow.last'], + [HistoryWindowType.FIXED, 'timewindow.fixed'], + [HistoryWindowType.INTERVAL, 'timewindow.relative'], + [HistoryWindowType.FOR_ALL_TIME, 'timewindow.for-all-time'] +]); + export type Interval = number | IntervalType; export class IntervalMath { @@ -128,8 +139,17 @@ export interface Aggregation { export interface Timewindow { displayValue?: string; displayTimezoneAbbr?: string; + /** + * @deprecated + */ hideInterval?: boolean; + /** + * @deprecated + */ hideQuickInterval?: boolean; + /** + * @deprecated + */ hideLastInterval?: boolean; hideAggregation?: boolean; hideAggInterval?: boolean; @@ -247,9 +267,6 @@ export const defaultTimewindow = (timeService: TimeService): Timewindow => { const currentTime = moment().valueOf(); return { displayValue: '', - hideInterval: false, - hideLastInterval: false, - hideQuickInterval: false, hideAggregation: false, hideAggInterval: false, hideTimezone: false, @@ -258,7 +275,10 @@ export const defaultTimewindow = (timeService: TimeService): Timewindow => { realtimeType: RealtimeWindowType.LAST_INTERVAL, interval: SECOND, timewindowMs: MINUTE, - quickInterval: QuickTimeInterval.CURRENT_DAY + quickInterval: QuickTimeInterval.CURRENT_DAY, + hideInterval: false, + hideLastInterval: false, + hideQuickInterval: false }, history: { historyType: HistoryWindowType.LAST_INTERVAL, @@ -268,7 +288,11 @@ export const defaultTimewindow = (timeService: TimeService): Timewindow => { startTimeMs: currentTime - DAY, endTimeMs: currentTime }, - quickInterval: QuickTimeInterval.CURRENT_DAY + quickInterval: QuickTimeInterval.CURRENT_DAY, + hideInterval: false, + hideLastInterval: false, + hideFixedInterval: false, + hideQuickInterval: false }, aggregation: { type: AggregationType.AVG, @@ -289,14 +313,34 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO historyOnly: boolean, timeService: TimeService): Timewindow => { const model = defaultTimewindow(timeService); if (value) { - model.hideInterval = value.hideInterval; - model.hideLastInterval = value.hideLastInterval; - model.hideQuickInterval = value.hideQuickInterval; model.hideAggregation = value.hideAggregation; model.hideAggInterval = value.hideAggInterval; model.hideTimezone = value.hideTimezone; model.selectedTab = getTimewindowType(value); + + // for backward compatibility + if (isDefinedAndNotNull(value.hideInterval)) { + model.realtime.hideInterval = value.hideInterval; + model.history.hideInterval = value.hideInterval; + } + if (isDefinedAndNotNull(value.hideLastInterval)) { + model.realtime.hideLastInterval = value.hideLastInterval; + } + if (isDefinedAndNotNull(value.hideQuickInterval)) { + model.realtime.hideQuickInterval = value.hideQuickInterval; + } + if (isDefined(value.realtime)) { + if (isDefinedAndNotNull(value.realtime.hideInterval)) { + model.realtime.hideInterval = value.realtime.hideInterval; + } + if (isDefinedAndNotNull(value.realtime.hideLastInterval)) { + model.realtime.hideLastInterval = value.realtime.hideLastInterval; + } + if (isDefinedAndNotNull(value.realtime.hideQuickInterval)) { + model.realtime.hideQuickInterval = value.realtime.hideQuickInterval; + } + if (isDefined(value.realtime.interval)) { model.realtime.interval = value.realtime.interval; } @@ -317,6 +361,19 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO } } if (isDefined(value.history)) { + if (isDefinedAndNotNull(value.history.hideInterval)) { + model.history.hideInterval = value.history.hideInterval; + } + if (isDefinedAndNotNull(value.history.hideLastInterval)) { + model.history.hideLastInterval = value.history.hideLastInterval; + } + if (isDefinedAndNotNull(value.history.hideFixedInterval)) { + model.history.hideFixedInterval = value.history.hideFixedInterval; + } + if (isDefinedAndNotNull(value.history.hideQuickInterval)) { + model.history.hideQuickInterval = value.history.hideQuickInterval; + } + if (isDefined(value.history.interval)) { model.history.interval = value.history.interval; } @@ -382,9 +439,6 @@ export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number, limit = timeService.getMaxDatapointsLimit(); } return { - hideInterval: timewindow.hideInterval || false, - hideLastInterval: timewindow.hideLastInterval || false, - hideQuickInterval: timewindow.hideQuickInterval || false, hideAggregation: timewindow.hideAggregation || false, hideAggInterval: timewindow.hideAggInterval || false, hideTimezone: timewindow.hideTimezone || false, @@ -395,7 +449,10 @@ export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number, startTimeMs, endTimeMs }, - interval: timeService.boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, AggregationType.AVG) + interval: timeService.boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, AggregationType.AVG), + hideInterval: timewindow.history?.hideInterval || false, + hideLastInterval: timewindow.history?.hideLastInterval || false, + hideQuickInterval: timewindow.history?.hideQuickInterval || false, }, aggregation: { type: aggType, @@ -850,20 +907,14 @@ export const createTimewindowForComparison = (subscriptionTimewindow: Subscripti export const cloneSelectedTimewindow = (timewindow: Timewindow): Timewindow => { const cloned: Timewindow = {}; - cloned.hideInterval = timewindow.hideInterval || false; - cloned.hideLastInterval = timewindow.hideLastInterval || false; - cloned.hideQuickInterval = timewindow.hideQuickInterval || false; cloned.hideAggregation = timewindow.hideAggregation || false; cloned.hideAggInterval = timewindow.hideAggInterval || false; cloned.hideTimezone = timewindow.hideTimezone || false; if (isDefined(timewindow.selectedTab)) { cloned.selectedTab = timewindow.selectedTab; - if (timewindow.selectedTab === TimewindowType.REALTIME) { - cloned.realtime = deepClone(timewindow.realtime); - } else if (timewindow.selectedTab === TimewindowType.HISTORY) { - cloned.history = deepClone(timewindow.history); - } } + cloned.realtime = deepClone(timewindow.realtime); + cloned.history = deepClone(timewindow.history); cloned.aggregation = deepClone(timewindow.aggregation); cloned.timezone = timewindow.timezone; return cloned; From 8c681508d51efc811114f41eff4c3cc69849b8f0 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Thu, 22 Aug 2024 15:05:59 +0300 Subject: [PATCH 24/84] changed list to set --- .../thingsboard/rule/engine/delay/TbMsgDelayNode.java | 6 ++++-- .../rule/engine/delay/TbMsgDelayNodeTest.java | 9 +++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index be569162c4..a336c2095f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -31,7 +31,9 @@ import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.EnumSet; import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -54,8 +56,8 @@ import java.util.concurrent.TimeUnit; ) public class TbMsgDelayNode implements TbNode { - private final List supportedTimeUnits = List.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); - private final String supportedTimeUnitsStr = String.join(",", TimeUnit.SECONDS.name(), TimeUnit.MINUTES.name(), TimeUnit.HOURS.name()); + private static final Set supportedTimeUnits = EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); + private static final String supportedTimeUnitsStr = String.join(",", TimeUnit.SECONDS.name(), TimeUnit.MINUTES.name(), TimeUnit.HOURS.name()); private TbMsgDelayNodeConfiguration config; private ConcurrentMap pendingMsgs; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java index 63de8b505b..06565465d9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java @@ -40,8 +40,9 @@ import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.util.List; +import java.util.EnumSet; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -63,7 +64,7 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("20107cf0-1c5e-4ac4-8131-7c466c955a7c")); private final RuleNodeId RULE_NODE_ID = new RuleNodeId(UUID.fromString("1be24225-b669-4b26-ab7e-083aaa82d0a0")); - private final List supportedTimeUnits = List.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); + private final Set supportedTimeUnits = EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); private final String supportedTimeUnitsStr = String.join(",", TimeUnit.SECONDS.name(), TimeUnit.MINUTES.name(), TimeUnit.HOURS.name()); private TbMsgDelayNode node; @@ -92,8 +93,8 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { @ParameterizedTest @ValueSource(ints = {-1, 0, 5000000}) - public void givenInvalidMaxPendingMsgsValue_whenInit_thenThrowsException() { - config.setMaxPendingMsgs(-1); + public void givenInvalidMaxPendingMsgsValue_whenInit_thenThrowsException(int maxPendingMsgs) { + config.setMaxPendingMsgs(maxPendingMsgs); assertThatThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) .isInstanceOf(TbNodeException.class) From aac427d6bee41711006ff19f1bda5819f113ee0b Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Fri, 23 Aug 2024 12:34:43 +0300 Subject: [PATCH 25/84] added one more verification in upgrade --- .../rule/engine/delay/TbMsgDelayNode.java | 3 +++ .../rule/engine/delay/TbMsgDelayNodeTest.java | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index a336c2095f..a3e0f6becc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -156,6 +156,9 @@ public class TbMsgDelayNode implements TbNode { ((ObjectNode) oldConfiguration).put(period, oldConfiguration.get(periodInSeconds).asText()); } hasChanges = true; + } else if (oldConfiguration.has(periodInSeconds)) { + ((ObjectNode) oldConfiguration).put(period, oldConfiguration.get(periodInSeconds).asText()); + hasChanges = true; } if (!oldConfiguration.has(period)) { ((ObjectNode) oldConfiguration).put(period, "60"); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java index 06565465d9..70511b03b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeTest.java @@ -235,7 +235,7 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { return Stream.of( - // config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns does not exist) + // config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns does not exist and periodInSeconds exists) Arguments.of(0, """ { @@ -245,6 +245,23 @@ public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { } """, true, + """ + { + "period": "13", + "timeUnit": "SECONDS", + "maxPendingMsgs": 1000 + } + """ + ), + // config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns and periodInSeconds do not exist) + Arguments.of(0, + """ + { + "maxPendingMsgs": 1000, + "periodInSecondsPattern": "17" + } + """, + true, """ { "period": "60", From f25b7eaf212eb494908747ba732ffabd2365fb91 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 26 Aug 2024 18:23:02 +0300 Subject: [PATCH 26/84] UI: adjust hide interval timewindow options --- .../timewindow-config-dialog.component.ts | 58 ++++++++++++++++++ .../time/timewindow-panel.component.ts | 60 ++++++++++--------- 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index b154b4140d..e7de899e48 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -199,6 +199,17 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('realtime.hideQuickInterval').enable(); } }); + this.timewindowForm.get('realtime.hideLastInterval').valueChanges.subscribe((hideLastInterval: boolean) => { + if (hideLastInterval && !this.timewindowForm.get('realtime.hideQuickInterval').value) { + this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); + } + }); + this.timewindowForm.get('realtime.hideQuickInterval').valueChanges.subscribe((hideQuickInterval: boolean) => { + if (hideQuickInterval && !this.timewindowForm.get('realtime.hideLastInterval').value) { + this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL); + } + }); + this.timewindowForm.get('history.hideInterval').valueChanges.subscribe((value: boolean) => { if (value) { this.timewindowForm.get('history.hideLastInterval').disable(); @@ -210,6 +221,33 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('history.hideFixedInterval').enable(); } }); + this.timewindowForm.get('history.hideLastInterval').valueChanges.subscribe((hideLastInterval: boolean) => { + if (hideLastInterval) { + if (!this.timewindowForm.get('history.hideFixedInterval').value) { + this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.FIXED); + } else if (!this.timewindowForm.get('history.hideQuickInterval').value) { + this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.INTERVAL); + } + } + }); + this.timewindowForm.get('history.hideFixedInterval').valueChanges.subscribe((hideFixedInterval: boolean) => { + if (hideFixedInterval) { + if (!this.timewindowForm.get('history.hideLastInterval').value) { + this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.LAST_INTERVAL); + } else if (!this.timewindowForm.get('history.hideQuickInterval').value) { + this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.INTERVAL); + } + } + }); + this.timewindowForm.get('history.hideQuickInterval').valueChanges.subscribe((hideQuickInterval: boolean) => { + if (hideQuickInterval) { + if (!this.timewindowForm.get('history.hideLastInterval').value) { + this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.LAST_INTERVAL); + } else if (!this.timewindowForm.get('history.hideFixedInterval').value) { + this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.FIXED); + } + } + }); } private checkLimit(limit?: number): number { @@ -234,6 +272,26 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On onTimewindowTypeChange() { this.timewindowForm.markAsDirty(); const timewindowFormValue = this.timewindowForm.getRawValue(); + if (this.timewindow.selectedTab === TimewindowType.REALTIME) { + if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { + this.timewindowForm.get('realtime').patchValue({ + realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? + RealtimeWindowType[HistoryWindowType[timewindowFormValue.history.historyType]] : + timewindowFormValue.realtime.realtimeType, + timewindowMs: timewindowFormValue.history.timewindowMs, + quickInterval: timewindowFormValue.history.quickInterval.startsWith('CURRENT') ? + timewindowFormValue.history.quickInterval : timewindowFormValue.realtime.quickInterval + }); + setTimeout(() => this.timewindowForm.get('realtime.interval').patchValue(timewindowFormValue.history.interval)); + } + } else { + this.timewindowForm.get('history').patchValue({ + historyType: HistoryWindowType[RealtimeWindowType[timewindowFormValue.realtime.realtimeType]], + timewindowMs: timewindowFormValue.realtime.timewindowMs, + quickInterval: timewindowFormValue.realtime.quickInterval + }); + setTimeout(() => this.timewindowForm.get('history.interval').patchValue(timewindowFormValue.realtime.interval)); + } this.timewindowForm.patchValue({ aggregation: { type: timewindowFormValue.aggregation.type, diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index cc75725c74..c5bc43c91c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -180,6 +180,37 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { const history = this.timewindow.history; const aggregation = this.timewindow.aggregation; + if (!this.isEdit) { + if (realtime.hideLastInterval && !realtime.hideQuickInterval) { + realtime.realtimeType = RealtimeWindowType.INTERVAL; + } + if (realtime.hideQuickInterval && !realtime.hideLastInterval) { + realtime.realtimeType = RealtimeWindowType.LAST_INTERVAL; + } + + if (history.hideLastInterval) { + if (!history.hideFixedInterval) { + history.historyType = HistoryWindowType.FIXED; + } else if (!history.hideQuickInterval) { + history.historyType = HistoryWindowType.INTERVAL; + } + } + if (history.hideFixedInterval) { + if (!history.hideLastInterval) { + history.historyType = HistoryWindowType.LAST_INTERVAL; + } else if (!history.hideQuickInterval) { + history.historyType = HistoryWindowType.INTERVAL; + } + } + if (history.hideQuickInterval) { + if (!history.hideLastInterval) { + history.historyType = HistoryWindowType.LAST_INTERVAL; + } else if (!history.hideFixedInterval) { + history.historyType = HistoryWindowType.FIXED; + } + } + } + this.timewindowForm = this.fb.group({ realtime: this.fb.group({ realtimeType: [{ @@ -448,35 +479,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { } } - // TODO: pay attention to selecting another option on hiding interval in config - // onHideLastIntervalChanged() { - // if (this.timewindow.realtime.hideLastInterval) { - // this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); - // if (!this.timewindow.realtime.hideQuickInterval) { - // this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); - // } - // } else { - // if (!this.timewindow.hideInterval) { - // this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); - // } - // } - // this.timewindowForm.markAsDirty(); - // } - - // onHideQuickIntervalChanged() { - // if (this.timewindow.realtime.hideQuickInterval) { - // this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); - // if (!this.timewindow.realtime.hideLastInterval) { - // this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL); - // } - // } else { - // if (!this.timewindow.hideInterval) { - // this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); - // } - // } - // this.timewindowForm.markAsDirty(); - // } - openTimewindowConfig() { this.prepareTimewindowConfig(); this.dialog.open( From adf8ec31c5f1e3000bd061386a90644e3dfa1cec Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 28 Aug 2024 19:18:23 +0300 Subject: [PATCH 27/84] Added workaround for version compatibility of Gateway MQTT Connectors --- .../device-info-table.component.ts | 6 +- .../mapping-data-keys-panel.component.ts | 4 +- .../mapping-table/mapping-table.component.ts | 23 +- .../mqtt-basic-config.component.ts | 191 ------ .../mqtt-basic-config.abstract.ts | 107 ++++ .../mqtt-basic-config.component.html | 2 +- .../mqtt-basic-config.component.scss | 0 .../mqtt-basic-config.component.ts | 107 ++++ .../mqtt-legacy-basic-config.component.ts | 128 ++++ .../broker-config-control.component.html | 0 .../broker-config-control.component.ts | 2 +- .../opc-ua-basic-config.component.ts | 8 - .../dialog/add-connector-dialog.component.ts | 16 +- .../dialog/mapping-dialog.component.ts | 27 +- .../gateway/gateway-connectors.component.html | 15 +- .../gateway/gateway-connectors.component.scss | 5 + .../gateway/gateway-connectors.component.ts | 27 +- .../lib/gateway/gateway-widget.models.ts | 215 +++++-- .../gateway/pipes/gateway-help-link.pipe.ts | 10 +- .../gateway-connector-version-mapping.util.ts | 66 ++ .../utils/mqtt-version-mapping.util.ts | 279 ++++++++ .../widget/widget-components.module.ts | 8 +- .../connector-default-configs/mqtt.json | 597 ++++++++++++------ 23 files changed, 1341 insertions(+), 502 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{mqtt-basic-config => mqtt/basic-config}/mqtt-basic-config.component.html (93%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{mqtt-basic-config => mqtt/basic-config}/mqtt-basic-config.component.scss (100%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mqtt}/broker-config-control/broker-config-control.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mqtt}/broker-config-control/broker-config-control.component.ts (97%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts index 569f40b185..91432b7353 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts @@ -42,8 +42,8 @@ import { import { DeviceInfoType, noLeadTrailSpacesRegex, - OPCUaSourceTypes, - SourceTypes, + OPCUaSourceType, + SourceType, SourceTypeTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -81,7 +81,7 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa required = false; @Input() - sourceTypes: Array = Object.values(SourceTypes); + sourceTypes: Array = Object.values(SourceType); deviceInfoTypeValue: any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts index 7867a7213f..de289dde2e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts @@ -42,7 +42,7 @@ import { MappingValueType, mappingValueTypesMap, noLeadTrailSpacesRegex, - OPCUaSourceTypes, + OPCUaSourceType, RpcMethodsMapping, } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -73,7 +73,7 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn keysType: MappingKeysType; @Input() - valueTypeKeys: Array = Object.values(MappingValueType); + valueTypeKeys: Array = Object.values(MappingValueType); @Input() valueTypeEnum = MappingValueType; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts index 05dcfa88ba..fa8dfa6466 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts @@ -40,17 +40,21 @@ import { Validator, } from '@angular/forms'; import { + AttributeUpdate, ConnectorMapping, + ConnectRequest, ConverterConnectorMapping, ConvertorTypeTranslationsMap, DeviceConnectorMapping, + DisconnectRequest, MappingInfo, MappingType, MappingTypeTranslationsMap, MappingValue, - RequestMappingData, + RequestMappingValue, RequestType, - RequestTypesTranslationsMap + RequestTypesTranslationsMap, + ServerSideRpc } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; @@ -259,16 +263,17 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A }; case MappingType.REQUESTS: let details: string; - if ((value as RequestMappingData).requestType === RequestType.ATTRIBUTE_UPDATE) { - details = (value as RequestMappingData).requestValue.attributeFilter; - } else if ((value as RequestMappingData).requestType === RequestType.SERVER_SIDE_RPC) { - details = (value as RequestMappingData).requestValue.methodFilter; + const requestValue = value as RequestMappingValue; + if (requestValue.requestType === RequestType.ATTRIBUTE_UPDATE) { + details = (requestValue.requestValue as AttributeUpdate).attributeFilter; + } else if (requestValue.requestType === RequestType.SERVER_SIDE_RPC) { + details = (requestValue.requestValue as ServerSideRpc).methodFilter; } else { - details = (value as RequestMappingData).requestValue.topicFilter; + details = (requestValue.requestValue as ConnectRequest | DisconnectRequest).topicFilter; } return { - requestType: (value as RequestMappingData).requestType, - type: this.translate.instant(RequestTypesTranslationsMap.get((value as RequestMappingData).requestType)), + requestType: (value as RequestMappingValue).requestType, + type: this.translate.instant(RequestTypesTranslationsMap.get((value as RequestMappingValue).requestType)), details }; case MappingType.OPCUA: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts deleted file mode 100644 index 25f8671201..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts +++ /dev/null @@ -1,191 +0,0 @@ -/// -/// Copyright © 2016-2024 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. -/// - -import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { - MappingType, - MQTTBasicConfig, - RequestMappingData, - RequestType, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { isObject } from 'lodash'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; -import { - BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; -import { - MappingTableComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; -import { isDefinedAndNotNull } from '@core/utils'; - -@Component({ - selector: 'tb-mqtt-basic-config', - templateUrl: './mqtt-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - WorkersConfigControlComponent, - BrokerConfigControlComponent, - MappingTableComponent, - ], - styleUrls: ['./mqtt-basic-config.component.scss'] -}) - -export class MqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - - @Input() - generalTabContent: TemplateRef; - - mappingTypes = MappingType; - basicFormGroup: FormGroup; - - private onChange: (value: MQTTBasicConfig) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ - dataMapping: [], - requestsMapping: [], - broker: [], - workers: [], - }); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(this.getMappedMQTTConfig(value)); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: MQTTBasicConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(basicConfig: MQTTBasicConfig): void { - const { broker, dataMapping = [], requestsMapping } = basicConfig; - const editedBase = { - workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { - maxNumberOfWorkers: broker.maxNumberOfWorkers, - maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, - } : {}, - dataMapping: dataMapping || [], - broker: broker || {}, - requestsMapping: Array.isArray(requestsMapping) - ? requestsMapping - : this.getRequestDataArray(requestsMapping), - }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); - } - - private getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { - let { broker, workers, dataMapping, requestsMapping } = basicConfig || {}; - - if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { - broker = { - ...broker, - ...workers, - }; - } - - if ((requestsMapping as RequestMappingData[])?.length) { - requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingData[]); - } - - return { broker, workers, dataMapping, requestsMapping }; - } - - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { - basicFormGroup: {valid: false} - }; - } - - private getRequestDataArray(value: Record): RequestMappingData[] { - const mappingConfigs = []; - - if (isObject(value)) { - Object.keys(value).forEach((configKey: string) => { - for (const mapping of value[configKey]) { - mappingConfigs.push({ - requestType: configKey, - requestValue: mapping - }); - } - }); - } - - return mappingConfigs; - } - - private getRequestDataObject(array: RequestMappingData[]): Record { - return array.reduce((result, { requestType, requestValue }) => { - result[requestType].push(requestValue); - return result; - }, { - connectRequests: [], - disconnectRequests: [], - attributeRequests: [], - attributeUpdates: [], - serverSideRpc: [], - }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts new file mode 100644 index 0000000000..12529d942f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts @@ -0,0 +1,107 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { isObject } from 'lodash'; +import { + MappingType, + MQTTBasicConfig, + MQTTLegacyBasicConfig, + RequestMappingData, + RequestMappingValue, + RequestType +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { Directive, OnDestroy } from '@angular/core'; + +@Directive() +export abstract class AbstractMqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { + + basicFormGroup: FormGroup; + mappingTypes = MappingType; + private destroy$ = new Subject(); + private onChange: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void; + private onTouched: () => void; + + constructor(protected fb: FormBuilder) { + this.basicFormGroup = this.fb.group({ + mapping: [], + requestsMapping: [], + broker: [], + workers: [], + }); + + this.basicFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => { + this.onChange(this.getMappedMQTTConfig(value)); + this.onTouched(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + validate(): ValidationErrors | null { + return this.basicFormGroup.valid ? null : { + basicFormGroup: {valid: false} + }; + } + + protected getRequestDataArray(value: Record): RequestMappingData[] { + const mappingConfigs = []; + + if (isObject(value)) { + Object.keys(value).forEach((configKey: string) => { + for (const mapping of value[configKey]) { + mappingConfigs.push({ + requestType: configKey, + requestValue: mapping + }); + } + }); + } + + return mappingConfigs; + } + + protected getRequestDataObject(array: RequestMappingValue[]): Record { + return array.reduce((result, { requestType, requestValue }) => { + result[requestType].push(requestValue); + return result; + }, { + connectRequests: [], + disconnectRequests: [], + attributeRequests: [], + attributeUpdates: [], + serverSideRpc: [], + }); + } + + abstract writeValue(basicConfig: MQTTBasicConfig | MQTTLegacyBasicConfig): void; + protected abstract getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig | MQTTLegacyBasicConfig; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html similarity index 93% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html index 9849923218..9ad25aa851 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html @@ -24,7 +24,7 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts new file mode 100644 index 0000000000..c4ebadb03d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts @@ -0,0 +1,107 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; +import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; +import { + MQTTBasicConfig, + RequestMappingData, + RequestMappingValue, + RequestType +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { + AbstractMqttBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; +import { isDefinedAndNotNull } from '@core/utils'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + WorkersConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; +import { + BrokerConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; +import { + MappingTableComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; + +@Component({ + selector: 'tb-mqtt-basic-config', + templateUrl: './mqtt-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + } + ], + styleUrls: ['./mqtt-basic-config.component.scss'], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + WorkersConfigControlComponent, + BrokerConfigControlComponent, + MappingTableComponent, + ], +}) +export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent { + + @Input() + generalTabContent: TemplateRef; + + writeValue(basicConfig: MQTTBasicConfig): void { + const { broker, mapping = [], requestsMapping } = basicConfig; + const editedBase = { + workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { + maxNumberOfWorkers: broker.maxNumberOfWorkers, + maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, + } : {}, + mapping: mapping || [], + broker: broker || {}, + requestsMapping: this.getRequestDataArray(requestsMapping as Record), + }; + + this.basicFormGroup.setValue(editedBase, {emitEvent: false}); + } + + protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { + let { broker, workers, mapping, requestsMapping } = basicConfig || {}; + + if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { + broker = { + ...broker, + ...workers, + }; + } + + if ((requestsMapping as RequestMappingData[])?.length) { + requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); + } + + return { broker, mapping, requestsMapping }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts new file mode 100644 index 0000000000..f499199739 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts @@ -0,0 +1,128 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; +import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; +import { + MQTTBasicConfig, + MQTTLegacyBasicConfig, + RequestMappingData, + RequestMappingValue, + RequestType +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; +import { + AbstractMqttBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; +import { isDefinedAndNotNull } from '@core/utils'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + WorkersConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; +import { + BrokerConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; +import { + MappingTableComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; + +@Component({ + selector: 'tb-mqtt-legacy-basic-config', + templateUrl: './mqtt-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), + multi: true + } + ], + styleUrls: ['./mqtt-basic-config.component.scss'], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + WorkersConfigControlComponent, + BrokerConfigControlComponent, + MappingTableComponent, + ], +}) +export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigComponent { + + @Input() + generalTabContent: TemplateRef; + + writeValue(basicConfig: any): void { + const { + broker, + mapping = [], + connectRequests = [], + disconnectRequests = [], + attributeRequests = [], + attributeUpdates = [], + serverSideRpc = [] + } = basicConfig; + const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToNewestVersion({ + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + }); + const editedBase = { + workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { + maxNumberOfWorkers: broker.maxNumberOfWorkers, + maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, + } : {}, + mapping: MqttVersionMappingUtil.mapMappingToNewestVersion(mapping) || [], + broker: broker || {}, + requestsMapping: this.getRequestDataArray(updatedRequestMapping), + }; + + this.basicFormGroup.setValue(editedBase, {emitEvent: false}); + } + + protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTLegacyBasicConfig { + let { broker, workers, mapping, requestsMapping } = basicConfig || {}; + + if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { + broker = { + ...broker, + ...workers, + }; + } + + if ((requestsMapping as RequestMappingData[])?.length) { + requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); + } + + return { + broker, + mapping: MqttVersionMappingUtil.mapMappingToLegacyVersion(mapping), + ...(MqttVersionMappingUtil.mapRequestsToLegacyVersion(requestsMapping as Record)) + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts similarity index 97% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts index d4bfdd4694..6f0eadd55f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts @@ -37,7 +37,7 @@ import { TranslateService } from '@ngx-translate/core'; import { generateSecret } from '@core/utils'; import { Subject } from 'rxjs'; import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { SecurityConfigComponent } from '../security-config/security-config.component'; +import { SecurityConfigComponent } from '../../security-config/security-config.component'; @Component({ selector: 'tb-broker-config-control', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts index 39198c9696..b6cf4a4081 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -36,12 +36,6 @@ import { Subject } from 'rxjs'; import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; -import { - BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; @@ -70,8 +64,6 @@ import { CommonModule, SharedModule, SecurityConfigComponent, - WorkersConfigControlComponent, - BrokerConfigControlComponent, MappingTableComponent, OpcServerConfigComponent, ], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts index eca1497d22..d79a1d3fec 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts @@ -26,12 +26,13 @@ import { AddConnectorConfigData, ConnectorType, CreatedConnectorConfigData, + GatewayConnector, GatewayConnectorDefaultTypesTranslatesMap, GatewayLogLevel, - getDefaultConfig, + GatewayVersionedDefaultConfig, noLeadTrailSpacesRegex } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Subject } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { ResourcesService } from '@core/services/resources.service'; import { takeUntil, tap } from 'rxjs/operators'; import { helpBaseUrl } from '@shared/models/constants'; @@ -42,7 +43,8 @@ import { helpBaseUrl } from '@shared/models/constants'; styleUrls: ['./add-connector-dialog.component.scss'], providers: [], }) -export class AddConnectorDialogComponent extends DialogComponent> implements OnInit, OnDestroy { +export class AddConnectorDialogComponent + extends DialogComponent> implements OnInit, OnDestroy { connectorForm: UntypedFormGroup; @@ -95,8 +97,8 @@ export class AddConnectorDialogComponent extends DialogComponent { - value.configurationJson = defaultConfig; + this.getDefaultConfig(value.type).subscribe(({current, legacy, ...defaultConfig}: GatewayVersionedDefaultConfig) => { + value.configurationJson = (this.data.gatewayVersion ? current : legacy) ?? defaultConfig; if (this.connectorForm.valid) { this.dialogRef.close(value); } @@ -130,4 +132,8 @@ export class AddConnectorDialogComponent extends DialogComponent { + return this.resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); + }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts index 9e5a43bc17..ef8d7ed7ce 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts @@ -43,15 +43,16 @@ import { MappingType, MappingTypeTranslationsMap, noLeadTrailSpacesRegex, - OPCUaSourceTypes, + OPCUaSourceType, QualityTypes, QualityTypeTranslationsMap, + RequestMappingData, RequestMappingFormValue, RequestType, RequestTypesTranslationsMap, RpcMethod, ServerSideRPCType, - SourceTypes, + SourceType, SourceTypeTranslationsMap, Timeseries } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -82,10 +83,10 @@ export class MappingDialogComponent extends DialogComponent; - OPCUaSourceTypesEnum = OPCUaSourceTypes; - sourceTypesEnum = SourceTypes; + sourceTypes: SourceType[] = Object.values(SourceType); + OPCUaSourceTypes = Object.values(OPCUaSourceType) as Array; + OPCUaSourceTypesEnum = OPCUaSourceType; + sourceTypesEnum = SourceType; SourceTypeTranslationsMap = SourceTypeTranslationsMap; requestTypes: RequestType[] = Object.values(RequestType); @@ -230,8 +231,8 @@ export class MappingDialogComponent extends DialogComponent + }; default: return this.data.value as DeviceConnectorMapping; } @@ -349,10 +350,10 @@ export class MappingDialogComponent extends DialogComponent {{ initialConnector?.type ? GatewayConnectorTypesTranslatesMap.get(initialConnector.type) : '' }} {{ 'gateway.configuration' | translate }} + v{{connectorForm.get('configVersion').value}}
@@ -175,10 +176,16 @@
- - + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss index f151887c92..5d8c6aad5b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss @@ -22,6 +22,11 @@ overflow-x: auto; padding: 0; + .version-placeholder { + color: gray; + font-size: 12px + } + .connector-container { height: 100%; width: 100%; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index e7e4d05e45..0da917312b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -65,6 +65,7 @@ import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway import { debounceTime, filter, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; +import { GatewayConnectorVersionMappingUtil } from './utils/gateway-connector-version-mapping.util'; export class ForceErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null): boolean { @@ -106,6 +107,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie mode: ConnectorConfigurationModes = this.ConnectorConfigurationModes.BASIC; initialConnector: GatewayConnector; + private gatewayVersion: string; private inactiveConnectors: Array; private attributeDataSource: AttributeDatasource; private inactiveConnectorsDataSource: AttributeDatasource; @@ -241,9 +243,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie delete value.class; } + if (this.gatewayVersion && !value.configVersion) { + value.configVersion = this.gatewayVersion; + } + value.ts = Date.now(); - return value; + return GatewayConnectorVersionMappingUtil.getMappedByVersion(value, this.gatewayVersion); } private updateData(reload: boolean = false): void { @@ -265,7 +271,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie }); } - isConnectorSynced(attribute: GatewayAttributeData) { + isConnectorSynced(attribute: GatewayAttributeData): boolean { const connectorData = attribute.value; if (!connectorData.ts || attribute.skipSync) { return false; @@ -342,7 +348,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie class: '', configuration: '', configurationJson: {}, - basicConfig: {} + basicConfig: {}, + configVersion: '' }, {emitEvent: false}); this.connectorForm.markAsPristine(); } @@ -475,7 +482,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie value.configurationJson = {} as ConnectorBaseConfig; } value.basicConfig = value.configurationJson; - this.updateConnector(value); + this.updateConnector({...value, configVersion: this.gatewayVersion ?? ''}); this.generate('basicConfig.broker.clientId'); setTimeout(() => this.saveConnector()); }); @@ -485,7 +492,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie return this.dialog.open(AddConnectorDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { dataSourceData: this.dataSource.data } + data: { + dataSourceData: this.dataSource.data, + gatewayVersion: this.gatewayVersion, + } }).afterClosed(); } @@ -528,7 +538,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie class: [''], configuration: [''], configurationJson: [{}, [Validators.required]], - basicConfig: [{}] + basicConfig: [{}], + configVersion: [''], }); this.connectorForm.disable(); } @@ -571,10 +582,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie forkJoin([ this.attributeService.getEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, ['active_connectors']), - this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, ['inactive_connectors']) + this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, ['inactive_connectors']), + this.attributeService.getEntityAttributes(this.device, AttributeScope.CLIENT_SCOPE, ['Version']) ]).pipe(takeUntil(this.destroy$)).subscribe(attributes => { this.activeConnectors = this.parseConnectors(attributes[0]); this.inactiveConnectors = this.parseConnectors(attributes[1]); + this.gatewayVersion = attributes[2][0]?.value; this.updateData(true); }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 2dde0b8f54..64cdc8e39b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -14,8 +14,6 @@ /// limitations under the License. /// -import { ResourcesService } from '@core/services/resources.service'; -import { Observable } from 'rxjs'; import { helpBaseUrl, ValueTypeData } from '@shared/models/constants'; import { AttributeData } from '@shared/models/telemetry/telemetry.models'; @@ -114,21 +112,37 @@ export interface GatewayAttributeData extends AttributeData { skipSync?: boolean; } -export interface GatewayConnector { +export interface GatewayConnectorBase { name: string; type: ConnectorType; configuration?: string; - configurationJson: ConnectorBaseConfig; - basicConfig?: ConnectorBaseConfig; logLevel: string; key?: string; class?: string; mode?: ConnectorConfigurationModes; + configVersion?: string; +} + +export type GatewayConnector = CurrentGatewayConnector | LegacyGatewayConnector; + +export interface CurrentGatewayConnector extends GatewayConnectorBase { + configurationJson: ConnectorBaseConfig; + basicConfig?: ConnectorBaseConfig; +} + +export interface LegacyGatewayConnector extends GatewayConnectorBase { + configurationJson: ConnectorLegacyBaseConfig; + basicConfig?: ConnectorLegacyBaseConfig; +} + +export interface GatewayVersionedDefaultConfig { + legacy: LegacyGatewayConnector; + current: GatewayConnector; } export interface DataMapping { topicFilter: string; - QoS: string; + QoS: string | number; converter: Converter; } @@ -180,12 +194,14 @@ export interface ConnectorSecurity { mode?: ModeType; } -export type ConnectorMapping = DeviceConnectorMapping | RequestMappingData | ConverterConnectorMapping; +export type ConnectorMapping = DeviceConnectorMapping | RequestMappingValue | ConverterConnectorMapping; export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue; export type ConnectorBaseConfig = ConnectorBaseInfo | MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig; +export type ConnectorLegacyBaseConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig; + export interface ConnectorBaseInfo { name: string; id: string; @@ -194,10 +210,21 @@ export interface ConnectorBaseInfo { } export interface MQTTBasicConfig { - dataMapping: ConverterConnectorMapping[]; - requestsMapping: Record | RequestMappingData[]; + mapping: ConverterConnectorMapping[]; + requestsMapping: Record | RequestMappingData[] | RequestMappingValue[]; + broker: BrokerConfig; + workers?: WorkersConfig; +} + +export interface MQTTLegacyBasicConfig { + mapping: LegacyConverterConnectorMapping[]; broker: BrokerConfig; workers?: WorkersConfig; + connectRequests: LegacyRequestMappingData[]; + disconnectRequests: LegacyRequestMappingData[]; + attributeRequests: LegacyRequestMappingData[]; + attributeUpdates: LegacyRequestMappingData[]; + serverSideRpc: LegacyRequestMappingData[]; } export interface OPCBasicConfig { @@ -215,11 +242,11 @@ export interface WorkersConfig { maxMessageNumberPerWorker: number; } -interface DeviceInfo { +export interface ConnectorDeviceInfo { deviceNameExpression: string; - deviceNameExpressionSource: string; + deviceNameExpressionSource: SourceType; deviceProfileExpression: string; - deviceProfileExpressionSource: string; + deviceProfileExpressionSource: SourceType; } export interface Attribute { @@ -252,20 +279,41 @@ export interface AttributesUpdate { export interface Converter { type: ConvertorType; - deviceNameJsonExpression: string; - deviceTypeJsonExpression: string; + deviceInfo?: ConnectorDeviceInfo; sendDataOnlyOnChange: boolean; timeout: number; - attributes: Attribute[]; - timeseries: Timeseries[]; + attributes?: Attribute[]; + timeseries?: Timeseries[]; + extension?: string; + cached?: boolean; + extensionConfig?: Record; +} + +export interface LegacyConverter extends Converter { + deviceNameJsonExpression?: string; + deviceTypeJsonExpression?: string; + deviceNameTopicExpression?: string; + deviceTypeTopicExpression?: string; + deviceNameExpression?: string; + deviceNameExpressionSource?: string; + deviceTypeExpression?: string; + deviceProfileExpression?: string; + deviceProfileExpressionSource?: string; + ['extension-config']?: Record; } export interface ConverterConnectorMapping { topicFilter: string; - subscriptionQos?: string; + subscriptionQos?: string | number; converter: Converter; } +export interface LegacyConverterConnectorMapping { + topicFilter: string; + subscriptionQos?: string | number; + converter: LegacyConverter; +} + export type ConverterMappingFormValue = Omit & { converter: { type: ConvertorType; @@ -274,8 +322,8 @@ export type ConverterMappingFormValue = Omit; + gatewayVersion: string; } export interface CreatedConnectorConfigData { @@ -590,13 +639,13 @@ export const ConvertorTypeTranslationsMap = new Map( ] ); -export enum SourceTypes { +export enum SourceType { MSG = 'message', TOPIC = 'topic', CONST = 'constant' } -export enum OPCUaSourceTypes { +export enum OPCUaSourceType { PATH = 'path', IDENTIFIER = 'identifier', CONST = 'constant' @@ -607,33 +656,116 @@ export enum DeviceInfoType { PARTIAL = 'partial' } -export const SourceTypeTranslationsMap = new Map( +export const SourceTypeTranslationsMap = new Map( [ - [SourceTypes.MSG, 'gateway.source-type.msg'], - [SourceTypes.TOPIC, 'gateway.source-type.topic'], - [SourceTypes.CONST, 'gateway.source-type.const'], - [OPCUaSourceTypes.PATH, 'gateway.source-type.path'], - [OPCUaSourceTypes.IDENTIFIER, 'gateway.source-type.identifier'], - [OPCUaSourceTypes.CONST, 'gateway.source-type.const'] + [SourceType.MSG, 'gateway.source-type.msg'], + [SourceType.TOPIC, 'gateway.source-type.topic'], + [SourceType.CONST, 'gateway.source-type.const'], + [OPCUaSourceType.PATH, 'gateway.source-type.path'], + [OPCUaSourceType.IDENTIFIER, 'gateway.source-type.identifier'], + [OPCUaSourceType.CONST, 'gateway.source-type.const'] ] ); -export interface RequestMappingData { +export interface RequestMappingValue { requestType: RequestType; - requestValue: RequestDataItem; + requestValue: RequestMappingData; } -export type RequestMappingFormValue = Omit & { - requestValue: Record; -}; - -export interface RequestDataItem { - type: string; - details: string; +export interface RequestMappingFormValue { requestType: RequestType; - methodFilter?: string; - attributeFilter?: string; - topicFilter?: string; + requestValue: Record; +} + +export type RequestMappingData = ConnectRequest | DisconnectRequest | AttributeRequest | AttributeUpdate | ServerSideRpc; + +export type LegacyRequestMappingData = + LegacyConnectRequest + | LegacyDisconnectRequest + | LegacyAttributeRequest + | LegacyAttributeUpdate + | LegacyServerSideRpc; + +export interface ConnectRequest { + topicFilter: string; + deviceInfo: ConnectorDeviceInfo; +} + +export interface DisconnectRequest { + topicFilter: string; + deviceInfo: ConnectorDeviceInfo; +} + +export interface AttributeRequest { + retain: boolean; + topicFilter: string; + deviceInfo: ConnectorDeviceInfo; + attributeNameExpressionSource: SourceType; + attributeNameExpression: string; + topicExpression: string; + valueExpression: string; +} + +export interface AttributeUpdate { + retain: boolean; + deviceNameFilter: string; + attributeFilter: string; + topicExpression: string; + valueExpression: string; +} + +export interface ServerSideRpc { + type: ServerSideRpcType; + deviceNameFilter: string; + methodFilter: string; + requestTopicExpression: string; + responseTopicExpression?: string; + responseTopicQoS?: number; + responseTimeout?: number; + valueExpression: string; +} + +export enum ServerSideRpcType { + WithResponse = 'twoWay', + WithoutResponse = 'oneWay' +} + +export interface LegacyConnectRequest { + topicFilter: string; + deviceNameJsonExpression?: string; + deviceNameTopicExpression?: string; +} + +interface LegacyDisconnectRequest { + topicFilter: string; + deviceNameJsonExpression?: string; + deviceNameTopicExpression?: string; +} + +interface LegacyAttributeRequest { + retain: boolean; + topicFilter: string; + deviceNameJsonExpression: string; + attributeNameJsonExpression: string; + topicExpression: string; + valueExpression: string; +} + +interface LegacyAttributeUpdate { + retain: boolean; + deviceNameFilter: string; + attributeFilter: string; + topicExpression: string; + valueExpression: string; +} + +interface LegacyServerSideRpc { + deviceNameFilter: string; + methodFilter: string; + requestTopicExpression: string; + responseTopicExpression?: string; + responseTimeout?: number; + valueExpression: string; } export enum RequestType { @@ -707,9 +839,6 @@ export enum ServerSideRPCType { TWO_WAY = 'twoWay' } -export const getDefaultConfig = (resourcesService: ResourcesService, type: string): Observable => - resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); - export enum MappingValueType { STRING = 'string', INTEGER = 'integer', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts index 0fec377860..06526ce08e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts @@ -17,8 +17,8 @@ import { Pipe, PipeTransform } from '@angular/core'; import { MappingValueType, - OPCUaSourceTypes, - SourceTypes + OPCUaSourceType, + SourceType } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Pipe({ @@ -26,9 +26,9 @@ import { standalone: true, }) export class GatewayHelpLinkPipe implements PipeTransform { - transform(field: string, sourceType: SourceTypes | OPCUaSourceTypes, sourceTypes?: Array ): string { - if (!sourceTypes || sourceTypes?.includes(OPCUaSourceTypes.PATH)) { - if (sourceType !== OPCUaSourceTypes.CONST) { + transform(field: string, sourceType: SourceType | OPCUaSourceType, sourceTypes?: Array ): string { + if (!sourceTypes || sourceTypes?.includes(OPCUaSourceType.PATH)) { + if (sourceType !== OPCUaSourceType.CONST) { return `widget/lib/gateway/${field}-${sourceType}_fn`; } else { return; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts new file mode 100644 index 0000000000..6a48b5e64b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts @@ -0,0 +1,66 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + ConnectorType, + GatewayConnector, + LegacyGatewayConnector, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { MqttVersionMappingUtil } from './mqtt-version-mapping.util'; + +export class GatewayConnectorVersionMappingUtil { + + static getMappedByVersion(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { + switch (connector.type) { + case ConnectorType.MQTT: + return this.getMappedMQTTByVersion(connector, gatewayVersion); + default: + return connector; + } + } + + private static getMappedMQTTByVersion( + connector: GatewayConnector | LegacyGatewayConnector, + gatewayVersion: string + ): GatewayConnector | LegacyGatewayConnector { + if (this.isVersionUpdateNeeded(gatewayVersion, connector.configVersion)) { + return this.isGatewayOutdated(gatewayVersion, connector.configVersion) + ? MqttVersionMappingUtil.getLegacyVersion(connector) + : MqttVersionMappingUtil.getNewestVersion(connector); + } + return connector; + } + + private static isGatewayOutdated(gatewayVersion: string, configVersion: string): boolean { + if (!gatewayVersion || !configVersion) { + return false; + } + + return this.parseVersion(configVersion) > this.parseVersion(gatewayVersion); + } + + private static isVersionUpdateNeeded(configVersion: string, gatewayVersion: string): boolean { + if (!gatewayVersion || !configVersion) { + return false; + } + + return this.parseVersion(configVersion) !== this.parseVersion(gatewayVersion); + } + + private static parseVersion(version: string): number { + return Number(version?.replace(/\./g, '')); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts new file mode 100644 index 0000000000..e32d020730 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts @@ -0,0 +1,279 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { deleteNullProperties, isEqual } from '@core/utils'; +import { + AttributeRequest, + ConnectorDeviceInfo, + Converter, + ConverterConnectorMapping, + ConvertorType, + CurrentGatewayConnector, + GatewayConnector, + LegacyConverter, + LegacyConverterConnectorMapping, + LegacyGatewayConnector, + LegacyRequestMappingData, + MQTTBasicConfig, + MQTTLegacyBasicConfig, + RequestMappingData, + RequestType, + ServerSideRpc, + ServerSideRpcType, + SourceType +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export class MqttVersionMappingUtil { + + static readonly mqttRequestTypeKeys = Object.values(RequestType); + static readonly mqttRequestMappingOldFields = + ['attributeNameJsonExpression', 'deviceNameJsonExpression', 'deviceNameTopicExpression', 'extension-config']; + static readonly mqttRequestMappingNewFields = + ['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; + + static getNewestVersion(mqttConnector: GatewayConnector): CurrentGatewayConnector { + const { + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + } = mqttConnector.configurationJson as MQTTLegacyBasicConfig; + let configurationJson = { + ...mqttConnector.configurationJson, + requestsMapping: this.mapRequestsToNewestVersion({ + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + }), + mapping: this.mapMappingToNewestVersion((mqttConnector.configurationJson as MQTTLegacyBasicConfig).mapping), + }; + + this.mqttRequestTypeKeys.forEach((key: RequestType) => { + const { [key]: removedKey, ...rest } = configurationJson as MQTTLegacyBasicConfig; + configurationJson = { ...rest } as any; + }); + + this.cleanUpConfigJson(configurationJson as MQTTBasicConfig); + + return { + ...mqttConnector, + configurationJson + } as CurrentGatewayConnector; + } + + static getLegacyVersion(mqttConnector: GatewayConnector): LegacyGatewayConnector { + const { requestsMapping, mapping, ...restConfig } = mqttConnector.configurationJson as MQTTBasicConfig; + + const updatedRequestsMapping = this.mapRequestsToLegacyVersion(requestsMapping as Record); + const updatedMapping = this.mapMappingToLegacyVersion(mapping); + + return { + ...mqttConnector, + configurationJson: { + ...restConfig, + ...updatedRequestsMapping, + mapping: updatedMapping, + }, + } as LegacyGatewayConnector; + } + + static mapMappingToNewestVersion(mapping: LegacyConverterConnectorMapping[] | ConverterConnectorMapping[]): ConverterConnectorMapping[] { + return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { + const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); + + const newConverter = { + ...converter, + deviceInfo, + extensionConfig: converter.extensionConfig || converter['extension-config'] || null + }; + + this.cleanUpOldFields(newConverter); + + return { converter: newConverter, topicFilter, subscriptionQos }; + }); + } + + static mapRequestsToNewestVersion( + requestMapping: Record + ): Record { + return this.mqttRequestTypeKeys.reduce((acc, key: RequestType) => { + if (!requestMapping[key]) { + return acc; + } + + acc[key] = requestMapping[key].map(value => { + const newValue = this.mapRequestToNewest(value as LegacyRequestMappingData, key); + + this.cleanUpOldFields(newValue as {}); + + return newValue; + }); + + return acc; + }, {}) as Record; + } + + static mapRequestsToLegacyVersion( + requestsMapping: Record + ): Record { + return this.mqttRequestTypeKeys.reduce((acc, key) => { + if (!requestsMapping[key]) { + return acc; + } + + acc[key] = requestsMapping[key].map((value: RequestMappingData) => { + if (key === RequestType.SERVER_SIDE_RPC) { + delete (value as ServerSideRpc).type; + } + + const { attributeNameExpression, deviceInfo, ...rest } = value as AttributeRequest; + + const newValue = { + ...rest, + attributeNameJsonExpression: attributeNameExpression || null, + deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource !== SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, + deviceNameTopicExpression: deviceInfo?.deviceNameExpressionSource === SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, + }; + + this.cleanUpNewFields(newValue); + + return newValue; + }); + + return acc; + }, {}) as Record; + } + + static mapMappingToLegacyVersion( + mapping: ConverterConnectorMapping[] + ): LegacyConverterConnectorMapping[] { + return mapping?.map((converterMapping: ConverterConnectorMapping) => { + const converter = this.mapConverterToLegacy(converterMapping.converter); + + this.cleanUpNewFields(converter as {}); + + return { converter, topicFilter: converterMapping.topicFilter }; + }); + } + + private static mapConverterToLegacy(converter: Converter): LegacyConverter { + const { deviceInfo, ...restConverter } = converter; + + return converter.type !== ConvertorType.BYTES ? { + ...restConverter, + deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource === SourceType.MSG ? deviceInfo.deviceNameExpression : null, + deviceTypeJsonExpression: + deviceInfo?.deviceProfileExpressionSource === SourceType.MSG ? deviceInfo.deviceProfileExpression : null, + deviceNameTopicExpression: + deviceInfo?.deviceNameExpressionSource !== SourceType.MSG + ? deviceInfo?.deviceNameExpression + : null, + deviceTypeTopicExpression: deviceInfo?.deviceProfileExpressionSource !== SourceType.MSG + ? deviceInfo?.deviceProfileExpression + : null, + } : { + ...restConverter, + deviceNameExpression: deviceInfo.deviceNameExpression, + deviceTypeExpression: deviceInfo.deviceProfileExpression, + ['extension-config']: converter.extensionConfig, + }; + } + + private static cleanUpOldFields(obj: Record): void { + this.mqttRequestMappingOldFields.forEach(field => delete obj[field]); + deleteNullProperties(obj); + } + + private static cleanUpNewFields(obj: Record): void { + this.mqttRequestMappingNewFields.forEach(field => delete obj[field]); + deleteNullProperties(obj); + } + + private static getTypeSourceByValue(value: string): SourceType { + if (value.includes('${')) { + return SourceType.MSG; + } + if (value.includes(`/`)) { + return SourceType.TOPIC; + } + return SourceType.CONST; + } + + private static cleanUpConfigJson(configurationJson: MQTTBasicConfig): void { + if (isEqual(configurationJson.requestsMapping, {})) { + delete configurationJson.requestsMapping; + } + + if (isEqual(configurationJson.mapping, [])) { + delete configurationJson.mapping; + } + } + + private static extractConverterDeviceInfo(converter: LegacyConverter): ConnectorDeviceInfo { + const deviceNameExpression = converter.deviceNameExpression + || converter.deviceNameJsonExpression + || converter.deviceNameTopicExpression + || null; + const deviceNameExpressionSource = converter.deviceNameExpressionSource + ? converter.deviceNameExpressionSource as SourceType + : deviceNameExpression ? this.getTypeSourceByValue(deviceNameExpression) : null; + const deviceProfileExpression = converter.deviceProfileExpression + || converter.deviceTypeTopicExpression + || converter.deviceTypeJsonExpression + || 'default'; + const deviceProfileExpressionSource = converter.deviceProfileExpressionSource + ? converter.deviceProfileExpressionSource as SourceType + : deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; + + return deviceNameExpression || deviceProfileExpression ? { + deviceNameExpression, + deviceNameExpressionSource, + deviceProfileExpression, + deviceProfileExpressionSource + } : null; + } + + private static mapRequestToNewest(value, key: RequestType): RequestMappingData { + const deviceNameExpression = value.deviceNameJsonExpression || value.deviceNameTopicExpression || null; + const deviceProfileExpression = value.deviceTypeTopicExpression || value.deviceTypeJsonExpression || 'default'; + const deviceProfileExpressionSource = deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; + const attributeNameExpression = value.attributeNameExpressionSource || value.attributeNameJsonExpression || null; + const responseTopicQoS = key === RequestType.SERVER_SIDE_RPC ? 1 : null; + const type = key === RequestType.SERVER_SIDE_RPC + ? (value as ServerSideRpc).responseTopicExpression + ? ServerSideRpcType.WithResponse + : ServerSideRpcType.WithoutResponse + : null; + + return { + ...value, + attributeNameExpression, + attributeNameExpressionSource: attributeNameExpression ? this.getTypeSourceByValue(attributeNameExpression) : null, + deviceInfo: value.deviceInfo ? value.deviceInfo : deviceNameExpression ? { + deviceNameExpression, + deviceNameExpressionSource: this.getTypeSourceByValue(deviceNameExpression), + deviceProfileExpression, + deviceProfileExpressionSource + } : null, + responseTopicQoS, + type + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 955fd1ecf6..f5f6614962 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -113,7 +113,7 @@ import { GatewayHelpLinkPipe } from '@home/components/widget/lib/gateway/pipes/g import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; import { BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; import { WorkersConfigControlComponent } from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; @@ -122,7 +122,7 @@ import { } from '@home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component'; import { MqttBasicConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component'; import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; @@ -145,6 +145,9 @@ import { ModbusRpcParametersComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component'; +import { + MqttLegacyBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component'; @NgModule({ declarations: [ @@ -233,6 +236,7 @@ import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/sc ModbusBasicConfigComponent, EllipsisChipListDirective, ModbusRpcParametersComponent, + MqttLegacyBasicConfigComponent, ], exports: [ EntitiesTableWidgetComponent, diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json index c60e1dd956..17ddbd1285 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json @@ -1,218 +1,399 @@ { - "broker": { - "name": "Default Local Broker", - "host": "127.0.0.1", - "port": 1883, - "clientId": "ThingsBoard_gateway", - "version": 5, - "maxMessageNumberPerWorker": 10, - "maxNumberOfWorkers": 100, - "sendDataOnlyOnChange": false, - "security": { - "type": "anonymous" - } - }, - "dataMapping": [ - { - "topicFilter": "sensor/data", - "subscriptionQos": 1, - "converter": { - "type": "json", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}", - "deviceProfileExpressionSource": "message", - "deviceProfileExpression": "${sensorType}" + "current": { + "broker": { + "name": "Default Local Broker", + "host": "127.0.0.1", + "port": 1883, + "clientId": "ThingsBoard_gateway", + "version": 5, + "maxMessageNumberPerWorker": 10, + "maxNumberOfWorkers": 100, + "sendDataOnlyOnChange": false, + "security": { + "type": "anonymous" + } }, - "sendDataOnlyOnChange": false, - "timeout": 60000, - "attributes": [ - { - "type": "string", - "key": "model", - "value": "${sensorModel}" - }, - { - "type": "string", - "key": "${sensorModel}", - "value": "on" - } + "mapping": [ + { + "topicFilter": "sensor/data", + "subscriptionQos": 1, + "converter": { + "type": "json", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}", + "deviceProfileExpressionSource": "message", + "deviceProfileExpression": "${sensorType}" + }, + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + }, + { + "type": "string", + "key": "${sensorModel}", + "value": "on" + } + ], + "timeseries": [ + { + "type": "string", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + }, + { + "type": "string", + "key": "combine", + "value": "${hum}:${temp}" + } + ] + } + }, + { + "topicFilter": "sensor/+/data", + "subscriptionQos": 1, + "converter": { + "type": "json", + "deviceInfo": { + "deviceNameExpressionSource": "topic", + "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/data)", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "Thermometer" + }, + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "string", + "key": "humidity", + "value": "${hum}" + } + ] + } + }, + { + "topicFilter": "sensor/raw_data", + "subscriptionQos": 1, + "converter": { + "type": "bytes", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "[0:4]", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "default" + }, + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "raw", + "key": "rawData", + "value": "[:]" + } + ], + "timeseries": [ + { + "type": "raw", + "key": "temp", + "value": "[4:]" + } + ] + } + }, + { + "topicFilter": "custom/sensors/+", + "subscriptionQos": 1, + "converter": { + "type": "custom", + "extension": "CustomMqttUplinkConverter", + "cached": true, + "extensionConfig": { + "temperature": 2, + "humidity": 2, + "batteryLevel": 1 + } + } + } ], - "timeseries": [ - { - "type": "string", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "double", - "key": "humidity", - "value": "${hum}" - }, - { - "type": "string", - "key": "combine", - "value": "${hum}:${temp}" - } - ] - } + "requestsMapping": { + "connectRequests": [ + { + "topicFilter": "sensor/connect", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "Thermometer" + } + }, + { + "topicFilter": "sensor/+/connect", + "deviceInfo": { + "deviceNameExpressionSource": "topic", + "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "Thermometer" + } + } + ], + "disconnectRequests": [ + { + "topicFilter": "sensor/disconnect", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}" + } + }, + { + "topicFilter": "sensor/+/disconnect", + "deviceInfo": { + "deviceNameExpressionSource": "topic", + "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)" + } + } + ], + "attributeRequests": [ + { + "retain": false, + "topicFilter": "v1/devices/me/attributes/request", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}" + }, + "attributeNameExpressionSource": "message", + "attributeNameExpression": "${versionAttribute}, ${pduAttribute}", + "topicExpression": "devices/${deviceName}/attrs", + "valueExpression": "${attributeKey}: ${attributeValue}" + } + ], + "attributeUpdates": [ + { + "retain": true, + "deviceNameFilter": ".*", + "attributeFilter": "firmwareVersion", + "topicExpression": "sensor/${deviceName}/${attributeKey}", + "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "type": "twoWay", + "deviceNameFilter": ".*", + "methodFilter": "echo", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", + "responseTopicQoS": 1, + "responseTimeout": 10000, + "valueExpression": "${params}" + }, + { + "type": "oneWay", + "deviceNameFilter": ".*", + "methodFilter": "no-reply", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "valueExpression": "${params}" + } + ] + } }, - { - "topicFilter": "sensor/+/data", - "subscriptionQos": 1, - "converter": { - "type": "json", - "deviceInfo": { - "deviceNameExpressionSource": "topic", - "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/data)", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "Thermometer" + "legacy": { + "broker": { + "name": "Default Local Broker", + "host": "127.0.0.1", + "port": 1883, + "clientId": "ThingsBoard_gateway", + "version": 5, + "maxMessageNumberPerWorker": 10, + "maxNumberOfWorkers": 100, + "sendDataOnlyOnChange": false, + "security": { + "type": "basic", + "username": "user", + "password": "password" + } }, - "sendDataOnlyOnChange": false, - "timeout": 60000, - "attributes": [ - { - "type": "string", - "key": "model", - "value": "${sensorModel}" - } + "mapping": [ + { + "topicFilter": "sensor/data", + "converter": { + "type": "json", + "deviceNameJsonExpression": "${serialNumber}", + "deviceTypeJsonExpression": "${sensorType}", + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + }, + { + "type": "string", + "key": "${sensorModel}", + "value": "on" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + }, + { + "type": "string", + "key": "combine", + "value": "${hum}:${temp}" + } + ] + } + }, + { + "topicFilter": "sensor/+/data", + "converter": { + "type": "json", + "deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/data)", + "deviceTypeTopicExpression": "Thermometer", + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + } + ] + } + }, + { + "topicFilter": "sensor/raw_data", + "converter": { + "type": "bytes", + "deviceNameExpression": "[0:4]", + "deviceTypeExpression": "default", + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "raw", + "key": "rawData", + "value": "[:]" + } + ], + "timeseries": [ + { + "type": "raw", + "key": "temp", + "value": "[4:]" + } + ] + } + }, + { + "topicFilter": "custom/sensors/+", + "converter": { + "type": "custom", + "extension": "CustomMqttUplinkConverter", + "cached": true, + "extension-config": { + "temperatureBytes": 2, + "humidityBytes": 2, + "batteryLevelBytes": 1 + } + } + } ], - "timeseries": [ - { - "type": "double", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "string", - "key": "humidity", - "value": "${hum}" - } - ] - } - }, - { - "topicFilter": "sensor/raw_data", - "subscriptionQos": 1, - "converter": { - "type": "bytes", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "[0:4]", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "default" - }, - "sendDataOnlyOnChange": false, - "timeout": 60000, - "attributes": [ - { - "type": "raw", - "key": "rawData", - "value": "[:]" - } + "connectRequests": [ + { + "topicFilter": "sensor/connect", + "deviceNameJsonExpression": "${serialNumber}" + }, + { + "topicFilter": "sensor/+/connect", + "deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/connect)" + } + ], + "disconnectRequests": [ + { + "topicFilter": "sensor/disconnect", + "deviceNameJsonExpression": "${serialNumber}" + }, + { + "topicFilter": "sensor/+/disconnect", + "deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/disconnect)" + } + ], + "attributeRequests": [ + { + "retain": false, + "topicFilter": "v1/devices/me/attributes/request", + "deviceNameJsonExpression": "${serialNumber}", + "attributeNameJsonExpression": "${versionAttribute}, ${pduAttribute}", + "topicExpression": "devices/${deviceName}/attrs", + "valueExpression": "${attributeKey}: ${attributeValue}" + } ], - "timeseries": [ - { - "type": "raw", - "key": "temp", - "value": "[4:]" - } + "attributeUpdates": [ + { + "retain": true, + "deviceNameFilter": ".*", + "attributeFilter": "firmwareVersion", + "topicExpression": "sensor/${deviceName}/${attributeKey}", + "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "deviceNameFilter": ".*", + "methodFilter": "echo", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", + "responseTimeout": 10000, + "valueExpression": "${params}" + }, + { + "deviceNameFilter": ".*", + "methodFilter": "no-reply", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "valueExpression": "${params}" + } ] - } - }, - { - "topicFilter": "custom/sensors/+", - "subscriptionQos": 1, - "converter": { - "type": "custom", - "extension": "CustomMqttUplinkConverter", - "cached": true, - "extensionConfig": { - "temperature": 2, - "humidity": 2, - "batteryLevel": 1 - } - } - } - ], - "requestsMapping": { - "connectRequests": [ - { - "topicFilter": "sensor/connect", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "Thermometer" - } - }, - { - "topicFilter": "sensor/+/connect", - "deviceInfo": { - "deviceNameExpressionSource": "topic", - "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "Thermometer" - } - } - ], - "disconnectRequests": [ - { - "topicFilter": "sensor/disconnect", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}" - } - }, - { - "topicFilter": "sensor/+/disconnect", - "deviceInfo": { - "deviceNameExpressionSource": "topic", - "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)" - } - } - ], - "attributeRequests": [ - { - "retain": false, - "topicFilter": "v1/devices/me/attributes/request", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}" - }, - "attributeNameExpressionSource": "message", - "attributeNameExpression": "${versionAttribute}, ${pduAttribute}", - "topicExpression": "devices/${deviceName}/attrs", - "valueExpression": "${attributeKey}: ${attributeValue}" - } - ], - "attributeUpdates": [ - { - "retain": true, - "deviceNameFilter": ".*", - "attributeFilter": "firmwareVersion", - "topicExpression": "sensor/${deviceName}/${attributeKey}", - "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" - } - ], - "serverSideRpc": [ - { - "type": "twoWay", - "deviceNameFilter": ".*", - "methodFilter": "echo", - "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", - "responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", - "responseTopicQoS": 1, - "responseTimeout": 10000, - "valueExpression": "${params}" - }, - { - "type": "oneWay", - "deviceNameFilter": ".*", - "methodFilter": "no-reply", - "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", - "valueExpression": "${params}" } - ] - } } From 33c8cc405058f1a9f42cb90adb7a8ef20d8a4829 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 28 Aug 2024 19:27:44 +0300 Subject: [PATCH 28/84] refactoring --- .../mqtt/basic-config/mqtt-legacy-basic-config.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts index f499199739..92e4e5b4b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts @@ -75,7 +75,7 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo @Input() generalTabContent: TemplateRef; - writeValue(basicConfig: any): void { + writeValue(basicConfig: MQTTLegacyBasicConfig): void { const { broker, mapping = [], From a6f79b51def3379519d38a5687fe8e44daef8ee3 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 29 Aug 2024 11:47:57 +0300 Subject: [PATCH 29/84] refactoring --- .../widget/lib/gateway/gateway-connectors.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 0da917312b..c2ee0c8d62 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -763,6 +763,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie case ConnectorType.OPCUA: case ConnectorType.MODBUS: this.connectorForm.get('mode').setValue(connector.mode || ConnectorConfigurationModes.BASIC, {emitEvent: false}); + this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false}); setTimeout(() => { this.connectorForm.patchValue(connector, {emitEvent: false}); this.connectorForm.markAsPristine(); From 78b7ef676935cca94d14751ae35a04b1e9e6ae78 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 2 Sep 2024 16:36:06 +0300 Subject: [PATCH 30/84] Clear styles and delete unused --- .../timewindow-config-dialog.component.html | 2 +- .../time/timewindow-panel.component.html | 5 +- .../time/timewindow-panel.component.scss | 2 +- .../time/timezone-panel.component.html | 23 ++++++-- .../time/timezone-panel.component.scss | 18 ------- .../components/time/timezone.component.html | 10 +--- .../components/time/timezone.component.ts | 54 +------------------ .../shared/models/widget-settings.models.ts | 13 ----- 8 files changed, 26 insertions(+), 101 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index 74a0204c26..d7cedd3b71 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -25,7 +25,7 @@ close -
+
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 45a735187c..adc1b9b8cd 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + @@ -121,8 +121,7 @@
- - +
- {{ computedTimezoneStyle.icon }}
- {{timezoneInfo}} + {{displayValue()}}
- {{ computedTimezoneStyle.icon }}
diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.ts b/ui-ngx/src/app/shared/components/time/timezone.component.ts index 8f0f2eee40..f662b4c452 100644 --- a/ui-ngx/src/app/shared/components/time/timezone.component.ts +++ b/ui-ngx/src/app/shared/components/time/timezone.component.ts @@ -20,10 +20,8 @@ import { forwardRef, HostBinding, Input, - OnChanges, OnInit, Renderer2, - SimpleChanges, ViewContainerRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -31,13 +29,6 @@ import { TranslateService } from '@ngx-translate/core'; import { TooltipPosition } from '@angular/material/tooltip'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { - ComponentStyle, - defaultTimezoneStyle, - iconStyle, - textStyle, - TimezoneStyle -} from '@shared/models/widget-settings.models'; import { TimezonePanelComponent, TimezoneSelectionResult } from '@shared/components/time/timezone-panel.component'; import { TbPopoverService } from '@shared/components/popover.service'; import { getTimezoneInfo, TimezoneInfo } from '@shared/models/time/time.models'; @@ -56,7 +47,7 @@ import { TimeService } from '@core/services/time.service'; } ] }) -export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChanges { +export class TimezoneComponent implements ControlValueAccessor, OnInit { @HostBinding('class.no-margin') @Input() @@ -91,24 +82,9 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange @coerceBoolean() hideLabel = false; - isEditValue = false; - - @Input() - set isEdit(val) { - this.isEditValue = coerceBooleanProperty(val); - this.timezoneDisabled = this.isTimezoneDisabled(); - } - - get isEdit() { - return this.isEditValue; - } - @Input() tooltipPosition: TooltipPosition = 'above'; - @Input() - timezoneStyle: TimezoneStyle; - @Input() @coerceBoolean() disabled: boolean; @@ -155,10 +131,6 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange timezoneDisabled: boolean; - computedTimezoneStyle: TimezoneStyle; - timezoneComponentStyle: ComponentStyle; - timezoneIconStyle: ComponentStyle; - private propagateChange = (_: any) => {}; constructor(private translate: TranslateService, @@ -170,19 +142,6 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange } ngOnInit() { - this.updateTimezoneStyle(); - } - - ngOnChanges(changes: SimpleChanges): void { - for (const propName of Object.keys(changes)) { - const change = changes[propName]; - if (!change.firstChange && change.currentValue !== change.previousValue) { - if (propName === 'timezoneStyle') { - this.updateTimezoneStyle(); - this.updateDisplayValue(); - } - } - } } toggleTimezone($event: Event) { @@ -221,17 +180,6 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit, OnChange this.cd.detectChanges(); } - private updateTimezoneStyle() { - if (!this.asButton) { - this.computedTimezoneStyle = {...defaultTimezoneStyle, ...(this.timezoneStyle || {})}; - this.timezoneComponentStyle = textStyle(this.computedTimezoneStyle.font); - if (this.computedTimezoneStyle.color) { - this.timezoneComponentStyle.color = this.computedTimezoneStyle.color; - } - this.timezoneIconStyle = this.computedTimezoneStyle.iconSize ? iconStyle(this.computedTimezoneStyle.iconSize) : {}; - } - } - registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index dbe75a5b36..5124458ff0 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -262,19 +262,6 @@ export const defaultTimewindowStyle: TimewindowStyle = { displayTypePrefix: true }; -export interface TimezoneStyle { - showIcon: boolean; - icon?: string; - iconSize?: string; - iconPosition?: 'left' | 'right'; - font?: Font; - color?: string; -} - -export const defaultTimezoneStyle: TimezoneStyle = { - showIcon: false -}; - export const constantColor = (color: string): ColorSettings => ({ type: ColorType.constant, color, From 1e63ca3d6a19cda9ee85fe458c7dee5edf48fa10 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 4 Sep 2024 18:50:12 +0300 Subject: [PATCH 31/84] UI: fix cancelling timewindow config change --- .../app/shared/components/time/timewindow-panel.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index c5bc43c91c..283d655a49 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -32,7 +32,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; -import { isDefined } from '@core/utils'; +import { deepClone, isDefined } from '@core/utils'; import { OverlayRef } from '@angular/cdk/overlay'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { TranslateService } from '@ngx-translate/core'; @@ -489,7 +489,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { data: { quickIntervalOnly: this.quickIntervalOnly, aggregation: this.aggregation, - timewindow: this.timewindow + timewindow: deepClone(this.timewindow) } }).afterClosed() .subscribe((res) => { From 460c80a2522a8b7bb0c7a12160b22c64aa548497 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 5 Sep 2024 18:30:43 +0300 Subject: [PATCH 32/84] UI: remove mat-tab-group from timewindow --- .../timewindow-config-dialog.component.html | 220 +++++++++--------- .../timewindow-config-dialog.component.scss | 18 +- .../timewindow-config-dialog.component.ts | 67 ++++-- .../time/timewindow-panel.component.html | 190 ++++++++------- .../time/timewindow-panel.component.scss | 40 ++-- .../time/timewindow-panel.component.ts | 42 +++- .../src/app/shared/models/overlay.models.ts | 2 +- .../src/app/shared/models/time/time.models.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 5 +- 9 files changed, 314 insertions(+), 272 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index d7cedd3b71..7be74360b7 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -25,125 +25,118 @@ close -
- - -
-
-
{{ 'timewindow.time-range' | translate }}
- - {{ 'timewindow.hide-time-range' | translate }} - + +
+ + +
+
+ +
+
{{ 'timewindow.timewindow' | translate }}
+ + {{ 'timewindow.hide-timewindow-section' | translate }} + + + + - - - - - - {{ 'timewindow.hide-last-interval' | translate }} - - - {{ 'timewindow.disable-custom-interval' | translate }} - - + + {{ 'timewindow.hide-last-interval' | translate }} + + + {{ 'timewindow.disable-custom-interval' | translate }} + + - - - - - - {{ 'timewindow.hide-relative-interval' | translate }} - - - - -
- - - + -
- - -
-
-
{{ 'timewindow.time-range' | translate }}
- - {{ 'timewindow.hide-time-range' | translate }} + + + + {{ 'timewindow.hide-relative-interval' | translate }} + + + +
+ + + +
+
{{ 'timewindow.timewindow' | translate }}
+ + {{ 'timewindow.hide-timewindow-section' | translate }} + + + + - - - - - - {{ 'timewindow.hide-last-interval' | translate }} - - - {{ 'timewindow.disable-custom-interval' | translate }} - - + + {{ 'timewindow.hide-last-interval' | translate }} + + + {{ 'timewindow.disable-custom-interval' | translate }} + + - - - - - - {{ 'timewindow.hide-fixed-interval' | translate }} - - + + {{ 'timewindow.hide-fixed-interval' | translate }} + + - - - - - - {{ 'timewindow.hide-relative-interval' | translate }} - - - - -
- + - + + + + {{ 'timewindow.hide-relative-interval' | translate }} + + + -
-
- - -
+
+ + +
{{ 'aggregation.aggregation' | translate }}
@@ -216,9 +209,8 @@
-
- - + +
{{ 'timezone.timezone' | translate }}
@@ -231,7 +223,7 @@ displayLabel="false">
-
+
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss index 9245856bb3..705aa17152 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss @@ -22,7 +22,7 @@ max-width: 100%; background-color: #fff; - .timewindow-settings-form { + .tb-timewindow-form { width: 600px; .tb-flex { @@ -33,6 +33,10 @@ overflow: hidden; padding: 0; } + + &-content { + overflow: auto; + } } .limit-slider-container { @@ -57,15 +61,3 @@ } } - -:host ::ng-deep { - .mat-mdc-tab-group { - min-height: 100%; - height: 100%; - } - - .mat-mdc-tab-body-content { - padding: 12px 16px; - } - -} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index e7de899e48..2d02dea5b5 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { aggregationTranslations, AggregationType, @@ -36,6 +36,8 @@ import { isDefined, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { TranslateService } from '@ngx-translate/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; export interface TimewindowConfigDialogData { quickIntervalOnly: boolean; @@ -48,7 +50,7 @@ export interface TimewindowConfigDialogData { templateUrl: './timewindow-config-dialog.component.html', styleUrls: ['./timewindow-config-dialog.component.scss'] }) -export class TimewindowConfigDialogComponent extends PageComponent implements OnInit { +export class TimewindowConfigDialogComponent extends PageComponent implements OnInit, OnDestroy { quickIntervalOnly = false; @@ -72,6 +74,17 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On result: Timewindow; + timewindowTypeOptions: ToggleHeaderOption[] = [ + { + name: this.translate.instant('timewindow.realtime'), + value: this.timewindowTypes.REALTIME + }, + { + name: this.translate.instant('timewindow.history'), + value: this.timewindowTypes.HISTORY + } + ]; + realtimeTimewindowOptions: ToggleHeaderOption[] = [ { name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.INTERVAL)), @@ -96,6 +109,8 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On realtimeTypeSelectionAvailable: boolean; + private destroy$ = new Subject(); + constructor(@Inject(MAT_DIALOG_DATA) public data: TimewindowConfigDialogData, public dialogRef: MatDialogRef, protected store: Store, @@ -123,6 +138,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On const aggregation = this.timewindow.aggregation; this.timewindowForm = this.fb.group({ + selectedTab: [isDefined(this.timewindow.selectedTab) ? this.timewindow.selectedTab : TimewindowType.REALTIME], realtime: this.fb.group({ realtimeType: [ isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL ], timewindowMs: [ isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null ], @@ -187,10 +203,19 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On }); this.updateValidators(this.timewindowForm.get('aggregation.type').value); - this.timewindowForm.get('aggregation.type').valueChanges.subscribe((aggregationType: AggregationType) => { + this.timewindowForm.get('aggregation.type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((aggregationType: AggregationType) => { this.updateValidators(aggregationType); }); - this.timewindowForm.get('realtime.hideInterval').valueChanges.subscribe((value: boolean) => { + this.timewindowForm.get('selectedTab').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((selectedTab: TimewindowType) => { + this.onTimewindowTypeChange(selectedTab); + }); + this.timewindowForm.get('realtime.hideInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: boolean) => { if (value) { this.timewindowForm.get('realtime.hideLastInterval').disable(); this.timewindowForm.get('realtime.hideQuickInterval').disable(); @@ -199,18 +224,24 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('realtime.hideQuickInterval').enable(); } }); - this.timewindowForm.get('realtime.hideLastInterval').valueChanges.subscribe((hideLastInterval: boolean) => { + this.timewindowForm.get('realtime.hideLastInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((hideLastInterval: boolean) => { if (hideLastInterval && !this.timewindowForm.get('realtime.hideQuickInterval').value) { this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); } }); - this.timewindowForm.get('realtime.hideQuickInterval').valueChanges.subscribe((hideQuickInterval: boolean) => { + this.timewindowForm.get('realtime.hideQuickInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((hideQuickInterval: boolean) => { if (hideQuickInterval && !this.timewindowForm.get('realtime.hideLastInterval').value) { this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL); } }); - this.timewindowForm.get('history.hideInterval').valueChanges.subscribe((value: boolean) => { + this.timewindowForm.get('history.hideInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: boolean) => { if (value) { this.timewindowForm.get('history.hideLastInterval').disable(); this.timewindowForm.get('history.hideQuickInterval').disable(); @@ -221,7 +252,9 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('history.hideFixedInterval').enable(); } }); - this.timewindowForm.get('history.hideLastInterval').valueChanges.subscribe((hideLastInterval: boolean) => { + this.timewindowForm.get('history.hideLastInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((hideLastInterval: boolean) => { if (hideLastInterval) { if (!this.timewindowForm.get('history.hideFixedInterval').value) { this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.FIXED); @@ -230,7 +263,9 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } } }); - this.timewindowForm.get('history.hideFixedInterval').valueChanges.subscribe((hideFixedInterval: boolean) => { + this.timewindowForm.get('history.hideFixedInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((hideFixedInterval: boolean) => { if (hideFixedInterval) { if (!this.timewindowForm.get('history.hideLastInterval').value) { this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.LAST_INTERVAL); @@ -239,7 +274,9 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } } }); - this.timewindowForm.get('history.hideQuickInterval').valueChanges.subscribe((hideQuickInterval: boolean) => { + this.timewindowForm.get('history.hideQuickInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((hideQuickInterval: boolean) => { if (hideQuickInterval) { if (!this.timewindowForm.get('history.hideLastInterval').value) { this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.LAST_INTERVAL); @@ -250,6 +287,11 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + private checkLimit(limit?: number): number { if (!limit || limit < this.minDatapointsLimit()) { return this.minDatapointsLimit(); @@ -269,10 +311,9 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('aggregation.limit').updateValueAndValidity({emitEvent: false}); } - onTimewindowTypeChange() { - this.timewindowForm.markAsDirty(); + private onTimewindowTypeChange(selectedTab: TimewindowType) { const timewindowFormValue = this.timewindowForm.getRawValue(); - if (this.timewindow.selectedTab === TimewindowType.REALTIME) { + if (selectedTab === TimewindowType.REALTIME) { if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { this.timewindowForm.get('realtime').patchValue({ realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index adc1b9b8cd..0db60d5db1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -15,122 +15,117 @@ limitations under the License. --> -
- - -
-
-
-
-
{{ 'timewindow.time-range' | translate }}
- - - - -
+ +
+ + + +
- +
+ +
+
+
+
{{ 'timewindow.timewindow' | translate }}
+ + +
- - - + +
+ + + + - - + + - - + - - + -
- -
- - -
-
-
-
-
{{ 'timewindow.time-range' | translate }}
- - - - -
+
+ - + +
+
+
+
{{ 'timewindow.timewindow' | translate }}
+ + +
- + +
+ + - - + - - + + - - + - - + + - - + - - + -
- -
-
- - - - - - - -
+
+ + +
{{ 'aggregation.aggregation' | translate }}
@@ -189,8 +184,9 @@
-
- + +
+
{{ 'timezone.timezone' | translate }}
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index 690ed78cc9..f1d978a8ca 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -23,17 +23,27 @@ max-width: 100%; background-color: #fff; - .tb-form-panel { - padding: 12px; - gap: 12px; - } - - .tb-timewindow-panel-form { + .tb-timewindow-form { + display: flex; + flex-direction: column; overflow: hidden; .tb-flex { gap: 16px; } + + &-header { + flex-direction: row; + align-items: center; + } + + &-type-options { + flex: 1; + } + + &-content { + overflow: auto; + } } .limit-slider-container { @@ -58,21 +68,3 @@ } } - -:host ::ng-deep { - .mat-mdc-tab-group:not(.tb-headless) { - height: 100%; - } - - .mat-mdc-tab-body-content { - padding: 12px 16px; - } - - .mat-mdc-tab-group>.mat-mdc-tab-header .mat-mdc-tab.timewindow-config-btn { - flex-grow: unset; - min-width: 0; - padding: 0 8px; - color: unset; - opacity: unset; - } -} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 283d655a49..dc1e2663dc 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core'; +import { Component, Inject, InjectionToken, OnDestroy, OnInit, ViewContainerRef } from '@angular/core'; import { aggregationTranslations, AggregationType, @@ -41,6 +41,8 @@ import { TimewindowConfigDialogComponent, TimewindowConfigDialogData } from '@shared/components/time/timewindow-config-dialog.component'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; export interface TimewindowPanelData { historyOnly: boolean; @@ -59,7 +61,7 @@ export const TIMEWINDOW_PANEL_DATA = new InjectionToken('TimewindowPanelDat templateUrl: './timewindow-panel.component.html', styleUrls: ['./timewindow-panel.component.scss'] }) -export class TimewindowPanelComponent extends PageComponent implements OnInit { +export class TimewindowPanelComponent extends PageComponent implements OnInit, OnDestroy { historyOnly = false; @@ -91,6 +93,11 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { result: Timewindow; + timewindowTypeOptions: ToggleHeaderOption[] = [{ + name: this.translate.instant('timewindow.history'), + value: this.timewindowTypes.HISTORY + }]; + realtimeTimewindowOptions: ToggleHeaderOption[] = []; historyTimewindowOptions: ToggleHeaderOption[] = []; @@ -101,6 +108,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { historyIntervalSelectionAvailable: boolean; aggregationOptionsAvailable: boolean; + private destroy$ = new Subject(); + constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, public overlayRef: OverlayRef, protected store: Store, @@ -118,6 +127,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timezone = data.timezone; this.isEdit = data.isEdit; + if (!this.historyOnly) { + this.timewindowTypeOptions.unshift({ + name: this.translate.instant('timewindow.realtime'), + value: this.timewindowTypes.REALTIME + }); + } + if ((this.isEdit || !this.timewindow.realtime.hideLastInterval) && !this.quickIntervalOnly) { this.realtimeTimewindowOptions.push({ name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.LAST_INTERVAL)), @@ -212,6 +228,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { } this.timewindowForm = this.fb.group({ + selectedTab: [isDefined(this.timewindow.selectedTab) ? this.timewindow.selectedTab : TimewindowType.REALTIME], realtime: this.fb.group({ realtimeType: [{ value: isDefined(realtime?.realtimeType) ? realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, @@ -268,9 +285,22 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { }] }); this.updateValidators(this.timewindowForm.get('aggregation.type').value); - this.timewindowForm.get('aggregation.type').valueChanges.subscribe((aggregationType: AggregationType) => { + this.timewindowForm.get('aggregation.type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((aggregationType: AggregationType) => { this.updateValidators(aggregationType); }); + + this.timewindowForm.get('selectedTab').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((selectedTab: TimewindowType) => { + this.onTimewindowTypeChange(selectedTab); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } private checkLimit(limit?: number): number { @@ -292,10 +322,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindowForm.get('aggregation.limit').updateValueAndValidity({emitEvent: false}); } - onTimewindowTypeChange() { - this.timewindowForm.markAsDirty(); + private onTimewindowTypeChange(selectedTab: TimewindowType) { const timewindowFormValue = this.timewindowForm.getRawValue(); - if (this.timewindow.selectedTab === TimewindowType.REALTIME) { + if (selectedTab === TimewindowType.REALTIME) { if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { this.timewindowForm.get('realtime').patchValue({ realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? @@ -332,6 +361,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { private prepareTimewindowConfig() { const timewindowFormValue = this.timewindowForm.getRawValue(); + this.timewindow.selectedTab = timewindowFormValue.selectedTab; this.timewindow.realtime = {...this.timewindow.realtime, ...{ realtimeType: timewindowFormValue.realtime.realtimeType, timewindowMs: timewindowFormValue.realtime.timewindowMs, diff --git a/ui-ngx/src/app/shared/models/overlay.models.ts b/ui-ngx/src/app/shared/models/overlay.models.ts index c852807e76..46a2f6f5bc 100644 --- a/ui-ngx/src/app/shared/models/overlay.models.ts +++ b/ui-ngx/src/app/shared/models/overlay.models.ts @@ -41,4 +41,4 @@ export const POSITION_MAP: { [key: string]: ConnectionPositionPair } = { }; export const DEFAULT_OVERLAY_POSITIONS = [POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft, - POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right]; + POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right, POSITION_MAP.bottom]; diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 71ab46bc43..603848f8b1 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -59,7 +59,7 @@ export const realtimeWindowTypeTranslations = new Map([ [HistoryWindowType.LAST_INTERVAL, 'timewindow.last'], - [HistoryWindowType.FIXED, 'timewindow.fixed'], + [HistoryWindowType.FIXED, 'timewindow.range'], [HistoryWindowType.INTERVAL, 'timewindow.relative'], [HistoryWindowType.FOR_ALL_TIME, 'timewindow.for-all-time'] ]); 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 6a81e48f4d..100ba2b0d7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5153,10 +5153,9 @@ "color": "Color", "displayTypePrefix": "Display Realtime/History prefix", "preview": "Preview", - "time-range": "Time range", "relative": "Relative", - "fixed": "Fixed", - "hide-time-range": "Hide time range from end-users", + "range": "Range", + "hide-timewindow-section": "Hide timewindow section from end-users", "hide-last-interval": "Hide last interval from end-users", "hide-relative-interval": "Hide relative interval from end-users", "hide-fixed-interval": "Hide fixed interval from end-users", From ac27bc65926a3b121ebcb2d35dd85e9dab812fbc Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 6 Sep 2024 14:57:41 +0300 Subject: [PATCH 33/84] Optimize stiles --- .../timewindow-config-dialog.component.scss | 37 ------ .../timewindow-config-dialog.component.ts | 2 +- .../components/time/timewindow-form.scss | 56 +++++++++ .../time/timewindow-panel.component.html | 110 +++++++++--------- .../time/timewindow-panel.component.scss | 36 ------ .../time/timewindow-panel.component.ts | 2 +- 6 files changed, 110 insertions(+), 133 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-form.scss diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss index 705aa17152..ccf1814728 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss @@ -13,51 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "../../../../scss/constants"; :host { - display: flex; - flex-direction: column; - max-height: 100%; - max-width: 100%; - background-color: #fff; - .tb-timewindow-form { width: 600px; - .tb-flex { - gap: 16px; - } - &.mat-mdc-dialog-content { overflow: hidden; padding: 0; } - - &-content { - overflow: auto; - } } - - .limit-slider-container { - .limit-slider-value { - margin-left: 16px; - min-width: 25px; - max-width: 100px; - } - mat-form-field input[type=number] { - text-align: center; - } - } - - @media #{$mat-gt-sm} { - .limit-slider-container { - > label { - margin-right: 16px; - width: min-content; - max-width: 40%; - } - } - } - } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 2d02dea5b5..58733ce498 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -48,7 +48,7 @@ export interface TimewindowConfigDialogData { @Component({ selector: 'tb-timewindow-config-dialog', templateUrl: './timewindow-config-dialog.component.html', - styleUrls: ['./timewindow-config-dialog.component.scss'] + styleUrls: ['./timewindow-config-dialog.component.scss', './timewindow-form.scss'] }) export class TimewindowConfigDialogComponent extends PageComponent implements OnInit, OnDestroy { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss new file mode 100644 index 0000000000..e10a62b1f6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2024 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. + */ +@import "../../../../scss/constants"; + +:host { + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 100%; + background-color: #fff; + + .tb-timewindow-form { + .tb-flex { + gap: 16px; + } + + &-content { + overflow: auto; + } + } + + .limit-slider-container { + .limit-slider-value { + margin-left: 16px; + min-width: 25px; + max-width: 100px; + } + mat-form-field input[type=number] { + text-align: center; + } + } + + @media #{$mat-gt-sm} { + .limit-slider-container { + > label { + margin-right: 16px; + width: min-content; + max-width: 40%; + } + } + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 0db60d5db1..0a0ba06eb9 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -45,28 +45,26 @@
- - - - + + - - - - + +
@@ -89,38 +87,35 @@ - - - - - - - - - + + - - - - + + + + +
@@ -195,12 +190,11 @@
- - - - + + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index f1d978a8ca..3a5ac63b3b 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -13,25 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "../../../../scss/constants"; :host { - display: flex; - flex-direction: column; width: 500px; - max-height: 100%; - max-width: 100%; - background-color: #fff; .tb-timewindow-form { display: flex; flex-direction: column; overflow: hidden; - .tb-flex { - gap: 16px; - } - &-header { flex-direction: row; align-items: center; @@ -40,31 +30,5 @@ &-type-options { flex: 1; } - - &-content { - overflow: auto; - } - } - - .limit-slider-container { - .limit-slider-value { - margin-left: 16px; - min-width: 25px; - max-width: 100px; - } - mat-form-field input[type=number] { - text-align: center; - } - } - - @media #{$mat-gt-sm} { - .limit-slider-container { - > label { - margin-right: 16px; - width: min-content; - max-width: 40%; - } - } } - } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index dc1e2663dc..02874f3523 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -59,7 +59,7 @@ export const TIMEWINDOW_PANEL_DATA = new InjectionToken('TimewindowPanelDat @Component({ selector: 'tb-timewindow-panel', templateUrl: './timewindow-panel.component.html', - styleUrls: ['./timewindow-panel.component.scss'] + styleUrls: ['./timewindow-panel.component.scss', './timewindow-form.scss'] }) export class TimewindowPanelComponent extends PageComponent implements OnInit, OnDestroy { From 2bfb99edbba51625326307ec47bc7c412973a89c Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 6 Sep 2024 19:02:25 +0300 Subject: [PATCH 34/84] UI: change timewindow aggregation options style --- .../time/timeinterval.component.scss | 6 --- .../components/time/timewindow-form.scss | 1 + .../time/timewindow-panel.component.html | 39 +++++++++---------- ui-ngx/src/form.scss | 3 -- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss index c91f37bb2d..5115f555cc 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss @@ -16,12 +16,6 @@ @import '../../../../scss/constants'; :host { - min-width: 355px; - - .tb-form-panel { - gap: 12px; - } - .advanced-input { gap: 8px; } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index e10a62b1f6..6d934a9b11 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -33,6 +33,7 @@ } .limit-slider-container { + width: 100%; .limit-slider-value { margin-left: 16px; min-width: 25px; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 0a0ba06eb9..f9dbe69491 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -122,8 +122,8 @@ -
-
{{ 'aggregation.aggregation' | translate }}
+
+
{{ 'aggregation.aggregation' | translate }}
@@ -133,31 +133,28 @@
-
-
{{ 'aggregation.limit' | translate }}
-
-
- - - - - - -
+
{{ 'aggregation.limit' | translate }}
+
+ + + + + +
-
-
{{ 'aggregation.group-interval' | translate }}
+
{{ 'aggregation.group-interval' | translate }}
Date: Mon, 9 Sep 2024 15:30:57 +0300 Subject: [PATCH 35/84] changed approach to abstract class for mapping --- ...ay-connector-version-processor.abstract.ts | 36 ++++++++ .../mqtt-version-processor.abstract.ts | 76 +++++++++++++++++ .../mqtt-basic-config.abstract.ts | 13 ++- .../mqtt-basic-config.component.ts | 8 +- .../mqtt-legacy-basic-config.component.ts | 14 ++-- .../dialog/add-connector-dialog.component.ts | 11 ++- .../gateway/gateway-connectors.component.ts | 8 +- .../lib/gateway/gateway-widget.models.ts | 19 ++--- .../gateway-connector-version-mapping.util.ts | 46 ++-------- .../utils/mqtt-version-mapping.util.ts | 84 +++---------------- .../connector-default-configs/mqtt.json | 2 +- 11 files changed, 168 insertions(+), 149 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts new file mode 100644 index 0000000000..1a1c4f05bc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -0,0 +1,36 @@ +import { GatewayConnector } from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export abstract class GatewayConnectorVersionProcessor { + gatewayVersion: number; + configVersion: number; + + protected constructor(protected gatewayVersionStr: string, protected connector: GatewayConnector) { + this.gatewayVersion = this.parseVersion(this.gatewayVersionStr); + this.configVersion = this.parseVersion(connector.configVersion); + } + + getProcessedByVersion(): BasicConfig { + if (this.isVersionUpdateNeeded()) { + return this.configVersion > this.gatewayVersion + ? this.getDowngradedVersion() + : this.getUpgradedVersion(); + } + + return this.connector.configurationJson as unknown as BasicConfig; + } + + private isVersionUpdateNeeded(): boolean { + if (!this.gatewayVersion || !this.configVersion) { + return false; + } + + return this.configVersion !== this.gatewayVersion; + } + + private parseVersion(version: string): number { + return Number(version?.replace(/\./g, '')); + } + + abstract getDowngradedVersion(): BasicConfig; + abstract getUpgradedVersion(): BasicConfig; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts new file mode 100644 index 0000000000..2a7bea0e1b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -0,0 +1,76 @@ +import { + GatewayConnectorVersionProcessor +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract'; +import { + GatewayConnector, + MQTTBasicConfig, MQTTBasicConfig_v3_5_2, + MQTTLegacyBasicConfig, + RequestMappingData, + RequestType, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { isEqual } from '@core/utils'; +import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; + +export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { + private readonly mqttRequestTypeKeys = Object.values(RequestType); + + constructor( + protected gatewayVersionStr: string, + protected connector: GatewayConnector + ) { + super(gatewayVersionStr, connector); + } + getUpgradedVersion(): MQTTBasicConfig { + const { + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + } = this.connector.configurationJson as MQTTLegacyBasicConfig; + let configurationJson = { + ...this.connector.configurationJson, + requestsMapping: MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + }), + mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion((this.connector.configurationJson as MQTTLegacyBasicConfig).mapping), + }; + + this.mqttRequestTypeKeys.forEach((key: RequestType) => { + const { [key]: removedKey, ...rest } = configurationJson as MQTTLegacyBasicConfig; + configurationJson = { ...rest } as any; + }); + + this.cleanUpConfigJson(configurationJson as MQTTBasicConfig_v3_5_2); + + return configurationJson as MQTTBasicConfig; + } + + getDowngradedVersion(): MQTTLegacyBasicConfig { + const { requestsMapping, mapping, ...restConfig } = this.connector.configurationJson as MQTTBasicConfig_v3_5_2; + + const updatedRequestsMapping = + MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record); + const updatedMapping = MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping); + + return { + ...restConfig, + ...updatedRequestsMapping, + mapping: updatedMapping, + }; + } + + private cleanUpConfigJson(configurationJson: MQTTBasicConfig_v3_5_2): void { + if (isEqual(configurationJson.requestsMapping, {})) { + delete configurationJson.requestsMapping; + } + + if (isEqual(configurationJson.mapping, [])) { + delete configurationJson.mapping; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts index 12529d942f..072d8ba834 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts @@ -17,24 +17,23 @@ import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { isObject } from 'lodash'; import { MappingType, MQTTBasicConfig, - MQTTLegacyBasicConfig, RequestMappingData, RequestMappingValue, RequestType } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { Directive, OnDestroy } from '@angular/core'; +import { isObject } from '@core/utils'; @Directive() -export abstract class AbstractMqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { +export abstract class AbstractMqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { basicFormGroup: FormGroup; mappingTypes = MappingType; private destroy$ = new Subject(); - private onChange: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void; + private onChange: (value: BasicConfig) => void; private onTouched: () => void; constructor(protected fb: FormBuilder) { @@ -58,7 +57,7 @@ export abstract class AbstractMqttBasicConfigComponent implements ControlValueAc this.destroy$.complete(); } - registerOnChange(fn: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void): void { + registerOnChange(fn: (value: BasicConfig) => void): void { this.onChange = fn; } @@ -102,6 +101,6 @@ export abstract class AbstractMqttBasicConfigComponent implements ControlValueAc }); } - abstract writeValue(basicConfig: MQTTBasicConfig | MQTTLegacyBasicConfig): void; - protected abstract getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig | MQTTLegacyBasicConfig; + abstract writeValue(basicConfig: BasicConfig): void; + protected abstract getMappedMQTTConfig(basicConfig: MQTTBasicConfig): BasicConfig; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts index c4ebadb03d..e2c9918179 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts @@ -17,7 +17,7 @@ import { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { - MQTTBasicConfig, + MQTTBasicConfig_v3_5_2, RequestMappingData, RequestMappingValue, RequestType @@ -68,12 +68,12 @@ import { MappingTableComponent, ], }) -export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent { +export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent { @Input() generalTabContent: TemplateRef; - writeValue(basicConfig: MQTTBasicConfig): void { + writeValue(basicConfig: MQTTBasicConfig_v3_5_2): void { const { broker, mapping = [], requestsMapping } = basicConfig; const editedBase = { workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { @@ -88,7 +88,7 @@ export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent { this.basicFormGroup.setValue(editedBase, {emitEvent: false}); } - protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { + protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { let { broker, workers, mapping, requestsMapping } = basicConfig || {}; if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts index 92e4e5b4b5..2ca5eba000 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts @@ -17,7 +17,7 @@ import { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { - MQTTBasicConfig, + MQTTBasicConfig_v3_5_2, MQTTLegacyBasicConfig, RequestMappingData, RequestMappingValue, @@ -70,7 +70,7 @@ import { MappingTableComponent, ], }) -export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigComponent { +export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigComponent { @Input() generalTabContent: TemplateRef; @@ -85,7 +85,7 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo attributeUpdates = [], serverSideRpc = [] } = basicConfig; - const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToNewestVersion({ + const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ connectRequests, disconnectRequests, attributeRequests, @@ -97,7 +97,7 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo maxNumberOfWorkers: broker.maxNumberOfWorkers, maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, } : {}, - mapping: MqttVersionMappingUtil.mapMappingToNewestVersion(mapping) || [], + mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion(mapping) || [], broker: broker || {}, requestsMapping: this.getRequestDataArray(updatedRequestMapping), }; @@ -105,7 +105,7 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo this.basicFormGroup.setValue(editedBase, {emitEvent: false}); } - protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTLegacyBasicConfig { + protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig_v3_5_2): MQTTLegacyBasicConfig { let { broker, workers, mapping, requestsMapping } = basicConfig || {}; if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { @@ -121,8 +121,8 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo return { broker, - mapping: MqttVersionMappingUtil.mapMappingToLegacyVersion(mapping), - ...(MqttVersionMappingUtil.mapRequestsToLegacyVersion(requestsMapping as Record)) + mapping: MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping), + ...(MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record)) }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts index d79a1d3fec..0168cf86af 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts @@ -97,8 +97,15 @@ export class AddConnectorDialogComponent this.submitted = true; const value = this.connectorForm.getRawValue(); if (value.useDefaults) { - this.getDefaultConfig(value.type).subscribe(({current, legacy, ...defaultConfig}: GatewayVersionedDefaultConfig) => { - value.configurationJson = (this.data.gatewayVersion ? current : legacy) ?? defaultConfig; + this.getDefaultConfig(value.type).subscribe((defaultConfig: GatewayVersionedDefaultConfig) => { + const gatewayVersion = this.data.gatewayVersion; + if (gatewayVersion) { + value.configVersion = gatewayVersion; + } + value.configurationJson = (gatewayVersion + ? defaultConfig[this.data.gatewayVersion] + : defaultConfig.legacy) + ?? defaultConfig; if (this.connectorForm.valid) { this.dialogRef.close(value); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index d7d412f6da..96e15a110f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -65,7 +65,9 @@ import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway import { debounceTime, filter, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; -import { GatewayConnectorVersionMappingUtil } from './utils/gateway-connector-version-mapping.util'; +import { + GatewayConnectorVersionMappingUtil +} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util'; export class ForceErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null): boolean { @@ -247,7 +249,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie value.ts = Date.now(); - return GatewayConnectorVersionMappingUtil.getMappedByVersion(value, this.gatewayVersion); + return value; } private updateData(reload: boolean = false): void { @@ -741,7 +743,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie ...connector, }; - connectorState.basicConfig = connectorState.configurationJson; + connectorState.basicConfig = GatewayConnectorVersionMappingUtil.getConfig(connectorState, this.gatewayVersion); this.initialConnector = connectorState; this.updateConnector(connectorState); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 4770eeea23..38040f15d3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -124,21 +124,14 @@ export interface GatewayConnectorBase { configVersion?: string; } -export type GatewayConnector = CurrentGatewayConnector | LegacyGatewayConnector; - -export interface CurrentGatewayConnector extends GatewayConnectorBase { +export interface GatewayConnector extends GatewayConnectorBase { configurationJson: ConnectorBaseConfig; basicConfig?: ConnectorBaseConfig; } -export interface LegacyGatewayConnector extends GatewayConnectorBase { - configurationJson: ConnectorLegacyBaseConfig; - basicConfig?: ConnectorLegacyBaseConfig; -} - export interface GatewayVersionedDefaultConfig { - legacy: LegacyGatewayConnector; - current: GatewayConnector; + legacy: GatewayConnector; + '3.5.1': GatewayConnector; } export interface DataMapping { @@ -201,8 +194,6 @@ export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingF export type ConnectorBaseConfig = ConnectorBaseInfo | MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig; -export type ConnectorLegacyBaseConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig; - export interface ConnectorBaseInfo { name: string; id: string; @@ -210,7 +201,9 @@ export interface ConnectorBaseInfo { logLevel: GatewayLogLevel; } -export interface MQTTBasicConfig { +export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig; + +export interface MQTTBasicConfig_v3_5_2 { mapping: ConverterConnectorMapping[]; requestsMapping: Record | RequestMappingData[] | RequestMappingValue[]; broker: BrokerConfig; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts index 6a48b5e64b..ab9e393fee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts @@ -15,52 +15,20 @@ /// import { + ConnectorBaseConfig, ConnectorType, GatewayConnector, - LegacyGatewayConnector, } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { MqttVersionMappingUtil } from './mqtt-version-mapping.util'; +import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract'; -export class GatewayConnectorVersionMappingUtil { +export abstract class GatewayConnectorVersionMappingUtil { - static getMappedByVersion(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { - switch (connector.type) { + static getConfig(connector: GatewayConnector, gatewayVersion: string): ConnectorBaseConfig { + switch(connector.type) { case ConnectorType.MQTT: - return this.getMappedMQTTByVersion(connector, gatewayVersion); + return new MqttVersionProcessor(gatewayVersion, connector).getProcessedByVersion(); default: - return connector; + return connector.configurationJson as ConnectorBaseConfig; } } - - private static getMappedMQTTByVersion( - connector: GatewayConnector | LegacyGatewayConnector, - gatewayVersion: string - ): GatewayConnector | LegacyGatewayConnector { - if (this.isVersionUpdateNeeded(gatewayVersion, connector.configVersion)) { - return this.isGatewayOutdated(gatewayVersion, connector.configVersion) - ? MqttVersionMappingUtil.getLegacyVersion(connector) - : MqttVersionMappingUtil.getNewestVersion(connector); - } - return connector; - } - - private static isGatewayOutdated(gatewayVersion: string, configVersion: string): boolean { - if (!gatewayVersion || !configVersion) { - return false; - } - - return this.parseVersion(configVersion) > this.parseVersion(gatewayVersion); - } - - private static isVersionUpdateNeeded(configVersion: string, gatewayVersion: string): boolean { - if (!gatewayVersion || !configVersion) { - return false; - } - - return this.parseVersion(configVersion) !== this.parseVersion(gatewayVersion); - } - - private static parseVersion(version: string): number { - return Number(version?.replace(/\./g, '')); - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts index e32d020730..ffd517c3e9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts @@ -14,21 +14,16 @@ /// limitations under the License. /// -import { deleteNullProperties, isEqual } from '@core/utils'; +import { deleteNullProperties } from '@core/utils'; import { AttributeRequest, ConnectorDeviceInfo, Converter, ConverterConnectorMapping, ConvertorType, - CurrentGatewayConnector, - GatewayConnector, LegacyConverter, LegacyConverterConnectorMapping, - LegacyGatewayConnector, LegacyRequestMappingData, - MQTTBasicConfig, - MQTTLegacyBasicConfig, RequestMappingData, RequestType, ServerSideRpc, @@ -44,56 +39,9 @@ export class MqttVersionMappingUtil { static readonly mqttRequestMappingNewFields = ['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; - static getNewestVersion(mqttConnector: GatewayConnector): CurrentGatewayConnector { - const { - connectRequests, - disconnectRequests, - attributeRequests, - attributeUpdates, - serverSideRpc - } = mqttConnector.configurationJson as MQTTLegacyBasicConfig; - let configurationJson = { - ...mqttConnector.configurationJson, - requestsMapping: this.mapRequestsToNewestVersion({ - connectRequests, - disconnectRequests, - attributeRequests, - attributeUpdates, - serverSideRpc - }), - mapping: this.mapMappingToNewestVersion((mqttConnector.configurationJson as MQTTLegacyBasicConfig).mapping), - }; - - this.mqttRequestTypeKeys.forEach((key: RequestType) => { - const { [key]: removedKey, ...rest } = configurationJson as MQTTLegacyBasicConfig; - configurationJson = { ...rest } as any; - }); - - this.cleanUpConfigJson(configurationJson as MQTTBasicConfig); - - return { - ...mqttConnector, - configurationJson - } as CurrentGatewayConnector; - } - - static getLegacyVersion(mqttConnector: GatewayConnector): LegacyGatewayConnector { - const { requestsMapping, mapping, ...restConfig } = mqttConnector.configurationJson as MQTTBasicConfig; - - const updatedRequestsMapping = this.mapRequestsToLegacyVersion(requestsMapping as Record); - const updatedMapping = this.mapMappingToLegacyVersion(mapping); - - return { - ...mqttConnector, - configurationJson: { - ...restConfig, - ...updatedRequestsMapping, - mapping: updatedMapping, - }, - } as LegacyGatewayConnector; - } - - static mapMappingToNewestVersion(mapping: LegacyConverterConnectorMapping[] | ConverterConnectorMapping[]): ConverterConnectorMapping[] { + static mapMappingToUpgradedVersion( + mapping: LegacyConverterConnectorMapping[] | ConverterConnectorMapping[] + ): ConverterConnectorMapping[] { return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); @@ -109,7 +57,7 @@ export class MqttVersionMappingUtil { }); } - static mapRequestsToNewestVersion( + static mapRequestsToUpgradedVersion( requestMapping: Record ): Record { @@ -119,7 +67,7 @@ export class MqttVersionMappingUtil { } acc[key] = requestMapping[key].map(value => { - const newValue = this.mapRequestToNewest(value as LegacyRequestMappingData, key); + const newValue = this.mapRequestToUpgradedVersion(value as LegacyRequestMappingData, key); this.cleanUpOldFields(newValue as {}); @@ -130,7 +78,7 @@ export class MqttVersionMappingUtil { }, {}) as Record; } - static mapRequestsToLegacyVersion( + static mapRequestsToDowngradedVersion( requestsMapping: Record ): Record { return this.mqttRequestTypeKeys.reduce((acc, key) => { @@ -161,11 +109,11 @@ export class MqttVersionMappingUtil { }, {}) as Record; } - static mapMappingToLegacyVersion( + static mapMappingToDowngradedVersion( mapping: ConverterConnectorMapping[] ): LegacyConverterConnectorMapping[] { return mapping?.map((converterMapping: ConverterConnectorMapping) => { - const converter = this.mapConverterToLegacy(converterMapping.converter); + const converter = this.mapConverterToDowngradedVersion(converterMapping.converter); this.cleanUpNewFields(converter as {}); @@ -173,7 +121,7 @@ export class MqttVersionMappingUtil { }); } - private static mapConverterToLegacy(converter: Converter): LegacyConverter { + private static mapConverterToDowngradedVersion(converter: Converter): LegacyConverter { const { deviceInfo, ...restConverter } = converter; return converter.type !== ConvertorType.BYTES ? { @@ -216,16 +164,6 @@ export class MqttVersionMappingUtil { return SourceType.CONST; } - private static cleanUpConfigJson(configurationJson: MQTTBasicConfig): void { - if (isEqual(configurationJson.requestsMapping, {})) { - delete configurationJson.requestsMapping; - } - - if (isEqual(configurationJson.mapping, [])) { - delete configurationJson.mapping; - } - } - private static extractConverterDeviceInfo(converter: LegacyConverter): ConnectorDeviceInfo { const deviceNameExpression = converter.deviceNameExpression || converter.deviceNameJsonExpression @@ -250,7 +188,7 @@ export class MqttVersionMappingUtil { } : null; } - private static mapRequestToNewest(value, key: RequestType): RequestMappingData { + private static mapRequestToUpgradedVersion(value, key: RequestType): RequestMappingData { const deviceNameExpression = value.deviceNameJsonExpression || value.deviceNameTopicExpression || null; const deviceProfileExpression = value.deviceTypeTopicExpression || value.deviceTypeJsonExpression || 'default'; const deviceProfileExpressionSource = deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json index 96ab233ef4..b3ba47215f 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json @@ -1,5 +1,5 @@ { - "current": { + "3.5.1": { "broker": { "host": "127.0.0.1", "port": 1883, From f97598ee081f0244cf83f16e8fc3da4553c83f90 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 9 Sep 2024 16:17:00 +0300 Subject: [PATCH 36/84] refactoring --- ...ay-connector-version-processor.abstract.ts | 6 +++--- .../mqtt-version-processor.abstract.ts | 19 +++++++++---------- .../mqtt-basic-config.abstract.ts | 2 ++ .../mqtt-basic-config.component.ts | 4 ++-- .../gateway/gateway-connectors.component.ts | 6 +----- .../utils/mqtt-version-mapping.util.ts | 6 +++--- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts index 1a1c4f05bc..31b8ba8cf8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -11,9 +11,9 @@ export abstract class GatewayConnectorVersionProcessor { getProcessedByVersion(): BasicConfig { if (this.isVersionUpdateNeeded()) { - return this.configVersion > this.gatewayVersion - ? this.getDowngradedVersion() - : this.getUpgradedVersion(); + return this.configVersion < this.gatewayVersion + ? this.getUpgradedVersion() + : this.getDowngradedVersion(); } return this.connector.configurationJson as unknown as BasicConfig; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts index 2a7bea0e1b..875b238d7e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -1,15 +1,14 @@ -import { - GatewayConnectorVersionProcessor -} from '@home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract'; +import { isEqual } from '@core/utils'; import { GatewayConnector, - MQTTBasicConfig, MQTTBasicConfig_v3_5_2, + MQTTBasicConfig, + MQTTBasicConfig_v3_5_2, MQTTLegacyBasicConfig, RequestMappingData, RequestType, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { isEqual } from '@core/utils'; -import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; +} from '../gateway-widget.models'; +import { MqttVersionMappingUtil } from '../utils/mqtt-version-mapping.util'; +import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { private readonly mqttRequestTypeKeys = Object.values(RequestType); @@ -20,7 +19,7 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { - const { [key]: removedKey, ...rest } = configurationJson as MQTTLegacyBasicConfig; + const { [key]: removedValue, ...rest } = configurationJson as MQTTLegacyBasicConfig; configurationJson = { ...rest } as any; }); this.cleanUpConfigJson(configurationJson as MQTTBasicConfig_v3_5_2); - return configurationJson as MQTTBasicConfig; + return configurationJson as MQTTBasicConfig_v3_5_2; } getDowngradedVersion(): MQTTLegacyBasicConfig { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts index 072d8ba834..0394604d42 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts @@ -32,7 +32,9 @@ export abstract class AbstractMqttBasicConfigComponent implements C basicFormGroup: FormGroup; mappingTypes = MappingType; + private destroy$ = new Subject(); + private onChange: (value: BasicConfig) => void; private onTouched: () => void; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts index e2c9918179..541866dab8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts @@ -80,8 +80,8 @@ export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent), }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 96e15a110f..b6e2dfc093 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -243,10 +243,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie delete value.class; } - if (this.gatewayVersion && !value.configVersion) { - value.configVersion = this.gatewayVersion; - } - value.ts = Date.now(); return value; @@ -482,7 +478,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie value.configurationJson = {} as ConnectorBaseConfig; } value.basicConfig = value.configurationJson; - this.updateConnector({...value, configVersion: this.gatewayVersion ?? ''}); + this.updateConnector(value); this.generate('basicConfig.broker.clientId'); setTimeout(() => this.saveConnector()); }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts index ffd517c3e9..23cc03dc4e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts @@ -40,7 +40,7 @@ export class MqttVersionMappingUtil { ['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; static mapMappingToUpgradedVersion( - mapping: LegacyConverterConnectorMapping[] | ConverterConnectorMapping[] + mapping: LegacyConverterConnectorMapping[] ): ConverterConnectorMapping[] { return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); @@ -54,12 +54,12 @@ export class MqttVersionMappingUtil { this.cleanUpOldFields(newConverter); return { converter: newConverter, topicFilter, subscriptionQos }; - }); + }) as ConverterConnectorMapping[]; } static mapRequestsToUpgradedVersion( requestMapping: Record + LegacyRequestMappingData[]> ): Record { return this.mqttRequestTypeKeys.reduce((acc, key: RequestType) => { if (!requestMapping[key]) { From 618d82b7ac0385babd566ae97fcb9b8032e4c841 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 9 Sep 2024 17:42:46 +0300 Subject: [PATCH 37/84] Changed approach to GatewayConnector generic --- ...ay-connector-version-processor.abstract.ts | 14 +++++------ .../mqtt-version-processor.abstract.ts | 24 ++++++++++++------- .../models/gateway-configuration.models.ts | 4 +++- .../gateway/gateway-connectors.component.ts | 6 ++--- .../lib/gateway/gateway-widget.models.ts | 16 ++++++++----- .../gateway-connector-version-mapping.util.ts | 8 +++---- 6 files changed, 43 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts index 31b8ba8cf8..923cf1920b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -4,23 +4,23 @@ export abstract class GatewayConnectorVersionProcessor { gatewayVersion: number; configVersion: number; - protected constructor(protected gatewayVersionStr: string, protected connector: GatewayConnector) { + protected constructor(protected gatewayVersionStr: string, protected connector: GatewayConnector) { this.gatewayVersion = this.parseVersion(this.gatewayVersionStr); this.configVersion = this.parseVersion(connector.configVersion); } - getProcessedByVersion(): BasicConfig { + getProcessedByVersion(): GatewayConnector { if (this.isVersionUpdateNeeded()) { - return this.configVersion < this.gatewayVersion + return !this.configVersion || this.configVersion < this.gatewayVersion ? this.getUpgradedVersion() : this.getDowngradedVersion(); } - return this.connector.configurationJson as unknown as BasicConfig; + return this.connector; } private isVersionUpdateNeeded(): boolean { - if (!this.gatewayVersion || !this.configVersion) { + if (!this.gatewayVersion) { return false; } @@ -31,6 +31,6 @@ export abstract class GatewayConnectorVersionProcessor { return Number(version?.replace(/\./g, '')); } - abstract getDowngradedVersion(): BasicConfig; - abstract getUpgradedVersion(): BasicConfig; + abstract getDowngradedVersion(): GatewayConnector; + abstract getUpgradedVersion(): GatewayConnector; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts index 875b238d7e..99591130a5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -15,11 +15,11 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor ) { super(gatewayVersionStr, connector); } - getUpgradedVersion(): MQTTBasicConfig_v3_5_2 { + getUpgradedVersion(): GatewayConnector { const { connectRequests, disconnectRequests, @@ -46,10 +46,14 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor; } - getDowngradedVersion(): MQTTLegacyBasicConfig { + getDowngradedVersion(): GatewayConnector { const { requestsMapping, mapping, ...restConfig } = this.connector.configurationJson as MQTTBasicConfig_v3_5_2; const updatedRequestsMapping = @@ -57,10 +61,14 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor; } private cleanUpConfigJson(configurationJson: MQTTBasicConfig_v3_5_2): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts index a23d8e7900..bfdd46997e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts @@ -16,7 +16,9 @@ import { ConfigurationModes, - GatewayConnector, LocalLogsConfigs, LogSavingPeriod, + GatewayConnector, + LocalLogsConfigs, + LogSavingPeriod, SecurityTypes, StorageTypes } from '@home/components/widget/lib/gateway/gateway-widget.models'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index b6e2dfc093..cbaeed8888 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -732,14 +732,14 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.enable(); } - const connectorState = { + const connectorState = GatewayConnectorVersionMappingUtil.getConfig({ configuration: '', key: 'auto', configurationJson: {} as ConnectorBaseConfig, ...connector, - }; + }, this.gatewayVersion); - connectorState.basicConfig = GatewayConnectorVersionMappingUtil.getConfig(connectorState, this.gatewayVersion); + connectorState.basicConfig = connectorState.configurationJson; this.initialConnector = connectorState; this.updateConnector(connectorState); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 38040f15d3..94d8ce7d2a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -124,14 +124,14 @@ export interface GatewayConnectorBase { configVersion?: string; } -export interface GatewayConnector extends GatewayConnectorBase { - configurationJson: ConnectorBaseConfig; - basicConfig?: ConnectorBaseConfig; +export interface GatewayConnector extends GatewayConnectorBase { + configurationJson: BaseConfig; + basicConfig?: BaseConfig; } export interface GatewayVersionedDefaultConfig { - legacy: GatewayConnector; - '3.5.1': GatewayConnector; + legacy: GatewayConnector; + '3.5.1': GatewayConnector; } export interface DataMapping { @@ -192,7 +192,11 @@ export type ConnectorMapping = DeviceConnectorMapping | RequestMappingValue | Co export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue; -export type ConnectorBaseConfig = ConnectorBaseInfo | MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig; +export type ConnectorBaseConfig = ConnectorBaseConfig_v_3_5_2 | ConnectorLegacyConfig; + +export type ConnectorLegacyConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig | OPCBasicConfig | ModbusBasicConfig; + +export type ConnectorBaseConfig_v_3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2; export interface ConnectorBaseInfo { name: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts index ab9e393fee..9de53e506d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts @@ -17,18 +17,18 @@ import { ConnectorBaseConfig, ConnectorType, - GatewayConnector, + GatewayConnector, MQTTBasicConfig, } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract'; export abstract class GatewayConnectorVersionMappingUtil { - static getConfig(connector: GatewayConnector, gatewayVersion: string): ConnectorBaseConfig { + static getConfig(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { switch(connector.type) { case ConnectorType.MQTT: - return new MqttVersionProcessor(gatewayVersion, connector).getProcessedByVersion(); + return new MqttVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); default: - return connector.configurationJson as ConnectorBaseConfig; + return connector; } } } From fdb991362526f4386c08754c2b2d352c7ab63ac5 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Sep 2024 17:54:29 +0300 Subject: [PATCH 38/84] UI: Timewindow - interval options style change --- .../time/timeinterval.component.html | 2 +- .../components/time/timewindow-form.scss | 2 +- .../time/timewindow-panel.component.html | 17 +++++++++-------- .../time/timewindow-panel.component.scss | 8 +++++++- .../time/timezone-panel.component.html | 8 ++++---- .../time/timezone-panel.component.scss | 9 ++++++--- .../time/timezone-select.component.html | 2 +- .../components/time/timezone.component.ts | 2 +- ui-ngx/src/form.scss | 18 ++++++++++++++++-- 9 files changed, 46 insertions(+), 22 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index dc81688418..b602df96b7 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -16,7 +16,7 @@ -->
- + {{ predefinedName }} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 6d934a9b11..32a0ef62be 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -37,7 +37,7 @@ .limit-slider-value { margin-left: 16px; min-width: 25px; - max-width: 100px; + max-width: 106px; } mat-form-field input[type=number] { text-align: center; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index f9dbe69491..f5db61fedc 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -20,7 +20,7 @@ -
@@ -85,7 +85,8 @@ - - +
-
{{ 'aggregation.aggregation' | translate }}
+
{{ 'aggregation.aggregation' | translate }}
@@ -152,9 +153,9 @@
-
-
{{ 'aggregation.group-interval' | translate }}
+
{{ 'aggregation.group-interval' | translate }}
-
-
{{ 'timezone.timezone' | translate }}
+
+
{{ 'timezone.timezone' | translate }}
+ displayLabel="false" class="flex">
-
+
-
+
{{ 'timewindow.timewindow' | translate }}
{{ 'timewindow.hide-timewindow-section' | translate }} @@ -44,43 +44,54 @@ - - {{ 'timewindow.hide-last-interval' | translate }} - + + + + {{ 'timewindow.disable-custom-interval' | translate }} - + + {{ 'timewindow.hide' | translate }} + + - + +
- - {{ 'timewindow.hide-relative-interval' | translate }} - - + + + + {{ 'timewindow.hide' | translate }} + + + - + +
-
+
{{ 'timewindow.timewindow' | translate }}
{{ 'timewindow.hide-timewindow-section' | translate }} @@ -90,78 +101,96 @@ - - {{ 'timewindow.hide-last-interval' | translate }} - + + + {{ 'timewindow.disable-custom-interval' | translate }} - + + {{ 'timewindow.hide' | translate }} + + - + + - - {{ 'timewindow.hide-fixed-interval' | translate }} - - + + + {{ 'timewindow.hide' | translate }} + + + - + + - - {{ 'timewindow.hide-relative-interval' | translate }} - - + + + {{ 'timewindow.hide' | translate }} + + + - + +
-
+
{{ 'aggregation.aggregation' | translate }}
- - {{ 'timewindow.hide-aggregation' | translate }} - - - - - - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - - - +
+ + + + {{ 'timewindow.hide' | translate }} + + + + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + + + +
-
{{ 'aggregation.limit' | translate }}
- - {{ 'timewindow.hide-max-values' | translate }} - -
-
+
+ + + + {{ 'timewindow.hide' | translate }} + +
@@ -177,7 +206,7 @@
-
+
{{ 'aggregation.group-interval' | translate }}
{{ 'timewindow.hide-group-interval' | translate }} @@ -186,42 +215,50 @@ {{ 'timewindow.disable-custom-interval' | translate }} - - +
+ + +
{{ 'timewindow.disable-custom-interval' | translate }} - - +
+ + +
-
+
{{ 'timezone.timezone' | translate }}
- - {{ 'timewindow.hide-timezone' | translate }} - - - +
+ + + {{ 'timewindow.hide' | translate }} + + + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss index ccf1814728..4632861ec6 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss @@ -22,5 +22,11 @@ overflow: hidden; padding: 0; } + + tb-timezone-select, + tb-quick-time-interval, + tb-datetime-period { + flex: 1; + } } } From 4323c6abd2bc3f911b28a0cb134a8503c9bb06d8 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 9 Sep 2024 19:10:49 +0300 Subject: [PATCH 42/84] [4114] refactoring --- .../abstract/gateway-connector-version-processor.abstract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts index b2062e428b..cad9300212 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -28,7 +28,7 @@ export abstract class GatewayConnectorVersionProcessor { } private isVersionUpgradeNeeded(): boolean { - return (!this.configVersion || this.configVersion < this.gatewayVersion) && this.gatewayVersionStr === GatewayVersion.Current; + return this.gatewayVersionStr === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion); } private parseVersion(version: string): number { From 1625f435b380faf1f23f58a02b18d3077d70447f Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 9 Sep 2024 19:11:37 +0300 Subject: [PATCH 43/84] [4114] license headers --- ...teway-connector-version-processor.abstract.ts | 16 ++++++++++++++++ .../abstract/mqtt-version-processor.abstract.ts | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts index cad9300212..e1addf0b71 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2024 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. +/// + import { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; export abstract class GatewayConnectorVersionProcessor { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts index 99591130a5..645eb64fa4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2024 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. +/// + import { isEqual } from '@core/utils'; import { GatewayConnector, From 64bcffbb1df34a062877c9d6780d7d612520a24f Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Tue, 10 Sep 2024 14:38:24 +0300 Subject: [PATCH 44/84] UI: Timewindow style change, remove redundant --- .../time/timeinterval.component.scss | 4 - .../timewindow-config-dialog.component.html | 281 +++++++++--------- .../timewindow-config-dialog.component.scss | 4 +- .../components/time/timewindow-form.scss | 6 + .../time/timewindow-panel.component.scss | 6 +- .../assets/locale/locale.constant-en_US.json | 7 - 6 files changed, 148 insertions(+), 160 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss index 5115f555cc..c08676fe69 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss @@ -23,10 +23,6 @@ @media #{$mat-xs} { min-width: 0; width: 100%; - - .advanced-input { - gap: 12px; - } } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index 0f3b2467ed..01a2cdf933 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -32,141 +32,126 @@
- -
-
{{ 'timewindow.timewindow' | translate }}
+
+
{{ 'timewindow.timewindow' | translate }}
+
{{ 'timewindow.hide-timewindow-section' | translate }} +
- - + + - - - - - + +
{{ 'timewindow.disable-custom-interval' | translate }} -
- - {{ 'timewindow.hide' | translate }} - - - -
- +
+
+ + {{ 'timewindow.hide' | translate }} + + + +
+
- -
- - - - {{ 'timewindow.hide' | translate }} - - - - -
-
-
- +
+ + {{ 'timewindow.hide' | translate }} + + + +
+
- -
-
{{ 'timewindow.timewindow' | translate }}
+
+
{{ 'timewindow.timewindow' | translate }}
+
{{ 'timewindow.hide-timewindow-section' | translate }} +
- - + + - - - - + +
{{ 'timewindow.disable-custom-interval' | translate }} -
- - {{ 'timewindow.hide' | translate }} - - - -
- +
+
+ + {{ 'timewindow.hide' | translate }} + + + +
+
- -
- - - {{ 'timewindow.hide' | translate }} - - - - -
-
+
+ + {{ 'timewindow.hide' | translate }} + + + +
- -
- - - {{ 'timewindow.hide' | translate }} - - - - -
-
-
- +
+ + {{ 'timewindow.hide' | translate }} + + + +
+
{{ 'aggregation.aggregation' | translate }}
- - {{ 'timewindow.hide' | translate }} @@ -185,9 +170,7 @@ *ngIf="timewindowForm.get('aggregation.type').value === aggregationTypes.NONE">
{{ 'aggregation.limit' | translate }}
- - {{ 'timewindow.hide' | translate }}
@@ -208,49 +191,63 @@
{{ 'aggregation.group-interval' | translate }}
- - {{ 'timewindow.hide-group-interval' | translate }} - - - - {{ 'timewindow.disable-custom-interval' | translate }} - -
- - + + +
+ + {{ 'timewindow.disable-custom-interval' | translate }} + +
+
+ + + + + +
- - - {{ 'timewindow.disable-custom-interval' | translate }} - -
- - + +
+ + {{ 'timewindow.disable-custom-interval' | translate }} + +
+
+ + + + + +
+ + + + {{ 'timewindow.hide' | translate }} + +
{{ 'timezone.timezone' | translate }}
- {{ 'timewindow.hide' | translate }} - Date: Tue, 10 Sep 2024 15:15:54 +0300 Subject: [PATCH 45/84] Timewindow: fix switching between tabs when some options are disabled --- .../time/timewindow-config-dialog.component.ts | 4 +++- .../components/time/timewindow-panel.component.ts | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 58733ce498..ba7ee3e58c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -314,7 +314,9 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On private onTimewindowTypeChange(selectedTab: TimewindowType) { const timewindowFormValue = this.timewindowForm.getRawValue(); if (selectedTab === TimewindowType.REALTIME) { - if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { + if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED + && !(this.quickIntervalOnly && timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL)) { + this.timewindowForm.get('realtime').patchValue({ realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? RealtimeWindowType[HistoryWindowType[timewindowFormValue.history.historyType]] : diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 02874f3523..8d51d2ec00 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -325,7 +325,11 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O private onTimewindowTypeChange(selectedTab: TimewindowType) { const timewindowFormValue = this.timewindowForm.getRawValue(); if (selectedTab === TimewindowType.REALTIME) { - if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { + if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED + && !((this.quickIntervalOnly || this.timewindow.realtime.hideLastInterval) + && timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) + && !(this.timewindow.realtime.hideQuickInterval && timewindowFormValue.history.historyType === HistoryWindowType.INTERVAL)) { + this.timewindowForm.get('realtime').patchValue({ realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? RealtimeWindowType[HistoryWindowType[timewindowFormValue.history.historyType]] : @@ -336,7 +340,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O }); setTimeout(() => this.timewindowForm.get('realtime.interval').patchValue(timewindowFormValue.history.interval)); } - } else { + } else if (!(this.timewindow.history.hideLastInterval && timewindowFormValue.realtime.realtimeType === RealtimeWindowType.LAST_INTERVAL) + && !(this.timewindow.history.hideQuickInterval && timewindowFormValue.realtime.realtimeType === RealtimeWindowType.INTERVAL)) { + this.timewindowForm.get('history').patchValue({ historyType: HistoryWindowType[RealtimeWindowType[timewindowFormValue.realtime.realtimeType]], timewindowMs: timewindowFormValue.realtime.timewindowMs, From 1b61916f4e7ea1b2faa4968226849ee25f8a46b5 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Tue, 10 Sep 2024 16:54:17 +0300 Subject: [PATCH 46/84] Timewindow: group interval component style change --- .../time/timeinterval.component.html | 19 +++++++++++-------- .../components/time/timewindow-form.scss | 3 ++- .../time/timewindow-panel.component.html | 7 ++++--- ui-ngx/src/form.scss | 4 ++++ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index b602df96b7..ea9fd8adad 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -16,14 +16,17 @@ -->
- - {{ predefinedName }} - - - {{ interval.name | translate:interval.translateParams }} - - - +
+ + + {{ predefinedName }} + + + {{ interval.name | translate:interval.translateParams }} + + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 0bf7a44e86..8a6176acbf 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -28,7 +28,8 @@ } &-content { - overflow: auto; + overflow-x: hidden; + overflow-y: auto; tb-timeinterval, tb-quick-time-interval, diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index f5db61fedc..70dd8e4267 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -124,8 +124,8 @@
-
{{ 'aggregation.aggregation' | translate }}
- +
{{ 'aggregation.aggregation' | translate }}
+ {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} @@ -155,7 +155,6 @@
-
{{ 'aggregation.group-interval' | translate }}
+
{{ 'aggregation.group-interval' | translate }}
@@ -174,6 +174,7 @@ subscriptSizing="dynamic" appearance="outline" [disabledAdvanced]="timewindow.history.disableCustomGroupInterval"> +
{{ 'aggregation.group-interval' | translate }}
diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index bd80301e19..40f0bd70d9 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -235,6 +235,10 @@ .fixed-title-width { min-width: 200px; + &-180 { + min-width: 180px; + } + &-230 { min-width: 230px; } From b142c72247f7050427d6ab49789ea40e403b263d Mon Sep 17 00:00:00 2001 From: mpetrov Date: Tue, 10 Sep 2024 18:05:59 +0300 Subject: [PATCH 47/84] [4114] Added Modbus version mapping and OPC initial mapping --- ...gateway-connector-basic-config.abstract.ts | 72 ++ ...ay-connector-version-processor.abstract.ts | 3 +- .../modbus-version-processor.abstract.ts | 67 ++ .../opc-version-processor.abstract.ts | 55 ++ .../modbus-basic-config.abstract.ts | 81 ++ .../modbus-basic-config.component.ts | 110 +-- .../modbus-legacy-basic-config.component.ts | 76 ++ .../mqtt-basic-config.abstract.ts | 58 +- .../mqtt-basic-config.component.ts | 26 +- .../mqtt-legacy-basic-config.component.ts | 28 +- .../workers-config-control.component.html | 0 .../workers-config-control.component.ts | 0 .../opc-server-config.component.html | 0 .../opc-server-config.component.scss | 0 .../opc-server-config.component.ts | 0 .../opc-ua-basic-config.component.html | 0 .../opc-ua-basic-config.component.scss | 0 .../opc-ua-basic-config.component.ts | 81 +- .../opc-ua-legacy-basic-config.component.ts | 87 +++ .../gateway/gateway-connectors.component.html | 28 +- .../lib/gateway/gateway-widget.models.ts | 68 +- .../gateway-connector-version-mapping.util.ts | 8 + .../utils/modbus-version-mapping.util.ts | 77 ++ .../gateway/utils/opc-version-mapping.util.ts | 113 +++ .../widget/widget-components.module.ts | 14 +- .../connector-default-configs/modbus.json | 698 ++++++++++++------ .../connector-default-configs/opcua.json | 180 +++-- 27 files changed, 1393 insertions(+), 537 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mqtt}/workers-config-control/workers-config-control.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mqtt}/workers-config-control/workers-config-control.component.ts (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => opc}/opc-server-config/opc-server-config.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => opc}/opc-server-config/opc-server-config.component.scss (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => opc}/opc-server-config/opc-server-config.component.ts (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => opc}/opc-ua-basic-config/opc-ua-basic-config.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => opc}/opc-ua-basic-config/opc-ua-basic-config.component.scss (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => opc}/opc-ua-basic-config/opc-ua-basic-config.component.ts (51%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts new file mode 100644 index 0000000000..d12e647254 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts @@ -0,0 +1,72 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Directive, inject, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Directive() +export abstract class GatewayConnectorBasicConfigDirective + implements ControlValueAccessor, Validator, OnDestroy { + + @Input() generalTabContent: TemplateRef; + + basicFormGroup: FormGroup; + + protected fb = inject(FormBuilder); + protected onChange!: (value: OutputBasicConfig) => void; + protected onTouched!: () => void; + protected destroy$ = new Subject(); + + constructor() { + this.basicFormGroup = this.initBasicFormGroup(); + + this.basicFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe((value) => this.onBasicFormGroupChange(value)); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + validate(): ValidationErrors | null { + return this.basicFormGroup.valid ? null : { basicFormGroup: { valid: false } }; + } + + registerOnChange(fn: (value: OutputBasicConfig) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(config: OutputBasicConfig): void { + this.basicFormGroup.setValue(this.mapConfigToFormValue(config), { emitEvent: false }); + } + + protected onBasicFormGroupChange(value: InputBasicConfig): void { + this.onChange(this.getMappedValue(value)); + this.onTouched(); + } + + protected abstract mapConfigToFormValue(config: OutputBasicConfig): InputBasicConfig; + protected abstract getMappedValue(config: InputBasicConfig): OutputBasicConfig; + protected abstract initBasicFormGroup(): FormGroup; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts index e1addf0b71..5f06659823 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -15,6 +15,7 @@ /// import { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { isString } from '@core/utils'; export abstract class GatewayConnectorVersionProcessor { gatewayVersion: number; @@ -48,7 +49,7 @@ export abstract class GatewayConnectorVersionProcessor { } private parseVersion(version: string): number { - return Number(version?.replace(/\./g, '')); + return isString(version) ? Number(version.replace(/\./g, '')) : 0; } protected abstract getDowngradedVersion(): GatewayConnector; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts new file mode 100644 index 0000000000..d520a5b1e8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -0,0 +1,67 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + GatewayConnector, + ModbusBasicConfig, + ModbusBasicConfig_v3_5_2, + ModbusLegacyBasicConfig, + ModbusLegacySlave, + ModbusMasterConfig, + ModbusSlave, +} from '../gateway-widget.models'; +import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; +import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; + +export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor { + + constructor( + protected gatewayVersionStr: string, + protected connector: GatewayConnector + ) { + super(gatewayVersionStr, connector); + } + getUpgradedVersion(): GatewayConnector { + const configurationJson = this.connector.configurationJson; + return { + ...this.connector, + configurationJson: { + master: configurationJson.master + ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master) + : {} as ModbusMasterConfig, + slave: configurationJson.slave + ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave) + : {} as ModbusSlave, + }, + configVersion: this.gatewayVersionStr + } as GatewayConnector; + } + + getDowngradedVersion(): GatewayConnector { + const configurationJson = this.connector.configurationJson; + return { + ...this.connector, + configurationJson: { + ...configurationJson, + slave: configurationJson.slave + ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(configurationJson.slave as ModbusSlave) + : {} as ModbusLegacySlave, + master: configurationJson.master, + }, + configVersion: this.gatewayVersionStr + } as GatewayConnector; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts new file mode 100644 index 0000000000..11c9908058 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + GatewayConnector, LegacyServerConfig, + OPCBasicConfig, + OPCBasicConfig_v3_5_2, + OPCLegacyBasicConfig, +} from '../gateway-widget.models'; +import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; +import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; + +export class OpcVersionProcessor extends GatewayConnectorVersionProcessor { + + constructor( + protected gatewayVersionStr: string, + protected connector: GatewayConnector + ) { + super(gatewayVersionStr, connector); + } + getUpgradedVersion(): GatewayConnector { + const server = this.connector.configurationJson.server as LegacyServerConfig; + return { + ...this.connector, + configurationJson: { + server: server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(server) : {}, + mapping: server.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(server.mapping) : [], + }, + configVersion: this.gatewayVersionStr + } as GatewayConnector; + } + + getDowngradedVersion(): GatewayConnector { + return { + ...this.connector, + configurationJson: { + server: OpcVersionMappingUtil.mapServerToDowngradedVersion(this.connector.configurationJson as OPCBasicConfig_v3_5_2) + }, + configVersion: this.gatewayVersionStr + } as GatewayConnector; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts new file mode 100644 index 0000000000..9e7be382d2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts @@ -0,0 +1,81 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Directive } from '@angular/core'; +import { FormControl, FormGroup, ValidationErrors } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { isEqual } from '@core/utils'; +import { GatewayConnectorBasicConfigDirective } from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; +import { + ModbusBasicConfig, + ModbusBasicConfig_v3_5_2, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +@Directive() +export abstract class ModbusBasicConfigDirective + extends GatewayConnectorBasicConfigDirective { + + enableSlaveControl: FormControl = new FormControl(false); + + constructor() { + super(); + + this.enableSlaveControl.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(enable => { + this.updateSlaveEnabling(enable); + this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); + }); + } + + override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void { + super.writeValue(basicConfig); + this.onEnableSlaveControl(basicConfig); + } + + override validate(): ValidationErrors | null { + const { master, slave } = this.basicFormGroup.value; + const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave); + if (!this.basicFormGroup.valid || isEmpty) { + return { basicFormGroup: { valid: false } }; + } + return null; + } + + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ + master: [], + slave: [], + }); + } + + protected override onBasicFormGroupChange(value: ModbusBasicConfig_v3_5_2): void { + super.onBasicFormGroupChange(value); + this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); + } + + private updateSlaveEnabling(isEnabled: boolean): void { + if (isEnabled) { + this.basicFormGroup.get('slave').enable({ emitEvent: false }); + } else { + this.basicFormGroup.get('slave').disable({ emitEvent: false }); + } + } + + private onEnableSlaveControl(basicConfig: ModbusBasicConfig): void { + this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {})); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index aa0424da93..21c37ed147 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -14,28 +14,21 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { - ControlValueAccessor, - FormBuilder, - FormControl, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormControl, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { ModbusBasicConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; + ModbusBasicConfig_v3_5_2, + ModbusMasterConfig, + ModbusSlave +} from '@home/components/widget/lib/gateway/gateway-widget.models'; import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; - -import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; +import { SharedModule } from '@shared/shared.module'; import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; -import { isEqual } from '@core/utils'; +import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; +import { + ModbusBasicConfigDirective +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; @Component({ selector: 'tb-modbus-basic-config', @@ -63,80 +56,19 @@ import { isEqual } from '@core/utils'; ], styleUrls: ['./modbus-basic-config.component.scss'], }) +export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { -export class ModbusBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - - @Input() generalTabContent: TemplateRef; - - basicFormGroup: FormGroup; - enableSlaveControl: FormControl; - - onChange: (value: ModbusBasicConfig) => void; - onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ - master: [], - slave: [], - }); - this.enableSlaveControl = new FormControl(false); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(({ master, slave }) => { - this.onChange({ master, slave: slave ?? {} }); - this.onTouched(); - }); - - this.enableSlaveControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(enable => { - this.updateSlaveEnabling(enable); - this.basicFormGroup.get('slave').updateValueAndValidity({emitEvent: !!this.onChange}); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: ModbusBasicConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(basicConfig: ModbusBasicConfig): void { - const editedBase = { - slave: basicConfig.slave ?? {}, - master: basicConfig.master ?? {}, + protected override mapConfigToFormValue(config: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { + return { + master: config.master ?? {} as ModbusMasterConfig, + slave: config.slave ?? {} as ModbusSlave, }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); - this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {})); - } - - validate(basicFormControl: UntypedFormControl): ValidationErrors | null { - const { master, slave } = basicFormControl.value; - const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave); - if (!this.basicFormGroup.valid || isEmpty) { - return { - basicFormGroup: {valid: false} - }; - } - return null; } - private updateSlaveEnabling(isEnabled: boolean): void { - if (isEnabled) { - this.basicFormGroup.get('slave').enable({emitEvent: false}); - } else { - this.basicFormGroup.get('slave').disable({emitEvent: false}); - } + protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { + return { + master: value.master, + slave: value.slave, + }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts new file mode 100644 index 0000000000..50f7d833fa --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + ModbusBasicConfig_v3_5_2, + ModbusLegacyBasicConfig, ModbusLegacySlave, + ModbusMasterConfig, + ModbusSlave +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; +import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; +import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; +import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; +import { + ModbusBasicConfigDirective +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; + +@Component({ + selector: 'tb-modbus-legacy-basic-config', + templateUrl: './modbus-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusSlaveConfigComponent, + ModbusMasterTableComponent, + EllipsisChipListDirective, + ], + styleUrls: ['./modbus-basic-config.component.scss'], +}) +export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { + + protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 { + return { + master: config.master ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) : {} as ModbusMasterConfig, + slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave, + }; + } + + protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusLegacyBasicConfig { + return { + master: value.master, + slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave) : {} as ModbusLegacySlave, + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts index aeef688333..ce87d80a75 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts @@ -14,63 +14,33 @@ /// limitations under the License. /// -import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { Directive } from '@angular/core'; +import { FormGroup } from '@angular/forms'; import { MappingType, - MQTTBasicConfig, + MQTTBasicConfig, MQTTBasicConfig_v3_5_2, RequestMappingData, RequestMappingValue, RequestType } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Directive, OnDestroy } from '@angular/core'; import { isObject } from '@core/utils'; +import { + GatewayConnectorBasicConfigDirective +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; @Directive() -export abstract class AbstractMqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { +export abstract class MqttBasicConfigDirective + extends GatewayConnectorBasicConfigDirective { - basicFormGroup: FormGroup; MappingType = MappingType; - private destroy$ = new Subject(); - - private onChange: (value: BasicConfig) => void; - private onTouched: () => void; - - constructor(protected fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ mapping: [], requestsMapping: [], broker: [], workers: [], }); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(this.getMappedMQTTConfig(value)); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: BasicConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { - basicFormGroup: {valid: false} - }; } protected getRequestDataArray(value: Record): RequestMappingData[] { @@ -103,6 +73,10 @@ export abstract class AbstractMqttBasicConfigComponent implements C }); } - abstract writeValue(basicConfig: BasicConfig): void; - protected abstract getMappedMQTTConfig(basicConfig: MQTTBasicConfig): BasicConfig; + writeValue(basicConfig: BasicConfig): void { + this.basicFormGroup.setValue(this.mapConfigToFormValue(basicConfig), { emitEvent: false }); + } + + protected abstract override mapConfigToFormValue(config: BasicConfig): MQTTBasicConfig_v3_5_2; + protected abstract override getMappedValue(config: MQTTBasicConfig): BasicConfig; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts index 541866dab8..9af627352a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts @@ -14,16 +14,17 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; +import { Component, forwardRef, ChangeDetectionStrategy } from '@angular/core'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { + BrokerConfig, MQTTBasicConfig_v3_5_2, RequestMappingData, RequestMappingValue, - RequestType + RequestType, WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { - AbstractMqttBasicConfigComponent + MqttBasicConfigDirective } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; import { isDefinedAndNotNull } from '@core/utils'; import { CommonModule } from '@angular/common'; @@ -33,7 +34,7 @@ import { } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; import { WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; import { BrokerConfigControlComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; @@ -68,27 +69,22 @@ import { MappingTableComponent, ], }) -export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent { +export class MqttBasicConfigComponent extends MqttBasicConfigDirective { - @Input() - generalTabContent: TemplateRef; - - writeValue(basicConfig: MQTTBasicConfig_v3_5_2): void { + protected override mapConfigToFormValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { const { broker, mapping = [], requestsMapping } = basicConfig; - const editedBase = { + return{ workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { maxNumberOfWorkers: broker.maxNumberOfWorkers, maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, - } : {}, + } : {} as WorkersConfig, mapping: mapping ?? [], - broker: broker ?? {}, + broker: broker ?? {} as BrokerConfig, requestsMapping: this.getRequestDataArray(requestsMapping as Record), }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); } - protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { + protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { let { broker, workers, mapping, requestsMapping } = basicConfig || {}; if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts index 2ca5eba000..e4577c653e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts @@ -14,18 +14,19 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; +import { Component, forwardRef, ChangeDetectionStrategy } from '@angular/core'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { + BrokerConfig, MQTTBasicConfig_v3_5_2, MQTTLegacyBasicConfig, RequestMappingData, RequestMappingValue, - RequestType + RequestType, WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; import { - AbstractMqttBasicConfigComponent + MqttBasicConfigDirective } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; import { isDefinedAndNotNull } from '@core/utils'; import { CommonModule } from '@angular/common'; @@ -35,7 +36,7 @@ import { } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; import { WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; import { BrokerConfigControlComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; @@ -70,12 +71,9 @@ import { MappingTableComponent, ], }) -export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigComponent { +export class MqttLegacyBasicConfigComponent extends MqttBasicConfigDirective { - @Input() - generalTabContent: TemplateRef; - - writeValue(basicConfig: MQTTLegacyBasicConfig): void { + protected override mapConfigToFormValue(config: MQTTLegacyBasicConfig): MQTTBasicConfig_v3_5_2 { const { broker, mapping = [], @@ -84,7 +82,7 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo attributeRequests = [], attributeUpdates = [], serverSideRpc = [] - } = basicConfig; + } = config as MQTTLegacyBasicConfig; const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ connectRequests, disconnectRequests, @@ -92,20 +90,18 @@ export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigCompo attributeUpdates, serverSideRpc }); - const editedBase = { + return { workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { maxNumberOfWorkers: broker.maxNumberOfWorkers, maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, - } : {}, + } : {} as WorkersConfig, mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion(mapping) || [], - broker: broker || {}, + broker: broker || {} as BrokerConfig, requestsMapping: this.getRequestDataArray(updatedRequestMapping), }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); } - protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig_v3_5_2): MQTTLegacyBasicConfig { + protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTLegacyBasicConfig { let { broker, workers, mapping, requestsMapping } = basicConfig || {}; if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts similarity index 51% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts index b6cf4a4081..1a45e74c7a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -14,34 +14,25 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { - ConnectorType, MappingType, - OPCBasicConfig, + OPCBasicConfig_v3_5_2, + ServerConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; +import { SharedModule } from '@shared/shared.module'; +import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - MappingTableComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; import { OpcServerConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; +import { + GatewayConnectorBasicConfigDirective +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; @Component({ selector: 'tb-opc-ua-basic-config', @@ -69,58 +60,28 @@ import { ], styleUrls: ['./opc-ua-basic-config.component.scss'] }) - -export class OpcUaBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - @Input() generalTabContent: TemplateRef; +export class OpcUaBasicConfigComponent extends GatewayConnectorBasicConfigDirective { mappingTypes = MappingType; - basicFormGroup: FormGroup; - - onChange!: (value: string) => void; - onTouched!: () => void; - - protected readonly connectorType = ConnectorType; - private destroy$ = new Subject(); - constructor(private fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ mapping: [], server: [], }); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; } - writeValue(basicConfig: OPCBasicConfig): void { - const editedBase = { - server: basicConfig.server || {}, - mapping: basicConfig.mapping || [], + protected override mapConfigToFormValue(config: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { + return { + server: config.server ?? {} as ServerConfig, + mapping: config.mapping ?? [], }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); } - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { - basicFormGroup: {valid: false} + protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { + return { + server: value.server, + mapping: value.mapping, }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts new file mode 100644 index 0000000000..12e05205a2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + MappingType, + OPCBasicConfig_v3_5_2, + OPCLegacyBasicConfig, ServerConfig, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + OpcServerConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; +import { + GatewayConnectorBasicConfigDirective +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; +import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; + +@Component({ + selector: 'tb-opc-ua-legacy-basic-config', + templateUrl: './opc-ua-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + MappingTableComponent, + OpcServerConfigComponent, + ], + styleUrls: ['./opc-ua-basic-config.component.scss'] +}) +export class OpcUaLegacyBasicConfigComponent extends GatewayConnectorBasicConfigDirective { + + mappingTypes = MappingType; + + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ + mapping: [], + server: [], + }); + } + + protected override mapConfigToFormValue(config: OPCLegacyBasicConfig): OPCBasicConfig_v3_5_2 { + return { + server: config.server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(config.server) : {} as ServerConfig, + mapping: config.server?.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(config.server.mapping) : [], + }; + } + + protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCLegacyBasicConfig { + return { + server: OpcVersionMappingUtil.mapServerToDowngradedVersion(value), + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index f0459496df..7c495d34f1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -186,14 +186,26 @@ - - - - + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index f7acac12e6..8a0e4005fd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -199,9 +199,9 @@ export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingF export type ConnectorBaseConfig = ConnectorBaseConfig_v_3_5_2 | ConnectorLegacyConfig; -export type ConnectorLegacyConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig | OPCBasicConfig | ModbusBasicConfig; +export type ConnectorLegacyConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig | OPCLegacyBasicConfig | ModbusBasicConfig; -export type ConnectorBaseConfig_v_3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2; +export type ConnectorBaseConfig_v_3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2 | OPCBasicConfig_v3_5_2; export interface ConnectorBaseInfo { name: string; @@ -230,16 +230,34 @@ export interface MQTTLegacyBasicConfig { serverSideRpc: LegacyRequestMappingData[]; } -export interface OPCBasicConfig { +export type OPCBasicConfig = OPCBasicConfig_v3_5_2 | OPCLegacyBasicConfig; + +export interface OPCBasicConfig_v3_5_2 { mapping: DeviceConnectorMapping[]; server: ServerConfig; } -export interface ModbusBasicConfig { +export interface OPCLegacyBasicConfig { + server: LegacyServerConfig; +} + +export interface LegacyServerConfig extends Omit { + mapping: LegacyDeviceConnectorMapping[]; + disableSubscriptions: boolean; +} + +export type ModbusBasicConfig = ModbusBasicConfig_v3_5_2 | ModbusLegacyBasicConfig; + +export interface ModbusBasicConfig_v3_5_2 { master: ModbusMasterConfig; slave: ModbusSlave; } +export interface ModbusLegacyBasicConfig { + master: ModbusMasterConfig; + slave: ModbusLegacySlave; +} + export interface WorkersConfig { maxNumberOfWorkers: number; maxMessageNumberPerWorker: number; @@ -258,12 +276,22 @@ export interface Attribute { value: string; } +export interface LegacyAttribute { + key: string; + path: string; +} + export interface Timeseries { key: string; type: string; value: string; } +export interface LegacyTimeseries { + key: string; + path: string; +} + interface RpcArgument { type: string; value: number; @@ -274,12 +302,22 @@ export interface RpcMethod { arguments: RpcArgument[]; } +export interface LegacyRpcMethod { + method: string; + arguments: unknown[]; +} + export interface AttributesUpdate { key: string; type: string; value: string; } +export interface LegacyDeviceAttributeUpdate { + attributeOnThingsBoard: string; + attributeOnDevice: string; +} + export interface Converter { type: ConvertorType; deviceInfo?: ConnectorDeviceInfo; @@ -333,6 +371,15 @@ export interface DeviceConnectorMapping { attributes_updates?: AttributesUpdate[]; } +export interface LegacyDeviceConnectorMapping { + deviceNamePattern: string; + deviceTypePattern: string; + attributes?: LegacyAttribute[]; + timeseries?: LegacyTimeseries[]; + rpc_methods?: LegacyRpcMethod[]; + attributes_updates?: LegacyDeviceAttributeUpdate[]; +} + export enum ConnectorType { MQTT = 'mqtt', MODBUS = 'modbus', @@ -1069,7 +1116,7 @@ export interface SlaveConfig { pollPeriod: number; unitId: number; deviceName: string; - deviceType: string; + deviceType?: string; sendDataOnlyOnChange: boolean; connectAttemptTimeMs: number; connectAttemptCount: number; @@ -1121,8 +1168,19 @@ export interface ModbusSlave { security: ModbusSecurity; } +export interface ModbusLegacySlave extends Omit { + values: ModbusLegacyRegisterValues; +} + export type ModbusValuesState = ModbusRegisterValues | ModbusValues; +export interface ModbusLegacyRegisterValues { + holding_registers: ModbusValues[]; + coils_initializer: ModbusValues[]; + input_registers: ModbusValues[]; + discrete_inputs: ModbusValues[]; +} + export interface ModbusRegisterValues { holding_registers: ModbusValues; coils_initializer: ModbusValues; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts index 12f3a7c439..90c89ea316 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts @@ -17,9 +17,13 @@ import { ConnectorType, GatewayConnector, + ModbusBasicConfig, MQTTBasicConfig, + OPCBasicConfig, } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract'; +import { OpcVersionProcessor } from '@home/components/widget/lib/gateway/abstract/opc-version-processor.abstract'; +import { ModbusVersionProcessor } from '@home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract'; export abstract class GatewayConnectorVersionMappingUtil { @@ -27,6 +31,10 @@ export abstract class GatewayConnectorVersionMappingUtil { switch(connector.type) { case ConnectorType.MQTT: return new MqttVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); + case ConnectorType.OPCUA: + return new OpcVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); + case ConnectorType.MODBUS: + return new ModbusVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); default: return connector; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts new file mode 100644 index 0000000000..b65d3e7449 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + ModbusDataType, + ModbusLegacyRegisterValues, + ModbusLegacySlave, + ModbusMasterConfig, + ModbusRegisterValues, + ModbusSlave, ModbusValue, ModbusValues, + SlaveConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export class ModbusVersionMappingUtil { + static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { + return { + slaves: master.slaves.map((slave: SlaveConfig) => ({ + ...slave, + deviceType: slave.deviceType ?? 'default', + })) + }; + } + + static mapSlaveToDowngradedVersion(slave: ModbusSlave): ModbusLegacySlave { + const values = Object.keys(slave.values).reduce((acc, valueKey) => { + acc = { + ...acc, + [valueKey]: [ + slave.values[valueKey] + ] + }; + return acc; + }, {} as ModbusLegacyRegisterValues); + return { + ...slave, + values + }; + } + + static mapSlaveToUpgradedVersion(slave: ModbusLegacySlave): ModbusSlave { + const values = Object.keys(slave.values).reduce((acc, valueKey) => { + acc = { + ...acc, + [valueKey]: this.mapValuesToUpgradedVersion(slave.values[valueKey][0]) + }; + return acc; + }, {} as ModbusRegisterValues); + return { + ...slave, + values + }; + } + + private static mapValuesToUpgradedVersion(registerValues: ModbusValues): ModbusValues { + return Object.keys(registerValues).reduce((acc, valueKey) => { + acc = { + ...acc, + [valueKey]: registerValues[valueKey].map((value: ModbusValue) => + ({ ...value, type: (value.type as string) === 'int' ? ModbusDataType.INT16 : value.type })) + }; + return acc; + }, {} as ModbusValues); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts new file mode 100644 index 0000000000..56eea67ddf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts @@ -0,0 +1,113 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + DeviceConnectorMapping, + LegacyDeviceConnectorMapping, + LegacyServerConfig, + OPCBasicConfig_v3_5_2, + ServerConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export class OpcVersionMappingUtil { + static mapServerToUpgradedVersion(server: LegacyServerConfig): ServerConfig { + const { mapping, disableSubscriptions, ...restServer } = server; + return { + ...restServer, + enableSubscriptions: !disableSubscriptions, + }; + } + + static mapServerToDowngradedVersion(config: OPCBasicConfig_v3_5_2): LegacyServerConfig { + const { mapping, server } = config; + const { enableSubscriptions, ...restServer } = server; + return { + ...restServer, + mapping: mapping ? this.mapMappingToDowngradedVersion(mapping) : [], + disableSubscriptions: !enableSubscriptions, + }; + } + + static mapMappingToUpgradedVersion(mapping: LegacyDeviceConnectorMapping[]): DeviceConnectorMapping[] { + return mapping?.map((oldMapping: any) => ({ + ...oldMapping, + deviceNodeSource: 'path', + deviceInfo: { + deviceNameExpression: oldMapping.deviceNamePattern, + deviceNameExpressionSource: 'path', + deviceProfileExpression: oldMapping.deviceTypePattern ?? 'default', + deviceProfileExpressionSource: 'path', + }, + attributes: oldMapping.attributes.map(attribute => ({ + key: attribute.key, + type: 'path', + value: attribute.path, + })), + attributes_updates: oldMapping.attributes_updates.map(attributeUpdate => ({ + key: attributeUpdate.attributeOnThingsBoard, + type: 'path', + value: attributeUpdate.attributeOnDevice, + })), + timeseries: oldMapping.timeseries.map(timeseries => ({ + key: timeseries.key, + type: 'path', + value: timeseries.path, + })), + rpc_methods: oldMapping.rpc_methods.map(rpcMethod => ({ + method: rpcMethod.method, + arguments: rpcMethod.arguments.map(arg => ({ + value: arg, + type: this.getArgumentType(arg), + })) + })) + })); + } + + static mapMappingToDowngradedVersion(mapping: DeviceConnectorMapping[]): LegacyDeviceConnectorMapping[] { + return mapping?.map((newMapping: DeviceConnectorMapping) => ({ + ...newMapping, + deviceNamePattern: newMapping.deviceInfo.deviceNameExpression, + deviceTypePattern: newMapping.deviceInfo.deviceProfileExpression, + attributes: newMapping.attributes.map((attribute: any) => ({ + key: attribute.key, + path: attribute.value, + })), + attributes_updates: newMapping.attributes_updates.map((attributeUpdate: any) => ({ + attributeOnThingsBoard: attributeUpdate.key, + attributeOnDevice: attributeUpdate.value, + })), + timeseries: newMapping.timeseries.map((timeseries: any) => ({ + key: timeseries.key, + path: timeseries.value, + })), + rpc_methods: newMapping.rpc_methods.map((rpcMethod: any) => ({ + method: rpcMethod.method, + arguments: rpcMethod.arguments.map((arg: any) => arg.value) + })) + })); + } + + private static getArgumentType(arg: unknown): string { + switch (typeof arg) { + case 'boolean': + return 'boolean'; + case 'number': + return Number.isInteger(arg) ? 'integer' : 'float'; + default: + return 'string'; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index b3663c9b48..f241bc91d0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -116,10 +116,10 @@ import { } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; import { WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; import { OpcServerConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; import { MqttBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component'; @@ -128,7 +128,7 @@ import { } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; import { OpcUaBasicConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component'; import { ModbusBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component'; @@ -154,6 +154,12 @@ import { import { GatewayAdvancedConfigurationComponent } from '@home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component'; +import { + OpcUaLegacyBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component'; +import { + ModbusLegacyBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component'; @NgModule({ declarations: [ @@ -245,6 +251,8 @@ import { MqttLegacyBasicConfigComponent, GatewayBasicConfigurationComponent, GatewayAdvancedConfigurationComponent, + OpcUaLegacyBasicConfigComponent, + ModbusLegacyBasicConfigComponent, ], exports: [ EntitiesTableWidgetComponent, diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index 441f63ab5f..96fb59dcf5 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -1,246 +1,474 @@ { - "master": { - "slaves": [ - { - "name": "Slave 1", - "host": "127.0.0.1", - "port": 5021, - "type": "tcp", - "method": "socket", - "timeout": 35, - "byteOrder": "LITTLE", - "wordOrder": "LITTLE", - "retries": true, - "retryOnEmpty": true, - "retryOnInvalid": true, - "pollPeriod": 5000, - "unitId": 1, - "deviceName": "Temp Sensor", - "deviceType": "default", - "sendDataOnlyOnChange": true, - "connectAttemptTimeMs": 5000, - "connectAttemptCount": 5, - "waitAfterFailedAttemptsMs": 300000, - "attributes": [ - { - "tag": "string_read", - "type": "string", - "functionCode": 4, - "objectsCount": 4, - "address": 1 - }, - { - "tag": "bits_read", - "type": "bits", - "functionCode": 4, - "objectsCount": 1, - "address": 5 - }, - { - "tag": "8int_read", - "type": "8int", - "functionCode": 4, - "objectsCount": 1, - "address": 6 - }, - { - "tag": "16int_read", - "type": "16int", - "functionCode": 4, - "objectsCount": 1, - "address": 7 - }, - { - "tag": "32int_read_divider", - "type": "32int", - "functionCode": 4, - "objectsCount": 2, - "address": 8, - "divider": 10 - }, - { - "tag": "8int_read_multiplier", - "type": "8int", - "functionCode": 4, - "objectsCount": 1, - "address": 10, - "multiplier": 10 - }, - { - "tag": "32int_read", - "type": "32int", - "functionCode": 4, - "objectsCount": 2, - "address": 11 - }, - { - "tag": "64int_read", - "type": "64int", - "functionCode": 4, - "objectsCount": 4, - "address": 13 - } - ], - "timeseries": [ - { - "tag": "8uint_read", - "type": "8uint", - "functionCode": 4, - "objectsCount": 1, - "address": 17 - }, - { - "tag": "16uint_read", - "type": "16uint", - "functionCode": 4, - "objectsCount": 2, - "address": 18 - }, - { - "tag": "32uint_read", - "type": "32uint", - "functionCode": 4, - "objectsCount": 4, - "address": 20 - }, - { - "tag": "64uint_read", - "type": "64uint", - "functionCode": 4, - "objectsCount": 1, - "address": 24 - }, - { - "tag": "16float_read", - "type": "16float", - "functionCode": 4, - "objectsCount": 1, - "address": 25 - }, - { - "tag": "32float_read", - "type": "32float", - "functionCode": 4, - "objectsCount": 2, - "address": 26 - }, - { - "tag": "64float_read", - "type": "64float", - "functionCode": 4, - "objectsCount": 4, - "address": 28 - } - ], - "attributeUpdates": [ - { - "tag": "shared_attribute_write", - "type": "32int", - "functionCode": 6, - "objectsCount": 2, - "address": 29 - } - ], - "rpc": [ - { - "tag": "setValue", - "type": "bits", - "functionCode": 5, - "objectsCount": 1, - "address": 31 - }, - { - "tag": "getValue", - "type": "bits", - "functionCode": 1, - "objectsCount": 1, - "address": 31 - }, - { - "tag": "setCPUFanSpeed", - "type": "32int", - "functionCode": 16, - "objectsCount": 2, - "address": 33 - }, - { - "tag": "getCPULoad", - "type": "32int", - "functionCode": 4, - "objectsCount": 2, - "address": 35 - } - ] - } - ] - }, - "slave": { - "type": "tcp", - "host": "127.0.0.1", - "port": 5026, - "method": "socket", - "deviceName": "Modbus Slave Example", - "deviceType": "default", - "pollPeriod": 5000, - "sendDataToThingsBoard": false, - "byteOrder": "LITTLE", - "wordOrder": "LITTLE", - "unitId": 0, - "values": { - "holding_registers": { - "attributes": [ + "3.5.1": { + "master": { + "slaves": [ { - "address": 1, - "type": "string", - "tag": "sm", - "objectsCount": 1, - "value": "ON" + "name": "Slave 1", + "host": "127.0.0.1", + "port": 5021, + "type": "tcp", + "method": "socket", + "timeout": 35, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "retries": true, + "retryOnEmpty": true, + "retryOnInvalid": true, + "pollPeriod": 5000, + "unitId": 1, + "deviceName": "Temp Sensor", + "deviceType": "default", + "sendDataOnlyOnChange": true, + "connectAttemptTimeMs": 5000, + "connectAttemptCount": 5, + "waitAfterFailedAttemptsMs": 300000, + "attributes": [ + { + "tag": "string_read", + "type": "string", + "functionCode": 4, + "objectsCount": 4, + "address": 1 + }, + { + "tag": "bits_read", + "type": "bits", + "functionCode": 4, + "objectsCount": 1, + "address": 5 + }, + { + "tag": "8int_read", + "type": "8int", + "functionCode": 4, + "objectsCount": 1, + "address": 6 + }, + { + "tag": "16int_read", + "type": "16int", + "functionCode": 4, + "objectsCount": 1, + "address": 7 + }, + { + "tag": "32int_read_divider", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 8, + "divider": 10 + }, + { + "tag": "8int_read_multiplier", + "type": "8int", + "functionCode": 4, + "objectsCount": 1, + "address": 10, + "multiplier": 10 + }, + { + "tag": "32int_read", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 11 + }, + { + "tag": "64int_read", + "type": "64int", + "functionCode": 4, + "objectsCount": 4, + "address": 13 + } + ], + "timeseries": [ + { + "tag": "8uint_read", + "type": "8uint", + "functionCode": 4, + "objectsCount": 1, + "address": 17 + }, + { + "tag": "16uint_read", + "type": "16uint", + "functionCode": 4, + "objectsCount": 2, + "address": 18 + }, + { + "tag": "32uint_read", + "type": "32uint", + "functionCode": 4, + "objectsCount": 4, + "address": 20 + }, + { + "tag": "64uint_read", + "type": "64uint", + "functionCode": 4, + "objectsCount": 1, + "address": 24 + }, + { + "tag": "16float_read", + "type": "16float", + "functionCode": 4, + "objectsCount": 1, + "address": 25 + }, + { + "tag": "32float_read", + "type": "32float", + "functionCode": 4, + "objectsCount": 2, + "address": 26 + }, + { + "tag": "64float_read", + "type": "64float", + "functionCode": 4, + "objectsCount": 4, + "address": 28 + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "getValue", + "type": "bits", + "functionCode": 1, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "setCPUFanSpeed", + "type": "32int", + "functionCode": 16, + "objectsCount": 2, + "address": 33 + }, + { + "tag": "getCPULoad", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 35 + } + ] } - ], - "timeseries": [ - { - "address": 2, - "type": "8int", - "tag": "smm", - "objectsCount": 1, - "value": "12334" - } - ], - "attributeUpdates": [ - { - "tag": "shared_attribute_write", - "type": "32int", - "functionCode": 6, - "objectsCount": 2, - "address": 29, - "value": 1243 + ] + }, + "slave": { + "type": "tcp", + "host": "127.0.0.1", + "port": 5026, + "method": "socket", + "deviceName": "Modbus Slave Example", + "deviceType": "default", + "pollPeriod": 5000, + "sendDataToThingsBoard": false, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "unitId": 0, + "values": { + "holding_registers": { + "attributes": [ + { + "address": 1, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "ON" + } + ], + "timeseries": [ + { + "address": 2, + "type": "8int", + "tag": "smm", + "objectsCount": 1, + "value": "12334" + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29, + "value": 1243 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31, + "value": 22 + } + ] + }, + "coils_initializer": { + "attributes": [ + { + "address": 5, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "12" + } + ], + "timeseries": [], + "attributeUpdates": [], + "rpc": [] } - ], - "rpc": [ + } + } + }, + "legacy": { + "master": { + "slaves": [ { - "tag": "setValue", - "type": "bits", - "functionCode": 5, - "objectsCount": 1, - "address": 31, - "value": 22 + "host": "127.0.0.1", + "port": 5021, + "type": "tcp", + "method": "socket", + "timeout": 35, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "retries": true, + "retryOnEmpty": true, + "retryOnInvalid": true, + "pollPeriod": 5000, + "unitId": 1, + "deviceName": "Temp Sensor", + "sendDataOnlyOnChange": true, + "connectAttemptTimeMs": 5000, + "connectAttemptCount": 5, + "waitAfterFailedAttemptsMs": 300000, + "attributes": [ + { + "tag": "string_read", + "type": "string", + "functionCode": 4, + "objectsCount": 4, + "address": 1 + }, + { + "tag": "bits_read", + "type": "bits", + "functionCode": 4, + "objectsCount": 1, + "address": 5 + }, + { + "tag": "16int_read", + "type": "16int", + "functionCode": 4, + "objectsCount": 1, + "address": 7 + }, + { + "tag": "32int_read_divider", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 8, + "divider": 10 + }, + { + "tag": "32int_read", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 11 + }, + { + "tag": "64int_read", + "type": "64int", + "functionCode": 4, + "objectsCount": 4, + "address": 13 + } + ], + "timeseries": [ + { + "tag": "16uint_read", + "type": "16uint", + "functionCode": 4, + "objectsCount": 2, + "address": 18 + }, + { + "tag": "32uint_read", + "type": "32uint", + "functionCode": 4, + "objectsCount": 4, + "address": 20 + }, + { + "tag": "64uint_read", + "type": "64uint", + "functionCode": 4, + "objectsCount": 1, + "address": 24 + }, + { + "tag": "16float_read", + "type": "16float", + "functionCode": 4, + "objectsCount": 1, + "address": 25 + }, + { + "tag": "32float_read", + "type": "32float", + "functionCode": 4, + "objectsCount": 2, + "address": 26 + }, + { + "tag": "64float_read", + "type": "64float", + "functionCode": 4, + "objectsCount": 4, + "address": 28 + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "getValue", + "type": "bits", + "functionCode": 1, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "setCPUFanSpeed", + "type": "32int", + "functionCode": 16, + "objectsCount": 2, + "address": 33 + }, + { + "tag": "getCPULoad", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 35 + } + ] } ] }, - "coils_initializer": { - "attributes": [ - { - "address": 5, - "type": "string", - "tag": "sm", - "objectsCount": 1, - "value": "12" - } - ], - "timeseries": [], - "attributeUpdates": [], - "rpc": [] + "slave": { + "type": "tcp", + "host": "127.0.0.1", + "port": 5026, + "method": "socket", + "deviceName": "Modbus Slave Example", + "deviceType": "default", + "pollPeriod": 5000, + "sendDataToThingsBoard": false, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "unitId": 0, + "values": { + "holding_registers": [ + { + "attributes": [ + { + "address": 1, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "ON" + } + ], + "timeseries": [ + { + "address": 2, + "type": "int", + "tag": "smm", + "objectsCount": 1, + "value": "12334" + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29, + "value": 1243 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31, + "value": 22 + } + ] + } + ], + "coils_initializer": [ + { + "attributes": [ + { + "address": 5, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "12" + } + ], + "timeseries": [], + "attributeUpdates": [], + "rpc": [] + } + ] + } } } - } } diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json index 7ff5b78433..eb33f9235e 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json @@ -1,66 +1,120 @@ { - "server": { - "url": "localhost:4840/freeopcua/server/", - "timeoutInMillis": 5000, - "scanPeriodInMillis": 3600000, - "pollPeriodInMillis": 5000, - "enableSubscriptions": true, - "subCheckPeriodInMillis": 100, - "showMap": false, - "security": "Basic128Rsa15", - "identity": { - "type": "anonymous" - } - }, - "mapping": [{ - "deviceNodePattern": "Root\\.Objects\\.Device1", - "deviceNodeSource": "path", - "deviceInfo": { - "deviceNameExpression": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", - "deviceNameExpressionSource": "path", - "deviceProfileExpression": "Device", - "deviceProfileExpressionSource": "constant" + "3.5.1": { + "server": { + "url": "localhost:4840/freeopcua/server/", + "timeoutInMillis": 5000, + "scanPeriodInMillis": 3600000, + "pollPeriodInMillis": 5000, + "enableSubscriptions": true, + "subCheckPeriodInMillis": 100, + "showMap": false, + "security": "Basic128Rsa15", + "identity": { + "type": "anonymous" + } + }, + "mapping": [{ + "deviceNodePattern": "Root\\.Objects\\.Device1", + "deviceNodeSource": "path", + "deviceInfo": { + "deviceNameExpression": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", + "deviceNameExpressionSource": "path", + "deviceProfileExpression": "Device", + "deviceProfileExpressionSource": "constant" + }, + "attributes": [ + { + "key": "temperature °C", + "type": "path", + "value": "${ns=2;i=5}" + } + ], + "timeseries": [ + { + "key": "humidity", + "type": "path", + "value": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" + }, + { + "key": "batteryLevel", + "type": "path", + "value": "${Battery\\.batteryLevel}" + } + ], + "rpc_methods": [ + { + "method": "multiply", + "arguments": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 4 + } + ] + } + ], + "attributes_updates": [ + { + "key": "deviceName", + "type": "path", + "value": "Root\\.Objects\\.Device1\\.serialNumber" + } + ] + }] }, - "attributes": [ - { - "key": "temperature °C", - "type": "path", - "value": "${ns=2;i=5}" - } - ], - "timeseries": [ - { - "key": "humidity", - "type": "path", - "value": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" - }, - { - "key": "batteryLevel", - "type": "path", - "value": "${Battery\\.batteryLevel}" - } - ], - "rpc_methods": [ - { - "method": "multiply", - "arguments": [ - { - "type": "integer", - "value": 2 - }, - { - "type": "integer", - "value": 4 - } - ] - } - ], - "attributes_updates": [ - { - "key": "deviceName", - "type": "path", - "value": "Root\\.Objects\\.Device1\\.serialNumber" - } - ] - }] + "legacy": { + "server": { + "name": "OPC-UA Default Server", + "url": "localhost:4840/freeopcua/server/", + "timeoutInMillis": 5000, + "scanPeriodInMillis": 5000, + "disableSubscriptions": false, + "subCheckPeriodInMillis": 100, + "showMap": false, + "security": "Basic128Rsa15", + "identity": { + "type": "anonymous" + }, + "mapping": [ + { + "deviceNodePattern": "Root\\.Objects\\.Device1", + "deviceNamePattern": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", + "attributes": [ + { + "key": "temperature °C", + "path": "${ns=2;i=5}" + } + ], + "timeseries": [ + { + "key": "humidity", + "path": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" + }, + { + "key": "batteryLevel", + "path": "${Battery\\.batteryLevel}" + } + ], + "rpc_methods": [ + { + "method": "multiply", + "arguments": [ + 2, + 4 + ] + } + ], + "attributes_updates": [ + { + "attributeOnThingsBoard": "deviceName", + "attributeOnDevice": "Root\\.Objects\\.Device1\\.serialNumber" + } + ] + } + ] + } + } } From d7c5c23afc81946efd145b29d3801815594f3a5a Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Tue, 10 Sep 2024 18:39:27 +0300 Subject: [PATCH 48/84] Timewindow: small fixes --- ui-ngx/src/app/shared/components/time/timewindow-form.scss | 4 ++++ .../shared/components/time/timewindow-panel.component.html | 2 +- .../shared/components/time/timewindow-panel.component.scss | 4 ---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 8a6176acbf..ac7cedaec1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -23,6 +23,10 @@ background-color: #fff; .tb-timewindow-form { + display: flex; + flex-direction: column; + overflow: hidden; + .tb-flex { gap: 16px; } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 70dd8e4267..cc53f62bd6 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -153,7 +153,7 @@
-
Date: Tue, 10 Sep 2024 13:15:28 +0200 Subject: [PATCH 49/84] removed wrong CONNECTION close header --- .../main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index f8ec81abcf..d7476bf08e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -135,7 +135,6 @@ public class TbHttpClient { this.webClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) - .defaultHeader(HttpHeaders.CONNECTION, "close") //In previous realization this header was present! (Added for hotfix "Connection reset") .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize( (config.getMaxInMemoryBufferSizeInKb() > 0 ? config.getMaxInMemoryBufferSizeInKb() : 256) * 1024)) .build(); From 5329fe3aa18a8ca386f0114171242e1ec147afde Mon Sep 17 00:00:00 2001 From: mpetrov Date: Tue, 10 Sep 2024 19:18:21 +0300 Subject: [PATCH 50/84] [4114] Adjusted OPC mapping --- .../lib/gateway/gateway-widget.models.ts | 9 +-- .../gateway/utils/opc-version-mapping.util.ts | 70 ++++++++++++------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 8a0e4005fd..b933ff1186 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -265,9 +265,9 @@ export interface WorkersConfig { export interface ConnectorDeviceInfo { deviceNameExpression: string; - deviceNameExpressionSource: SourceType; + deviceNameExpressionSource: SourceType | OPCUaSourceType; deviceProfileExpression: string; - deviceProfileExpressionSource: SourceType; + deviceProfileExpressionSource: SourceType | OPCUaSourceType; } export interface Attribute { @@ -292,7 +292,7 @@ export interface LegacyTimeseries { path: string; } -interface RpcArgument { +export interface RpcArgument { type: string; value: number; } @@ -363,7 +363,7 @@ export type ConverterMappingFormValue = Omit ({ - ...oldMapping, - deviceNodeSource: 'path', + return mapping.map((legacyMapping: LegacyDeviceConnectorMapping) => ({ + ...legacyMapping, + deviceNodeSource: this.getTypeSourceByValue(legacyMapping.deviceNodePattern), deviceInfo: { - deviceNameExpression: oldMapping.deviceNamePattern, - deviceNameExpressionSource: 'path', - deviceProfileExpression: oldMapping.deviceTypePattern ?? 'default', - deviceProfileExpressionSource: 'path', + deviceNameExpression: legacyMapping.deviceNamePattern, + deviceNameExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceNamePattern), + deviceProfileExpression: legacyMapping.deviceTypePattern ?? 'default', + deviceProfileExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceTypePattern ?? 'default'), }, - attributes: oldMapping.attributes.map(attribute => ({ + attributes: legacyMapping.attributes.map((attribute: LegacyAttribute) => ({ key: attribute.key, - type: 'path', + type: this.getTypeSourceByValue(attribute.path), value: attribute.path, })), - attributes_updates: oldMapping.attributes_updates.map(attributeUpdate => ({ + attributes_updates: legacyMapping.attributes_updates.map((attributeUpdate: LegacyDeviceAttributeUpdate) => ({ key: attributeUpdate.attributeOnThingsBoard, - type: 'path', + type: this.getTypeSourceByValue(attributeUpdate.attributeOnDevice), value: attributeUpdate.attributeOnDevice, })), - timeseries: oldMapping.timeseries.map(timeseries => ({ + timeseries: legacyMapping.timeseries.map((timeseries: LegacyTimeseries) => ({ key: timeseries.key, - type: 'path', + type: this.getTypeSourceByValue(timeseries.path), value: timeseries.path, })), - rpc_methods: oldMapping.rpc_methods.map(rpcMethod => ({ + rpc_methods: legacyMapping.rpc_methods.map((rpcMethod: LegacyRpcMethod) => ({ method: rpcMethod.method, arguments: rpcMethod.arguments.map(arg => ({ value: arg, type: this.getArgumentType(arg), - })) + } as RpcArgument)) })) })); } static mapMappingToDowngradedVersion(mapping: DeviceConnectorMapping[]): LegacyDeviceConnectorMapping[] { - return mapping?.map((newMapping: DeviceConnectorMapping) => ({ - ...newMapping, - deviceNamePattern: newMapping.deviceInfo.deviceNameExpression, - deviceTypePattern: newMapping.deviceInfo.deviceProfileExpression, - attributes: newMapping.attributes.map((attribute: any) => ({ + return mapping.map((upgradedMapping: DeviceConnectorMapping) => ({ + ...upgradedMapping, + deviceNamePattern: upgradedMapping.deviceInfo.deviceNameExpression, + deviceTypePattern: upgradedMapping.deviceInfo.deviceProfileExpression, + attributes: upgradedMapping.attributes.map((attribute: Attribute) => ({ key: attribute.key, path: attribute.value, })), - attributes_updates: newMapping.attributes_updates.map((attributeUpdate: any) => ({ + attributes_updates: upgradedMapping.attributes_updates.map((attributeUpdate: AttributesUpdate) => ({ attributeOnThingsBoard: attributeUpdate.key, attributeOnDevice: attributeUpdate.value, })), - timeseries: newMapping.timeseries.map((timeseries: any) => ({ + timeseries: upgradedMapping.timeseries.map((timeseries: Timeseries) => ({ key: timeseries.key, path: timeseries.value, })), - rpc_methods: newMapping.rpc_methods.map((rpcMethod: any) => ({ + rpc_methods: upgradedMapping.rpc_methods.map((rpcMethod: RpcMethod) => ({ method: rpcMethod.method, - arguments: rpcMethod.arguments.map((arg: any) => arg.value) + arguments: rpcMethod.arguments.map((arg: RpcArgument) => arg.value) })) })); } + private static getTypeSourceByValue(value: string): OPCUaSourceType { + if (value.includes('${')) { + return OPCUaSourceType.IDENTIFIER; + } + if (value.includes(`/`) || value.includes('\\')) { + return OPCUaSourceType.PATH; + } + return OPCUaSourceType.CONST; + } + private static getArgumentType(arg: unknown): string { switch (typeof arg) { case 'boolean': From 4b697482cac4a48931b81c1e9136ef57fee8bf28 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 10 Sep 2024 18:19:04 +0200 Subject: [PATCH 51/84] added ability to configure maxConnections in TbHttpClient using env TB_POOL_MAX_CONNECTIONS --- .../rule/engine/rest/TbHttpClient.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index d7476bf08e..b0b41fba4b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; import reactor.netty.transport.ProxyProvider; import javax.net.ssl.SSLException; @@ -95,7 +96,12 @@ public class TbHttpClient { semaphore = new Semaphore(config.getMaxParallelRequestsCount()); } - HttpClient httpClient = HttpClient.create() + ConnectionProvider connectionProvider = ConnectionProvider + .builder("http") + .maxConnections(getPoolMaxConnections()) + .build(); + + HttpClient httpClient = HttpClient.create(connectionProvider) .runOn(getSharedOrCreateEventLoopGroup(eventLoopGroupShared)) .doOnConnected(c -> c.addHandlerLast(new ReadTimeoutHandler(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS))); @@ -143,6 +149,18 @@ public class TbHttpClient { } } + private int getPoolMaxConnections() { + String poolMaxConnectionsEnv = System.getenv("TB_POOL_MAX_CONNECTIONS"); + + int poolMaxConnections; + if (poolMaxConnectionsEnv != null) { + poolMaxConnections = Integer.parseInt(poolMaxConnectionsEnv); + } else { + poolMaxConnections = ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS; + } + return poolMaxConnections; + } + private void validateMaxInMemoryBufferSize(TbRestApiCallNodeConfiguration config) throws TbNodeException { int systemMaxInMemoryBufferSizeInKb = 25000; try { From 6104778cd24c1a767cd0193bf689a757b4fc03c7 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Tue, 10 Sep 2024 19:34:03 +0300 Subject: [PATCH 52/84] [4114] refactoring --- .../gateway/abstract/modbus-version-processor.abstract.ts | 1 + .../lib/gateway/abstract/mqtt-version-processor.abstract.ts | 2 ++ .../lib/gateway/abstract/opc-version-processor.abstract.ts | 1 + .../widget/lib/gateway/gateway-connectors.component.ts | 4 ++++ .../widget/lib/gateway/utils/modbus-version-mapping.util.ts | 5 ++++- .../widget/lib/gateway/utils/opc-version-mapping.util.ts | 1 + 6 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts index d520a5b1e8..abcf6ce597 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -34,6 +34,7 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor { const configurationJson = this.connector.configurationJson; return { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts index 645eb64fa4..f55269eadf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -27,6 +27,7 @@ import { MqttVersionMappingUtil } from '../utils/mqtt-version-mapping.util'; import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { + private readonly mqttRequestTypeKeys = Object.values(RequestType); constructor( @@ -35,6 +36,7 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { const { connectRequests, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts index 11c9908058..e0337d9fe3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts @@ -31,6 +31,7 @@ export class OpcVersionProcessor extends GatewayConnectorVersionProcessor { const server = this.connector.configurationJson.server as LegacyServerConfig; return { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 9be5b550ce..03765613da 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -245,6 +245,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie delete value.class; } + if (this.gatewayVersion && !value.configVersion) { + value.configVersion = this.gatewayVersion; + } + value.ts = Date.now(); return value; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts index b65d3e7449..fbb78059e1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts @@ -20,11 +20,14 @@ import { ModbusLegacySlave, ModbusMasterConfig, ModbusRegisterValues, - ModbusSlave, ModbusValue, ModbusValues, + ModbusSlave, + ModbusValue, + ModbusValues, SlaveConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; export class ModbusVersionMappingUtil { + static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { return { slaves: master.slaves.map((slave: SlaveConfig) => ({ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts index 161eaf7daa..35807065e9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts @@ -33,6 +33,7 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; export class OpcVersionMappingUtil { + static mapServerToUpgradedVersion(server: LegacyServerConfig): ServerConfig { const { mapping, disableSubscriptions, ...restServer } = server; return { From 23bf12e41c0d481ccd5e9baa11bdc62e5b181659 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 11 Sep 2024 10:53:23 +0300 Subject: [PATCH 53/84] UI: Change link for entity view documentation --- ui-ngx/src/app/shared/models/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 829e49257f..b43081c6e8 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -161,7 +161,7 @@ export const HelpLinks = { assetProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/asset-profiles`, edges: `${helpBaseUrl}/docs/edge/getting-started-guides/what-is-edge`, assets: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/assets`, - entityViews: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/entity-views`, + entityViews: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/entity-views`, entitiesImport: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/bulk-provisioning`, rulechains: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/rule-chains`, lwm2mResourceLibrary: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/lwm2m-api`, From 4431a32659082ddd1b2cefb3050e6983c9df6d66 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 11 Sep 2024 12:09:11 +0200 Subject: [PATCH 54/84] minor refactoring due to comments --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index b0b41fba4b..c7f0109271 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -150,7 +150,7 @@ public class TbHttpClient { } private int getPoolMaxConnections() { - String poolMaxConnectionsEnv = System.getenv("TB_POOL_MAX_CONNECTIONS"); + String poolMaxConnectionsEnv = System.getenv("TB_HTTP_POOL_MAX_CONNECTIONS"); int poolMaxConnections; if (poolMaxConnectionsEnv != null) { From 54e86b27a808e265173587352d4c7af6d878bdca Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 11 Sep 2024 15:26:06 +0200 Subject: [PATCH 55/84] minor refactoring due to comments --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index c7f0109271..566430bd8d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -97,7 +97,7 @@ public class TbHttpClient { } ConnectionProvider connectionProvider = ConnectionProvider - .builder("http") + .builder("rule-engine-http-client") .maxConnections(getPoolMaxConnections()) .build(); @@ -150,7 +150,7 @@ public class TbHttpClient { } private int getPoolMaxConnections() { - String poolMaxConnectionsEnv = System.getenv("TB_HTTP_POOL_MAX_CONNECTIONS"); + String poolMaxConnectionsEnv = System.getenv("TB_RE_HTTP_CLIENT_POOL_MAX_CONNECTIONS"); int poolMaxConnections; if (poolMaxConnectionsEnv != null) { From d435f91fac0c33f185ba015276e44341b1937069 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 11 Sep 2024 16:34:30 +0300 Subject: [PATCH 56/84] Made Resource details page read only for LwM2M resources --- .../resource/resources-library.component.html | 2 +- .../resource/resources-library.component.ts | 39 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html index 5c3d9da081..441a311021 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html @@ -58,7 +58,7 @@ resource.title - + {{ 'resource.title-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts index f6a8befe9b..de61618dd7 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts @@ -43,8 +43,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme readonly resourceType = ResourceType; readonly resourceTypes: ResourceType[] = Object.values(this.resourceType); readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; - - maxResourceSize = getCurrentAuthState(this.store).maxResourceSize; + readonly maxResourceSize = getCurrentAuthState(this.store).maxResourceSize; private destroy$ = new Subject(); @@ -57,20 +56,20 @@ export class ResourcesLibraryComponent extends EntityComponent impleme super(store, fb, entityValue, entitiesTableConfigValue, cd); } - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); if (this.isAdd) { this.observeResourceTypeChange(); } } - ngOnDestroy() { + ngOnDestroy(): void { super.ngOnDestroy(); this.destroy$.next(); this.destroy$.complete(); } - hideDelete() { + hideDelete(): boolean { if (this.entitiesTableConfig) { return !this.entitiesTableConfig.deleteEnabled(this.entity); } else { @@ -87,19 +86,18 @@ export class ResourcesLibraryComponent extends EntityComponent impleme }); } - updateForm(entity: Resource) { + updateForm(entity: Resource): void { + const { resourceType, fileName, title, data } = entity; + this.updateTableReadonlyConfig(); + if (this.isEdit) { - this.entityForm.get('resourceType').disable({emitEvent: false}); - if (entity.resourceType !== ResourceType.JS_MODULE) { - this.entityForm.get('fileName').disable({emitEvent: false}); + this.entityForm.get('resourceType').disable({ emitEvent: false }); + if (resourceType !== ResourceType.JS_MODULE) { + this.entityForm.get('fileName').disable({ emitEvent: false }); } } - this.entityForm.patchValue({ - resourceType: entity.resourceType, - fileName: entity.fileName, - title: entity.title, - data: entity.data - }); + + this.entityForm.patchValue({ resourceType, fileName, title, data }); } prepareFormValue(formValue: Resource): Resource { @@ -109,7 +107,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme return super.prepareFormValue(formValue); } - getAllowedExtensions() { + getAllowedExtensions(): string { try { return ResourceTypeExtension.get(this.entityForm.get('resourceType').value); } catch (e) { @@ -117,7 +115,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme } } - getAcceptType() { + getAcceptType(): string { try { return ResourceTypeMIMETypes.get(this.entityForm.get('resourceType').value); } catch (e) { @@ -129,7 +127,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme return window.btoa(data); } - onResourceIdCopied() { + onResourceIdCopied(): void { this.store.dispatch(new ActionNotificationShow( { message: this.translate.instant('resource.idCopiedMessage'), @@ -140,6 +138,11 @@ export class ResourcesLibraryComponent extends EntityComponent impleme })); } + private updateTableReadonlyConfig(): void { + this.entitiesTableConfigValue.detailsReadonly = () => + this.resourceType.LWM2M_MODEL === this.entityForm.get('resourceType').value; + } + private observeResourceTypeChange(): void { this.entityForm.get('resourceType').valueChanges.pipe( startWith(ResourceType.JS_MODULE), From 148108006c29fa5537efca6cd02b1830987244e3 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 11 Sep 2024 17:15:20 +0300 Subject: [PATCH 57/84] [4114] fixed number version bug, added hide new field for opc and refactoring --- ...teway-connector-version-processor.abstract.ts | 16 ++++++++++------ .../modbus-version-processor.abstract.ts | 8 ++++---- .../abstract/mqtt-version-processor.abstract.ts | 8 ++++---- .../abstract/opc-version-processor.abstract.ts | 8 ++++---- .../opc-server-config.component.html | 2 +- .../opc-server-config.component.ts | 15 +++++++++++++-- .../opc-ua-basic-config.component.html | 2 +- .../opc-ua-basic-config.component.ts | 1 + .../opc-ua-legacy-basic-config.component.ts | 1 + .../widget/lib/gateway/gateway-widget.models.ts | 8 ++++---- .../connector-default-configs/modbus.json | 2 +- .../metadata/connector-default-configs/mqtt.json | 2 +- .../connector-default-configs/opcua.json | 2 +- 13 files changed, 46 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts index 5f06659823..944b5ffe6c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -15,14 +15,14 @@ /// import { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { isString } from '@core/utils'; +import { isNumber, isString } from '@core/utils'; export abstract class GatewayConnectorVersionProcessor { gatewayVersion: number; configVersion: number; - protected constructor(protected gatewayVersionStr: string, protected connector: GatewayConnector) { - this.gatewayVersion = this.parseVersion(this.gatewayVersionStr); + protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector) { + this.gatewayVersion = this.parseVersion(this.gatewayVersionIn); this.configVersion = this.parseVersion(connector.configVersion); } @@ -45,11 +45,15 @@ export abstract class GatewayConnectorVersionProcessor { } private isVersionUpgradeNeeded(): boolean { - return this.gatewayVersionStr === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion); + return this.gatewayVersionIn === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion); } - private parseVersion(version: string): number { - return isString(version) ? Number(version.replace(/\./g, '')) : 0; + private parseVersion(version: string | number): number { + if (isNumber(version)) { + return version as number; + } + + return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0; } protected abstract getDowngradedVersion(): GatewayConnector; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts index abcf6ce597..6ea8f4516d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -29,10 +29,10 @@ import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/ut export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor { constructor( - protected gatewayVersionStr: string, + protected gatewayVersionIn: string, protected connector: GatewayConnector ) { - super(gatewayVersionStr, connector); + super(gatewayVersionIn, connector); } getUpgradedVersion(): GatewayConnector { @@ -47,7 +47,7 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor; } @@ -62,7 +62,7 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts index f55269eadf..351ad76494 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -31,10 +31,10 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor ) { - super(gatewayVersionStr, connector); + super(gatewayVersionIn, connector); } getUpgradedVersion(): GatewayConnector { @@ -67,7 +67,7 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor; } @@ -85,7 +85,7 @@ export class MqttVersionProcessor extends GatewayConnectorVersionProcessor; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts index e0337d9fe3..e89c48fc28 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts @@ -26,10 +26,10 @@ import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils export class OpcVersionProcessor extends GatewayConnectorVersionProcessor { constructor( - protected gatewayVersionStr: string, + protected gatewayVersionIn: string, protected connector: GatewayConnector ) { - super(gatewayVersionStr, connector); + super(gatewayVersionIn, connector); } getUpgradedVersion(): GatewayConnector { @@ -40,7 +40,7 @@ export class OpcVersionProcessor extends GatewayConnectorVersionProcessor; } @@ -50,7 +50,7 @@ export class OpcVersionProcessor extends GatewayConnectorVersionProcessor; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html index 584679fa2c..523705d029 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html @@ -84,7 +84,7 @@
-
+
{{ 'gateway.poll-period' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts index c838121b76..c000139ded 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, OnDestroy } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -39,6 +39,7 @@ import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; import { HOUR } from '@shared/models/time/time.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-opc-server-config', @@ -64,7 +65,11 @@ import { HOUR } from '@shared/models/time/time.models'; SecurityConfigComponent, ] }) -export class OpcServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { +export class OpcServerConfigComponent implements ControlValueAccessor, Validator, AfterViewInit, OnDestroy { + + @Input() + @coerceBoolean() + hideNewFields: boolean = false; securityPolicyTypes = SecurityPolicyTypes; serverConfigFormGroup: UntypedFormGroup; @@ -95,6 +100,12 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator }); } + ngAfterViewInit(): void { + if (this.hideNewFields) { + this.serverConfigFormGroup.get('pollPeriodInMillis').disable({emitEvent: false}); + } + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html index 5f8b25af9d..2296a472a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html @@ -20,7 +20,7 @@ - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts index 1a45e74c7a..c45eafcc67 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -63,6 +63,7 @@ import { export class OpcUaBasicConfigComponent extends GatewayConnectorBasicConfigDirective { mappingTypes = MappingType; + isLegacy = false; protected override initBasicFormGroup(): FormGroup { return this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts index 12e05205a2..9458858cbf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts @@ -64,6 +64,7 @@ import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils export class OpcUaLegacyBasicConfigComponent extends GatewayConnectorBasicConfigDirective { mappingTypes = MappingType; + isLegacy = true; protected override initBasicFormGroup(): FormGroup { return this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index b933ff1186..0f940e28ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -131,7 +131,7 @@ export interface GatewayConnector extends Gate export interface GatewayVersionedDefaultConfig { legacy: GatewayConnector; - '3.5.1': GatewayConnector; + '3.5.2': GatewayConnector; } export interface DataMapping { @@ -189,7 +189,7 @@ export interface ConnectorSecurity { } export enum GatewayVersion { - Current = '3.5.1', + Current = '3.5.2', Legacy = 'legacy' } @@ -197,11 +197,11 @@ export type ConnectorMapping = DeviceConnectorMapping | RequestMappingValue | Co export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue; -export type ConnectorBaseConfig = ConnectorBaseConfig_v_3_5_2 | ConnectorLegacyConfig; +export type ConnectorBaseConfig = ConnectorBaseConfig_v3_5_2 | ConnectorLegacyConfig; export type ConnectorLegacyConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig | OPCLegacyBasicConfig | ModbusBasicConfig; -export type ConnectorBaseConfig_v_3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2 | OPCBasicConfig_v3_5_2; +export type ConnectorBaseConfig_v3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2 | OPCBasicConfig_v3_5_2; export interface ConnectorBaseInfo { name: string; diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index 96fb59dcf5..38bb44e036 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -1,5 +1,5 @@ { - "3.5.1": { + "3.5.2": { "master": { "slaves": [ { diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json index b3ba47215f..5b8b8dbd44 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json @@ -1,5 +1,5 @@ { - "3.5.1": { + "3.5.2": { "broker": { "host": "127.0.0.1", "port": 1883, diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json index eb33f9235e..c0ef993dd1 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json @@ -1,5 +1,5 @@ { - "3.5.1": { + "3.5.2": { "server": { "url": "localhost:4840/freeopcua/server/", "timeoutInMillis": 5000, From 63da803251412bc249861430b629894c63b36803 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 11 Sep 2024 17:39:21 +0300 Subject: [PATCH 58/84] Timewindow: fix mobile layout --- .../timewindow-config-dialog.component.html | 52 +++++++++---------- .../components/time/timewindow-form.scss | 5 -- .../time/timewindow-panel.component.html | 32 ++++++------ .../time/timewindow-panel.component.scss | 2 + .../components/time/timezone.component.scss | 2 +- .../src/app/shared/models/overlay.models.ts | 4 +- 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index 01a2cdf933..e36996197c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -15,17 +15,17 @@ limitations under the License. --> - -

{{ 'timewindow.timewindow-settings' | translate }}

- - - -
+ +

{{ 'timewindow.timewindow-settings' | translate }}

+ + + +
@@ -258,20 +258,20 @@
+ +
+ + +
- -
- - -
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index ac7cedaec1..0aca6157a6 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -16,10 +16,6 @@ @import "../../../../scss/constants"; :host { - display: flex; - flex-direction: column; - max-height: 100%; - max-width: 100%; background-color: #fff; .tb-timewindow-form { @@ -32,7 +28,6 @@ } &-content { - overflow-x: hidden; overflow-y: auto; tb-timeinterval, diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index cc53f62bd6..f47fb90271 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -195,20 +195,20 @@ formControlName="timezone"> + +
+ + +
- -
- - -
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index eae99d1e7a..9d9f12a5ce 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -18,6 +18,8 @@ width: 450px; .tb-timewindow-form { + max-height: 100%; + &-header { flex-direction: row; align-items: center; diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.scss b/ui-ngx/src/app/shared/components/time/timezone.component.scss index db4f492985..92f6c7fcc7 100644 --- a/ui-ngx/src/app/shared/components/time/timezone.component.scss +++ b/ui-ngx/src/app/shared/components/time/timezone.component.scss @@ -14,7 +14,7 @@ * limitations under the License. */ :host { - min-width: 48px; + min-width: 88px; margin: 8px 0; max-width: 100%; &.no-margin { diff --git a/ui-ngx/src/app/shared/models/overlay.models.ts b/ui-ngx/src/app/shared/models/overlay.models.ts index 46a2f6f5bc..40f871a6ad 100644 --- a/ui-ngx/src/app/shared/models/overlay.models.ts +++ b/ui-ngx/src/app/shared/models/overlay.models.ts @@ -40,5 +40,5 @@ export const POSITION_MAP: { [key: string]: ConnectionPositionPair } = { ) }; -export const DEFAULT_OVERLAY_POSITIONS = [POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft, - POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right, POSITION_MAP.bottom]; +export const DEFAULT_OVERLAY_POSITIONS = [POSITION_MAP.bottom, POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft, + POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right]; From 949b2c5f2e2c0b999eaf46d34a69fc94c2d0056c Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 11 Sep 2024 17:46:06 +0300 Subject: [PATCH 59/84] [3969] refactoring --- .../resources-library-table-config.resolve.ts | 5 ++++- .../resource/resources-library.component.ts | 16 ++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index 1e7eef1c41..148a47feac 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -22,7 +22,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { Resolve, Router } from '@angular/router'; -import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models'; +import { Resource, ResourceInfo, ResourceType, ResourceTypeTranslationMap } from '@shared/models/resource.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { DatePipe } from '@angular/common'; @@ -150,6 +150,9 @@ export class ResourcesLibraryTableConfigResolver implements Resolve impleme updateForm(entity: Resource): void { const { resourceType, fileName, title, data } = entity; - this.updateTableReadonlyConfig(); + this.entityForm.patchValue({ resourceType, fileName, title, data }); + } - if (this.isEdit) { + override updateFormState(): void { + super.updateFormState(); + if (this.isEdit && this.entityForm) { this.entityForm.get('resourceType').disable({ emitEvent: false }); - if (resourceType !== ResourceType.JS_MODULE) { + if (this.entityForm.get('resourceType').value !== ResourceType.JS_MODULE) { this.entityForm.get('fileName').disable({ emitEvent: false }); } } - - this.entityForm.patchValue({ resourceType, fileName, title, data }); } prepareFormValue(formValue: Resource): Resource { @@ -138,11 +139,6 @@ export class ResourcesLibraryComponent extends EntityComponent impleme })); } - private updateTableReadonlyConfig(): void { - this.entitiesTableConfigValue.detailsReadonly = () => - this.resourceType.LWM2M_MODEL === this.entityForm.get('resourceType').value; - } - private observeResourceTypeChange(): void { this.entityForm.get('resourceType').valueChanges.pipe( startWith(ResourceType.JS_MODULE), From 8d0f5744f331259e2835a0e563e99e22de7047ef Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 11 Sep 2024 18:32:53 +0300 Subject: [PATCH 60/84] Refactoring --- .../components/time/timeinterval.component.ts | 4 +++- .../time/timewindow-panel.component.html | 8 ++++---- ui-ngx/src/form.scss | 17 ++++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index a10f4c21c9..9cb43388cb 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -77,7 +77,9 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor, OnDe if (this.disabledAdvancedState !== disabledAdvanced) { this.disabledAdvancedState = disabledAdvanced; this.updateIntervalValue(); - this.boundInterval(); + if (!this.disabledAdvancedState) { + this.boundInterval(); + } } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index f47fb90271..0b93857a81 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -32,7 +32,7 @@
-
+
{{ 'timewindow.timewindow' | translate }}
@@ -73,7 +73,7 @@
-
+
{{ 'timewindow.timewindow' | translate }}
@@ -182,8 +182,8 @@
-
-
{{ 'timezone.timezone' | translate }}
+
+
{{ 'timezone.timezone' | translate }}
diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 40f0bd70d9..83c773cbbb 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -264,18 +264,21 @@ } .tb-flex { - display: flex; flex: 1; - gap: 8px; + &.no-flex { flex: none; - &.flex-xs { - @media #{$mat-xs} { - width: auto; - flex: 1; - } + } + + &-xs { + @media #{$mat-xs} { + flex: 1; } } + } + [class*="tb-flex"] { + display: flex; + gap: 8px; &.row { flex-direction: row; } From 358374ab697564d7e91f5ce8233dc13c94fef1ca Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 11 Sep 2024 18:44:53 +0300 Subject: [PATCH 61/84] [3969] table actions disabled fix --- .../resource/resources-library-table-config.resolve.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index 148a47feac..74535ca413 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -118,7 +118,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve this.isResourceEditable(resource, authUser.authority); this.config.entitySelectionEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); - this.config.detailsReadonly = (resource) => !this.isResourceEditable(resource, authUser.authority); + this.config.detailsReadonly = (resource) => !this.isResourceEditable(resource, authUser.authority, true); return this.config; } @@ -149,8 +149,8 @@ export class ResourcesLibraryTableConfigResolver implements Resolve Date: Wed, 11 Sep 2024 19:08:21 +0300 Subject: [PATCH 62/84] [4054] fixed otaPackage api call in accordance with documentation --- ui-ngx/src/app/core/http/ota-package.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/ota-package.service.ts b/ui-ngx/src/app/core/http/ota-package.service.ts index 9952dd4916..de062a94ee 100644 --- a/ui-ngx/src/app/core/http/ota-package.service.ts +++ b/ui-ngx/src/app/core/http/ota-package.service.ts @@ -59,7 +59,7 @@ export class OtaPackageService { } public getOtaPackage(otaPackageId: string, config?: RequestConfig): Observable { - return this.http.get(`/api/otaPackages/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); + return this.http.get(`/api/otaPackage/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); } public getOtaPackageInfo(otaPackageId: string, config?: RequestConfig): Observable { From c4eed0109a0d98883bc7a178ef511833865725ab Mon Sep 17 00:00:00 2001 From: Andrew1031 Date: Wed, 11 Sep 2024 09:21:37 -0700 Subject: [PATCH 63/84] Fixed function name typo in template script --- packaging/js/scripts/init/template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/js/scripts/init/template b/packaging/js/scripts/init/template index 4a8104ae05..a713a9bfcc 100644 --- a/packaging/js/scripts/init/template +++ b/packaging/js/scripts/init/template @@ -183,7 +183,7 @@ restart() { stop && start } -orce_reload() { +force_reload() { working_dir=$(dirname "$mainfile") pushd "$working_dir" > /dev/null [[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; } From 37ac8cac449ed8aa7acb4e752ea27bf6caf7fc66 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 11 Sep 2024 20:03:42 +0300 Subject: [PATCH 64/84] Add timezone components to modules map --- ui-ngx/src/app/modules/common/modules-map.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index c06863c6e8..a7d2e9ffae 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -335,6 +335,8 @@ import * as AssetProfileAutocompleteComponent from '@home/components/profile/ass import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-chain-select.component'; import { IModulesMap } from '@modules/common/modules-map.models'; +import { TimezoneComponent } from '@shared/components/time/timezone.component'; +import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; declare const System; @@ -466,6 +468,8 @@ class ModulesMap implements IModulesMap { '@shared/components/time/datetime-period.component': DatetimePeriodComponent, '@shared/components/time/datetime.component': DatetimeComponent, '@shared/components/time/timezone-select.component': TimezoneSelectComponent, + '@shared/components/time/timezone.component': TimezoneComponent, + '@shared/components/time/timezone-panel.component': TimezonePanelComponent, '@shared/components/value-input.component': ValueInputComponent, '@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent, '@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent, From 2e52c50b80188a955a05c1ed18cbfb7b42e48ac5 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 11 Sep 2024 20:48:57 +0300 Subject: [PATCH 65/84] Timewindow: move datapoints limit to a separate component --- ui-ngx/src/app/modules/common/modules-map.ts | 2 + .../time/datapoints-limit.component.html | 30 ++++ .../time/datapoints-limit.component.scss | 38 +++++ .../time/datapoints-limit.component.ts | 138 ++++++++++++++++++ .../timewindow-config-dialog.component.html | 18 +-- .../timewindow-config-dialog.component.ts | 11 +- .../components/time/timewindow-form.scss | 27 +--- .../time/timewindow-panel.component.html | 16 +- .../time/timewindow-panel.component.ts | 11 +- ui-ngx/src/app/shared/shared.module.ts | 3 + 10 files changed, 223 insertions(+), 71 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/time/datapoints-limit.component.html create mode 100644 ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index a7d2e9ffae..512719c513 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -337,6 +337,7 @@ import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-ch import { IModulesMap } from '@modules/common/modules-map.models'; import { TimezoneComponent } from '@shared/components/time/timezone.component'; import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; +import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; declare const System; @@ -470,6 +471,7 @@ class ModulesMap implements IModulesMap { '@shared/components/time/timezone-select.component': TimezoneSelectComponent, '@shared/components/time/timezone.component': TimezoneComponent, '@shared/components/time/timezone-panel.component': TimezonePanelComponent, + '@shared/components/time/datapoints-limit': DatapointsLimitComponent, '@shared/components/value-input.component': ValueInputComponent, '@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent, '@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html new file mode 100644 index 0000000000..8e2a5ac459 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html @@ -0,0 +1,30 @@ + +
+ + + + + + +
diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss new file mode 100644 index 0000000000..c2ffa0bb34 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2024 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. + */ +@import "../../../../scss/constants"; + +.limit-slider-container { + width: 100%; + .limit-slider-value { + margin-left: 16px; + min-width: 25px; + max-width: 106px; + } + mat-form-field input[type=number] { + text-align: center; + } +} + +@media #{$mat-gt-sm} { + .limit-slider-container { + > label { + margin-right: 16px; + width: min-content; + max-width: 40%; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts new file mode 100644 index 0000000000..21aff5a647 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR, UntypedFormGroup, Validators } from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { getTimezoneInfo } from '@shared/models/time/time.models'; +import { TimeService } from '@core/services/time.service'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'tb-datapoints-limit', + templateUrl: './datapoints-limit.component.html', + styleUrls: ['./datapoints-limit.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatapointsLimitComponent), + multi: true + }] +}) +export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, OnDestroy { + + datapointsLimitFormGroup: UntypedFormGroup; + + modelValue: number | null; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + const newVal = coerceBooleanProperty(value); + if (this.requiredValue !== newVal) { + this.requiredValue = newVal; + this.updateValidators(); + } + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder, + private timeService: TimeService) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.datapointsLimitFormGroup = this.fb.group({ + limit: [null] + }); + this.datapointsLimitFormGroup.get('limit').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateView(value); + }); + } + + updateValidators() { + if (this.datapointsLimitFormGroup) { + this.datapointsLimitFormGroup.get('limit').setValidators(this.required + ? [Validators.required, Validators.min(this.minDatapointsLimit()), + Validators.max(this.maxDatapointsLimit())] + : []); + this.datapointsLimitFormGroup.get('limit').updateValueAndValidity(); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.datapointsLimitFormGroup.disable({emitEvent: false}); + } else { + this.datapointsLimitFormGroup.enable({emitEvent: false}); + } + } + + private checkLimit(limit?: number): number { + if (!limit || limit < this.minDatapointsLimit()) { + return this.minDatapointsLimit(); + } else if (limit > this.maxDatapointsLimit()) { + return this.maxDatapointsLimit(); + } + return limit; + } + + writeValue(value: number | null): void { + this.modelValue = this.checkLimit(value); + this.datapointsLimitFormGroup.patchValue( + { limit: this.modelValue }, {emitEvent: false} + ); + } + + updateView(value: number | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + minDatapointsLimit() { + return this.timeService.getMinDatapointsLimit(); + } + + maxDatapointsLimit() { + return this.timeService.getMaxDatapointsLimit(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index e36996197c..333c70f24f 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -173,19 +173,11 @@ {{ 'timewindow.hide' | translate }} -
- - - - - - -
+ + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index ba7ee3e58c..cb16d7694a 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -191,7 +191,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On }), aggregation: this.fb.group({ type: [ isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null ], - limit: [ isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null ] + limit: [ isDefined(aggregation?.limit) ? this.timewindow.aggregation.limit : null ] }), timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ], hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation) @@ -292,15 +292,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.destroy$.complete(); } - private checkLimit(limit?: number): number { - if (!limit || limit < this.minDatapointsLimit()) { - return this.minDatapointsLimit(); - } else if (limit > this.maxDatapointsLimit()) { - return this.maxDatapointsLimit(); - } - return limit; - } - private updateValidators(aggType: AggregationType) { if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 0aca6157a6..2379910a79 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "../../../../scss/constants"; :host { background-color: #fff; @@ -32,32 +31,10 @@ tb-timeinterval, tb-quick-time-interval, - tb-datetime-period { + tb-datetime-period, + tb-datapoints-limit { flex: 1; } } } - - .limit-slider-container { - width: 100%; - .limit-slider-value { - margin-left: 16px; - min-width: 25px; - max-width: 106px; - } - mat-form-field input[type=number] { - text-align: center; - } - } - - @media #{$mat-gt-sm} { - .limit-slider-container { - > label { - margin-right: 16px; - width: min-content; - max-width: 40%; - } - } - } - } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 0b93857a81..e631eff949 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -137,19 +137,9 @@
{{ 'aggregation.limit' | translate }}
-
- - - - - - -
+ +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 8d51d2ec00..7b0286383a 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -275,7 +275,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O disabled: hideAggregation }], limit: [{ - value: isDefined(aggregation?.limit) ? this.checkLimit(aggregation.limit) : null, + value: isDefined(aggregation?.limit) ? aggregation.limit : null, disabled: hideAggInterval }, []] }), @@ -303,15 +303,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O this.destroy$.complete(); } - private checkLimit(limit?: number): number { - if (!limit || limit < this.minDatapointsLimit()) { - return this.minDatapointsLimit(); - } else if (limit > this.maxDatapointsLimit()) { - return this.maxDatapointsLimit(); - } - return limit; - } - private updateValidators(aggType: AggregationType) { if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 5afaf80fdb..130e272c21 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -228,6 +228,7 @@ import { ScadaSymbolInputComponent } from '@shared/components/image/scada-symbol import { CountryAutocompleteComponent } from '@shared/components/country-autocomplete.component'; import { CountryData } from '@shared/models/country.models'; import { SvgXmlComponent } from '@shared/components/svg-xml.component'; +import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -317,6 +318,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezoneComponent, TimezonePanelComponent, QuickTimeIntervalComponent, + DatapointsLimitComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -529,6 +531,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezoneComponent, TimezonePanelComponent, QuickTimeIntervalComponent, + DatapointsLimitComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, From 4bad947bad8bd99a4eec434b59ba91e8a761e149 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 12 Sep 2024 09:30:55 +0300 Subject: [PATCH 66/84] UI: Fixed behavior --- .../data/json/system/scada_symbols/vertical-ball-valve.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg b/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg index 3aee266486..f284c7f86d 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg @@ -93,7 +93,7 @@ }, "valueToData": { "type": "CONSTANT", - "constantValue": false, + "constantValue": true, "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" } }, @@ -127,7 +127,7 @@ }, "valueToData": { "type": "CONSTANT", - "constantValue": true, + "constantValue": false, "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" } }, From b49bf05e45d549ef2f08a024c7ce3cd756026465 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Sep 2024 09:31:07 +0300 Subject: [PATCH 67/84] UI: Minor fix. --- .../home/components/widget/lib/scada/scada-symbol.models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index 1163351fb8..8e5993902d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -933,7 +933,7 @@ export class ScadaSymbolObject { this.iconRegistry.getDefaultFontSetClass() ).filter(className => className.length > 0); fontSetClasses.forEach(className => textElement.addClass(className)); - textElement.font({size}); + textElement.font({size: `${size}px`}); textElement.attr({ 'text-anchor': 'start', 'dominant-baseline': 'hanging', From 0def9aed9b2d1f099d06c2b8ceabdfd413567f8b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 12 Sep 2024 09:40:24 +0300 Subject: [PATCH 68/84] UI: Refactoring resources-library improvement readonly --- .../resources-library-table-config.resolve.ts | 12 ++++++++---- .../admin/resource/resources-library.component.ts | 3 +-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index 74535ca413..66a704996b 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -118,7 +118,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve this.isResourceEditable(resource, authUser.authority); this.config.entitySelectionEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); - this.config.detailsReadonly = (resource) => !this.isResourceEditable(resource, authUser.authority, true); + this.config.detailsReadonly = (resource) => this.detailsReadonly(resource, authUser.authority); return this.config; } @@ -149,10 +149,14 @@ export class ResourcesLibraryTableConfigResolver implements Resolve impleme } updateForm(entity: Resource): void { - const { resourceType, fileName, title, data } = entity; - this.entityForm.patchValue({ resourceType, fileName, title, data }); + this.entityForm.patchValue(entity); } override updateFormState(): void { From e9475e5b06aa1bbae7244d659be556b0d7eae404 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Thu, 12 Sep 2024 10:16:23 +0300 Subject: [PATCH 69/84] Update event-table-config.ts --- .../app/modules/home/components/event/event-table-config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 107d08c2aa..c8947ed55d 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -40,7 +40,7 @@ import { EventContentDialogData } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; -import { historyInterval, HOUR, MINUTE } from '@shared/models/time/time.models'; +import { historyInterval, MINUTE } from '@shared/models/time/time.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ChangeDetectorRef, EventEmitter, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; @@ -94,7 +94,7 @@ export class EventTableConfig extends EntityTableConfig { this.loadDataOnInit = false; this.tableTitle = ''; this.useTimePageLink = true; - this.defaultTimewindowInterval = historyInterval(HOUR); + this.defaultTimewindowInterval = historyInterval(MINUTE * 15); this.detailsPanelEnabled = false; this.selectionEnabled = false; this.searchEnabled = false; From 56d7ab3bb6dd193d25c174f3d0927a668481f42c Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 12 Sep 2024 11:46:06 +0300 Subject: [PATCH 70/84] Timewindow: datapoints limit validation fix --- .../time/datapoints-limit.component.ts | 44 ++++++++++++++----- .../timewindow-config-dialog.component.ts | 11 +---- .../time/timewindow-panel.component.ts | 11 +---- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts index 21aff5a647..6818c2522d 100644 --- a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts @@ -15,9 +15,17 @@ /// import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR, UntypedFormGroup, 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 { getTimezoneInfo } from '@shared/models/time/time.models'; import { TimeService } from '@core/services/time.service'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @@ -26,15 +34,22 @@ import { Subject } from 'rxjs'; selector: 'tb-datapoints-limit', templateUrl: './datapoints-limit.component.html', styleUrls: ['./datapoints-limit.component.scss'], - providers: [{ + providers: [ + { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DatapointsLimitComponent), multi: true - }] + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DatapointsLimitComponent), + multi: true + } + ] }) -export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, OnDestroy { +export class DatapointsLimitComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { - datapointsLimitFormGroup: UntypedFormGroup; + datapointsLimitFormGroup: FormGroup; modelValue: number | null; @@ -71,7 +86,7 @@ export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, O ngOnInit() { this.datapointsLimitFormGroup = this.fb.group({ - limit: [null] + limit: [null, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]] }); this.datapointsLimitFormGroup.get('limit').valueChanges.pipe( takeUntil(this.destroy$) @@ -82,10 +97,11 @@ export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, O updateValidators() { if (this.datapointsLimitFormGroup) { - this.datapointsLimitFormGroup.get('limit').setValidators(this.required - ? [Validators.required, Validators.min(this.minDatapointsLimit()), - Validators.max(this.maxDatapointsLimit())] - : []); + if (this.required) { + this.datapointsLimitFormGroup.get('limit').addValidators(Validators.required); + } else { + this.datapointsLimitFormGroup.get('limit').removeValidators(Validators.required); + } this.datapointsLimitFormGroup.get('limit').updateValueAndValidity(); } } @@ -122,6 +138,12 @@ export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, O } } + validate(): ValidationErrors { + return this.datapointsLimitFormGroup.get('limit').valid ? null : { + datapointsLimitFormGroup: false, + }; + } + minDatapointsLimit() { return this.timeService.getMinDatapointsLimit(); } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index cb16d7694a..2e33ef0390 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -296,8 +296,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); } else { - this.timewindowForm.get('aggregation.limit').setValidators([Validators.min(this.minDatapointsLimit()), - Validators.max(this.maxDatapointsLimit())]); + this.timewindowForm.get('aggregation.limit').setValidators([Validators.required]); } this.timewindowForm.get('aggregation.limit').updateValueAndValidity({emitEvent: false}); } @@ -351,14 +350,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.dialogRef.close(); } - minDatapointsLimit() { - return this.timeService.getMinDatapointsLimit(); - } - - maxDatapointsLimit() { - return this.timeService.getMaxDatapointsLimit(); - } - minRealtimeAggInterval() { return this.timeService.minIntervalLimit(this.currentRealtimeTimewindow()); } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 7b0286383a..844815f51a 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -307,8 +307,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); } else { - this.timewindowForm.get('aggregation.limit').setValidators([Validators.min(this.minDatapointsLimit()), - Validators.max(this.maxDatapointsLimit())]); + this.timewindowForm.get('aggregation.limit').setValidators([Validators.required]); } this.timewindowForm.get('aggregation.limit').updateValueAndValidity({emitEvent: false}); } @@ -456,14 +455,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O this.overlayRef.dispose(); } - minDatapointsLimit() { - return this.timeService.getMinDatapointsLimit(); - } - - maxDatapointsLimit() { - return this.timeService.getMaxDatapointsLimit(); - } - minRealtimeAggInterval() { return this.timeService.minIntervalLimit(this.currentRealtimeTimewindow()); } From 127e2c19794533587e2cdef2b3d8d4326de3fa3b Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 12 Sep 2024 11:56:55 +0300 Subject: [PATCH 71/84] Refactoring --- .../src/app/shared/models/time/time.models.ts | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 603848f8b1..d0d4153b5f 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -139,18 +139,6 @@ export interface Aggregation { export interface Timewindow { displayValue?: string; displayTimezoneAbbr?: string; - /** - * @deprecated - */ - hideInterval?: boolean; - /** - * @deprecated - */ - hideQuickInterval?: boolean; - /** - * @deprecated - */ - hideLastInterval?: boolean; hideAggregation?: boolean; hideAggInterval?: boolean; hideTimezone?: boolean; @@ -319,15 +307,18 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO model.selectedTab = getTimewindowType(value); // for backward compatibility - if (isDefinedAndNotNull(value.hideInterval)) { - model.realtime.hideInterval = value.hideInterval; - model.history.hideInterval = value.hideInterval; + if (isDefinedAndNotNull((value as any).hideInterval)) { + model.realtime.hideInterval = (value as any).hideInterval; + model.history.hideInterval = (value as any).hideInterval; + delete (value as any).hideInterval; } - if (isDefinedAndNotNull(value.hideLastInterval)) { - model.realtime.hideLastInterval = value.hideLastInterval; + if (isDefinedAndNotNull((value as any).hideLastInterval)) { + model.realtime.hideLastInterval = (value as any).hideLastInterval; + delete (value as any).hideLastInterval; } - if (isDefinedAndNotNull(value.hideQuickInterval)) { - model.realtime.hideQuickInterval = value.hideQuickInterval; + if (isDefinedAndNotNull((value as any).hideQuickInterval)) { + model.realtime.hideQuickInterval = (value as any).hideQuickInterval; + delete (value as any).hideQuickInterval; } if (isDefined(value.realtime)) { From a66360ef6682d7c5d480b11562a7494f59b7445e Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 12 Sep 2024 12:13:20 +0300 Subject: [PATCH 72/84] Timewindow: change order of default overlay positions --- ui-ngx/src/app/shared/models/overlay.models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/models/overlay.models.ts b/ui-ngx/src/app/shared/models/overlay.models.ts index 40f871a6ad..46a2f6f5bc 100644 --- a/ui-ngx/src/app/shared/models/overlay.models.ts +++ b/ui-ngx/src/app/shared/models/overlay.models.ts @@ -40,5 +40,5 @@ export const POSITION_MAP: { [key: string]: ConnectionPositionPair } = { ) }; -export const DEFAULT_OVERLAY_POSITIONS = [POSITION_MAP.bottom, POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft, - POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right]; +export const DEFAULT_OVERLAY_POSITIONS = [POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft, + POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right, POSITION_MAP.bottom]; From 6b99fb917a2fd5ec177c5f2258ba461711a39a04 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 12 Sep 2024 12:52:30 +0300 Subject: [PATCH 73/84] Fix for NPE --- .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 55696c9e4b..d58249fef2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -557,7 +557,7 @@ public class DeviceServiceImpl extends CachedVersionedEntityService Date: Thu, 12 Sep 2024 12:54:50 +0300 Subject: [PATCH 74/84] Avoided additional info creation on provisioning if the device is not a gateway --- .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index d58249fef2..3cd25cc390 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -556,11 +556,11 @@ public class DeviceServiceImpl extends CachedVersionedEntityService Date: Thu, 12 Sep 2024 13:06:51 +0300 Subject: [PATCH 75/84] Timewindow: fix configuration dialog header height --- .../time/timewindow-config-dialog.component.scss | 7 +++++++ ui-ngx/src/app/shared/components/time/timewindow-form.scss | 2 -- .../shared/components/time/timewindow-panel.component.scss | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss index 358d28bf5a..eb25955005 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.scss @@ -28,3 +28,10 @@ } } } + +:host-context(.mat-mdc-dialog-container) { + .tb-timewindow-form { + display: grid; + grid-template-rows: min-content min-content minmax(auto, 1fr) min-content min-content; + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 2379910a79..5f436acc09 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -18,8 +18,6 @@ background-color: #fff; .tb-timewindow-form { - display: flex; - flex-direction: column; overflow: hidden; .tb-flex { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index 9d9f12a5ce..dcf03ed094 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -18,6 +18,8 @@ width: 450px; .tb-timewindow-form { + display: flex; + flex-direction: column; max-height: 100%; &-header { From 9eee7634c46f84d8dbeb90673b5be6760d878fec Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Sep 2024 13:23:57 +0300 Subject: [PATCH 76/84] WEB UI microservice: fallback to index.html for non-existent resource. --- msa/web-ui/server.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/msa/web-ui/server.ts b/msa/web-ui/server.ts index fc35e4cef3..6c69e0e9bf 100644 --- a/msa/web-ui/server.ts +++ b/msa/web-ui/server.ts @@ -99,7 +99,12 @@ let connections: Socket[] = []; const root = path.join(webDir, 'public'); - app.use(express.static(root)); + const staticServe = express.static(root); + + app.use(staticServe); + app.use((req, res, next) => { + res.sendFile(path.join(root, 'index.html')); + }); server.listen(bindPort, bindAddress, () => { logger.info('==> 🌎 Listening on port %s.', bindPort); From a8380f18e0fefa4c8e7f652c04fe9e6ef9f6d89a Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 12 Sep 2024 13:49:48 +0300 Subject: [PATCH 77/84] Modbus fixes and adjustments --- .../abstract/modbus-version-processor.abstract.ts | 5 ++--- .../modbus-basic-config.abstract.ts | 5 ----- .../modbus-basic-config.component.ts | 6 +++--- .../modbus-legacy-basic-config.component.ts | 4 +++- .../modbus-data-keys-panel.component.html | 12 +++++++++--- .../modbus-data-keys-panel.component.scss | 4 ++++ .../modbus-slave-config.component.html | 10 ++++++++++ .../modbus-slave-config.component.ts | 3 +++ .../modbus-slave-dialog.component.html | 2 +- .../modbus-slave-dialog.component.ts | 2 +- .../modbus-values/modbus-values.component.html | 8 ++++++++ .../gateway/dialog/mapping-dialog.component.html | 14 ++++++++++++++ .../lib/gateway/gateway-connectors.component.ts | 2 +- .../widget/lib/gateway/gateway-widget.models.ts | 5 +++-- .../gateway/utils/modbus-version-mapping.util.ts | 6 ++++++ 15 files changed, 68 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts index 6ea8f4516d..b38a07cd0f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -20,7 +20,6 @@ import { ModbusBasicConfig_v3_5_2, ModbusLegacyBasicConfig, ModbusLegacySlave, - ModbusMasterConfig, ModbusSlave, } from '../gateway-widget.models'; import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; @@ -40,9 +39,9 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor }); } - protected override onBasicFormGroupChange(value: ModbusBasicConfig_v3_5_2): void { - super.onBasicFormGroupChange(value); - this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); - } - private updateSlaveEnabling(isEnabled: boolean): void { if (isEnabled) { this.basicFormGroup.get('slave').enable({ emitEvent: false }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index 21c37ed147..b5fab7c92d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -58,10 +58,10 @@ import { }) export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { - protected override mapConfigToFormValue(config: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { + protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { return { - master: config.master ?? {} as ModbusMasterConfig, - slave: config.slave ?? {} as ModbusSlave, + master: master?.slaves ? master : { slaves: [] } as ModbusMasterConfig, + slave: slave ?? {} as ModbusSlave, }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts index 50f7d833fa..5bca9b0292 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts @@ -62,7 +62,9 @@ export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 { return { - master: config.master ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) : {} as ModbusMasterConfig, + master: config.master?.slaves + ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) + : { slaves: [] } as ModbusMasterConfig, slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave, }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html index cfb97f674d..fc94fe49ea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html @@ -31,9 +31,15 @@
-
{{ 'gateway.key' | translate }}: {{ keyControl.get('tag').value }}
-
{{ 'gateway.address' | translate }}: {{ keyControl.get('address').value }}
-
{{ 'gateway.type' | translate }}: {{ keyControl.get('type').value }}
+
{{ 'gateway.key' | translate }}: + {{ keyControl.get('tag').value }} +
+
{{ 'gateway.address' | translate }}: + {{ keyControl.get('address').value }} +
+
{{ 'gateway.type' | translate }}: + {{ keyControl.get('type').value }} +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss index 445ac6c0ae..d242fd4e71 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss @@ -23,6 +23,10 @@ width: 180px; } + .key-label { + font-weight: 400; + } + .key-panel { height: 500px; overflow: auto; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html index 9360ef499a..134a9baf0a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html @@ -197,6 +197,16 @@
+
+
gateway.word-order
+
+ + + {{ order }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts index 5f5b4d293c..f4dfa57f96 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts @@ -113,6 +113,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat pollPeriod: [5000, [Validators.required]], sendDataToThingsBoard: [false], byteOrder:[ModbusOrderType.BIG], + wordOrder: [ModbusOrderType.BIG], security: [], identity: this.fb.group({ vendorName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], @@ -243,6 +244,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat pollPeriod = 5000, sendDataToThingsBoard = false, byteOrder = ModbusOrderType.BIG, + wordOrder = ModbusOrderType.BIG, security = {}, identity = { vendorName: '', @@ -266,6 +268,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat pollPeriod, sendDataToThingsBoard: !!sendDataToThingsBoard, byteOrder, + wordOrder, security, identity, values, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index 1cf95ad06e..6304cdc0cb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -260,7 +260,7 @@
-
+
gateway.word-order
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index 5568a0e34c..0167eaafb7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -101,7 +101,7 @@ export class ModbusSlaveDialogComponent extends DialogComponent(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html index a6b4382638..8d1048e3cb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html @@ -48,6 +48,8 @@ mat-icon-button color="primary" [disabled]="disabled" + matTooltip="{{ 'action.edit' | translate }}" + matTooltipPosition="above" #attributesButton (click)="manageKeys($event, attributesButton, ModbusValueKey.ATTRIBUTES, register)"> edit @@ -69,6 +71,8 @@ mat-icon-button color="primary" [disabled]="disabled" + matTooltip="{{ 'action.edit' | translate }}" + matTooltipPosition="above" #telemetryButton (click)="manageKeys($event, telemetryButton, ModbusValueKey.TIMESERIES, register)"> edit @@ -90,6 +94,8 @@ mat-icon-button [disabled]="disabled" color="primary" + matTooltip="{{ 'action.edit' | translate }}" + matTooltipPosition="above" #attributesUpdatesButton (click)="manageKeys($event, attributesUpdatesButton, ModbusValueKey.ATTRIBUTES_UPDATES, register)"> edit @@ -111,6 +117,8 @@ mat-icon-button color="primary" [disabled]="disabled" + matTooltip="{{ 'action.edit' | translate }}" + matTooltipPosition="above" #rpcRequestsButton (click)="manageKeys($event, rpcRequestsButton, ModbusValueKey.RPC_REQUESTS, register)"> edit diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html index 17c002115c..b56449892b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html @@ -110,6 +110,8 @@ -
-