From 873aef9522ba14e58eb8f2f282347ede3230a442 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 27 Mar 2024 13:44:10 +0200 Subject: [PATCH 01/19] UI: added mqtt gateway connector configuration --- .../broker-security.component.html | 122 ++++ .../broker-security.component.scss | 20 + .../broker-security.component.ts | 167 ++++++ .../device-info-table.component.html | 87 +++ .../device-info-table.component.scss | 27 + .../device-info-table.component.ts | 169 ++++++ .../mapping-data-keys-panel.component.html | 152 +++++ .../mapping-data-keys-panel.component.scss | 32 ++ .../mapping-data-keys-panel.component.ts | 219 +++++++ .../mapping-table.component.html | 94 +++ .../mapping-table.component.scss | 134 +++++ .../mapping-table.component.ts | 342 +++++++++++ .../mqtt-basic-config.component.html | 175 ++++++ .../mqtt-basic-config.component.scss | 27 + .../mqtt-basic-config.component.ts | 150 +++++ .../add-connector-dialog.component.html | 102 ++++ .../add-connector-dialog.component.scss | 26 + .../dialog/add-connector-dialog.component.ts | 122 ++++ .../dialog/mapping-dialog.component.html | 539 ++++++++++++++++++ .../dialog/mapping-dialog.component.scss | 36 ++ .../dialog/mapping-dialog.component.ts | 351 ++++++++++++ .../gateway/gateway-connectors.component.html | 267 +++++++-- .../gateway/gateway-connectors.component.scss | 38 +- .../gateway/gateway-connectors.component.ts | 214 +++++-- .../lib/gateway/gateway-widget.models.ts | 158 +++++ .../widget/widget-components.module.ts | 21 + .../assets/locale/locale.constant-en_US.json | 141 +++++ .../connector-default-configs/mqtt.json | 218 +++++++ ui-ngx/src/form.scss | 12 + 29 files changed, 4083 insertions(+), 79 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts create mode 100644 ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html new file mode 100644 index 0000000000..30200f0fa5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html @@ -0,0 +1,122 @@ + +
+
+
gateway.security
+ + + {{ SecurityTypeTranslations.get(type) | translate }} + + +
+ + +
+
gateway.username
+
+ + + + warning + + +
+
+
+
gateway.password
+
+ + +
+ + + warning + +
+
+
+
+
+ +
{{ 'gateway.path-hint' | translate }}
+
+
gateway.CA-certificate-path
+
+ + + + warning + + +
+
+
+
gateway.private-key-path
+
+ + + + warning + + +
+
+
+
gateway.client-cert-path
+
+ + + + warning + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.scss new file mode 100644 index 0000000000..416f368279 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.scss @@ -0,0 +1,20 @@ +/** + * 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 { + width: 100%; + height: 100%; + display: block; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts new file mode 100644 index 0000000000..6cc2e1c128 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts @@ -0,0 +1,167 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, forwardRef, + NgZone, + OnDestroy, + OnInit, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { Subject } from 'rxjs'; +import { Overlay } from '@angular/cdk/overlay'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, ValidationErrors, Validator, Validators +} from '@angular/forms'; +import { + BrokerSecurityTypes, + BrokerSecurityTypeTranslations, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { startWith, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-broker-security', + templateUrl: './broker-security.component.html', + styleUrls: ['./broker-security.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => BrokerSecurityComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => BrokerSecurityComponent), + multi: true + } + ] +}) +export class BrokerSecurityComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { + + BrokerSecurityTypes = BrokerSecurityTypes; + + securityTypes = Object.values(BrokerSecurityTypes); + + SecurityTypeTranslations = BrokerSecurityTypeTranslations; + + securityFormGroup: UntypedFormGroup; + + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(protected store: Store, + public translate: TranslateService, + public dialog: MatDialog, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private dialogService: DialogService, + private entityService: EntityService, + private utils: UtilsService, + private zone: NgZone, + private cd: ChangeDetectorRef, + private elementRef: ElementRef, + private fb: FormBuilder) { + super(store); + this.securityFormGroup = this.fb.group({ + type: [BrokerSecurityTypes.ANONYMOUS, []], + username: ['', [Validators.required]], + password: ['', [Validators.required]], + pathToCACert: ['', [Validators.required]], + pathToPrivateKey: ['', [Validators.required]], + pathToClientCert: ['', [Validators.required]] + }); + this.securityFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateView(value); + }); + this.securityFormGroup.get('type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((type) => { + this.updateValidators(type); + }); + } + + ngOnInit() { + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + ngAfterViewInit() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(deviceInfo: any) { + if (!deviceInfo.type) { + deviceInfo.type = BrokerSecurityTypes.ANONYMOUS; + } + this.securityFormGroup.reset(deviceInfo); + this.updateView(deviceInfo); + } + + validate(): ValidationErrors | null { + return this.securityFormGroup.valid ? null : { + securityForm: { valid: false } + }; + } + + updateView(value: any) { + this.propagateChange(value); + } + + private updateValidators(type) { + if (type) { + this.securityFormGroup.get('username').disable({emitEvent: false}); + this.securityFormGroup.get('password').disable({emitEvent: false}); + this.securityFormGroup.get('pathToCACert').disable({emitEvent: false}); + this.securityFormGroup.get('pathToPrivateKey').disable({emitEvent: false}); + this.securityFormGroup.get('pathToClientCert').disable({emitEvent: false}); + if (type === BrokerSecurityTypes.BASIC) { + this.securityFormGroup.get('username').enable({emitEvent: false}); + this.securityFormGroup.get('password').enable({emitEvent: false}); + } else if (type === BrokerSecurityTypes.CERTIFICATES) { + this.securityFormGroup.get('pathToCACert').enable({emitEvent: false}); + this.securityFormGroup.get('pathToPrivateKey').enable({emitEvent: false}); + this.securityFormGroup.get('pathToClientCert').enable({emitEvent: false}); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html new file mode 100644 index 0000000000..bd748788c1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html @@ -0,0 +1,87 @@ + +
+
+ gateway.device-info-settings +
+
+
+
gateway.device-info.parameter
+
gateway.device-info.source
+
+ gateway.device-info.expression +
+
+
+ +
+
gateway.device-info.device-name
+
+ + + + {{ SourceTypeTranslationsMap.get(type) | translate }} + + + +
+
+ + + + warning + + +
+
+ +
+
gateway.device-info.device-profile
+
+ + + + {{ SourceTypeTranslationsMap.get(type) | translate }} + + + +
+
+ + + + warning + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.scss new file mode 100644 index 0000000000..2853e6ada6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.scss @@ -0,0 +1,27 @@ +/** + * 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 { + width: 100%; + height: 100%; + display: block; + + .tb-form-row { + .tb-form-table-header { + min-height: 48px; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts new file mode 100644 index 0000000000..9ae150abe8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts @@ -0,0 +1,169 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Input, + NgZone, + OnDestroy, + OnInit, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Overlay } from '@angular/cdk/overlay'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { + ControlValueAccessor, + FormBuilder, NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, ValidationErrors, Validator, Validators +} from '@angular/forms'; +import { + SourceTypes, + SourceTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +@Component({ + selector: 'tb-device-info-table', + templateUrl: './device-info-table.component.html', + styleUrls: ['./device-info-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceInfoTableComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceInfoTableComponent), + multi: true + } + ] +}) +export class DeviceInfoTableComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { + + sourceTypes = Object.values(SourceTypes); + + SourceTypeTranslationsMap = SourceTypeTranslationsMap; + + @coerceBoolean() + @Input() + useSource = true; + + @coerceBoolean() + @Input() + required = false; + + deviceInfoTypeValue: any; + + get deviceInfoType(): any { + return this.deviceInfoTypeValue; + } + + @Input() + set deviceInfoType(value: any) { + if (this.deviceInfoTypeValue !== value) { + this.deviceInfoTypeValue = value; + } + } + + mappingFormGroup: UntypedFormGroup; + + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(protected store: Store, + public translate: TranslateService, + public dialog: MatDialog, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private dialogService: DialogService, + private entityService: EntityService, + private utils: UtilsService, + private zone: NgZone, + private cd: ChangeDetectorRef, + private elementRef: ElementRef, + private fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.mappingFormGroup = this.fb.group({ + deviceNameExpression: ['', this.required ? [Validators.required] : []] + }); + this.mappingFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateView(value); + }); + if (this.useSource) { + this.mappingFormGroup.addControl('deviceNameExpressionSource', + this.fb.control(SourceTypes.MSG, [])); + } + if (this.deviceInfoTypeValue === 'full') { + if (this.useSource) { + this.mappingFormGroup.addControl('deviceProfileExpressionSource', + this.fb.control(SourceTypes.MSG, [])); + } + this.mappingFormGroup.addControl('deviceProfileExpression', + this.fb.control('', this.required ? [Validators.required] : [])); + } + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + ngAfterViewInit() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(deviceInfo: any) { + this.mappingFormGroup.patchValue(deviceInfo, {emitEvent: false}); + } + + validate(): ValidationErrors | null { + return this.mappingFormGroup.valid ? null : { + mappingForm: { valid: false } + }; + } + + updateView(value: any) { + this.propagateChange(value); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html new file mode 100644 index 0000000000..bef11bfc5b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html @@ -0,0 +1,152 @@ + +
+
+
{{ panelTitle | translate }}{{' (' + keysListFormArray.controls.length + ')'}}
+ +
+ + + + +
+
+ + + + + + + {{ keyControl.get('key').value + '-' + valueTitle(keyControl.get('value').value) }} + + + + + + + + + + + + + + +
+
gateway.key
+
+ + + + warning + + + + + +
+
+
+
gateway.value
+
+ + + +
+ + + + {{ (rawData ? 'gateway.raw' : valueTypes.get(keyControl.get('type').value)?.name) | translate}} + +
+
+ + + + + {{ valueTypes.get(valueTypeEnum[valueType]).name | translate }} + + + + {{ 'gateway.raw' | translate }} + +
+
+ + + + warning + + + + + + +
+
+
+
+
+
+ +
+ +
+
+ +
+
+ + {{ noKeysText }} + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss new file mode 100644 index 0000000000..2a86058fee --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss @@ -0,0 +1,32 @@ +/** + * 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 { + .tb-mapping-keys-panel { + min-width: 700px; + max-width: 700px; + + .key-panel { + max-height: 500px; + overflow: auto; + } + + tb-value-input { + width: 100%; + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts new file mode 100644 index 0000000000..b6a4a6a6b8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts @@ -0,0 +1,219 @@ +/// +/// 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, + EventEmitter, + Input, + OnInit, + Output +} from '@angular/core'; +import { + AbstractControl, + UntypedFormArray, + UntypedFormBuilder, + Validators +} from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { DataKey, DatasourceType, JsonSettingsSchema, widgetType } from '@shared/models/widget.models'; +import { dataKeyRowValidator, dataKeyValid } from '@home/components/widget/config/basic/common/data-key-row.component'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { UtilsService } from '@core/services/utils.service'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { WidgetService } from '@core/http/widget.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { ColorSettings } from '@shared/models/widget-settings.models'; +import { TranslateService } from '@ngx-translate/core'; +import { isDefinedAndNotNull } from '@core/utils'; +import { ValueType, valueTypesMap } from '@shared/models/constants'; +import { MappingKeysType } from '@home/components/widget/lib/gateway/gateway-widget.models'; + +@Component({ + selector: 'tb-mapping-data-keys-panel', + templateUrl: './mapping-data-keys-panel.component.html', + styleUrls: ['./mapping-data-keys-panel.component.scss'], + providers: [] +}) +export class MappingDataKeysPanelComponent extends PageComponent implements OnInit { + + @Input() + panelTitle: string; + + @Input() + addKeyTitle: string; + + @Input() + deleteKeyTitle: string; + + @Input() + noKeysText: string; + + @Input() + keys: string; + + @Input() + keysType: string; + + @Input() + @coerceBoolean() + rawData = false; + + @Input() + popover: TbPopoverComponent; + + @Output() + keysDataApplied = new EventEmitter>(); + + valueTypeKeys = Object.keys(ValueType); + + MappingKeysType = MappingKeysType; + + valueTypeEnum = ValueType; + + valueTypes = valueTypesMap; + + dataKeyType: DataKeyType; + + keysListFormArray: UntypedFormArray; + + errorText = ''; + + // get dragEnabled(): boolean { + // return this.keysFormArray().controls.length > 1; + // } + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit() { + this.keysListFormArray = this.prepareKeysFormArray(this.keys) + // this.keysListFormArray.valueChanges.subscribe( + // () => { + // let keys: DataKey[] = this.keysListFormArray.get('keys').value; + // if (keys) { + // keys = keys.filter(k => dataKeyValid(k)); + // } + // } + // ); + } + + // keyDrop(event: CdkDragDrop) { + // const keysArray = this.keysListFormGroup.get('keys') as UntypedFormArray; + // const key = keysArray.at(event.previousIndex); + // keysArray.removeAt(event.previousIndex); + // keysArray.insert(event.currentIndex, key); + // } + + // keysFormArray(): UntypedFormArray { + // return this.keysListFormArray; + // } + + trackByKey(index: number, keyControl: AbstractControl): any { + return keyControl; + } + + removeKey(index: number) { + this.keysListFormArray.removeAt(index); + } + + addKey(): void { + const dataKeyFormGroup = this.fb.group({ + key: ['', Validators.required], + value: ['', Validators.required] + }); + if (this.keysType !== MappingKeysType.CUSTOM) { + dataKeyFormGroup.addControl('type', this.fb.control(this.rawData ? 'raw' : ValueType.STRING)); + // if (this.rawData) { + // dataKeyFormGroup.get('type').disable(); + // } + } + // const keyControl = this.fb.control(dataKey, [this.dataKeyValidator]); + this.keysListFormArray.push(dataKeyFormGroup); + } + + deleteKey($event: Event, index: number): void { + if ($event) { + $event.stopPropagation(); + } + this.keysListFormArray.removeAt(index); + } + + cancel() { + this.popover?.hide(); + } + + applyKeysData() { + let keys = this.keysListFormArray.value; + if (this.keysType === MappingKeysType.CUSTOM) { + keys = {}; + for (let key of this.keysListFormArray.value) { + keys[key.key] = key.value; + } + } + this.keysDataApplied.emit(keys); + } + + private prepareKeysFormArray(keys: any): UntypedFormArray { + const keysControlGroups: Array = []; + if (keys) { + if (this.keysType === MappingKeysType.CUSTOM) { + keys = Object.keys(keys).map(key => { + return {key, value: keys[key], type: ''}; + }); + } + keys.forEach((keyData) => { + const { key, value, type } = keyData; + const dataKeyFormGroup = this.fb.group({ + key: [key, Validators.required], + value: [value, Validators.required], + type: [type, []] + }); + keysControlGroups.push(dataKeyFormGroup); + }); + console.log(keysControlGroups, 'keysControlGroups'); + } + return this.fb.array(keysControlGroups); + } + + valueTitle(value: any): any { + if (isDefinedAndNotNull(value)) { + if (typeof value === 'object') { + return JSON.stringify(value); + } + return value; + } + return ''; + } + + // private dataKeyValidator(control: AbstractControl): ValidationErrors | null { + // const dataKey: any = control.value; + // if (!(!!dataKey && !!dataKey.type && !!dataKey.name)) { + // return { + // required: true + // }; + // } + // return null; + // } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html new file mode 100644 index 0000000000..6e7afd1a45 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html @@ -0,0 +1,94 @@ + +
+
+ +
+
+ {{mappingTypeTranslationsMap.get(mappingType) | translate}} +
+ + + +
+
+ +
+ + +   + + + +
+
+
+ + + {{ column.title | translate }} + {{ mapping[column.def] }} + + + + +
+ + +
+
+
+ + +
+
+ +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss new file mode 100644 index 0000000000..f95c8504b5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss @@ -0,0 +1,134 @@ +/** + * 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 { + width: 100%; + height: 100%; + display: block; + .tb-entity-table { + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; + overflow: hidden; + + &.tb-outlined-border { + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + } + + .mat-toolbar-tools{ + min-height: auto; + } + + .title-container{ + overflow: hidden; + } + + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + .mat-mdc-table { + table-layout: fixed; + min-width: 450px; + } + + .tb-add-new { + display: flex; + z-index: 1; + pointer-events: none; + + button.connector { + height: auto; + padding-right: 12px; + font-size: 20px; + border-style: dashed; + border-width: 2px; + border-radius: 8px; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + color: rgba(0, 0, 0, 0.38); + } + } + } + + .tb-entity-table-info{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .button-widget-action{ + margin-left: auto; + overflow: hidden; + text-overflow: ellipsis; + } + + .ellipsis { + overflow: hidden; + text-overflow: ellipsis; + } + + .attribute-copy { + visibility: hidden; + transition: visibility 0.1s; + } + + .attribute:hover { + .attribute-copy { + visibility: visible; + } + } + } + } + + @media #{$mat-xs} { + .mat-toolbar { + height: auto; + min-height: 100px; + + .tb-entity-table-title{ + padding-bottom: 5px; + width: 100%; + } + } + } +} + +:host ::ng-deep { + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } + mat-cell.tb-value-cell { + cursor: pointer; + .mat-icon { + height: 24px; + width: 24px; + font-size: 24px; + color: #757575 + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts new file mode 100644 index 0000000000..9e1f9801ee --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts @@ -0,0 +1,342 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Input, + NgZone, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { PageLink } from '@shared/models/page/page-link'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { debounceTime, distinctUntilChanged, map, take, takeUntil, tap } from 'rxjs/operators'; +import { Overlay } from '@angular/cdk/overlay'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { ControlValueAccessor, FormArray, FormBuilder, NG_VALUE_ACCESSOR, UntypedFormArray } from '@angular/forms'; +import { + ConvertorTypeTranslationsMap, + MappingTypes, + MappingTypeTranslationsMap, + RequestTypes +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections'; +import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; +import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; + +@Component({ + selector: 'tb-mapping-table', + templateUrl: './mapping-table.component.html', + styleUrls: ['./mapping-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MappingTableComponent), + multi: true + } + ] +}) +export class MappingTableComponent extends PageComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy { + + mappingTypeTranslationsMap = MappingTypeTranslationsMap; + displayedColumns = []; + mappingColumns = []; + textSearchMode = false; + dataSource: MappingDatasource; + hidePageSize = false; + + activeValue = false; + dirtyValue = false; + + viewsInited = false; + + mappingTypeValue: MappingTypes; + + get mappingType(): MappingTypes { + return this.mappingTypeValue; + } + + @Input() + set mappingType(value: MappingTypes) { + if (this.mappingTypeValue !== value) { + this.mappingTypeValue = value; + } + } + + @ViewChild('searchInput') searchInputField: ElementRef; + + // @ViewChild(MatPaginator) paginator: MatPaginator; + // @ViewChild(MatSort) sort: MatSort; + + mappingFormGroup: UntypedFormArray; + textSearch = this.fb.control('', {nonNullable: true}); + + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(protected store: Store, + public translate: TranslateService, + public dialog: MatDialog, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private dialogService: DialogService, + private entityService: EntityService, + private utils: UtilsService, + private zone: NgZone, + private cd: ChangeDetectorRef, + private elementRef: ElementRef, + private fb: FormBuilder) { + super(store); + this.mappingFormGroup = this.fb.array([]); + this.dirtyValue = !this.activeValue; + // const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC }; + this.dataSource = new MappingDatasource(); + } + + ngOnInit() { + if (this.mappingType === MappingTypes.DATA) { + this.mappingColumns.push( + {def: 'topicFilter', title: 'gateway.topic-filter'}, + {def: 'QoS', title: 'gateway.mqtt-qos'}, + {def: 'converter', title: 'gateway.converter'} + ) + } else { + this.mappingColumns.push( + {def: 'type', title: 'gateway.type'}, + {def: 'details', title: 'gateway.details'} + ); + } + this.displayedColumns.push(...this.mappingColumns.map(column => column.def), 'actions'); + this.mappingFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateTableData(value); + this.updateView(value); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + ngAfterViewInit() { + this.textSearch.valueChanges.pipe( + debounceTime(150), + distinctUntilChanged((prev, current) => (prev ?? '') === current.trim()), + takeUntil(this.destroy$) + ).subscribe((text) => { + const searchText = text.trim(); + this.updateTableData(this.mappingFormGroup.value, searchText.trim()) + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(config: any) { + if (isUndefinedOrNull(config)) { + config = this.mappingType === MappingTypes.REQUESTS ? {} : []; + } + let mappingConfigs = config; + if (this.mappingType === MappingTypes.REQUESTS) { + mappingConfigs = []; + + Object.keys(config).forEach((configKey) => { + for (let mapping of config[configKey]) { + mappingConfigs.push({ + requestType: configKey, + requestValue: mapping + }); + } + }); + } + this.mappingFormGroup.clear({emitEvent: false}); + for (let mapping of mappingConfigs) { + this.mappingFormGroup.push(this.fb.group(mapping), {emitEvent: false}); + } + this.updateTableData(mappingConfigs); + } + + updateView(mappingConfigs: Array) { + let config; + if (this.mappingType === MappingTypes.REQUESTS) { + config = {}; + for (let mappingConfig of mappingConfigs) { + if (config[mappingConfig.requestType]) { + config[mappingConfig.requestType].push(mappingConfig.requestValue); + } else { + config[mappingConfig.requestType] = [mappingConfig.requestValue]; + } + } + } else { + config = mappingConfigs; + } + + this.propagateChange(config); + } + + enterFilterMode() { + this.textSearchMode = true; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.textSearch.reset(); + } + + manageMapping($event: Event, index?: number) { + if ($event) { + $event.stopPropagation(); + } + const value = isDefinedAndNotNull(index) ? this.mappingFormGroup.at(index).value : {}; + this.dialog.open(MappingDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + mappingType: this.mappingType, + value, + buttonTitle: isUndefinedOrNull(index) ? 'action.add' : 'action.apply' + } + }).afterClosed().subscribe( + (res) => { + if (res) { + if (isDefinedAndNotNull(index)) { + this.mappingFormGroup.at(index).patchValue(res); + } else { + this.mappingFormGroup.push(this.fb.group(res)); + } + } + } + ); + } + + updateTableData(value, textSearch?: string): void { + let tableValue = value; + if (this.mappingType === MappingTypes.DATA) { + tableValue = tableValue.map((value) => { + return { + topicFilter: value.topicFilter, + QoS: value.subscriptionQos, + converter: this.translate.instant(ConvertorTypeTranslationsMap.get(value.converter.type)) + }; + }); + } else { + tableValue = tableValue.map((value) => { + let details; + if (value.requestType === RequestTypes.ATTRIBUTE_UPDATE) { + details = value.requestValue.attributeFilter; + } else if (value.requestType === RequestTypes.SERVER_SIDE_RPC) { + details = value.requestValue.methodFilter; + } else { + details = value.requestValue.topicFilter; + } + return { + type: value.requestType, + details + }; + }); + } + if (textSearch) { + tableValue = tableValue.filter(value => + Object.values(value).some(val => + val.toString().toLowerCase().includes(textSearch.toLowerCase()) + ) + ); + } + this.dataSource.loadMappings(tableValue); + } + + deleteMapping($event: Event, index: number) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('gateway.delete-mapping-title'), + '', + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + this.mappingFormGroup.removeAt(index); + } + }); + } + +} + +export class MappingDatasource implements DataSource { + + private mappingSubject = new BehaviorSubject([]); + + private allMappings: Observable>; + + constructor() {} + + connect(collectionViewer: CollectionViewer): Observable> { + return this.mappingSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.mappingSubject.complete(); + } + + loadMappings(mappings: any, pageLink?: PageLink, reload: boolean = false): void { + if (reload) { + this.allMappings = null; + } + this.mappingSubject.next(mappings); + } + + isEmpty(): Observable { + return this.mappingSubject.pipe( + map((mappings) => !mappings.length) + ); + } + + total(): Observable { + return this.mappingSubject.pipe( + map((mappings) => mappings.length) + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.html new file mode 100644 index 0000000000..be64ea7e98 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.html @@ -0,0 +1,175 @@ + + + + +
+
+
gateway.broker.name
+
+ + + + warning + + +
+
+
+
gateway.host
+
+ + + + warning + + +
+
+
+
gateway.port
+
+ + + + warning + + +
+
+
+
gateway.mqtt-version
+
+ + + {{ version.name }} + + +
+
+
+
gateway.client-id
+
+ + + + + + + + + + + + + + + +
+
+ + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ gateway.max-number-of-workers +
+
+ + + + warning + + +
+
+
+
+ gateway.max-messages-queue-for-worker +
+
+ + + + warning + + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss new file mode 100644 index 0000000000..4d6211765a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss @@ -0,0 +1,27 @@ +/** + * 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 { + //width: 100%; + //height: 100%; + //display: block; + // + //.tb-form-row { + // .tb-form-table-header { + // min-height: 48px; + // } + //} + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts new file mode 100644 index 0000000000..e1b9b48833 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts @@ -0,0 +1,150 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Input, + NgZone, + OnDestroy, + OnInit, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Overlay } from '@angular/cdk/overlay'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { + ControlValueAccessor, + FormBuilder, FormGroup, NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, ValidationErrors, Validator, Validators +} from '@angular/forms'; +import { + MappingTypes, + MqttVersions, + SourceTypes, + SourceTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { generateSecret } from '@core/utils'; + +@Component({ + selector: 'tb-mqtt-basic-config', + templateUrl: './mqtt-basic-config.component.html', + styleUrls: ['./mqtt-basic-config.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + } + ] +}) +export class MqttBasicConfigComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { + + mqttConfigFormGroup: UntypedFormGroup; + + mqttVersions = MqttVersions; + + mappingTypes = MappingTypes; + + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(protected store: Store, + public translate: TranslateService, + public dialog: MatDialog, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private dialogService: DialogService, + private entityService: EntityService, + private utils: UtilsService, + private zone: NgZone, + private cd: ChangeDetectorRef, + private elementRef: ElementRef, + private fb: FormBuilder) { + super(store); + console.log('mqttconfig inited'); + } + + ngOnInit() { + this.mqttConfigFormGroup = this.fb.group({ + broker: this.fb.group({ + name: ['', [Validators.required]], + host: ['', [Validators.required]], + port: [null, [Validators.required]], + version: [5, []], + clientId: ['', []], + maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], + maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], + security: [{}, [Validators.required]] + }), + dataMapping: [[], Validators.required], + requestsMapping: [{}, []] + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + ngAfterViewInit() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(deviceInfo: any) { + this.mqttConfigFormGroup.patchValue(deviceInfo, {emitEvent: false}); + } + + validate(): ValidationErrors | null { + return this.mqttConfigFormGroup.valid ? null : { + mqttConfigForm: { valid: false } + }; + } + + updateView(value: any) { + this.propagateChange(value); + } + + generate(formControlName: string) { + this.mqttConfigFormGroup.get(formControlName).patchValue(generateSecret(5)); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html new file mode 100644 index 0000000000..b90c2a758f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html @@ -0,0 +1,102 @@ + +
+ +

{{ "gateway.add-connector" | translate}}

+ +
+ +
+ + +
+
+
+
+
gateway.type
+
+ + + + {{ type.value }} + + + +
+
+
+
gateway.name
+
+ + + + warning + + +
+
+
+
gateway.remote-logging-level
+
+ + + {{ logLevel }} + + +
+
+
+ + + {{ 'gateway.fill-connector-defaults' | translate }} + + +
+
+ + + {{ 'gateway.send-change-data' | translate }} + + +
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss new file mode 100644 index 0000000000..813b4ec9ff --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss @@ -0,0 +1,26 @@ +/** + * 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 { + form { + min-width: 400px; + width: 500px; + } +} + +:host ::ng-deep .mat-padding{ + padding: 0; +} 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 new file mode 100644 index 0000000000..0f0b2bae0e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts @@ -0,0 +1,122 @@ +/// +/// 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, OnDestroy, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, UntypedFormControl, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { + GatewayConnectorDefaultTypesTranslates, + GatewayLogLevel, getDefaultConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { forkJoin, Observable, of, Subject } from 'rxjs'; +import { catchError, map, takeUntil } from 'rxjs/operators'; +import { camelCase } from '@core/utils'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { ResourcesService } from '@core/services/resources.service'; + +@Component({ + selector: 'tb-add-connector-dialog', + templateUrl: './add-connector-dialog.component.html', + styleUrls: ['./add-connector-dialog.component.scss'], + providers: [], +}) +export class AddConnectorDialogComponent extends DialogComponent> implements OnInit, OnDestroy { + + connectorForm: UntypedFormGroup; + + gatewayConnectorDefaultTypes = GatewayConnectorDefaultTypesTranslates; + gatewayLogLevel = Object.values(GatewayLogLevel); + + submitted = false; + + private destroy$ = new Subject(); + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private resourcesService: ResourcesService) { + super(store, router, dialogRef); + this.connectorForm = this.fb.group({ + type: ['mqtt', []], + name: ['', [Validators.required, this.uniqNameRequired()]], + logLevel: [GatewayLogLevel.INFO, []], + useDefaults: [true, []], + sendDataOnlyOnChange: [false, []], + }); + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + super.ngOnDestroy(); + } + + helpLinkId(): string { + return ""; + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + this.submitted = true; + const value = this.connectorForm.getRawValue(); + if (value.useDefaults) { + getDefaultConfig(this.resourcesService, value.type).subscribe((defaultConfig) => { + console.log(defaultConfig, 'defaultConfig'); + value.configurationJson = defaultConfig; + if (this.connectorForm.valid) { + this.dialogRef.close(value); + } + }); + } + if (this.connectorForm.valid) { + this.dialogRef.close(value); + } + } + + private uniqNameRequired(): ValidatorFn { + return (c: UntypedFormControl) => { + const newName = c.value.trim().toLowerCase(); + const found = this.data.dataSourceData.find((connectorAttr) => { + const connectorData = connectorAttr.value; + return connectorData.name.toLowerCase() === newName; + }); + if (found) { + if (c.hasError('required')) { + return c.getError('required'); + } + return { + duplicateName: { + valid: false + } + }; + } + return null; + }; + } +} 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 new file mode 100644 index 0000000000..0fc5156f80 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html @@ -0,0 +1,539 @@ + +
+ +

{{ MappingTypeTranslationsMap.get(this.data?.mappingType) | translate}}

+ +
+ +
+ + +
+
+
+
+ {{ MappingHintTranslationsMap.get(this.data?.mappingType) | translate }} +
+ + +
+
gateway.topic-filter
+
+ + + + warning + + +
+
+
+
gateway.mqtt-qos
+
+ + + + {{ QualityTranslationsMap.get(type) | translate }} + + + +
+
+ +
+
+
gateway.converter
+ + + {{ ConvertorTypeTranslationsMap.get(type) | translate }} + + +
+ + + + + + + + + +
+
+
gateway.attributes
+
+ + + {{ attribute.key }} + + + +
+
+
+
gateway.telemetry
+
+ + + {{ telemetry.key }} + + + + +
+
+
+ + +
+
+ +
gateway.extension
+
+ + + +
+
+
+
gateway.extension-configuration
+
{{ 'gateway.extension-configuration-hint' | translate }}
+
+
gateway.keys
+
+ + + {{ telemetry.key }} + + + + +
+
+
+
+ +
+
+
+
+ +
+ +
gateway.request-type
+
+ + + + {{ RequestTypesTranslationsMap.get(type) | translate }} + + + +
+
+ + +
+ +
gateway.topic-filter
+
+ + + + warning + + +
+
+ + + + + + + + + +
+
gateway.from-device-request-settings
+ + +
+
+ gateway.attribute-name-expression +
+
+ + + + {{ SourceTypeTranslationsMap.get(type) | translate }} + + + + + + + warning + + +
+
+
+
+
gateway.to-device-response-settings
+
+
+ gateway.response-topic-expression +
+
+ + + + warning + + +
+
+
+ + + {{ 'gateway.retain' | translate }} + + +
+
+ +
+ gateway.response-value-expression +
+
+ + + + warning + + +
+
+
+
+ +
+ +
gateway.device-name-filter
+
+ + + + warning + + +
+
+
+ +
+ gateway.attribute-filter +
+
+ + + + warning + + +
+
+
+ +
gateway.response-topic-expression
+
+ + + + warning + + +
+
+
+ + + {{ 'gateway.retain' | translate }} + + +
+
+ +
gateway.response-value-expression
+
+ + + + warning + + +
+
+
+ +
+ + + {{ 'gateway.with-response' | translate }} + + + {{ 'gateway.without-response' | translate }} + + +
+
+ +
+ gateway.device-name-filter +
+
+ + + + warning + + +
+
+
+ +
+ gateway.method-filter +
+
+ + + + warning + + +
+
+
+ +
+ gateway.request-topic-expression +
+
+ + + + warning + + +
+
+
+
+ gateway.value-expression +
+
+ + + + warning + + +
+
+ +
+
gateway.response-topic-expression
+
+ + + + warning + + +
+
+
+
gateway.response-topic-Qos
+ + + + {{ QualityTranslationsMap.get(type) | translate }} + + + +
+
+
gateway.response-timeout
+
+ + + + warning + + +
+
+
+
+
+
+
+
+ + + + + + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss new file mode 100644 index 0000000000..d2fd342407 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss @@ -0,0 +1,36 @@ +/** + * 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 { + form { + min-width: 700px; + width: 900px; + } +} + +:host ::ng-deep { + .mat-padding { + padding: 0; + } + form { + .mat-mdc-chip-listbox { + .mdc-evolution-chip-set__chips { + justify-content: flex-end; + align-items: center; + } + } + } +} 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 new file mode 100644 index 0000000000..8fe22ac45b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts @@ -0,0 +1,351 @@ +/// +/// 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, OnDestroy, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { + ConvertorTypes, + ConvertorTypeTranslationsMap, + MappingHintTranslationsMap, + MappingKeysAddKeyTranslationsMap, + MappingKeysDeleteKeyTranslationsMap, + MappingKeysNoKeysTextTranslationsMap, + MappingKeysPanelTitleTranslationsMap, + MappingKeysType, + MappingTypes, + MappingTypeTranslationsMap, + QualityTypes, + QualityTypeTranslationsMap, + RequestTypes, + RequestTypesTranslationsMap, + ServerSideRPCType, + SourceTypes, + SourceTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { Subject } from 'rxjs'; +import { startWith, takeUntil } from 'rxjs/operators'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component'; + +@Component({ + selector: 'tb-mapping-dialog', + templateUrl: './mapping-dialog.component.html', + styleUrls: ['./mapping-dialog.component.scss'], + providers: [], +}) +export class MappingDialogComponent extends DialogComponent> implements OnInit, OnDestroy { + + mappingForm: UntypedFormGroup; + + MappingTypes = MappingTypes; + + qualityTypes = QualityTypes; + QualityTranslationsMap = QualityTypeTranslationsMap; + + convertorTypes = Object.values(ConvertorTypes); + ConvertorTypes = ConvertorTypes; + ConvertorTypeTranslationsMap = ConvertorTypeTranslationsMap; + + sourceTypes = Object.values(SourceTypes); + SourceTypeTranslationsMap = SourceTypeTranslationsMap; + + requestTypes = Object.values(RequestTypes); + RequestTypes = RequestTypes; + RequestTypesTranslationsMap = RequestTypesTranslationsMap; + + ServerSideRPCType = ServerSideRPCType; + + MappingKeysType = MappingKeysType; + + MappingHintTranslationsMap = MappingHintTranslationsMap; + + MappingTypeTranslationsMap = MappingTypeTranslationsMap; + + submitted = false; + + private destroy$ = new Subject(); + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) { + super(store, router, dialogRef); + + this.createMappingForm(); + } + + get converterAttributes(): Array { + if (this.converterType) { + return this.mappingForm.get('converter').get(this.converterType).value.attributes; + } + } + + get converterTelemetry(): Array { + if (this.converterType) { + return this.mappingForm.get('converter').get(this.converterType).value.timeseries; + } + } + + get converterType(): ConvertorTypes { + return this.mappingForm.get('converter').get('type').value; + } + + get customKeys(): any { + return this.mappingForm.get('converter').get('custom').value.extensionConfig; + } + + get requestMappingType(): any { + return this.mappingForm.get('requestType').value; + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + super.ngOnDestroy(); + } + + private createMappingForm(): void { + this.mappingForm = this.fb.group({}); + + // switch (this.data.connectorType) { + // case 'mqtt': + if (this.data.mappingType === MappingTypes.DATA) { + this.mappingForm.addControl('topicFilter', this.fb.control('', [Validators.required])); + this.mappingForm.addControl('subscriptionQos', this.fb.control(0)); + this.mappingForm.addControl('converter', this.fb.group({ + type: [ConvertorTypes.JSON, []], + json: this.fb.group({ + // deviceNameSource: ['', []], + // deviceProfileSource: ['', []], + // deviceNameExpression: ['', []], + // deviceProfileExpression: ['', []], + deviceInfo: [{}, []], + attributes: [[], []], + timeseries: [[], []] + }), + bytes: this.fb.group({ + // deviceNameExpression: ['', []], + // deviceProfileExpression: ['', []], + deviceInfo: [{}, []], + attributes: [[], []], + timeseries: [[], []] + }), + custom: this.fb.group({ + extension: ['', []], + extensionConfig: [{}, []] + }), + })); + // this.createConvertorGroup(); + this.mappingForm.patchValue(this.prepareFormValueData()); + this.mappingForm.get('converter.type').valueChanges.pipe( + startWith(this.mappingForm.get('converter.type').value), + takeUntil(this.destroy$) + ).subscribe((value) => { + const converterGroup = this.mappingForm.get('converter'); + converterGroup.get('json').disable({emitEvent: false}); + converterGroup.get('bytes').disable({emitEvent: false}); + converterGroup.get('custom').disable({emitEvent: false}); + converterGroup.get(value).enable({emitEvent: false}); + }) + } + + if (this.data.mappingType === MappingTypes.REQUESTS) { + this.mappingForm.addControl('requestType', this.fb.control(RequestTypes.CONNECT_REQUEST, [])); + this.mappingForm.addControl('requestValue', this.fb.group({ + connectRequests: this.fb.group({ + topicFilter: ['', [Validators.required]], + deviceInfo: [{}, []], + // deviceNameSource: ['', []], + // deviceProfileSource: ['', []], + // deviceNameExpression: ['', []], + // deviceProfileExpression: ['', []], + }), + disconnectRequests: this.fb.group({ + topicFilter: ['', [Validators.required]], + deviceInfo: [{}, []], + // deviceNameSource: ['', []], + // deviceNameExpression: ['', []] + }), + attributeRequests: this.fb.group({ + topicFilter: ['', [Validators.required]], + deviceInfo: [{}, [Validators.required]], + attributeNameExpressionSource: [SourceTypes.MSG], + attributeNameExpression: ['', [Validators.required]], + topicExpression: ['', [Validators.required]], + valueExpression: ['', [Validators.required]], + retain: [false, []] + }), + attributeUpdates: this.fb.group({ + deviceNameFilter: ['', [Validators.required]], + attributeFilter: ['', [Validators.required]], + topicExpression: ['', [Validators.required]], + valueExpression: ['', [Validators.required]], + retain: [true, []] + }), + serverSideRpc: this.fb.group({ + type: [ServerSideRPCType.TWO_WAY, []], + deviceNameFilter: ['', [Validators.required]], + methodFilter: ['', [Validators.required]], + requestTopicExpression: ['', [Validators.required]], + responseTopicExpression: ['', [Validators.required]], + valueExpression: ['', [Validators.required]], + responseTopicQoS: [0, []], + responseTimeout: [10000, [Validators.required]], + }) + })); + this.mappingForm.patchValue(this.prepareFormValueData()); + this.mappingForm.get('requestType').valueChanges.pipe( + startWith(this.mappingForm.get('requestType').value), + takeUntil(this.destroy$) + ).subscribe((value) => { + const requestValueGroup = this.mappingForm.get('requestValue'); + requestValueGroup.get('connectRequests').disable({emitEvent: false}); + requestValueGroup.get('disconnectRequests').disable({emitEvent: false}); + requestValueGroup.get('attributeRequests').disable({emitEvent: false}); + requestValueGroup.get('attributeUpdates').disable({emitEvent: false}); + requestValueGroup.get('serverSideRpc').disable({emitEvent: false}); + requestValueGroup.get(value).enable({emitEvent: false}); + }); + this.mappingForm.get('requestValue.serverSideRpc.type').valueChanges.pipe( + startWith(this.mappingForm.get('requestValue.serverSideRpc.type').value), + takeUntil(this.destroy$) + ).subscribe((value) => { + const requestValueGroup = this.mappingForm.get('requestValue.serverSideRpc'); + if (value === ServerSideRPCType.ONE_WAY) { + requestValueGroup.get('responseTopicExpression').disable({emitEvent: false}); + requestValueGroup.get('responseTopicQoS').disable({emitEvent: false}); + requestValueGroup.get('responseTimeout').disable({emitEvent: false}); + } else { + requestValueGroup.get('responseTopicExpression').enable({emitEvent: false}); + requestValueGroup.get('responseTopicQoS').enable({emitEvent: false}); + requestValueGroup.get('responseTimeout').enable({emitEvent: false}); + } + }); + } + // } + } + + // private createConvertorGroup(): void { + // + // } + + helpLinkId(): string { + return ""; + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + this.submitted = true; + if (this.mappingForm.valid) { + this.dialogRef.close(this.prepareMappingData()); + // this.dialogRef.close(this.mappingForm.getRawValue()); + } + } + + manageKeys($event: Event, matButton: MatButton, keysType: MappingKeysType) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const keysControl = this.mappingForm.get('converter').get(this.converterType).get(keysType); + const ctx: any = { + keys: keysControl.value, + keysType: keysType, + rawData: this.mappingForm.get('converter.type').value === ConvertorTypes.BYTES, + panelTitle: MappingKeysPanelTitleTranslationsMap.get(keysType), + addKeyTitle: MappingKeysAddKeyTranslationsMap.get(keysType), + deleteKeyTitle: MappingKeysDeleteKeyTranslationsMap.get(keysType), + noKeysText: MappingKeysNoKeysTextTranslationsMap.get(keysType) + }; + const dataKeysPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, MappingDataKeysPanelComponent, 'leftBottom', true, null, + ctx, + {}, + {}, {}, true); + dataKeysPanelPopover.tbComponentRef.instance.popover = dataKeysPanelPopover; + dataKeysPanelPopover.tbComponentRef.instance.keysDataApplied.subscribe((keysData) => { + dataKeysPanelPopover.hide(); + keysControl.patchValue(keysData); + }); + } + } + + private prepareMappingData(): any { + // try to optimise just by using getRawValue() + const formValue = this.mappingForm.value; + if (this.data.mappingType === MappingTypes.DATA) { + const { converter, topicFilter, subscriptionQos } = formValue; + return { + topicFilter, + subscriptionQos, + converter: { + type: converter.type, + ...converter[converter.type] + } + }; + } else { + return { + requestType: formValue.requestType, + requestValue: formValue.requestValue[formValue.requestType] + }; + } + } + + private prepareFormValueData(): any { + console.log(this.data, 'this.data'); + if (this.data.value && Object.keys(this.data.value).length) { + if (this.data.mappingType === MappingTypes.DATA) { + const {converter, topicFilter, subscriptionQos} = this.data.value; + return { + topicFilter, + subscriptionQos, + converter: { + type: converter.type, + [converter.type]: { ...converter } + } + }; + } else { + return { + requestType: this.data.value.requestType, + requestValue: { + [this.data.value.requestType]: this.data.value.requestValue + } + }; + } + } + return this.data.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 2b9329dc21..f9fa84d52f 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 @@ -22,13 +22,21 @@
+
+ +
@@ -137,49 +145,226 @@ + *matRowDef="let attribute; let i = index; columns: displayedColumns;" (click)="selectConnector(attribute)">
-
-
-
- - {{ 'gateway.connectors-table-name' | translate }} - - - - {{ 'gateway.connectors-table-type' | translate }} - - {{ type.value }} - - - - {{ 'gateway.connectors-table-key' | translate }} - - - - {{ 'gateway.connectors-table-class' | translate }} - - - - gateway.remote-logging-level - - {{ logLevel }} - - -
-
-
- - +
+
+
+ {{initialConnector?.type ? gatewayConnectorDefaultTypes.get(initialConnector.type) : ''}} + {{'gateway.configuration' | translate}} +
+ + + {{ 'gateway.basic' | translate }} + + + {{ 'gateway.advanced' | translate }} + + +
+
+ + +
+
+
gateway.name
+
+ + + + warning + + +
+
+
+
gateway.remote-logging-level
+
+ + + {{ logLevel }} + + +
+
+
+ + + {{ 'gateway.send-change-data' | translate }} + + +
+
+
+ + + +
+
+
gateway.broker.name
+
+ + + + warning + + +
+
+
+
gateway.host
+
+ + + + warning + + +
+
+
+
gateway.port
+
+ + + + warning + + +
+
+
+
gateway.mqtt-version
+
+ + + {{ version.name }} + + +
+
+
+
gateway.client-id
+
+ + + + +
+
+ + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ gateway.max-number-of-workers +
+
+ + + + warning + + +
+
+
+
+ gateway.max-messages-queue-for-worker +
+
+ + + + warning + + +
+
+
+
+
+
+
+ + + + + + +
-
gateway.device-info.device-name
@@ -55,7 +54,6 @@
-
gateway.device-info.device-profile
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts index 9ae150abe8..2f6b903646 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts @@ -70,8 +70,6 @@ import { coerceBoolean } from '@shared/decorators/coercion'; }) export class DeviceInfoTableComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { - sourceTypes = Object.values(SourceTypes); - SourceTypeTranslationsMap = SourceTypeTranslationsMap; @coerceBoolean() @@ -82,6 +80,9 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa @Input() required = false; + @Input() + sourceTypes: Array = Object.values(SourceTypes); + deviceInfoTypeValue: any; get deviceInfoType(): any { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html index bef11bfc5b..f9abdd7434 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html @@ -18,32 +18,14 @@
{{ panelTitle | translate }}{{' (' + keysListFormArray.controls.length + ')'}}
- -
- - - - -
+
+
- - - + {{ keyControl.get('key').value + '-' + valueTitle(keyControl.get('value').value) }} - - - - - - - - - - - @@ -60,9 +42,6 @@ class="tb-error"> warning - - -
@@ -83,9 +62,9 @@ - + - {{ valueTypes.get(valueTypeEnum[valueType]).name | translate }} + {{ valueTypes.get(valueType).name | translate }} @@ -105,10 +84,6 @@ warning - - - -
@@ -123,7 +98,6 @@ delete
-
-
- -
-
gateway.extension
@@ -151,19 +147,16 @@ (click)="manageKeys($event, keysButton, MappingKeysType.CUSTOM)"> edit -
-
-
gateway.request-type
@@ -268,7 +261,6 @@
-
gateway.response-value-expression
@@ -291,7 +283,6 @@
-
gateway.device-name-filter
@@ -329,7 +320,6 @@
-
gateway.response-topic-expression
@@ -354,7 +344,6 @@
-
gateway.response-value-expression
@@ -404,7 +393,6 @@
-
gateway.method-filter
@@ -424,7 +412,6 @@
-
gateway.request-topic-expression
@@ -513,13 +500,6 @@ - - - - - - -
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 8fe22ac45b..bcf0d24365 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 @@ -67,6 +67,7 @@ export class MappingDialogComponent extends DialogComponent { + const converterGroup = this.mappingForm.get('converter'); + converterGroup.get('json').disable({emitEvent: false}); + converterGroup.get('bytes').disable({emitEvent: false}); + converterGroup.get('custom').disable({emitEvent: false}); + converterGroup.get(value).enable({emitEvent: false}); + }) + } - // switch (this.data.connectorType) { - // case 'mqtt': - if (this.data.mappingType === MappingTypes.DATA) { - this.mappingForm.addControl('topicFilter', this.fb.control('', [Validators.required])); - this.mappingForm.addControl('subscriptionQos', this.fb.control(0)); - this.mappingForm.addControl('converter', this.fb.group({ - type: [ConvertorTypes.JSON, []], - json: this.fb.group({ - // deviceNameSource: ['', []], - // deviceProfileSource: ['', []], - // deviceNameExpression: ['', []], - // deviceProfileExpression: ['', []], - deviceInfo: [{}, []], - attributes: [[], []], - timeseries: [[], []] - }), - bytes: this.fb.group({ - // deviceNameExpression: ['', []], - // deviceProfileExpression: ['', []], - deviceInfo: [{}, []], - attributes: [[], []], - timeseries: [[], []] - }), - custom: this.fb.group({ - extension: ['', []], - extensionConfig: [{}, []] - }), - })); - // this.createConvertorGroup(); - this.mappingForm.patchValue(this.prepareFormValueData()); - this.mappingForm.get('converter.type').valueChanges.pipe( - startWith(this.mappingForm.get('converter.type').value), - takeUntil(this.destroy$) - ).subscribe((value) => { - const converterGroup = this.mappingForm.get('converter'); - converterGroup.get('json').disable({emitEvent: false}); - converterGroup.get('bytes').disable({emitEvent: false}); - converterGroup.get('custom').disable({emitEvent: false}); - converterGroup.get(value).enable({emitEvent: false}); - }) - } - - if (this.data.mappingType === MappingTypes.REQUESTS) { - this.mappingForm.addControl('requestType', this.fb.control(RequestTypes.CONNECT_REQUEST, [])); - this.mappingForm.addControl('requestValue', this.fb.group({ - connectRequests: this.fb.group({ - topicFilter: ['', [Validators.required]], - deviceInfo: [{}, []], - // deviceNameSource: ['', []], - // deviceProfileSource: ['', []], - // deviceNameExpression: ['', []], - // deviceProfileExpression: ['', []], - }), - disconnectRequests: this.fb.group({ - topicFilter: ['', [Validators.required]], - deviceInfo: [{}, []], - // deviceNameSource: ['', []], - // deviceNameExpression: ['', []] - }), - attributeRequests: this.fb.group({ - topicFilter: ['', [Validators.required]], - deviceInfo: [{}, [Validators.required]], - attributeNameExpressionSource: [SourceTypes.MSG], - attributeNameExpression: ['', [Validators.required]], - topicExpression: ['', [Validators.required]], - valueExpression: ['', [Validators.required]], - retain: [false, []] - }), - attributeUpdates: this.fb.group({ - deviceNameFilter: ['', [Validators.required]], - attributeFilter: ['', [Validators.required]], - topicExpression: ['', [Validators.required]], - valueExpression: ['', [Validators.required]], - retain: [true, []] - }), - serverSideRpc: this.fb.group({ - type: [ServerSideRPCType.TWO_WAY, []], - deviceNameFilter: ['', [Validators.required]], - methodFilter: ['', [Validators.required]], - requestTopicExpression: ['', [Validators.required]], - responseTopicExpression: ['', [Validators.required]], - valueExpression: ['', [Validators.required]], - responseTopicQoS: [0, []], - responseTimeout: [10000, [Validators.required]], - }) - })); - this.mappingForm.patchValue(this.prepareFormValueData()); - this.mappingForm.get('requestType').valueChanges.pipe( - startWith(this.mappingForm.get('requestType').value), - takeUntil(this.destroy$) - ).subscribe((value) => { - const requestValueGroup = this.mappingForm.get('requestValue'); - requestValueGroup.get('connectRequests').disable({emitEvent: false}); - requestValueGroup.get('disconnectRequests').disable({emitEvent: false}); - requestValueGroup.get('attributeRequests').disable({emitEvent: false}); - requestValueGroup.get('attributeUpdates').disable({emitEvent: false}); - requestValueGroup.get('serverSideRpc').disable({emitEvent: false}); - requestValueGroup.get(value).enable({emitEvent: false}); - }); - this.mappingForm.get('requestValue.serverSideRpc.type').valueChanges.pipe( - startWith(this.mappingForm.get('requestValue.serverSideRpc.type').value), - takeUntil(this.destroy$) - ).subscribe((value) => { - const requestValueGroup = this.mappingForm.get('requestValue.serverSideRpc'); - if (value === ServerSideRPCType.ONE_WAY) { - requestValueGroup.get('responseTopicExpression').disable({emitEvent: false}); - requestValueGroup.get('responseTopicQoS').disable({emitEvent: false}); - requestValueGroup.get('responseTimeout').disable({emitEvent: false}); - } else { - requestValueGroup.get('responseTopicExpression').enable({emitEvent: false}); - requestValueGroup.get('responseTopicQoS').enable({emitEvent: false}); - requestValueGroup.get('responseTimeout').enable({emitEvent: false}); - } - }); + if (this.data.mappingType === MappingTypes.REQUESTS) { + this.mappingForm.addControl('requestType', this.fb.control(RequestTypes.CONNECT_REQUEST, [])); + this.mappingForm.addControl('requestValue', this.fb.group({ + connectRequests: this.fb.group({ + topicFilter: ['', [Validators.required]], + deviceInfo: [{}, []] + }), + disconnectRequests: this.fb.group({ + topicFilter: ['', [Validators.required]], + deviceInfo: [{}, []] + }), + attributeRequests: this.fb.group({ + topicFilter: ['', [Validators.required]], + deviceInfo: [{}, [Validators.required]], + attributeNameExpressionSource: [SourceTypes.MSG], + attributeNameExpression: ['', [Validators.required]], + topicExpression: ['', [Validators.required]], + valueExpression: ['', [Validators.required]], + retain: [false, []] + }), + attributeUpdates: this.fb.group({ + deviceNameFilter: ['', [Validators.required]], + attributeFilter: ['', [Validators.required]], + topicExpression: ['', [Validators.required]], + valueExpression: ['', [Validators.required]], + retain: [true, []] + }), + serverSideRpc: this.fb.group({ + type: [ServerSideRPCType.TWO_WAY, []], + deviceNameFilter: ['', [Validators.required]], + methodFilter: ['', [Validators.required]], + requestTopicExpression: ['', [Validators.required]], + responseTopicExpression: ['', [Validators.required]], + valueExpression: ['', [Validators.required]], + responseTopicQoS: [0, []], + responseTimeout: [10000, [Validators.required]], + }) + })); + this.mappingForm.patchValue(this.prepareFormValueData()); + this.mappingForm.get('requestType').valueChanges.pipe( + startWith(this.mappingForm.get('requestType').value), + takeUntil(this.destroy$) + ).subscribe((value) => { + const requestValueGroup = this.mappingForm.get('requestValue'); + requestValueGroup.get('connectRequests').disable({emitEvent: false}); + requestValueGroup.get('disconnectRequests').disable({emitEvent: false}); + requestValueGroup.get('attributeRequests').disable({emitEvent: false}); + requestValueGroup.get('attributeUpdates').disable({emitEvent: false}); + requestValueGroup.get('serverSideRpc').disable({emitEvent: false}); + requestValueGroup.get(value).enable({emitEvent: false}); + }); + this.mappingForm.get('requestValue.serverSideRpc.type').valueChanges.pipe( + startWith(this.mappingForm.get('requestValue.serverSideRpc.type').value), + takeUntil(this.destroy$) + ).subscribe((value) => { + const requestValueGroup = this.mappingForm.get('requestValue.serverSideRpc'); + if (value === ServerSideRPCType.ONE_WAY) { + requestValueGroup.get('responseTopicExpression').disable({emitEvent: false}); + requestValueGroup.get('responseTopicQoS').disable({emitEvent: false}); + requestValueGroup.get('responseTimeout').disable({emitEvent: false}); + } else { + requestValueGroup.get('responseTopicExpression').enable({emitEvent: false}); + requestValueGroup.get('responseTopicQoS').enable({emitEvent: false}); + requestValueGroup.get('responseTimeout').enable({emitEvent: false}); } - // } + }); + } } - // private createConvertorGroup(): void { - // - // } - helpLinkId(): string { return ""; } @@ -268,7 +248,6 @@ export class MappingDialogComponent extends DialogComponent div { flex-grow: 1; } - //tb-toggle-password { - // TODO move to form.scss + .mat-mdc-icon-button.mat-mdc-button-base { width: 36px; height: 40px; @@ -140,6 +139,5 @@ .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { height: 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 93e4212990..2a70d60b02 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 @@ -21,7 +21,7 @@ import { FormBuilder, FormGroup, UntypedFormControl, ValidatorFn, Validators } f import { EntityId } from '@shared/models/id/entity-id'; import { AttributeService } from '@core/http/attribute.service'; import { TranslateService } from '@ngx-translate/core'; -import { forkJoin, Observable, Subscription } from 'rxjs'; +import { forkJoin, Observable, Subject, Subscription } from 'rxjs'; import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; import { PageComponent } from '@shared/components/page.component'; import { PageLink } from '@shared/models/page/page-link'; @@ -44,13 +44,13 @@ import { ConnectorConfigurationModes, GatewayConnector, GatewayConnectorDefaultTypesTranslates, - GatewayLogLevel, getDefaultConfig, + GatewayLogLevel, MappingTypes, MqttVersions } from './gateway-widget.models'; import { MatDialog } from '@angular/material/dialog'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; -import { ResourcesService } from '@core/services/resources.service'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-gateway-connector', @@ -121,6 +121,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } }; + private destroy$ = new Subject(); private subscription: IWidgetSubscription; constructor(protected store: Store, @@ -149,9 +150,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie key: ['auto'], class: [''], configuration: [''], - // TODO dynamic structure building configurationJson: [{}, [Validators.required]], - basicConfig: this.fb.group({ // TODO TRANSITION TO CUSTOM COMPONENT IN THE END + basicConfig: this.fb.group({ }) }); this.connectorForm.disable(); @@ -159,7 +159,9 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie ngAfterViewInit() { - this.connectorForm.get('type').valueChanges.subscribe(type => { + this.connectorForm.get('type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(type => { if (type && !this.initialConnector) { this.attributeService.getEntityAttributes(this.device, AttributeScope.CLIENT_SCOPE, [`${type.toUpperCase()}_DEFAULT_CONFIG`], {ignoreErrors: true}).subscribe(defaultConfig=>{ @@ -174,7 +176,9 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } }); - this.connectorForm.get('configurationJson').valueChanges.subscribe((config) => { + this.connectorForm.get('configurationJson').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((config) => { const basicConfig = this.connectorForm.get('basicConfig'); const connectorName = this.connectorForm.get('name').value; const type = this.connectorForm.get('type').value; @@ -245,6 +249,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie }; } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + super.ngOnDestroy(); + } + saveConnector(): void { const value = this.connectorForm.value; value.configuration = camelCase(value.name) + '.json'; @@ -313,7 +323,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.generateSubscription(); }); this.inactiveConnectorsDataSource.loadAttributes(this.device, AttributeScope.SHARED_SCOPE, this.pageLink, reload).subscribe(data => { - // console.log(this.activeData, 'updateData (this.activeData)'); this.sharedAttributeData = data.data.filter(value => this.activeConnectors.includes(value.key)); this.combineData(); }); @@ -343,24 +352,14 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } private combineData() { - // console.log('combineData: ', this.activeData, this.inactiveData, this.sharedAttributeData); this.dataSource.data = [...this.activeData, ...this.inactiveData, ...this.sharedAttributeData].filter((item, index, self) => index === self.findIndex((t) => t.key === item.key) ).map(attribute => { attribute.value = typeof attribute.value === 'string' ? JSON.parse(attribute.value) : attribute.value; return attribute; }); - // console.log(this.dataSource.data, 'this.dataSource.data'); } - // addAttribute(): void { - // if (this.connectorForm.disabled) { - // this.connectorForm.enable(); - // } - // this.nameInput.nativeElement.focus(); - // this.clearOutConnectorForm(); - // } - private clearOutConnectorForm(): void { this.initialConnector = null; this.connectorForm.setControl('basicConfig', this.fb.group({}), {emitEvent: false}); @@ -439,8 +438,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if ($event) { $event.stopPropagation(); } - //TODO discuss this change - // this.initialConnector = attribute.value; const title = `Delete connector ${attribute.key}?`; const content = `All connector data will be deleted.`; this.dialogService.confirm(title, content, 'Cancel', 'Delete').subscribe(result => { @@ -546,10 +543,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if (this.connectorForm.disabled) { this.connectorForm.enable(); } - // if (value.useDefaults) { - // value.configurationJson = getDefaultConfig(this.resourcesService, value.type); - // console.log(value.configurationJson, 'value.configurationJson'); - // } if (!value.configurationJson) { value.configurationJson = {}; } @@ -631,7 +624,9 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if (this.basicConfigSub) { this.basicConfigSub.unsubscribe(); } - this.basicConfigSub = this.connectorForm.get('basicConfig').valueChanges.subscribe((config) => { + this.basicConfigSub = this.connectorForm.get('basicConfig').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((config) => { const configJson = this.connectorForm.get('configurationJson'); const connectorName = this.connectorForm.get('name').value; const type = this.connectorForm.get('type').value; 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 3f53e1cb6e..a402d259ad 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,11 +14,9 @@ /// limitations under the License. /// -import { Input } from '@angular/core'; -import { Validators } from '@angular/forms'; import { ResourcesService } from '@core/services/resources.service'; import { Observable } from 'rxjs'; -import { Unit } from '@shared/models/unit.models'; +import { ValueTypeData } from '@shared/models/constants'; export enum StorageTypes { MEMORY = 'memory', @@ -99,8 +97,7 @@ export const GecurityTypesTranslationsMap = new Map( [ [SecurityTypes.ACCESS_TOKEN, 'gateway.security-types.access-token'], [SecurityTypes.USERNAME_PASSWORD, 'gateway.security-types.username-password'], - [SecurityTypes.TLS_ACCESS_TOKEN, 'gateway.security-types.tls-access-token'], - // [SecurityTypes.TLS_PRIVATE_KEY, 'gateway.security-types.tls-private-key'], + [SecurityTypes.TLS_ACCESS_TOKEN, 'gateway.security-types.tls-access-token'] ] ); @@ -223,7 +220,6 @@ export enum SourceTypes { MSG = 'message', TOPIC = 'topic', CONST = 'constant' - // CUSTOM = 'json' } export const SourceTypeTranslationsMap = new Map( @@ -298,3 +294,44 @@ export enum ServerSideRPCType { export const getDefaultConfig = (resourcesService: ResourcesService, type: string): Observable => resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); + +export enum MappingValueType { + STRING = 'string', + INTEGER = 'integer', + DOUBLE = 'double', + BOOLEAN = 'boolean' +} + +export const mappingValueTypesMap = new Map( + [ + [ + MappingValueType.STRING, + { + name: 'value.string', + icon: 'mdi:format-text' + } + ], + [ + MappingValueType.INTEGER, + { + name: 'value.integer', + icon: 'mdi:numeric' + } + ], + [ + MappingValueType.DOUBLE, + { + name: 'value.double', + icon: 'mdi:numeric' + } + ], + [ + MappingValueType.BOOLEAN, + { + name: 'value.boolean', + icon: 'mdi:checkbox-marked-outline' + } + ] + ] +); + 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 476a0c30de..01b2eb0bf2 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 @@ -81,7 +81,6 @@ import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dial import { DeviceInfoTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/device-info-table.component'; import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component'; import { BrokerSecurityComponent } from '@home/components/widget/lib/gateway/connectors-configuration/broker-security.component'; -import { MqttBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component'; @NgModule({ declarations: @@ -113,7 +112,6 @@ import { MqttBasicConfigComponent } from '@home/components/widget/lib/gateway/co DeviceInfoTableComponent, MappingDataKeysPanelComponent, BrokerSecurityComponent, - MqttBasicConfigComponent, GatewayLogsComponent, GatewayStatisticsComponent, GatewayServiceRPCComponent, @@ -172,7 +170,6 @@ import { MqttBasicConfigComponent } from '@home/components/widget/lib/gateway/co DeviceInfoTableComponent, MappingDataKeysPanelComponent, BrokerSecurityComponent, - MqttBasicConfigComponent, GatewayLogsComponent, GatewayStatisticsComponent, GatewayServiceRPCComponent, diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/bacnet.json b/ui-ngx/src/assets/metadata/connector-default-configs/bacnet.json new file mode 100644 index 0000000000..a03bbdb975 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/bacnet.json @@ -0,0 +1,58 @@ +{ + "general": { + "objectName": "TB_gateway", + "address": "0.0.0.0:47808", + "objectIdentifier": 599, + "maxApduLengthAccepted": 1476, + "segmentationSupported": "segmentedBoth", + "vendorIdentifier": 15 + }, + "devices": [ + { + "deviceName": "BACnet Device ${objectName}", + "deviceType": "default", + "address": "192.168.2.110:47808", + "pollPeriod": 10000, + "attributes": [ + { + "key": "temperature", + "type": "string", + "objectId": "analogOutput:1", + "propertyId": "presentValue" + } + ], + "timeseries": [ + { + "key": "state", + "type": "bool", + "objectId": "binaryValue:1", + "propertyId": "presentValue" + } + ], + "attributeUpdates": [ + { + "key": "brightness", + "requestType": "writeProperty", + "objectId": "analogOutput:1", + "propertyId": "presentValue" + } + ], + "serverSideRpc": [ + { + "method": "set_state", + "requestType": "writeProperty", + "requestTimeout": 10000, + "objectId": "binaryOutput:1", + "propertyId": "presentValue" + }, + { + "method": "get_state", + "requestType": "readProperty", + "requestTimeout": 10000, + "objectId": "binaryOutput:1", + "propertyId": "presentValue" + } + ] + } + ] +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/ble.json b/ui-ngx/src/assets/metadata/connector-default-configs/ble.json new file mode 100644 index 0000000000..609e742f2e --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/ble.json @@ -0,0 +1,73 @@ +{ + "passiveScanMode": true, + "showMap": false, + "scanner": { + "timeout": 10000, + "deviceName": "Device name" + }, + "devices": [ + { + "name": "Temperature and humidity sensor", + "MACAddress": "4C:65:A8:DF:85:C0", + "pollPeriod": 500000, + "showMap": false, + "timeout": 10000, + "connectRetry": 5, + "connectRetryInSeconds": 0, + "waitAfterConnectRetries": 10, + "telemetry": [ + { + "key": "temperature", + "method": "notify", + "characteristicUUID": "226CAA55-6476-4566-7562-66734470666D", + "valueExpression": "[2]" + }, + { + "key": "humidity", + "method": "notify", + "characteristicUUID": "226CAA55-6476-4566-7562-66734470666D", + "valueExpression": "[0]" + } + ], + "attributes": [ + { + "key": "name", + "method": "read", + "characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB", + "valueExpression": "[0:2]cm [2:]A" + }, + { + "key": "values", + "method": "read", + "characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB", + "valueExpression": "All values: [:]" + } + ], + "attributeUpdates": [ + { + "attributeOnThingsBoard": "sharedName", + "characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB" + } + ], + "serverSideRpc": [ + { + "methodRPC": "rpcMethod1", + "withResponse": true, + "characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB", + "methodProcessing": "read" + }, + { + "methodRPC": "rpcMethod2", + "withResponse": true, + "characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB", + "methodProcessing": "write" + }, + { + "methodRPC": "rpcMethod3", + "withResponse": true, + "methodProcessing": "scan" + } + ] + } + ] +} diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/can.json b/ui-ngx/src/assets/metadata/connector-default-configs/can.json new file mode 100644 index 0000000000..ea9d162960 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/can.json @@ -0,0 +1,89 @@ +{ + "interface": "socketcan", + "channel": "vcan0", + "backend": { + "fd": true + }, + "reconnectPeriod": 5, + "devices": [ + { + "name": "Car", + "sendDataOnlyOnChange": false, + "enableUnknownRpc": true, + "strictEval": false, + "attributes": [ + { + "key": "isDriverDoorOpened", + "nodeId": 41, + "command": "2:2:big:8717", + "value": "4:1:int", + "expression": "bool(value & 0b00000100)", + "polling": { + "type": "once", + "dataInHex": "AB CD AB CD" + } + } + ], + "timeseries": [ + { + "key": "rpm", + "nodeId": 1918, + "isExtendedId": true, + "command": "2:2:big:48059", + "value": "4:2:big:int", + "expression": "value / 4", + "polling": { + "type": "always", + "period": 5, + "dataInHex": "aaaa bbbb aaaa bbbb" + } + }, + { + "key": "milliage", + "nodeId": 1918, + "isExtendedId": true, + "value": "4:2:little:int", + "expression": "value * 10", + "polling": { + "type": "always", + "period": 30, + "dataInHex": "aa bb cc dd ee ff aa bb" + } + } + ], + "attributeUpdates": [ + { + "attributeOnThingsBoard": "softwareVersion", + "nodeId": 64, + "isExtendedId": true, + "dataLength": 4, + "dataExpression": "value + 5", + "dataByteorder": "little" + } + ], + "serverSideRpc": [ + { + "method": "sendSameData", + "nodeId": 4, + "isExtendedId": true, + "isFd": true, + "bitrateSwitch": true, + "dataInHex": "aa bb cc dd ee ff aa bb aa bb cc d ee ff" + }, + { + "method": "setLightLevel", + "nodeId": 5, + "dataLength": 2, + "dataByteorder": "little", + "dataBefore": "00AA" + }, + { + "method": "setSpeed", + "nodeId": 16, + "dataAfter": "0102", + "dataExpression": "userSpeed if maxAllowedSpeed > userSpeed else maxAllowedSpeed" + } + ] + } + ] +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/ftp.json b/ui-ngx/src/assets/metadata/connector-default-configs/ftp.json new file mode 100644 index 0000000000..3a193e43da --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/ftp.json @@ -0,0 +1,65 @@ +{ + "host": "0.0.0.0", + "port": 21, + "TLSSupport": false, + "security": { + "type": "basic", + "username": "admin", + "password": "admin" + }, + "paths": [ + { + "devicePatternName": "asd", + "devicePatternType": "Device", + "delimiter": ",", + "path": "fol/*_hello*.txt", + "readMode": "FULL", + "maxFileSize": 5, + "pollPeriod": 500, + "txtFileDataView": "SLICED", + "withSortingFiles": true, + "attributes": [ + { + "key": "temp", + "value": "[1:]" + }, + { + "key": "tmp", + "value": "[0:1]" + } + ], + "timeseries": [ + { + "type": "int", + "key": "[0:1]", + "value": "[0:1]" + }, + { + "type": "int", + "key": "temp", + "value": "[1:]" + } + ] + } + ], + "attributeUpdates": [ + { + "path": "fol/hello.json", + "deviceNameFilter": ".*", + "writingMode": "WRITE", + "valueExpression": "{'${attributeKey}':'${attributeValue}'}" + } + ], + "serverSideRpc": [ + { + "deviceNameFilter": ".*", + "methodFilter": "read", + "valueExpression": "${params}" + }, + { + "deviceNameFilter": ".*", + "methodFilter": "write", + "valueExpression": "${params}" + } + ] +} diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json new file mode 100644 index 0000000000..437a6abd95 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -0,0 +1,248 @@ +{ + "master": { + "slaves": [ + { + "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": "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": [ + { + "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/mqtt.json b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json index cb67ab690f..4d1a12b3f8 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json @@ -30,29 +30,29 @@ "timeout": 60000, "attributes": [ { - "type": "STRING", + "type": "string", "key": "model", "value": "${sensorModel}" }, { - "type": "STRING", + "type": "string", "key": "${sensorModel}", "value": "on" } ], "timeseries": [ { - "type": "DOUBLE", + "type": "string", "key": "temperature", "value": "${temp}" }, { - "type": "DOUBLE", + "type": "double", "key": "humidity", "value": "${hum}" }, { - "type": "STRING", + "type": "string", "key": "combine", "value": "${hum}:${temp}" } @@ -74,19 +74,19 @@ "timeout": 60000, "attributes": [ { - "type": "STRING", + "type": "string", "key": "model", "value": "${sensorModel}" } ], "timeseries": [ { - "type": "DOUBLE", + "type": "double", "key": "temperature", "value": "${temp}" }, { - "type": "DOUBLE", + "type": "string", "key": "humidity", "value": "${hum}" } @@ -99,7 +99,9 @@ "converter": { "type": "bytes", "deviceInfo": { + "deviceNameExpressionSource": "message", "deviceNameExpression": "[0:4]", + "deviceProfileExpressionSource": "constant", "deviceProfileExpression": "default" }, "sendDataOnlyOnChange": false, diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/ocpp.json b/ui-ngx/src/assets/metadata/connector-default-configs/ocpp.json new file mode 100644 index 0000000000..35d878836a --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/ocpp.json @@ -0,0 +1,66 @@ +{ + "centralSystem": { + "name": "Central System", + "host": "127.0.0.1", + "port": 9000, + "connection": { + "type": "insecure" + }, + "security": [ + { + "type": "token", + "tokens": [ + "Bearer ACCESS_TOKEN" + ] + }, + { + "type": "basic", + "credentials": [ + { + "username": "admin", + "password": "admin" + } + ] + } + ] + }, + "chargePoints": [ + { + "idRegexpPattern": "bidon/hello/CP_1", + "deviceNameExpression": "${Vendor} ${Model}", + "deviceTypeExpression": "default", + "attributes": [ + { + "messageTypeFilter": "MeterValues,", + "key": "temp1", + "value": "${meter_value[:].sampled_value[:].value}" + }, + { + "messageTypeFilter": "MeterValues,", + "key": "vendorId", + "value": "${connector_id}" + } + ], + "timeseries": [ + { + "messageTypeFilter": "DataTransfer,", + "key": "temp", + "value": "${data.temp}" + } + ], + "attributeUpdates": [ + { + "attributeOnThingsBoard": "shared", + "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "methodRPC": "rpc1", + "withResponse": true, + "valueExpression": "${params}" + } + ] + } + ] +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/odbc.json b/ui-ngx/src/assets/metadata/connector-default-configs/odbc.json new file mode 100644 index 0000000000..dc70cdf421 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/odbc.json @@ -0,0 +1,58 @@ +{ + "connection": { + "str": "Driver={PostgreSQL};Server=localhost;Port=5432;Database=thingsboard;Uid=postgres;Pwd=postgres;", + "attributes": { + "autocommit": true, + "timeout": 0 + }, + "encoding": "utf-8", + "decoding": { + "char": "utf-8", + "wchar": "utf-8", + "metadata": "utf-16le" + }, + "reconnect": true, + "reconnectPeriod": 60 + }, + "pyodbc": { + "pooling": false + }, + "polling": { + "query": "SELECT bool_v, str_v, dbl_v, long_v, entity_id, ts FROM ts_kv WHERE ts > ? ORDER BY ts ASC LIMIT 10", + "period": 10, + "iterator": { + "column": "ts", + "query": "SELECT MIN(ts) - 1 FROM ts_kv", + "persistent": false + } + }, + "mapping": { + "device": { + "type": "postgres", + "name": "'ODBC ' + entity_id" + }, + "sendDataOnlyOnChange": false, + "attributes": "*", + "timeseries": [ + { + "name": "value", + "value": "[i for i in [str_v, long_v, dbl_v,bool_v] if i is not None][0]" + } + ] + }, + "serverSideRpc": { + "enableUnknownRpc": false, + "overrideRpcConfig": true, + "methods": [ + "procedureOne", + { + "name": "procedureTwo", + "args": [ + "One", + 2, + 3.0 + ] + } + ] + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json new file mode 100644 index 0000000000..91ee295648 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json @@ -0,0 +1,52 @@ +{ + "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" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/opcua_asyncio.json b/ui-ngx/src/assets/metadata/connector-default-configs/opcua_asyncio.json new file mode 100644 index 0000000000..91ee295648 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/opcua_asyncio.json @@ -0,0 +1,52 @@ +{ + "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" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/request.json b/ui-ngx/src/assets/metadata/connector-default-configs/request.json new file mode 100644 index 0000000000..c5b96ff4cf --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/request.json @@ -0,0 +1,169 @@ +{ + "host": "http://127.0.0.1:5000", + "SSLVerify": true, + "security": { + "type": "basic", + "username": "user", + "password": "password" + }, + "mapping": [ + { + "url": "getdata", + "httpMethod": "GET", + "httpHeaders": { + "ACCEPT": "application/json" + }, + "content": { + "name": "morpheus", + "job": "leader" + }, + "allowRedirects": true, + "timeout": 0.5, + "scanPeriod": 5, + "converter": { + "type": "json", + "deviceNameJsonExpression": "SD8500", + "deviceTypeJsonExpression": "SD", + "attributes": [ + { + "key": "serialNumber", + "type": "string", + "value": "${serial}" + } + ], + "telemetry": [ + { + "key": "Maintainer", + "type": "string", + "value": "${Developer}" + }, + { + "key": "combine", + "type": "string", + "value": "${Developer}:${hum}" + } + ] + } + }, + { + "url": "get_info", + "httpMethod": "GET", + "httpHeaders": { + "ACCEPT": "application/json" + }, + "allowRedirects": true, + "timeout": 0.5, + "scanPeriod": 100, + "converter": { + "type": "custom", + "deviceNameJsonExpression": "SD8500", + "deviceTypeJsonExpression": "SD", + "extension": "CustomRequestUplinkConverter", + "extension-config": [ + { + "key": "Totaliser", + "type": "float", + "fromByte": 0, + "toByte": 4, + "byteorder": "big", + "signed": true, + "multiplier": 1 + }, + { + "key": "Flow", + "type": "int", + "fromByte": 4, + "toByte": 6, + "byteorder": "big", + "signed": true, + "multiplier": 0.01 + }, + { + "key": "Temperature", + "type": "int", + "fromByte": 8, + "toByte": 10, + "byteorder": "big", + "signed": true, + "multiplier": 0.01 + }, + { + "key": "Pressure", + "type": "int", + "fromByte": 12, + "toByte": 14, + "byteorder": "big", + "signed": true, + "multiplier": 0.01 + }, + { + "key": "deviceStatus", + "type": "int", + "byteAddress": 15, + "fromBit": 4, + "toBit": 8, + "byteorder": "big", + "signed": false + }, + { + "key": "OUT2", + "type": "int", + "byteAddress": 15, + "fromBit": 1, + "toBit": 2, + "byteorder": "big" + }, + { + "key": "OUT1", + "type": "int", + "byteAddress": 15, + "fromBit": 0, + "toBit": 1, + "byteorder": "big" + } + ] + } + } + ], + "attributeUpdates": [ + { + "httpMethod": "POST", + "httpHeaders": { + "CONTENT-TYPE": "application/json" + }, + "timeout": 0.5, + "tries": 3, + "allowRedirects": true, + "deviceNameFilter": "SD.*", + "attributeFilter": "send_data", + "requestUrlExpression": "sensor/${deviceName}/${attributeKey}", + "requestValueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "deviceNameFilter": ".*", + "methodFilter": "echo", + "requestUrlExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "responseTimeout": 1, + "httpMethod": "GET", + "requestValueExpression": "${params}", + "responseValueExpression": "${temp}", + "timeout": 0.5, + "tries": 3, + "httpHeaders": { + "Content-Type": "application/json" + } + }, + { + "deviceNameFilter": ".*", + "methodFilter": "no-reply", + "requestUrlExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "httpMethod": "POST", + "requestValueExpression": "${params}", + "httpHeaders": { + "Content-Type": "application/json" + } + } + ] +} diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/rest.json b/ui-ngx/src/assets/metadata/connector-default-configs/rest.json new file mode 100644 index 0000000000..f927b8f8b2 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/rest.json @@ -0,0 +1,190 @@ +{ + "host": "127.0.0.1", + "port": "5000", + "SSL": false, + "security": { + "cert": "~/ssl/cert.pem", + "key": "~/ssl/key.pem" + }, + "mapping": [ + { + "endpoint": "/my_devices", + "HTTPMethods": [ + "POST" + ], + "security": { + "type": "anonymous" + }, + "converter": { + "type": "json", + "deviceNameExpression": "${sensorName}", + "deviceTypeExpression": "${sensorType}", + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + }, + { + "type": "string", + "key": "certificateNumber", + "value": "${certificateNumber}" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + } + ] + } + }, + { + "endpoint": "/anon1", + "HTTPMethods": [ + "GET", + "POST" + ], + "security": { + "type": "anonymous" + }, + "converter": { + "type": "json", + "deviceNameExpression": "Device 2", + "deviceTypeExpression": "default", + "attributes": [ + { + "type": "string", + "key": "model", + "value": "Model2" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + } + ] + } + }, + { + "endpoint": "/anon2", + "HTTPMethods": [ + "POST" + ], + "security": { + "type": "anonymous" + }, + "converter": { + "type": "custom", + "deviceNameExpression": "SuperAnonDevice", + "deviceTypeExpression": "default", + "extension": "CustomRestUplinkConverter", + "extension-config": [ + { + "key": "Totaliser", + "datatype": "float", + "fromByte": 0, + "toByte": 4, + "byteorder": "big", + "signed": true, + "multiplier": 1 + } + ] + } + } + ], + "attributeRequests": [ + { + "endpoint": "/sharedAttributes", + "type": "shared", + "HTTPMethods": [ + "POST" + ], + "security": { + "type": "anonymous" + }, + "timeout": 10.0, + "deviceNameExpression": "${deviceName}", + "attributeNameExpression": "${attribute}${attribute1}" + } + ], + "attributeUpdates": [ + { + "HTTPMethod": "POST", + "SSLVerify": false, + "httpHeaders": { + "CONTENT-TYPE": "application/json" + }, + "security": { + "type": "anonymous" + }, + "timeout": 0.5, + "tries": 3, + "allowRedirects": true, + "deviceNameFilter": "SN.*", + "attributeFilter": ".*", + "requestUrlExpression": "http://127.0.0.1:5001/", + "valueExpression": "{\"deviceName\":\"${deviceName}\",\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "deviceNameFilter": ".*", + "methodFilter": "echo", + "requestUrlExpression": "http://127.0.0.1:5001/${deviceName}", + "responseTimeout": 1, + "HTTPMethod": "GET", + "valueExpression": "${params}", + "timeout": 10.0, + "tries": 3, + "httpHeaders": { + "Content-Type": "application/json" + }, + "security": { + "type": "anonymous" + } + }, + { + "deviceNameFilter": "SN.*", + "methodFilter": "post_attributes", + "requestUrlExpression": "http://127.0.0.1:5000/my_devices", + "responseTimeout": 1, + "HTTPMethod": "POST", + "valueExpression": "{\"sensorName\":\"${deviceName}\", \"sensorModel\":\"${params.sensorModel}\", \"certificateNumber\":\"${params.certificateNumber}\", \"temp\":\"${params.temp}\", \"hum\":\"${params.hum}\"}", + "timeout": 10.0, + "tries": 3, + "httpHeaders": { + "Content-Type": "application/json" + }, + "security": { + "type": "anonymous" + } + }, + { + "deviceNameFilter": ".*", + "methodFilter": "no-reply", + "requestUrlExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "HTTPMethod": "POST", + "valueExpression": "${params}", + "httpHeaders": { + "Content-Type": "application/json" + }, + "security": { + "type": "anonymous" + } + } + ] +} \ No newline at end of file diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/snmp.json b/ui-ngx/src/assets/metadata/connector-default-configs/snmp.json new file mode 100644 index 0000000000..1d782dc665 --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/snmp.json @@ -0,0 +1,138 @@ +{ + "devices": [ + { + "deviceName": "SNMP router", + "deviceType": "snmp", + "ip": "snmp.live.gambitcommunications.com", + "port": 161, + "pollPeriod": 5000, + "community": "public", + "attributes": [ + { + "key": "ReceivedFromGet", + "method": "get", + "oid": "1.3.6.1.2.1.1.1.0", + "timeout": 6 + }, + { + "key": "ReceivedFromMultiGet", + "method": "multiget", + "oid": [ + "1.3.6.1.2.1.1.1.0", + "1.3.6.1.2.1.1.2.0" + ], + "timeout": 6 + }, + { + "key": "ReceivedFromGetNext", + "method": "getnext", + "oid": "1.3.6.1.2.1.1.1.0", + "timeout": 6 + }, + { + "key": "ReceivedFromMultiWalk", + "method": "multiwalk", + "oid": [ + "1.3.6.1.2.1.1.1.0", + "1.3.6.0.1.2.1" + ] + }, + { + "key": "ReceivedFromBulkWalk", + "method": "bulkwalk", + "oid": [ + "1.3.6.1.2.1.1.1.0", + "1.3.6.1.2.1.1.2.0" + ] + }, + { + "key": "ReceivedFromBulkGet", + "method": "bulkget", + "scalarOid": [ + "1.3.6.1.2.1.1.1.0", + "1.3.6.1.2.1.1.2.0" + ], + "repeatingOid": [ + "1.3.6.1.2.1.1.1.0", + "1.3.6.1.2.1.1.2.0" + ], + "maxListSize": 10 + } + ], + "telemetry": [ + { + "key": "ReceivedFromWalk", + "community": "private", + "method": "walk", + "oid": "1.3.6.1.2.1.1.1.0" + }, + { + "key": "ReceivedFromTable", + "method": "table", + "oid": "1.3.6.1.2.1.1" + } + ], + "attributeUpdateRequests": [ + { + "attributeFilter": "dataToSet", + "method": "set", + "oid": "1.3.6.1.2.1.1.1.0" + }, + { + "attributeFilter": "dataToMultiSet", + "method": "multiset", + "mappings": { + "1.2.3": "10", + "2.3.4": "${attribute}" + } + } + ], + "serverSideRpcRequests": [ + { + "requestFilter": "setData", + "method": "set", + "oid": "1.3.6.1.2.1.1.1.0" + }, + { + "requestFilter": "multiSetData", + "method": "multiset" + }, + { + "requestFilter": "getData", + "method": "get", + "oid": "1.3.6.1.2.1.1.1.0" + }, + { + "requestFilter": "runBulkWalk", + "method": "bulkwalk", + "oid": [ + "1.3.6.1.2.1.1.1.0", + "1.3.6.1.2.1.1.2.0" + ] + } + ] + }, + { + "deviceName": "SNMP router", + "deviceType": "snmp", + "ip": "127.0.0.1", + "pollPeriod": 5000, + "community": "public", + "converter": "CustomSNMPConverter", + "attributes": [ + { + "key": "ReceivedFromGetWithCustomConverter", + "method": "get", + "oid": "1.3.6.1.2.1.1.1.0" + } + ], + "telemetry": [ + { + "key": "ReceivedFromTableWithCustomConverter", + "method": "table", + "oid": "1.3.6.1.2.1.1.1.0" + } + ] + } + ] +} diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/socket.json b/ui-ngx/src/assets/metadata/connector-default-configs/socket.json new file mode 100644 index 0000000000..47fd59af4a --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/socket.json @@ -0,0 +1,59 @@ +{ + "type": "TCP", + "address": "127.0.0.1", + "port": 50000, + "bufferSize": 1024, + "devices": [ + { + "address": "127.0.0.1:50001", + "deviceName": "Device Example", + "deviceType": "default", + "encoding": "utf-8", + "telemetry": [ + { + "key": "temp", + "byteFrom": 0, + "byteTo": -1 + }, + { + "key": "hum", + "byteFrom": 0, + "byteTo": 2 + } + ], + "attributes": [ + { + "key": "name", + "byteFrom": 0, + "byteTo": -1 + }, + { + "key": "num", + "byteFrom": 2, + "byteTo": 4 + } + ], + "attributeRequests": [ + { + "type": "shared", + "requestExpression": "${[0:3]==atr}", + "attributeNameExpression": "[3:]" + } + ], + "attributeUpdates": [ + { + "encoding": "utf-16", + "attributeOnThingsBoard": "sharedName" + } + ], + "serverSideRpc": [ + { + "methodRPC": "rpcMethod1", + "withResponse": true, + "methodProcessing": "write", + "encoding": "utf-8" + } + ] + } + ] +} diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/xmpp.json b/ui-ngx/src/assets/metadata/connector-default-configs/xmpp.json new file mode 100644 index 0000000000..e66504363e --- /dev/null +++ b/ui-ngx/src/assets/metadata/connector-default-configs/xmpp.json @@ -0,0 +1,53 @@ +{ + "server": { + "jid": "gateway@localhost", + "password": "password", + "host": "localhost", + "port": 5222, + "use_ssl": false, + "disable_starttls": false, + "force_starttls": true, + "timeout": 10000, + "plugins": [ + "xep_0030", + "xep_0323", + "xep_0325" + ] + }, + "devices": [ + { + "jid": "device@localhost/TMP_1101", + "deviceNameExpression": "${serialNumber}", + "deviceTypeExpression": "default", + "attributes": [ + { + "key": "temperature", + "value": "${temp}" + } + ], + "timeseries": [ + { + "key": "humidity", + "value": "${hum}" + }, + { + "key": "combination", + "value": "${temp}:${hum}" + } + ], + "attributeUpdates": [ + { + "attributeOnThingsBoard": "shared", + "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "methodRPC": "rpc1", + "withResponse": true, + "valueExpression": "${params}" + } + ] + } + ] +} From 89f13592f000aa269bbda3cc06cdef9c09d73691 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 28 Mar 2024 11:03:30 +0200 Subject: [PATCH 03/19] UI: added error state matcher for gateway connectors config --- .../gateway/gateway-connectors.component.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 2a70d60b02..4ad9fce364 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 @@ -17,7 +17,15 @@ import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, NgZone, ViewChild } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormGroup, UntypedFormControl, ValidatorFn, Validators } from '@angular/forms'; +import { + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, NgForm, + UntypedFormControl, + ValidatorFn, + Validators +} from '@angular/forms'; import { EntityId } from '@shared/models/id/entity-id'; import { AttributeService } from '@core/http/attribute.service'; import { TranslateService } from '@ngx-translate/core'; @@ -51,10 +59,18 @@ import { import { MatDialog } from '@angular/material/dialog'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; import { takeUntil } from 'rxjs/operators'; +import { ErrorStateMatcher } from '@angular/material/core'; + +export class ForceErrorStateMatcher implements ErrorStateMatcher { + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + return (control && control.invalid); + } +} @Component({ selector: 'tb-gateway-connector', templateUrl: './gateway-connectors.component.html', + providers: [{ provide: ErrorStateMatcher, useClass: ForceErrorStateMatcher }], styleUrls: ['./gateway-connectors.component.scss'] }) export class GatewayConnectorComponent extends PageComponent implements AfterViewInit { From a78e8fce5e7fc9ee9f4bb9a8697ce38a461f6708 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 28 Mar 2024 18:25:00 +0200 Subject: [PATCH 04/19] UI: imrovements for gateway mqtt connector --- .../mapping-data-keys-panel.component.html | 9 +- .../mapping-data-keys-panel.component.scss | 2 +- .../mapping-data-keys-panel.component.ts | 1 - .../mqtt-basic-config.component.html | 175 ------------------ .../mqtt-basic-config.component.scss | 27 --- .../mqtt-basic-config.component.ts | 150 --------------- .../dialog/mapping-dialog.component.html | 2 +- .../dialog/mapping-dialog.component.ts | 11 +- ui-ngx/src/form.scss | 3 + 9 files changed, 21 insertions(+), 359 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.html delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html index f9abdd7434..594cd03874 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html @@ -18,8 +18,9 @@
{{ panelTitle | translate }}{{' (' + keysListFormArray.controls.length + ')'}}
-
-
+
+
@@ -106,7 +107,9 @@
- {{ noKeysText }} +
+ {{ noKeysText }} +
- - - - - - - - - - - - -
-
- - -
- - - -
- -
-
- -
- -
-
- -
- -
-
-
- gateway.max-number-of-workers -
-
- - - - warning - - -
-
-
-
- gateway.max-messages-queue-for-worker -
-
- - - - warning - - -
-
-
-
-
-
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss deleted file mode 100644 index 4d6211765a..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.scss +++ /dev/null @@ -1,27 +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. - */ -:host { - //width: 100%; - //height: 100%; - //display: block; - // - //.tb-form-row { - // .tb-form-table-header { - // min-height: 48px; - // } - //} - -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts deleted file mode 100644 index e1b9b48833..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config.component.ts +++ /dev/null @@ -1,150 +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 { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - forwardRef, - Input, - NgZone, - OnDestroy, - OnInit, - ViewContainerRef -} from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; -import { DialogService } from '@core/services/dialog.service'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { Overlay } from '@angular/cdk/overlay'; -import { UtilsService } from '@core/services/utils.service'; -import { EntityService } from '@core/http/entity.service'; -import { - ControlValueAccessor, - FormBuilder, FormGroup, NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, ValidationErrors, Validator, Validators -} from '@angular/forms'; -import { - MappingTypes, - MqttVersions, - SourceTypes, - SourceTypeTranslationsMap -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { generateSecret } from '@core/utils'; - -@Component({ - selector: 'tb-mqtt-basic-config', - templateUrl: './mqtt-basic-config.component.html', - styleUrls: ['./mqtt-basic-config.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - } - ] -}) -export class MqttBasicConfigComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { - - mqttConfigFormGroup: UntypedFormGroup; - - mqttVersions = MqttVersions; - - mappingTypes = MappingTypes; - - private destroy$ = new Subject(); - private propagateChange = (v: any) => {}; - - constructor(protected store: Store, - public translate: TranslateService, - public dialog: MatDialog, - private overlay: Overlay, - private viewContainerRef: ViewContainerRef, - private dialogService: DialogService, - private entityService: EntityService, - private utils: UtilsService, - private zone: NgZone, - private cd: ChangeDetectorRef, - private elementRef: ElementRef, - private fb: FormBuilder) { - super(store); - console.log('mqttconfig inited'); - } - - ngOnInit() { - this.mqttConfigFormGroup = this.fb.group({ - broker: this.fb.group({ - name: ['', [Validators.required]], - host: ['', [Validators.required]], - port: [null, [Validators.required]], - version: [5, []], - clientId: ['', []], - maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], - maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], - security: [{}, [Validators.required]] - }), - dataMapping: [[], Validators.required], - requestsMapping: [{}, []] - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - ngAfterViewInit() { - } - - registerOnChange(fn: any): void { - this.propagateChange = fn; - } - - registerOnTouched(fn: any): void {} - - writeValue(deviceInfo: any) { - this.mqttConfigFormGroup.patchValue(deviceInfo, {emitEvent: false}); - } - - validate(): ValidationErrors | null { - return this.mqttConfigFormGroup.valid ? null : { - mqttConfigForm: { valid: false } - }; - } - - updateView(value: any) { - this.propagateChange(value); - } - - generate(formControlName: string) { - this.mqttConfigFormGroup.get(formControlName).patchValue(generateSecret(5)); - } - -} 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 e7639c1c2b..b1b48654eb 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 @@ -512,7 +512,7 @@
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 bcf0d24365..566ab0016a 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 @@ -82,6 +82,8 @@ export class MappingDialogComponent extends DialogComponent(); @@ -241,7 +243,9 @@ export class MappingDialogComponent extends DialogComponent { dataKeysPanelPopover.hide(); keysControl.patchValue(keysData); + keysControl.markAsDirty(); + }); + dataKeysPanelPopover.tbHideStart.subscribe(() => { + this.keysPopupClosed = true; }); } } diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index e72f38deaf..3d0eef9718 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -245,6 +245,9 @@ display: flex; flex: 1; gap: 8px; + &.no-flex { + flex: none; + } &.row { flex-direction: row; } From 9b7b31f97b223f250f678f50c4b1ea4da73bae63 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Tue, 2 Apr 2024 11:45:47 +0300 Subject: [PATCH 05/19] UI: refactoring gateway connectors --- .../broker-security.component.html | 42 +---------- .../broker-security.component.ts | 31 ++++---- .../device-info-table.component.ts | 8 +- .../mapping-data-keys-panel.component.ts | 7 +- .../mapping-table.component.html | 6 +- .../mapping-table.component.scss | 51 +------------ .../mapping-table.component.ts | 44 +++++------ .../add-connector-dialog.component.html | 4 +- .../dialog/add-connector-dialog.component.ts | 9 ++- .../dialog/mapping-dialog.component.html | 69 +++++++++-------- .../dialog/mapping-dialog.component.scss | 12 +++ .../dialog/mapping-dialog.component.ts | 60 ++++++++------- .../gateway/gateway-connectors.component.html | 39 ++++------ .../gateway/gateway-connectors.component.scss | 71 +++++++++++------- .../gateway/gateway-connectors.component.ts | 75 ++++++++++--------- ...gateway-service-rpc-connector.component.ts | 4 +- .../lib/gateway/gateway-widget.models.ts | 67 ++++++++++------- .../assets/locale/locale.constant-en_US.json | 1 + 18 files changed, 291 insertions(+), 309 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html index 30200f0fa5..319b10f0b2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html @@ -20,12 +20,12 @@
gateway.security
- {{ SecurityTypeTranslations.get(type) | translate }} + {{ SecurityTypeTranslationsMap.get(type) | translate }}
- +
gateway.username
@@ -50,36 +50,18 @@
- - warning -
- +
{{ 'gateway.path-hint' | translate }}
gateway.CA-certificate-path
- - warning -
@@ -88,15 +70,6 @@
- - warning -
@@ -105,15 +78,6 @@
- - warning -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts index f0e397e1ac..179329a38d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts @@ -40,11 +40,14 @@ import { FormBuilder, NG_VALIDATORS, NG_VALUE_ACCESSOR, - UntypedFormGroup, ValidationErrors, Validator, Validators + UntypedFormGroup, + ValidationErrors, + Validator, + Validators } from '@angular/forms'; import { - BrokerSecurityTypes, - BrokerSecurityTypeTranslations, + BrokerSecurityType, + BrokerSecurityTypeTranslationsMap, } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { takeUntil } from 'rxjs/operators'; @@ -68,11 +71,11 @@ import { takeUntil } from 'rxjs/operators'; }) export class BrokerSecurityComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { - BrokerSecurityTypes = BrokerSecurityTypes; + BrokerSecurityType = BrokerSecurityType; - securityTypes = Object.values(BrokerSecurityTypes); + securityTypes = Object.values(BrokerSecurityType); - SecurityTypeTranslations = BrokerSecurityTypeTranslations; + SecurityTypeTranslationsMap = BrokerSecurityTypeTranslationsMap; securityFormGroup: UntypedFormGroup; @@ -93,12 +96,12 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal private fb: FormBuilder) { super(store); this.securityFormGroup = this.fb.group({ - type: [BrokerSecurityTypes.ANONYMOUS, []], + type: [BrokerSecurityType.ANONYMOUS, []], username: ['', [Validators.required]], - password: ['', [Validators.required]], - pathToCACert: ['', [Validators.required]], - pathToPrivateKey: ['', [Validators.required]], - pathToClientCert: ['', [Validators.required]] + password: ['', []], + pathToCACert: ['', []], + pathToPrivateKey: ['', []], + pathToClientCert: ['', []] }); this.securityFormGroup.valueChanges.pipe( takeUntil(this.destroy$) @@ -132,7 +135,7 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal writeValue(deviceInfo: any) { if (!deviceInfo.type) { - deviceInfo.type = BrokerSecurityTypes.ANONYMOUS; + deviceInfo.type = BrokerSecurityType.ANONYMOUS; } this.securityFormGroup.reset(deviceInfo); this.updateView(deviceInfo); @@ -155,10 +158,10 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal this.securityFormGroup.get('pathToCACert').disable({emitEvent: false}); this.securityFormGroup.get('pathToPrivateKey').disable({emitEvent: false}); this.securityFormGroup.get('pathToClientCert').disable({emitEvent: false}); - if (type === BrokerSecurityTypes.BASIC) { + if (type === BrokerSecurityType.BASIC) { this.securityFormGroup.get('username').enable({emitEvent: false}); this.securityFormGroup.get('password').enable({emitEvent: false}); - } else if (type === BrokerSecurityTypes.CERTIFICATES) { + } else if (type === BrokerSecurityType.CERTIFICATES) { this.securityFormGroup.get('pathToCACert').enable({emitEvent: false}); this.securityFormGroup.get('pathToPrivateKey').enable({emitEvent: false}); this.securityFormGroup.get('pathToClientCert').enable({emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts index 2f6b903646..e9cec11420 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts @@ -42,9 +42,13 @@ import { ControlValueAccessor, FormBuilder, NG_VALIDATORS, NG_VALUE_ACCESSOR, - UntypedFormGroup, ValidationErrors, Validator, Validators + UntypedFormGroup, + ValidationErrors, + Validator, + Validators } from '@angular/forms'; import { + DeviceInfoType, SourceTypes, SourceTypeTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -129,7 +133,7 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa this.mappingFormGroup.addControl('deviceNameExpressionSource', this.fb.control(SourceTypes.MSG, [])); } - if (this.deviceInfoTypeValue === 'full') { + if (this.deviceInfoType === DeviceInfoType.FULL) { if (this.useSource) { this.mappingFormGroup.addControl('deviceProfileExpressionSource', this.fb.control(SourceTypes.MSG, [])); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts index 341646e6f1..a9ee443f22 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts @@ -35,6 +35,7 @@ import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; import { isDefinedAndNotNull } from '@core/utils'; import { + MappingDataKey, MappingKeysType, MappingValueType, mappingValueTypesMap @@ -61,7 +62,7 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn noKeysText: string; @Input() - keys: string; + keys: Array | {[key: string]: any}; @Input() keysType: string; @@ -140,7 +141,7 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn this.keysDataApplied.emit(keys); } - private prepareKeysFormArray(keys: any): UntypedFormArray { + private prepareKeysFormArray(keys: Array | {[key: string]: any}): UntypedFormArray { const keysControlGroups: Array = []; if (keys) { if (this.keysType === MappingKeysType.CUSTOM) { @@ -161,7 +162,7 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn return this.fb.array(keysControlGroups); } - valueTitle(value: any): any { + valueTitle(value: any): string { if (isDefinedAndNotNull(value)) { if (typeof value === 'object') { return JSON.stringify(value); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html index 6e7afd1a45..2cc388a2aa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html @@ -15,12 +15,12 @@ limitations under the License. --> -
-
+
+
- {{mappingTypeTranslationsMap.get(mappingType) | translate}} + {{mappingTypeTranslationsMap.get(mappingType) | translate}}
-
+
- - {{ column.title | translate }} - {{ mapping[column.def] }} + + + {{ column.title | translate }} + + + {{ mapping[column.def] }} + - - - -
+ + + + +
+
+ + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss index 51ef102eae..affc4e5982 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss @@ -52,6 +52,15 @@ .mat-mdc-table { table-layout: fixed; min-width: 450px; + + .table-value-column { + padding: 0 12px; + width: 23%; + + &.request-column { + width: 38%; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts index 9f940f3aec..a5c6addfe4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts @@ -69,6 +69,7 @@ import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; export class MappingTableComponent extends PageComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy { mappingTypeTranslationsMap = MappingTypeTranslationsMap; + mappingTypeEnum = MappingType; displayedColumns = []; mappingColumns = []; textSearchMode = false; @@ -124,7 +125,7 @@ export class MappingTableComponent extends PageComponent implements ControlValue this.mappingColumns.push( {def: 'topicFilter', title: 'gateway.topic-filter'}, {def: 'QoS', title: 'gateway.mqtt-qos'}, - {def: 'converter', title: 'gateway.converter'} + {def: 'converter', title: 'gateway.payload-type'} ) } else { this.mappingColumns.push( @@ -299,7 +300,7 @@ export class MappingTableComponent extends PageComponent implements ControlValue } -export class MappingDatasource implements DataSource { +export class MappingDatasource implements DataSource<{[key: string]: any}> { private mappingSubject = new BehaviorSubject>([]); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html index b4a04e1cbd..b4dd8515df 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ "gateway.add-connector" | translate}}

@@ -30,7 +30,7 @@
-
+
gateway.type
@@ -47,12 +47,14 @@
gateway.name
- + warning @@ -83,7 +85,7 @@
-
+
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss index 813b4ec9ff..56e4bc229c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss @@ -15,12 +15,8 @@ */ :host { - form { + .add-connector { min-width: 400px; width: 500px; } } - -:host ::ng-deep .mat-padding{ - padding: 0; -} 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 41994b3bf0..b635618ec9 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 @@ -25,7 +25,9 @@ import { Router } from '@angular/router'; import { ConnectorType, GatewayConnectorDefaultTypesTranslatesMap, - GatewayLogLevel, getDefaultConfig + GatewayLogLevel, + getDefaultConfig, + noLeadTrailSpacesRegex } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { Subject } from 'rxjs'; import { ResourcesService } from '@core/services/resources.service'; @@ -58,7 +60,7 @@ export class AddConnectorDialogComponent extends DialogComponent -
+

{{ MappingTypeTranslationsMap.get(this.data?.mappingType) | translate}}

@@ -26,26 +26,15 @@ close
- - -
-
+
{{ MappingHintTranslationsMap.get(this.data?.mappingType) | translate }}
-
-
gateway.topic-filter
-
-
-
+
gateway.topic-filter
@@ -53,15 +42,24 @@ matTooltipPosition="above" matTooltipClass="tb-error-tooltip" [matTooltip]="('gateway.topic-required') | translate" - *ngIf="mappingForm.get('topicFilter').hasError('required') && mappingForm.get('topicFilter').touched" + *ngIf="mappingForm.get('topicFilter').hasError('required') && + mappingForm.get('topicFilter').touched;" class="tb-error"> warning +
+
-
gateway.mqtt-qos
+
+ gateway.mqtt-qos +
@@ -73,14 +71,18 @@
+
+
gateway.payload-type
+ + + {{ ConvertorTypeTranslationsMap.get(type) | translate }} + + +
-
-
gateway.converter
- - - {{ ConvertorTypeTranslationsMap.get(type) | translate }} - - +
gateway.data-conversion
+
+ {{ DataConversionTranslationsMap.get(converterType) | translate }}
@@ -97,9 +99,12 @@
gateway.attributes
- + - {{ attribute.key }} + {{ attribute }} + + +
-
gateway.telemetry
+
gateway.timeseries
- + - {{ telemetry.key }} + {{ telemetry }} + + +
@@ -228,18 +237,40 @@
gateway.from-device-request-settings
- - -
+
-
gateway.attribute-name-expression
-
-
+
gateway.device-info.device-name-expression
+
+ + + + {{ SourceTypeTranslationsMap.get(type) | translate }} + + + + + + + warning + +
+
+
+
+
+
+
gateway.attribute-name-expression
@@ -259,6 +290,12 @@ class="tb-error"> warning +
+
@@ -266,62 +303,58 @@
gateway.to-device-response-settings
-
-
gateway.response-topic-expression
-
-
-
+
gateway.response-value-expression
- + warning +
+
- - - {{ 'gateway.retain' | translate }} - - -
-
-
-
gateway.response-value-expression
-
-
-
+
gateway.response-topic-expression
- + warning +
+
+
+ + + {{ 'gateway.retain' | translate }} + + +
@@ -362,62 +395,58 @@
-
-
gateway.response-topic-expression
-
-
-
+
gateway.response-value-expression
- + warning +
+
- - - {{ 'gateway.retain' | translate }} - - -
-
-
-
gateway.response-value-expression
-
-
-
+
gateway.response-topic-expression
- + warning +
+
+
+ + + {{ 'gateway.retain' | translate }} + + +
@@ -469,15 +498,7 @@
-
-
gateway.request-topic-expression
-
-
-
+
gateway.request-topic-expression
@@ -490,19 +511,17 @@ class="tb-error"> warning +
+
-
-
gateway.value-expression
-
-
-
+
gateway.value-expression
@@ -515,20 +534,18 @@ class="tb-error"> warning +
+
-
-
gateway.response-topic-expression
-
-
-
+
gateway.response-topic-expression
@@ -541,11 +558,19 @@ class="tb-error"> warning +
+
-
gateway.response-topic-Qos
+
+ gateway.response-topic-Qos +
@@ -577,20 +602,19 @@ -
+
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss index d976d5df7b..9dbe29d77b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss @@ -15,17 +15,25 @@ */ :host { - form { + .key-mapping { min-width: 700px; width: 900px; + height: 770px; + display: flex; + flex-direction: column; + + .mat-toolbar { + min-height: 64px; + } + + tb-toggle-select { + padding: 4px 0; + } } } :host ::ng-deep { - .mat-padding { - padding: 0; - } - form { + .key-mapping { .mat-mdc-chip-listbox { .mdc-evolution-chip-set__chips { justify-content: flex-end; @@ -45,4 +53,14 @@ width: 0; } } + + .see-example { + width: 32px; + height: 32px; + margin: 4px; + } + + .mat-mdc-form-field-icon-suffix { + display: flex; + } } 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 9cb558e20c..0457c3693a 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 @@ -24,8 +24,12 @@ import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { ConvertorType, - ConvertorTypeTranslationsMap, DeviceInfoType, MappingDataKey, - MappingHintTranslationsMap, MappingInfo, + ConvertorTypeTranslationsMap, + DataConversionTranslationsMap, + DeviceInfoType, + MappingDataKey, + MappingHintTranslationsMap, + MappingInfo, MappingKeysAddKeyTranslationsMap, MappingKeysDeleteKeyTranslationsMap, MappingKeysNoKeysTextTranslationsMap, @@ -33,6 +37,7 @@ import { MappingKeysType, MappingType, MappingTypeTranslationsMap, + noLeadTrailSpacesRegex, QualityTypes, QualityTypeTranslationsMap, RequestType, @@ -84,6 +89,10 @@ export class MappingDialogComponent extends DialogComponent { + get converterAttributes(): Array { if (this.converterType) { - return this.mappingForm.get('converter').get(this.converterType).value.attributes; + return this.mappingForm.get('converter').get(this.converterType).value.attributes.map(value => value.key); } } get converterTelemetry(): Array { if (this.converterType) { - return this.mappingForm.get('converter').get(this.converterType).value.timeseries; + return this.mappingForm.get('converter').get(this.converterType).value.timeseries.map(value => value.key); } } @@ -120,7 +129,7 @@ export class MappingDialogComponent extends DialogComponent + *matRowDef="let attribute; let i = index; columns: displayedColumns;" (click)="selectConnector($event, attribute)">
-
-
- {{initialConnector?.type ? gatewayConnectorDefaultTypes.get(initialConnector.type) : ''}} - {{'gateway.configuration' | translate}} +
+
+ {{ initialConnector?.type ? gatewayConnectorDefaultTypes.get(initialConnector.type) : '' }} + {{ 'gateway.configuration' | translate }}
@@ -164,6 +164,11 @@
+ + gateway.select-connector +
@@ -184,14 +189,24 @@
-
-
gateway.remote-logging-level
-
- - - {{ logLevel }} - - +
+
gateway.logs-configuration
+
+ + + {{ 'gateway.enable-remote-logging' | translate }} + + +
+
+
gateway.remote-logging-level
+
+ + + {{ logLevel }} + + +
@@ -230,7 +245,7 @@
gateway.port
- + ([]); this.connectorForm = this.fb.group({ - mode: [ConnectorConfigurationModes.BASIC, []], - name: ['', [Validators.required, this.uniqNameRequired()]], - type: ['', [Validators.required]], - logLevel: ['', [Validators.required]], - sendDataOnlyOnChange: [false, []], - key: ['auto'], - class: [''], - configuration: [''], - configurationJson: [{}, [Validators.required]], - basicConfig: this.fb.group({ + mode: [ConnectorConfigurationModes.BASIC, []], + name: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(noLeadTrailSpacesRegex)]], + type: ['', [Validators.required]], + enableRemoteLogging: [false, []], + logLevel: ['', [Validators.required]], + sendDataOnlyOnChange: [false, []], + key: ['auto'], + class: [''], + configuration: [''], + configurationJson: [{}, [Validators.required]], + basicConfig: this.fb.group({ }) }); this.connectorForm.disable(); @@ -392,6 +394,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie name: '', type: ConnectorType.MQTT, sendDataOnlyOnChange: false, + enableRemoteLogging: false, logLevel: GatewayLogLevel.INFO, key: 'auto', class: '', @@ -402,34 +405,41 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.markAsPristine(); } - selectConnector(attribute: AttributeData): void { - const connector = attribute.value; - if (connector?.name !== this.initialConnector?.name) { - if (this.connectorForm.disabled) { - this.connectorForm.enable(); - } - if (!connector.configuration) { - connector.configuration = ''; - } - if (!connector.key) { - connector.key = 'auto'; - } - if (!connector.configurationJson) { - connector.configurationJson = {}; - } - connector.basicConfig = connector.configurationJson; + selectConnector($event: Event, attribute: AttributeData): void { + if ($event) { + $event.stopPropagation(); + } + this.confirmConnectorChange().subscribe((result) => { + if (result) { + const connector = attribute.value; + if (connector?.name !== this.initialConnector?.name) { + if (this.connectorForm.disabled) { + this.connectorForm.enable(); + } + if (!connector.configuration) { + connector.configuration = ''; + } + if (!connector.key) { + connector.key = 'auto'; + } + if (!connector.configurationJson) { + connector.configurationJson = {}; + } + connector.basicConfig = connector.configurationJson; - this.initialConnector = connector; + this.initialConnector = connector; - if (connector.type === ConnectorType.MQTT) { - this.addMQTTConfigControls(); - } else { - this.connectorForm.setControl('basicConfig', this.fb.group({}), {emitEvent: false}); - } + if (connector.type === ConnectorType.MQTT) { + this.addMQTTConfigControls(); + } else { + this.connectorForm.setControl('basicConfig', this.fb.group({}), {emitEvent: false}); + } - this.connectorForm.patchValue(connector, {emitEvent: false}); - this.connectorForm.markAsPristine(); - } + this.connectorForm.patchValue(connector, {emitEvent: false}); + this.connectorForm.markAsPristine(); + } + } + }); } isSameConnector(attribute: AttributeData): boolean { @@ -462,7 +472,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if ($event) { $event.stopPropagation(); } - const title = `Delete connector ${attribute.key}?`; + const title = `Delete connector \"${attribute.key}\"?`; const content = `All connector data will be deleted.`; this.dialogService.confirm(title, content, 'Cancel', 'Delete').subscribe(result => { if (result) { @@ -562,28 +572,31 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie dataSourceData: this.dataSource.data } }).afterClosed().subscribe((value) => { - if (value) { - this.initialConnector = null; - if (this.connectorForm.disabled) { - this.connectorForm.enable(); - } - if (!value.configurationJson) { - value.configurationJson = {}; - } - value.basicConfig = value.configurationJson; - if (value.type === ConnectorType.MQTT) { - this.addMQTTConfigControls(); - } else { - this.connectorForm.setControl('basicConfig', this.fb.group({}), {emitEvent: false}); + this.confirmConnectorChange().subscribe((changeConfirmed) => { + if (value && changeConfirmed) { + this.initialConnector = null; + if (this.connectorForm.disabled) { + this.connectorForm.enable(); + } + if (!value.configurationJson) { + value.configurationJson = {}; + } + value.basicConfig = value.configurationJson; + if (value.type === ConnectorType.MQTT) { + this.addMQTTConfigControls(); + } else { + this.connectorForm.setControl('basicConfig', this.fb.group({}), {emitEvent: false}); + } + this.connectorForm.patchValue(value, {emitEvent: false}); + this.generate('basicConfig.broker.clientId'); + this.saveConnector(); } - this.connectorForm.patchValue(value, {emitEvent: false}); - this.saveConnector() - } + }) }); } generate(formControlName: string): void { - this.connectorForm.get(formControlName).patchValue(generateSecret(5)); + this.connectorForm.get(formControlName).patchValue('tb_gw_' + generateSecret(5)); } private onDataUpdateError(e: any): void { @@ -625,10 +638,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const configControl = this.fb.group({}); const brokerGroup = this.fb.group({ name: ['', []], - host: ['', [Validators.required]], + host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], port: [null, [Validators.required]], version: [5, []], - clientId: ['', []], + clientId: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], security: [{}, [Validators.required]] @@ -663,4 +676,17 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } }); } + + private confirmConnectorChange(): Observable { + if (this.initialConnector && this.connectorForm.dirty) { + return this.dialogService.confirm( + this.translate.instant('gateway.change-connector-title'), + this.translate.instant('gateway.change-connector-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ); + } + return of(true); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index e1919041f5..2ea5964b84 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -37,6 +37,7 @@ import { HTTPMethods, ModbusCodesTranslate, ModbusCommandTypes, + noLeadTrailSpacesRegex, RPCCommand, RPCTemplateConfig, SNMPMethods, @@ -53,8 +54,6 @@ import { import { jsonRequired } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; -export const noLeadTrailSpacesRegex: RegExp = /^(?! )[\S\s]*(?( [ [MappingKeysType.ATTRIBUTES, 'gateway.attributes'], - [MappingKeysType.TIMESERIES, 'gateway.telemetry'], + [MappingKeysType.TIMESERIES, 'gateway.timeseries'], [MappingKeysType.CUSTOM, 'gateway.keys'] ] ); @@ -445,7 +447,7 @@ export const MappingKeysPanelTitleTranslationsMap = new Map( [ [MappingKeysType.ATTRIBUTES, 'gateway.add-attribute'], - [MappingKeysType.TIMESERIES, 'gateway.add-telemetry'], + [MappingKeysType.TIMESERIES, 'gateway.add-timeseries'], [MappingKeysType.CUSTOM, 'gateway.add-key'] ] ); @@ -453,7 +455,7 @@ export const MappingKeysAddKeyTranslationsMap = new Map export const MappingKeysDeleteKeyTranslationsMap = new Map( [ [MappingKeysType.ATTRIBUTES, 'gateway.delete-attribute'], - [MappingKeysType.TIMESERIES, 'gateway.delete-telemetry'], + [MappingKeysType.TIMESERIES, 'gateway.delete-timeseries'], [MappingKeysType.CUSTOM, 'gateway.delete-key'] ] ); @@ -461,7 +463,7 @@ export const MappingKeysDeleteKeyTranslationsMap = new Map( [ [MappingKeysType.ATTRIBUTES, 'gateway.no-attributes'], - [MappingKeysType.TIMESERIES, 'gateway.no-telemetry'], + [MappingKeysType.TIMESERIES, 'gateway.no-timeseries'], [MappingKeysType.CUSTOM, 'gateway.no-keys'] ] ); @@ -515,3 +517,10 @@ export const mappingValueTypesMap = new Map( ] ); +export const DataConversionTranslationsMap = new Map( + [ + [ConvertorType.JSON, 'gateway.JSON-hint'], + [ConvertorType.BYTES, 'gateway.bytes-hint'], + [ConvertorType.CUSTOM, 'gateway.custom-hint'] + ] +); 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 855c035ff1..6c9a733b47 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 @@ -92,6 +92,7 @@ import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dial import { DeviceInfoTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/device-info-table.component'; import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component'; import { BrokerSecurityComponent } from '@home/components/widget/lib/gateway/connectors-configuration/broker-security.component'; +import { EllipsisChipListDirective } from '@home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive'; @NgModule({ declarations: @@ -132,6 +133,7 @@ import { BrokerSecurityComponent } from '@home/components/widget/lib/gateway/con GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, GatewayServiceRPCConnectorTemplateDialogComponent, + EllipsisChipListDirective, ValueCardWidgetComponent, AggregatedValueCardWidgetComponent, CountWidgetComponent, @@ -189,6 +191,7 @@ import { BrokerSecurityComponent } from '@home/components/widget/lib/gateway/con GatewayLogsComponent, GatewayServiceRPCConnectorComponent, GatewayServiceRPCConnectorTemplatesComponent, + EllipsisChipListDirective, GatewayStatisticsComponent, GatewayServiceRPCComponent, DeviceGatewayCommandComponent, 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 4a3fa1f34f..36e198d49b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2708,12 +2708,12 @@ "add-entry": "Add configuration", "add-attribute": "Add attribute", "add-key": "Add key", - "add-telemetry": "Add telemetry", + "add-timeseries": "Add timeseries", "add-mapping": "Add mapping", "advanced": "Advanced", "attributes": "Attributes", "attribute-filter": "Attribute filter", - "attribute-filter-hint": "Filter for incoming attribute name, supports regular expression.", + "attribute-filter-hint": "Filter for incoming attribute name from ThingsBoard, supports regular expression.", "attribute-filter-required": "Attribute filter required.", "attribute-name-expression": "Attribute name expression", "attribute-name-expression-required": "Attribute name expression required.", @@ -2731,6 +2731,8 @@ }, "CA-certificate-path": "Path to CA certificate file", "path-to-CA-cert-required": "Path to CA certificate file is required.", + "change-connector-title": "Confirm connector change", + "change-connector-text": "Switching connectors will discard any unsaved changes. Continue?", "checking-device-activity": "Checking device activity", "command": "Docker commands", "command-copied-message": "Docker command has been copied to clipboard", @@ -2760,14 +2762,15 @@ "install-docker-compose": "Use the instructions to download, install and setup docker compose", "device-info-settings": "Device info settings", "device-info": { - "parameter": "Parameter", + "entity-field": "Entity field", "source": "Source", - "expression": "Expression", + "expression": "Value / Expression", "expression-hint": "Show help", - "device-name": "Device name", - "device-profile": "Device profile", + "name": "Name", + "profile-name": "Profile name", + "device-name-expression": "Device name expression", "device-name-expression-required": "Device name expression required.", - "device-profile-expression-required": "Device profil expression required." + "device-profile-expression-required": "Device profile expression required." }, "device-name-filter": "Device name filter", "device-name-filter-hint": "Regular expression for device name.", @@ -2776,8 +2779,11 @@ "delete-mapping-title": "Delete mapping ?", "download-configuration-file": "Download configuration file", "download-docker-compose": "Download docker-compose.yml for your gateway", + "enable-remote-logging": "Enable remote logging", + "ellipsis-chips-text": "+ {{count}} more", "launch-gateway": "Launch gateway", "launch-docker-compose": "Start the gateway using the following command in the terminal from folder with docker-compose.yml file", + "logs-configuration": "Logs configuration", "create-new-gateway": "Create a new gateway", "create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?", "created-time": "Created time", @@ -2786,19 +2792,26 @@ "configuration-delete-dialog-input": "Gateway name", "configuration-delete-dialog-input-required": "Gateway name is mandatory", "configuration-delete-dialog-confirm": "Turn Off", - "converter": "Converter", + "connector-duplicate-name": "Connector with such name already exists.", + "connector-side": "Connector side", + "payload-type": "Payload type", + "platform-side": "Platform side", "JSON": "JSON", + "JSON-hint": "Converter for this payload type processes MQTT messages in JSON format. It uses JSON Path expressions to extract vital details such as device names, device profile names, attributes, and time series from the message. And regular expressions to get device details from topics.", "bytes": "Bytes", + "bytes-hint": "Converter for this payload type designed for binary MQTT payloads, this converter directly interprets binary data to retrieve device names and device profile names, along with attributes and time series, using specific byte positions for data extraction.", "custom": "Custom", + "custom-hint": "This option allows you to use a custom converter for specific data tasks. You need to add your custom converter to the extension folder and enter its class name in the UI settings. Any keys you provide will be sent as configuration to your custom converter.", "client-cert-path": "Path to client certificate file", "path-to-client-cert-required": "Path to client certificate file is required.", "client-id": "Client ID", + "data-conversion": "Data conversion", "data-mapping": "Data mapping", "data-mapping-hint": "Data mapping provides the capability to parse and convert the data received from a MQTT client in incoming messages into specific attributes and telemetry data keys.", "delete": "Delete configuration", "delete-attribute": "Delete attribute", "delete-key": "Delete key", - "delete-telemetry": "Delete telemetry", + "delete-timeseries": "Delete timeseries", "default": "Default", "download-tip": "Download configuration file", "drop-file": "Drop file here or", @@ -2808,8 +2821,8 @@ "extension-configuration-hint": "Configuration for convertor", "fill-connector-defaults": "Fill configuration with default values", "fill-connector-defaults-hint": "This property allows to fill connector configuration with default values on it's creation.", - "from-device-request-settings": "From device request settings", - "to-device-response-settings": "To device request settings", + "from-device-request-settings": "Input request parsing", + "to-device-response-settings": "Output request handling", "gateway": "Gateway", "gateway-exists": "Device with same name is already exists.", "gateway-name": "Gateway name", @@ -2890,7 +2903,7 @@ "no-data": "No configurations", "no-gateway-found": "No gateway found.", "no-gateway-matching": " '{{item}}' not found.", - "no-telemetry": "No telemetry", + "no-timeseries": "No timeseries", "no-keys": "No keys", "path-hint": "The path is local to the gateway file system", "path-logs": "Path to log files", @@ -3016,6 +3029,7 @@ "response-timeout": "Response timeout (ms)", "response-timeout-required": "Response timeout is required.", "response-topic-Qos": "Response topic QoS", + "response-topic-Qos-hint": "MQTT Quality of Service (QoS) is an agreement between the message sender and receiver that defines the level of delivery guarantee for a specific message.", "response-topic-expression": "Response topic expression", "response-topic-expression-hint": "Response topic expression hint", "response-topic-expression-required": "Response topic expression is required.", @@ -3040,6 +3054,7 @@ "tls-access-token": "TLS + Access Token", "tls-private-key": "TLS + Private Key" }, + "select-connector": "Select connector to display config", "send-change-data": "Send data only on change", "send-change-data-hint": "The values will be saved to the database only if they are different from the corresponding values in the previous converted message. This functionality applies to both attributes and telemetry in the converter output.", "server-port": "Server port", @@ -3110,7 +3125,7 @@ "workers-settings": "Workers settings", "thingsboard": "ThingsBoard", "general": "General", - "telemetry": "Telemetry", + "timeseries": "Timeseries", "key": "Key", "keys": "Keys", "key-required": "Key is required.", 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 4d1a12b3f8..c60e1dd956 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json @@ -9,9 +9,7 @@ "maxNumberOfWorkers": 100, "sendDataOnlyOnChange": false, "security": { - "type": "basic", - "username": "user", - "password": "password" + "type": "anonymous" } }, "dataMapping": [ diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 3d0eef9718..9e96571b6f 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -505,6 +505,13 @@ gap: 12px; padding-bottom: 12px; + &.no-padding { + padding: 0; + } + &.no-gap { + gap: 0; + } + .tb-form-table-body { display: flex; flex-direction: column; From 214f9d178e7b28a917ec2346d7c5fdd41781fcf4 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Tue, 16 Apr 2024 15:47:47 +0300 Subject: [PATCH 08/19] UI: gateway dashboard improvements --- .../main/data/json/tenant/dashboards/gateways.json | 4 ++-- .../dialog/add-connector-dialog.component.ts | 2 +- .../gateway/dialog/mapping-dialog.component.scss | 13 +++++++++---- .../lib/gateway/dialog/mapping-dialog.component.ts | 7 +------ .../lib/gateway/gateway-connectors.component.html | 11 ++++++----- .../lib/gateway/gateway-connectors.component.ts | 11 ++++++++++- ui-ngx/src/assets/locale/locale.constant-en_US.json | 9 +++++---- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 4ccee86d5e..8acf269f8a 100644 --- a/application/src/main/data/json/tenant/dashboards/gateways.json +++ b/application/src/main/data/json/tenant/dashboards/gateways.json @@ -5218,7 +5218,7 @@ "widgets": { "02a05113-d878-b086-328e-75bd6f02b2bb": { "sizeX": 24, - "sizeY": 11, + "sizeY": 12, "row": 0, "col": 0 } @@ -5230,7 +5230,7 @@ "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, - "mobileAutoFillHeight": false, + "mobileAutoFillHeight": true, "mobileRowHeight": 70, "outerMargin": true } 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 b635618ec9..6294eebbc4 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 @@ -77,7 +77,7 @@ export class AddConnectorDialogComponent extends DialogComponentgateway.name
- + gateway.port
- + warning 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 d006031f47..79776f93e7 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 @@ -178,6 +178,15 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.disable(); } + get portErrorTooltip(): string { + if (this.connectorForm.get('basicConfig.broker.port').hasError('required')) { + return 'gateway.port-required'; + } else if (this.connectorForm.get('basicConfig.broker.port').hasError('min')) { + return 'gateway.only-natural-numbers'; + } + return ''; + } + ngAfterViewInit() { this.connectorForm.get('type').valueChanges.pipe( takeUntil(this.destroy$) @@ -639,7 +648,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const brokerGroup = this.fb.group({ name: ['', []], host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - port: [null, [Validators.required]], + port: [null, [Validators.required, Validators.min(0)]], version: [5, []], clientId: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], 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 36e198d49b..d9f4105ee4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2797,9 +2797,9 @@ "payload-type": "Payload type", "platform-side": "Platform side", "JSON": "JSON", - "JSON-hint": "Converter for this payload type processes MQTT messages in JSON format. It uses JSON Path expressions to extract vital details such as device names, device profile names, attributes, and time series from the message. And regular expressions to get device details from topics.", + "JSON-hint": "Converter for this payload type processes MQTT messages in JSON format. It uses JSON Path expressions to extract vital details such as device names, device profile names, attributes, and timeseries from the message. And regular expressions to get device details from topics.", "bytes": "Bytes", - "bytes-hint": "Converter for this payload type designed for binary MQTT payloads, this converter directly interprets binary data to retrieve device names and device profile names, along with attributes and time series, using specific byte positions for data extraction.", + "bytes-hint": "Converter for this payload type designed for binary MQTT payloads, this converter directly interprets binary data to retrieve device names and device profile names, along with attributes and timeseries, using specific byte positions for data extraction.", "custom": "Custom", "custom-hint": "This option allows you to use a custom converter for specific data tasks. You need to add your custom converter to the extension folder and enter its class name in the UI settings. Any keys you provide will be sent as configuration to your custom converter.", "client-cert-path": "Path to client certificate file", @@ -2807,7 +2807,7 @@ "client-id": "Client ID", "data-conversion": "Data conversion", "data-mapping": "Data mapping", - "data-mapping-hint": "Data mapping provides the capability to parse and convert the data received from a MQTT client in incoming messages into specific attributes and telemetry data keys.", + "data-mapping-hint": "Data mapping provides the capability to parse and convert the data received from a MQTT client in incoming messages into specific attributes and timeseries data keys.", "delete": "Delete configuration", "delete-attribute": "Delete attribute", "delete-key": "Delete key", @@ -3044,6 +3044,7 @@ "with-response": "With response", "without-response": "Without response", "other": "Other", + "only-natural-numbers": "Only natural numbers allowed.", "save-tip": "Save configuration file", "security": "Security", "security-type": "Security type", @@ -3056,7 +3057,7 @@ }, "select-connector": "Select connector to display config", "send-change-data": "Send data only on change", - "send-change-data-hint": "The values will be saved to the database only if they are different from the corresponding values in the previous converted message. This functionality applies to both attributes and telemetry in the converter output.", + "send-change-data-hint": "The values will be saved to the database only if they are different from the corresponding values in the previous converted message. This functionality applies to both attributes and timeseries in the converter output.", "server-port": "Server port", "set": "Set", "statistics": { From 355acfc30a25eafefdea95abc0da23ae5eba2db5 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Fri, 19 Apr 2024 12:56:11 +0300 Subject: [PATCH 09/19] UI: gateway dashboard bug-fixes and improvements --- .../data/json/tenant/dashboards/gateways.json | 9 +- .../broker-security.component.ts | 16 ++-- .../device-info-table.component.ts | 9 +- .../ellipsis-chip-list.directive.ts | 37 +++++---- .../mapping-data-keys-panel.component.html | 10 ++- .../add-connector-dialog.component.html | 6 +- .../dialog/add-connector-dialog.component.ts | 13 ++- .../dialog/mapping-dialog.component.html | 18 +++- .../dialog/mapping-dialog.component.scss | 1 + .../dialog/mapping-dialog.component.ts | 12 +-- .../gateway/gateway-connectors.component.html | 13 +-- .../gateway/gateway-connectors.component.ts | 83 ++++++++++--------- .../lib/gateway/gateway-widget.models.ts | 15 ++++ .../widget/lib/gateway/expressions_fn.md | 3 - .../assets/locale/locale.constant-en_US.json | 52 ++++++------ 15 files changed, 168 insertions(+), 129 deletions(-) diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 8acf269f8a..3b0ccc3cd8 100644 --- a/application/src/main/data/json/tenant/dashboards/gateways.json +++ b/application/src/main/data/json/tenant/dashboards/gateways.json @@ -136,7 +136,7 @@ { "name": "active_connectors", "type": "attribute", - "label": "Active Connectors", + "label": "Enabled Connectors", "color": "#3f51b5", "settings": { "columnWidth": "20%", @@ -248,7 +248,7 @@ "type": "customPretty", "customHtml": "
\r\n \r\n

Add gateway

\r\n \r\n \r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n \r\n Name\r\n \r\n \r\n Gateway name is required.\r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n", "customCss": ".add-entity-form {\r\n min-width: 400px !important;\r\n}\r\n\r\n.add-entity-form .boolean-value-input {\r\n padding-left: 5px;\r\n}\r\n\r\n.add-entity-form .boolean-value-input .checkbox-label {\r\n margin-bottom: 8px;\r\n color: rgba(0,0,0,0.54);\r\n font-size: 12px;\r\n}\r\n\r\n.relations-list .header {\r\n padding-right: 5px;\r\n padding-bottom: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .header .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n font-size: 12px;\r\n font-weight: 700;\r\n color: rgba(0, 0, 0, .54);\r\n white-space: nowrap;\r\n}\r\n\r\n.relations-list .mat-form-field-infix {\r\n width: auto !important;\r\n}\r\n\r\n.relations-list .body {\r\n padding-right: 5px;\r\n padding-bottom: 15px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .row {\r\n padding-top: 5px;\r\n}\r\n\r\n.relations-list .body .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .md-button {\r\n margin: 0;\r\n}\r\n\r\n", - "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Docker commands\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n setTimeout(function() {\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button-touch-target\").addEventListener('click', goToConfigState);\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button.mat-primary\").addEventListener('click', goToConfigState);\r\n }, 500);\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button-touch-target\").removeEventListener('click', goToConfigState);\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button.mat-primary\").removeEventListener('click', goToConfigState);\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n", + "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Docker commands\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n goToConfigState();\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n", "customResources": [], "openInSeparateDialog": false, "openInPopover": false, @@ -567,7 +567,8 @@ "padding": "8px", "settings": { "useMarkdownTextFunction": true, - "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n
\n \n ${generateMatHeader(index)}\n ${label}\n
\n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Active | Inactive)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `
${blockData}
`;", + "markdownTextPattern": "# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.", + "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n
\n \n ${generateMatHeader(index)}\n ${label}\n
\n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Enabled | Disabled)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `
${blockData}
`;", "applyDefaultMarkdownStyle": false, "markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}" }, @@ -6741,4 +6742,4 @@ }, "externalId": null, "name": "Gateway" -} +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts index e67bb61207..655545e5fb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts @@ -15,14 +15,13 @@ /// import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, - ElementRef, forwardRef, + ElementRef, + forwardRef, NgZone, OnDestroy, - OnInit, ViewContainerRef } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; @@ -47,7 +46,8 @@ import { } from '@angular/forms'; import { BrokerSecurityType, - BrokerSecurityTypeTranslationsMap, noLeadTrailSpacesRegex, + BrokerSecurityTypeTranslationsMap, + noLeadTrailSpacesRegex } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { takeUntil } from 'rxjs/operators'; @@ -69,7 +69,7 @@ import { takeUntil } from 'rxjs/operators'; } ] }) -export class BrokerSecurityComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { +export class BrokerSecurityComponent extends PageComponent implements ControlValueAccessor, Validator, OnDestroy { BrokerSecurityType = BrokerSecurityType; @@ -115,18 +115,12 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal }); } - ngOnInit() { - } - ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); super.ngOnDestroy(); } - ngAfterViewInit() { - } - registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts index f292993682..8a40f25430 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts @@ -15,7 +15,6 @@ /// import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -40,7 +39,8 @@ import { UtilsService } from '@core/services/utils.service'; import { EntityService } from '@core/http/entity.service'; import { ControlValueAccessor, - FormBuilder, NG_VALIDATORS, + FormBuilder, + NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormGroup, ValidationErrors, @@ -73,7 +73,7 @@ import { coerceBoolean } from '@shared/decorators/coercion'; } ] }) -export class DeviceInfoTableComponent extends PageComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { +export class DeviceInfoTableComponent extends PageComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { SourceTypeTranslationsMap = SourceTypeTranslationsMap; @@ -156,9 +156,6 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa this.destroy$.complete(); } - ngAfterViewInit() { - } - registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts index 6d525aa4c0..ac9e976e50 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts @@ -15,26 +15,31 @@ /// import { - AfterViewInit, Directive, ElementRef, + Inject, Input, OnDestroy, Renderer2 } from '@angular/core'; import { isEqual } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; +import { WINDOW } from '@core/services/window.service'; +import { fromEvent, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector selector: '[tb-ellipsis-chip-list]' }) -export class EllipsisChipListDirective implements AfterViewInit, OnDestroy { +export class EllipsisChipListDirective implements OnDestroy { chipsValue: string[]; + private destroy$ = new Subject(); + @Input('tb-ellipsis-chip-list') - set chips(value: any[]) { + set chips(value: string[]) { if (!isEqual(this.chipsValue, value)) { this.chipsValue = value; setTimeout(() => { @@ -45,11 +50,15 @@ export class EllipsisChipListDirective implements AfterViewInit, OnDestroy { constructor(private el: ElementRef, private renderer: Renderer2, - private translate: TranslateService) {} - - ngAfterViewInit(): void { - this.adjustChips(); - window.addEventListener('resize', this.adjustChips.bind(this)); + private translate: TranslateService, + @Inject(WINDOW) private window: Window) { + this.renderer.setStyle(this.el.nativeElement, 'max-height', '48px'); + this.renderer.setStyle(this.el.nativeElement, 'overflow', 'auto'); + fromEvent(window, 'resize').pipe( + takeUntil(this.destroy$) + ).subscribe(() => { + this.adjustChips(); + }); } private adjustChips(): void { @@ -58,17 +67,16 @@ export class EllipsisChipListDirective implements AfterViewInit, OnDestroy { const chipNodes = chipListElement.querySelectorAll('mat-chip:not(.ellipsis-chip)'); const ellipsisChip = this.el.nativeElement.querySelector('.ellipsis-chip'); this.renderer.setStyle(ellipsisChip,'display', 'inline-flex'); + ellipsisText.innerHTML = this.translate.instant('gateway.ellipsis-chips-text', + {count: (this.chipsValue.length)}); - const margin = parseFloat(window.getComputedStyle(ellipsisChip).marginLeft) | 0; + const margin = parseFloat(this.window.getComputedStyle(ellipsisChip).marginLeft) || 0; const availableWidth = chipListElement.offsetWidth - (ellipsisChip.offsetWidth + margin); let usedWidth = 0; let visibleChipsCount = 0; chipNodes.forEach((chip) => { this.renderer.setStyle(chip, 'display', 'inline-flex'); - }); - - chipNodes.forEach((chip) => { if ((usedWidth + (chip.offsetWidth + margin) <= availableWidth) && (visibleChipsCount < this.chipsValue.length)) { visibleChipsCount++; usedWidth += chip.offsetWidth + margin; @@ -85,7 +93,8 @@ export class EllipsisChipListDirective implements AfterViewInit, OnDestroy { } } - ngOnDestroy() { - window.removeEventListener('resize', this.adjustChips.bind(this)); + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html index d7b2222e59..95274d54e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html @@ -35,7 +35,10 @@
gateway.platform-side
-
gateway.key
+
+ gateway.key +
@@ -89,7 +92,10 @@
-
gateway.value
+
+ gateway.value +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html index b4dd8515df..ec1fa9a87f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html @@ -26,9 +26,6 @@ close - - -
@@ -91,13 +88,12 @@
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 6294eebbc4..78a22cd267 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, Inject, OnDestroy } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -23,7 +23,9 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { + AddConnectorConfigData, ConnectorType, + CreatedConnectorConfigData, GatewayConnectorDefaultTypesTranslatesMap, GatewayLogLevel, getDefaultConfig, @@ -38,7 +40,7 @@ import { ResourcesService } from '@core/services/resources.service'; styleUrls: ['./add-connector-dialog.component.scss'], providers: [], }) -export class AddConnectorDialogComponent extends DialogComponent> implements OnInit, OnDestroy { +export class AddConnectorDialogComponent extends DialogComponent> implements OnDestroy { connectorForm: UntypedFormGroup; @@ -53,8 +55,8 @@ export class AddConnectorDialogComponent extends DialogComponent, protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: any, - public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: AddConnectorConfigData, + public dialogRef: MatDialogRef, private fb: FormBuilder, private resourcesService: ResourcesService) { super(store, router, dialogRef); @@ -67,9 +69,6 @@ export class AddConnectorDialogComponent extends DialogComponent
-
gateway.extension
+
+ gateway.extension +
@@ -237,6 +240,9 @@
gateway.from-device-request-settings
+
+ gateway.from-device-request-settings-hint +
gateway.device-info.device-name-expression
@@ -302,6 +308,9 @@
gateway.to-device-response-settings
+
+ gateway.to-device-response-settings-hint +
gateway.response-value-expression
@@ -359,7 +368,10 @@
-
gateway.device-name-filter
+
+ gateway.device-name-filter +
@@ -368,7 +380,7 @@ matTooltipClass="tb-error-tooltip" [matTooltip]="('gateway.device-name-filter-required') | translate" *ngIf="mappingForm.get('requestValue.attributeUpdates.deviceNameFilter').hasError('required') && - mappingForm.get('requestValue.attributeUpdates.deviceNameFilter').touched" + mappingForm.get('requestValue.attributeUpdates.deviceNameFilter').touched" class="tb-error"> warning diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss index 253a81e0d8..8a35e7b17d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss @@ -34,6 +34,7 @@ .mat-mdc-dialog-content { max-height: 670px; + height: 670px; } } 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 fc51d17bc0..1347116fd4 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -27,7 +27,6 @@ import { ConvertorTypeTranslationsMap, DataConversionTranslationsMap, DeviceInfoType, - MappingDataKey, MappingHintTranslationsMap, MappingInfo, MappingKeysAddKeyTranslationsMap, @@ -58,7 +57,7 @@ import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gatew styleUrls: ['./mapping-dialog.component.scss'], providers: [], }) -export class MappingDialogComponent extends DialogComponent> implements OnInit, OnDestroy { +export class MappingDialogComponent extends DialogComponent> implements OnDestroy { mappingForm: UntypedFormGroup; @@ -118,7 +117,7 @@ export class MappingDialogComponent extends DialogComponent { + get converterTelemetry(): Array { if (this.converterType) { return this.mappingForm.get('converter').get(this.converterType).value.timeseries.map(value => value.key); } @@ -128,7 +127,7 @@ export class MappingDialogComponent extends DialogComponent { return Object.keys(this.mappingForm.get('converter').get('custom').value.extensionConfig); } @@ -136,9 +135,6 @@ export class MappingDialogComponent extends DialogComponent @@ -127,7 +128,7 @@ private_connectivity
+ + widget.no-data-found + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss index affc4e5982..707b5fa056 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss @@ -71,6 +71,10 @@ } } + .no-data-found { + height: calc(100% - 120px); + } + @media #{$mat-xs} { .mat-toolbar { height: auto; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts index a5c6addfe4..4ef9695c65 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts @@ -215,6 +215,7 @@ export class MappingTableComponent extends PageComponent implements ControlValue } exitFilterMode() { + this.updateTableData(this.mappingFormGroup.value); this.textSearchMode = false; this.textSearch.reset(); } From fb91898bd3e561483014483c1e2a5cde93af4248 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 2 May 2024 16:57:25 +0300 Subject: [PATCH 19/19] UI: improvements for connectors config --- .../ellipsis-chip-list.directive.ts | 16 ++++++++++++---- .../mapping-data-keys-panel.component.scss | 2 +- .../dialog/mapping-dialog.component.html | 10 ++++++---- .../gateway/dialog/mapping-dialog.component.ts | 17 +++++++++++++++-- .../assets/locale/locale.constant-en_US.json | 5 +---- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts index e5a6c5a54c..aa4738d707 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts @@ -79,6 +79,10 @@ export class EllipsisChipListDirective implements OnDestroy { chipNodes.forEach((chip) => { this.renderer.setStyle(chip, 'display', 'inline-flex'); + const textLabelContainer = chip.querySelector('.mdc-evolution-chip__text-label'); + + this.applyMaxChipTextWidth(textLabelContainer, (availableWidth / 3)); + if ((usedWidth + (chip.offsetWidth + margin) <= availableWidth) && (visibleChipsCount < this.chipsValue.length)) { visibleChipsCount++; usedWidth += chip.offsetWidth + margin; @@ -104,15 +108,19 @@ export class EllipsisChipListDirective implements OnDestroy { this.renderer.setStyle(ellipsisChip, 'display', 'none'); this.renderer.setStyle(chipNodes[0], 'display', 'inline-flex'); - this.renderer.setStyle(textLabelContainer, 'max-width', computedTextWidth + 'px'); - this.renderer.setStyle(textLabelContainer, 'overflow', 'hidden'); - this.renderer.setStyle(textLabelContainer, 'text-overflow', 'ellipsis'); - this.renderer.setStyle(textLabelContainer, 'white-space', 'nowrap'); + this.applyMaxChipTextWidth(textLabelContainer, computedTextWidth); } else { this.renderer.setStyle(ellipsisChip, 'display', 'none'); } } + private applyMaxChipTextWidth(element: HTMLElement, widthLimit: number): void { + this.renderer.setStyle(element, 'max-width', widthLimit + 'px'); + this.renderer.setStyle(element, 'overflow', 'hidden'); + this.renderer.setStyle(element, 'text-overflow', 'ellipsis'); + this.renderer.setStyle(element, 'white-space', 'nowrap'); + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss index 7f6aef05c0..24498863c9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss @@ -20,7 +20,7 @@ max-width: 700px; .title-container { - max-width: 14vw; + max-width: 11vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap 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 97366bd26b..78ceaa566f 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 @@ -595,13 +595,15 @@
gateway.response-timeout
- + warning 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 b083439ef7..d01050b52e 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 @@ -50,6 +50,7 @@ import { startWith, takeUntil } from 'rxjs/operators'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-mapping-dialog', @@ -105,7 +106,8 @@ export class MappingDialogComponent extends DialogComponent