From e4e028e0b4e54b92a602dbc49c261009b4b83885 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 27 Jun 2024 16:19:02 +0300 Subject: [PATCH 01/16] Gateway - Connectors - OPCUA extended functionality --- .../broker-config-control.component.html | 86 +++++ .../broker-config-control.component.ts | 129 +++++++ .../device-info-table.component.html | 10 +- .../device-info-table.component.scss | 0 .../device-info-table.component.ts | 25 +- .../gateway/connectors-configuration/index.ts | 24 ++ .../mapping-data-keys-panel.component.html | 73 +++- .../mapping-data-keys-panel.component.scss | 0 .../mapping-data-keys-panel.component.ts | 80 ++-- .../mapping-table.component.html | 6 +- .../mapping-table.component.scss | 0 .../mapping-table.component.ts | 241 ++++++------ .../security-config.component.html} | 44 ++- .../security-config.component.scss} | 0 .../security-config.component.ts} | 115 +++--- .../server-config.component.html | 125 +++++++ .../server-config.component.scss | 20 + .../server-config/server-config.component.ts | 106 ++++++ .../type-value-panel.component.html | 92 +++++ .../type-value-panel.component.scss | 52 +++ .../type-value-panel.component.ts | 144 ++++++++ .../workers-config-control.component.html | 63 ++++ .../workers-config-control.component.ts | 94 +++++ .../dialog/mapping-dialog.component.html | 118 ++++++ .../dialog/mapping-dialog.component.scss | 8 +- .../dialog/mapping-dialog.component.ts | 347 ++++++++++-------- .../gateway/gateway-connectors.component.html | 170 ++------- .../gateway/gateway-connectors.component.ts | 275 +++++++++----- .../lib/gateway/gateway-widget.models.ts | 205 ++++++++++- .../widget/widget-components.module.ts | 25 +- .../gateway-help-link.pipe.ts | 39 ++ ui-ngx/src/app/modules/home/pipes/index.ts | 17 + .../validators/form-array.validators.ts | 24 ++ .../lib/gateway/attributes-identifier_fn.md | 54 +++ .../widget/lib/gateway/attributes-path_fn.md | 36 ++ .../attributes_updates-identifier_fn.md | 46 +++ .../lib/gateway/attributes_updates-path_fn.md | 34 ++ .../lib/gateway/device-node-identifier_fn.md | 34 ++ .../widget/lib/gateway/device-node-path_fn.md | 20 + .../lib/gateway/name-field-identifier_fn.md | 67 ++++ .../widget/lib/gateway/name-field-path_fn.md | 41 +++ .../lib/gateway/profile-name-identifier_fn.md | 60 +++ .../lib/gateway/profile-name-path_fn.md | 38 ++ .../lib/gateway/timeseries-identifier_fn.md | 62 ++++ .../widget/lib/gateway/timeseries-path_fn.md | 45 +++ .../assets/locale/locale.constant-en_US.json | 50 ++- .../connector-default-configs/opcua.json | 78 ++-- 47 files changed, 2736 insertions(+), 686 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => device-info-table}/device-info-table.component.html (88%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => device-info-table}/device-info-table.component.scss (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => device-info-table}/device-info-table.component.ts (83%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/index.ts rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mapping-data-keys-panel}/mapping-data-keys-panel.component.html (71%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mapping-data-keys-panel}/mapping-data-keys-panel.component.scss (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mapping-data-keys-panel}/mapping-data-keys-panel.component.ts (64%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mapping-table}/mapping-table.component.html (96%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mapping-table}/mapping-table.component.scss (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{ => mapping-table}/mapping-table.component.ts (59%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{broker-security.component.html => security-config/security-config.component.html} (65%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{broker-security.component.scss => security-config/security-config.component.scss} (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{broker-security.component.ts => security-config/security-config.component.ts} (57%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts create mode 100644 ui-ngx/src/app/modules/home/pipes/gateway-help-link/gateway-help-link.pipe.ts create mode 100644 ui-ngx/src/app/modules/home/pipes/index.ts create mode 100644 ui-ngx/src/app/shared/validators/form-array.validators.ts create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-identifier_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-path_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-identifier_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-path_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-identifier_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-path_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-identifier_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-identifier_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-path_fn.md diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html new file mode 100644 index 0000000000..4c2db19345 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html @@ -0,0 +1,86 @@ + +
+
+
gateway.host
+
+ + + + warning + + +
+
+
+
gateway.port
+
+ + + + warning + + +
+
+
+
gateway.mqtt-version
+
+ + + {{ version.name }} + + +
+
+
+
gateway.client-id
+
+ + + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts new file mode 100644 index 0000000000..d980bed4a7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts @@ -0,0 +1,129 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + inject, + Input, + OnDestroy, + OnInit +} from '@angular/core'; +import { + ControlContainer, + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + MqttVersions, + noLeadTrailSpacesRegex, + PortLimits, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; +import { generateSecret } from '@core/utils'; +import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration'; + +@Component({ + selector: 'tb-broker-config-control', + templateUrl: './broker-config-control.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + ], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => BrokerConfigControlComponent), + multi: true + } + ] +}) +export class BrokerConfigControlComponent implements ControlValueAccessor, OnInit, OnDestroy { + @Input() controlKey = 'broker'; + + brokerConfigFormGroup: UntypedFormGroup; + mqttVersions = MqttVersions; + portLimits = PortLimits; + + get parentFormGroup(): FormGroup { + return this.parentContainer.control as FormGroup; + } + + private parentContainer = inject(ControlContainer); + private translate = inject(TranslateService); + + constructor(private fb: FormBuilder) { + this.brokerConfigFormGroup = this.fb.group({ + name: ['', []], + host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], + version: [5, []], + clientId: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], + maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], + security: [{}, [Validators.required]] + }); + } + + get portErrorTooltip(): string { + if (this.brokerConfigFormGroup.get('port').hasError('required')) { + return this.translate.instant('gateway.port-required'); + } else if ( + this.brokerConfigFormGroup.get('port').hasError('min') || + this.brokerConfigFormGroup.get('port').hasError('max') + ) { + return this.translate.instant('gateway.port-limits-error', + {min: PortLimits.MIN, max: PortLimits.MAX}); + } + return ''; + } + + ngOnInit(): void { + this.addSelfControl(); + } + + ngOnDestroy(): void { + this.removeSelfControl(); + } + + generate(formControlName: string): void { + this.brokerConfigFormGroup.get(formControlName)?.patchValue('tb_gw_' + generateSecret(5)); + } + + registerOnChange(fn: any): void {} + + registerOnTouched(fn: any): void {} + + writeValue(obj: any): void {} + + private addSelfControl(): void { + this.parentFormGroup.addControl(this.controlKey, this.brokerConfigFormGroup); + } + + private removeSelfControl(): void { + this.parentFormGroup.removeControl(this.controlKey); + } +} 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/device-info-table.component.html similarity index 88% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.html index 2a3108b8b4..0a33f5b885 100644 --- 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/device-info-table.component.html @@ -50,9 +50,10 @@ class="tb-error"> warning -
@@ -83,9 +84,10 @@ class="tb-error"> 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/device-info-table.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.scss 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/device-info-table.component.ts similarity index 83% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts index 8a40f25430..569f40b185 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/device-info-table.component.ts @@ -16,27 +16,19 @@ import { 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, @@ -50,6 +42,7 @@ import { import { DeviceInfoType, noLeadTrailSpacesRegex, + OPCUaSourceTypes, SourceTypes, SourceTypeTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -88,7 +81,7 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa required = false; @Input() - sourceTypes: Array = Object.values(SourceTypes); + sourceTypes: Array = Object.values(SourceTypes); deviceInfoTypeValue: any; @@ -111,14 +104,6 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa 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); } @@ -131,13 +116,13 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa if (this.useSource) { this.mappingFormGroup.addControl('deviceNameExpressionSource', - this.fb.control(SourceTypes.MSG, [])); + this.fb.control(this.sourceTypes[0], [])); } if (this.deviceInfoType === DeviceInfoType.FULL) { if (this.useSource) { this.mappingFormGroup.addControl('deviceProfileExpressionSource', - this.fb.control(SourceTypes.MSG, [])); + this.fb.control(this.sourceTypes[0], [])); } this.mappingFormGroup.addControl('deviceProfileExpression', this.fb.control('', this.required ? @@ -154,6 +139,7 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); + super.ngOnDestroy(); } registerOnChange(fn: any): void { @@ -175,5 +161,4 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa updateView(value: any) { this.propagateChange(value); } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/index.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/index.ts new file mode 100644 index 0000000000..bd0d4787d6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/index.ts @@ -0,0 +1,24 @@ +/// +/// 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. +/// + +export * from './mapping-table/mapping-table.component'; +export * from './device-info-table/device-info-table.component'; +export * from './security-config/security-config.component'; +export * from './server-config/server-config.component'; +export * from './mapping-data-keys-panel/mapping-data-keys-panel.component'; +export * from './type-value-panel/type-value-panel.component'; +export * from './broker-config-control/broker-config-control.component'; +export * from './workers-config-control/workers-config-control.component'; 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/mapping-data-keys-panel.component.html similarity index 71% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html index 4c3a6434fd..8b95c0bd3a 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/mapping-data-keys-panel.component.html @@ -26,14 +26,15 @@ -
{{ keyControl.get('key').value }}
- {{ '-' }} -
{{ valueTitle(keyControl.get('value').value) }}
+
+ {{ keyControl.get('key').value }}{{ '-' }} +
+
{{ valueTitle(keyControl) }}
+ *ngIf="keysType !== MappingKeysType.CUSTOM && keysType !== MappingKeysType.RPC_METHODS">
gateway.platform-side
@@ -53,12 +54,6 @@ class="tb-error"> warning -
-
@@ -71,18 +66,25 @@
- + - - {{ (rawData ? 'gateway.raw' : valueTypes.get(keyControl.get('type').value)?.name) | translate}} + + {{ (valueTypes.get(keyControl.get('type').value)?.name || valueTypes.get(keyControl.get('type').value)) | translate }} + + {{ 'gateway.raw' | translate }} +
- + - {{ valueTypes.get(valueType).name | translate }} + + {{ valueTypes.get(valueType).name || valueTypes.get(valueType) | translate }} + @@ -112,7 +114,8 @@
@@ -120,7 +123,7 @@
- +
gateway.key
@@ -152,7 +155,41 @@
- +
+
+
+
+ gateway.method-name +
+
+ + + + warning + + +
+
+
+ + + +
+ {{ 'gateway.arguments' | translate }}{{' (' + keyControl.get('arguments').value?.length + ')'}} +
+
+
+ + + +
+
+
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/mapping-data-keys-panel.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.scss 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/mapping-data-keys-panel.component.ts similarity index 64% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts index 40ee2728bd..7867a7213f 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/mapping-data-keys-panel.component.ts @@ -23,6 +23,8 @@ import { } from '@angular/core'; import { AbstractControl, + FormControl, + FormGroup, UntypedFormArray, UntypedFormBuilder, Validators @@ -39,7 +41,9 @@ import { MappingKeysType, MappingValueType, mappingValueTypesMap, - noLeadTrailSpacesRegex + noLeadTrailSpacesRegex, + OPCUaSourceTypes, + RpcMethodsMapping, } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Component({ @@ -66,7 +70,16 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn keys: Array | {[key: string]: any}; @Input() - keysType: string; + keysType: MappingKeysType; + + @Input() + valueTypeKeys: Array = Object.values(MappingValueType); + + @Input() + valueTypeEnum = MappingValueType; + + @Input() + valueTypes: Map = mappingValueTypesMap; @Input() @coerceBoolean() @@ -76,16 +89,10 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn popover: TbPopoverComponent; @Output() - keysDataApplied = new EventEmitter | {[key: string]: any}>(); - - valueTypeKeys = Object.values(MappingValueType); + keysDataApplied = new EventEmitter | {[key: string]: unknown}>(); MappingKeysType = MappingKeysType; - valueTypeEnum = MappingValueType; - - valueTypes = mappingValueTypesMap; - dataKeyType: DataKeyType; keysListFormArray: UntypedFormArray; @@ -97,8 +104,8 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn super(store); } - ngOnInit() { - this.keysListFormArray = this.prepareKeysFormArray(this.keys) + ngOnInit(): void { + this.keysListFormArray = this.prepareKeysFormArray(this.keys); } trackByKey(index: number, keyControl: AbstractControl): any { @@ -106,12 +113,21 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn } addKey(): void { - const dataKeyFormGroup = this.fb.group({ - key: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }); - if (this.keysType !== MappingKeysType.CUSTOM) { - dataKeyFormGroup.addControl('type', this.fb.control(this.rawData ? 'raw' : MappingValueType.STRING)); + let dataKeyFormGroup: FormGroup; + if (this.keysType === MappingKeysType.RPC_METHODS) { + dataKeyFormGroup = this.fb.group({ + method: ['', [Validators.required]], + arguments: [[], []] + }); + } else { + dataKeyFormGroup = this.fb.group({ + key: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] + }); + } + if (this.keysType !== MappingKeysType.CUSTOM && this.keysType !== MappingKeysType.RPC_METHODS) { + const controlValue = this.rawData ? 'raw' : this.valueTypeKeys[0]; + dataKeyFormGroup.addControl('type', this.fb.control(controlValue)); } this.keysListFormArray.push(dataKeyFormGroup); } @@ -124,11 +140,11 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn this.keysListFormArray.markAsDirty(); } - cancel() { + cancel(): void { this.popover?.hide(); } - applyKeysData() { + applyKeysData(): void { let keys = this.keysListFormArray.value; if (this.keysType === MappingKeysType.CUSTOM) { keys = {}; @@ -139,7 +155,7 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn this.keysDataApplied.emit(keys); } - private prepareKeysFormArray(keys: Array | {[key: string]: any}): UntypedFormArray { + private prepareKeysFormArray(keys: Array | {[key: string]: any}): UntypedFormArray { const keysControlGroups: Array = []; if (keys) { if (this.keysType === MappingKeysType.CUSTOM) { @@ -148,19 +164,28 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn }); } keys.forEach((keyData) => { - const { key, value, type } = keyData; - const dataKeyFormGroup = this.fb.group({ - key: [key, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - type: [type, []] - }); + let dataKeyFormGroup: FormGroup; + if (this.keysType === MappingKeysType.RPC_METHODS) { + dataKeyFormGroup = this.fb.group({ + method: [keyData.method, [Validators.required]], + arguments: [[...keyData.arguments], []] + }); + } else { + const { key, value, type } = keyData; + dataKeyFormGroup = this.fb.group({ + key: [key, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: [value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + type: [type, []] + }); + } keysControlGroups.push(dataKeyFormGroup); }); } return this.fb.array(keysControlGroups); } - valueTitle(value: any): string { + valueTitle(keyControl: FormControl): string { + const value = keyControl.get(this.keysType === MappingKeysType.RPC_METHODS ? 'method' : 'value').value; if (isDefinedAndNotNull(value)) { if (typeof value === 'object') { return JSON.stringify(value); @@ -169,5 +194,4 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn } return ''; } - } 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/mapping-table.component.html similarity index 96% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html index e5c703d8d0..48e280bc67 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/mapping-table.component.html @@ -60,10 +60,12 @@
- + {{ 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/mapping-table.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.scss 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/mapping-table.component.ts similarity index 59% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts index 4ef9695c65..75274c9099 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/mapping-table.component.ts @@ -17,41 +17,43 @@ import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, ElementRef, forwardRef, + inject, 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 { 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 { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { debounceTime, distinctUntilChanged, map, 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_VALUE_ACCESSOR, UntypedFormArray } from '@angular/forms'; +import { debounceTime, distinctUntilChanged, map, take, takeUntil } from 'rxjs/operators'; import { - ConvertorTypeTranslationsMap, + ControlContainer, + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, + UntypedFormArray, +} from '@angular/forms'; +import { + ConnectorMapping, ConverterConnectorMapping, + ConvertorTypeTranslationsMap, DeviceConnectorMapping, MappingInfo, MappingType, - MappingTypeTranslationsMap, + MappingTypeTranslationsMap, MappingValue, RequestMappingData, RequestType, RequestTypesTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { validateArrayIsNotEmpty } from '@shared/validators/form-array.validators'; @Component({ selector: 'tb-mapping-table', @@ -66,8 +68,13 @@ import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; } ] }) -export class MappingTableComponent extends PageComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy { +export class MappingTableComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy { + @Input() controlKey = 'dataMapping'; + + @coerceBoolean() + @Input() required = false; + parentContainer = inject(ControlContainer); mappingTypeTranslationsMap = MappingTypeTranslationsMap; mappingTypeEnum = MappingType; displayedColumns = []; @@ -79,14 +86,16 @@ export class MappingTableComponent extends PageComponent implements ControlValue activeValue = false; dirtyValue = false; - viewsInited = false; - mappingTypeValue: MappingType; get mappingType(): MappingType { return this.mappingTypeValue; } + get parentFormGroup(): FormGroup { + return this.parentContainer.control as FormGroup; + } + @Input() set mappingType(value: MappingType) { if (this.mappingTypeValue !== value) { @@ -100,54 +109,34 @@ export class MappingTableComponent extends PageComponent implements ControlValue textSearch = this.fb.control('', {nonNullable: true}); private destroy$ = new Subject(); - private propagateChange = (v: any) => {}; - constructor(protected store: Store, - public translate: TranslateService, + constructor(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; this.dataSource = new MappingDatasource(); } - ngOnInit() { - if (this.mappingType === MappingType.DATA) { - this.mappingColumns.push( - {def: 'topicFilter', title: 'gateway.topic-filter'}, - {def: 'QoS', title: 'gateway.mqtt-qos'}, - {def: 'converter', title: 'gateway.payload-type'} - ) - } else { - this.mappingColumns.push( - {def: 'type', title: 'gateway.type'}, - {def: 'details', title: 'gateway.details'} - ); - } + ngOnInit(): void { + this.setMappingColumns(); 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); }); + this.addSelfControl(); } - ngOnDestroy() { + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); + this.removeSelfControl(); } - ngAfterViewInit() { + ngAfterViewInit(): void { this.textSearch.valueChanges.pipe( debounceTime(150), distinctUntilChanged((prev, current) => (prev ?? '') === current.trim()), @@ -158,55 +147,13 @@ export class MappingTableComponent extends PageComponent implements ControlValue }); } - registerOnChange(fn: any): void { - this.propagateChange = fn; - } + registerOnChange(fn: any): void {} registerOnTouched(fn: any): void {} - writeValue(config: any) { - if (isUndefinedOrNull(config)) { - config = this.mappingType === MappingType.REQUESTS ? {} : []; - } - let mappingConfigs = config; - if (this.mappingType === MappingType.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); - } + writeValue(obj: any): void {} - updateView(mappingConfigs: Array<{[key: string]: any}>) { - let config; - if (this.mappingType === MappingType.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() { + enterFilterMode(): void { this.textSearchMode = true; setTimeout(() => { this.searchInputField.nativeElement.focus(); @@ -214,18 +161,18 @@ export class MappingTableComponent extends PageComponent implements ControlValue }, 10); } - exitFilterMode() { + exitFilterMode(): void { this.updateTableData(this.mappingFormGroup.value); this.textSearchMode = false; this.textSearch.reset(); } - manageMapping($event: Event, index?: number) { + manageMapping($event: Event, index?: number): void { if ($event) { $event.stopPropagation(); } const value = isDefinedAndNotNull(index) ? this.mappingFormGroup.at(index).value : {}; - this.dialog.open(MappingDialogComponent, { + this.dialog.open(MappingDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { @@ -233,45 +180,23 @@ export class MappingTableComponent extends PageComponent implements ControlValue value, buttonTitle: isUndefinedOrNull(index) ? 'action.add' : 'action.apply' } - }).afterClosed().subscribe( - (res) => { + }).afterClosed() + .pipe(take(1), takeUntil(this.destroy$)) + .subscribe(res => { if (res) { if (isDefinedAndNotNull(index)) { this.mappingFormGroup.at(index).patchValue(res); } else { this.mappingFormGroup.push(this.fb.group(res)); } + this.mappingFormGroup.markAsDirty(); } - } - ); + }); } - updateTableData(value: Array<{[key: string]: any}>, textSearch?: string): void { - let tableValue = value; - if (this.mappingType === MappingType.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 === RequestType.ATTRIBUTE_UPDATE) { - details = value.requestValue.attributeFilter; - } else if (value.requestType === RequestType.SERVER_SIDE_RPC) { - details = value.requestValue.methodFilter; - } else { - details = value.requestValue.topicFilter; - } - return { - type: this.translate.instant(RequestTypesTranslationsMap.get(value.requestType)), - details - }; - }); - } + updateTableData(value: ConnectorMapping[], textSearch?: string): void { + let tableValue = + value.map((value: ConnectorMapping) => this.getMappingValue(value)); if (textSearch) { tableValue = tableValue.filter(value => Object.values(value).some(val => @@ -282,7 +207,7 @@ export class MappingTableComponent extends PageComponent implements ControlValue this.dataSource.loadMappings(tableValue); } - deleteMapping($event: Event, index: number) { + deleteMapping($event: Event, index: number): void { if ($event) { $event.stopPropagation(); } @@ -295,10 +220,81 @@ export class MappingTableComponent extends PageComponent implements ControlValue ).subscribe((result) => { if (result) { this.mappingFormGroup.removeAt(index); + this.mappingFormGroup.markAsDirty(); } }); } + private getMappingValue(value: ConnectorMapping): MappingValue { + switch (this.mappingType) { + case MappingType.DATA: + return { + topicFilter: (value as ConverterConnectorMapping).topicFilter, + QoS: (value as ConverterConnectorMapping).subscriptionQos, + converter: this.translate.instant(ConvertorTypeTranslationsMap.get((value as ConverterConnectorMapping).converter.type)) + }; + case MappingType.REQUESTS: + let details; + if ((value as RequestMappingData).requestType === RequestType.ATTRIBUTE_UPDATE) { + details = (value as RequestMappingData).requestValue.attributeFilter; + } else if ((value as RequestMappingData).requestType === RequestType.SERVER_SIDE_RPC) { + details = (value as RequestMappingData).requestValue.methodFilter; + } else { + details = (value as RequestMappingData).requestValue.topicFilter; + } + return { + requestType: (value as RequestMappingData).requestType, + type: this.translate.instant(RequestTypesTranslationsMap.get((value as RequestMappingData).requestType)), + details + }; + case MappingType.OPCUA: + const deviceNamePattern = (value as DeviceConnectorMapping).deviceInfo?.deviceNameExpression; + const deviceProfileExpression = (value as DeviceConnectorMapping).deviceInfo?.deviceProfileExpression; + const { deviceNodePattern } = value as DeviceConnectorMapping; + return { + deviceNodePattern, + deviceNamePattern, + deviceProfileExpression + }; + default: + return {} as MappingValue; + } + } + + private setMappingColumns(): void { + switch (this.mappingType) { + case MappingType.DATA: + this.mappingColumns.push( + { def: 'topicFilter', title: 'gateway.topic-filter' }, + { def: 'QoS', title: 'gateway.mqtt-qos' }, + { def: 'converter', title: 'gateway.payload-type' } + ); + break; + case MappingType.REQUESTS: + this.mappingColumns.push( + { def: 'type', title: 'gateway.type' }, + { def: 'details', title: 'gateway.details' } + ); + break; + case MappingType.OPCUA: + this.mappingColumns.push( + { def: 'deviceNodePattern', title: 'gateway.device-node' }, + { def: 'deviceNamePattern', title: 'gateway.device-name' }, + { def: 'deviceProfileExpression', title: 'gateway.device-profile' } + ); + } + } + + private addSelfControl(): void { + this.parentFormGroup.addControl(this.controlKey, this.mappingFormGroup); + if (this.required) { + this.mappingFormGroup.addValidators(validateArrayIsNotEmpty()); + } + } + + private removeSelfControl(): void { + this.parentFormGroup.removeControl(this.controlKey); + } } export class MappingDatasource implements DataSource<{[key: string]: any}> { @@ -335,5 +331,4 @@ export class MappingDatasource implements DataSource<{[key: string]: any}> { map((mappings) => mappings.length) ); } - } 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/security-config/security-config.component.html similarity index 65% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.html index 319b10f0b2..87976abf08 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/security-config/security-config.component.html @@ -17,7 +17,7 @@ -->
-
gateway.security
+
{{ title | translate }}
{{ SecurityTypeTranslationsMap.get(type) | translate }} @@ -81,6 +81,48 @@
+ +
+
gateway.mode
+
+ + + + {{ type }} + + + +
+
+
+
gateway.username
+
+ + + + warning + + +
+
+
+
gateway.password
+
+ + +
+ +
+
+
+
+
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/security-config/security-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.scss 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/security-config/security-config.component.ts similarity index 57% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-security.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts index 655545e5fb..94f1eb8395 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/security-config/security-config.component.ts @@ -16,85 +16,72 @@ import { ChangeDetectionStrategy, - ChangeDetectorRef, Component, - ElementRef, forwardRef, - NgZone, + Input, OnDestroy, - ViewContainerRef + OnInit, } 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 { BrokerSecurityType, BrokerSecurityTypeTranslationsMap, + ModeType, noLeadTrailSpacesRegex } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { takeUntil } from 'rxjs/operators'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; @Component({ - selector: 'tb-broker-security', - templateUrl: './broker-security.component.html', - styleUrls: ['./broker-security.component.scss'], + selector: 'tb-security-config', + templateUrl: './security-config.component.html', + styleUrls: ['./security-config.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, providers: [ { provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => BrokerSecurityComponent), + useExisting: forwardRef(() => SecurityConfigComponent), multi: true }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => BrokerSecurityComponent), - multi: true - } + ], + standalone: true, + imports:[ + CommonModule, + SharedModule, ] }) -export class BrokerSecurityComponent extends PageComponent implements ControlValueAccessor, Validator, OnDestroy { +export class SecurityConfigComponent implements ControlValueAccessor, OnInit, OnDestroy { + @Input() + title: string = 'gateway.security'; + + @Input() + @coerceBoolean() + extendCertificatesModel = false; BrokerSecurityType = BrokerSecurityType; securityTypes = Object.values(BrokerSecurityType); + modeTypes = Object.values(ModeType); + SecurityTypeTranslationsMap = BrokerSecurityTypeTranslationsMap; 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); + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { this.securityFormGroup = this.fb.group({ type: [BrokerSecurityType.ANONYMOUS, []], username: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -103,11 +90,9 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal pathToPrivateKey: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], pathToClientCert: ['', [Validators.pattern(noLeadTrailSpacesRegex)]] }); - this.securityFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.updateView(value); - }); + if (this.extendCertificatesModel) { + this.securityFormGroup.addControl('mode', this.fb.control(ModeType.NONE, [])); + } this.securityFormGroup.get('type').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((type) => { @@ -115,43 +100,25 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal }); } - ngOnDestroy() { + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); - super.ngOnDestroy(); } - registerOnChange(fn: any): void { - this.propagateChange = fn; - } + registerOnChange(fn: any): void {} registerOnTouched(fn: any): void {} - writeValue(deviceInfo: any) { - if (!deviceInfo.type) { - deviceInfo.type = BrokerSecurityType.ANONYMOUS; - } - this.securityFormGroup.reset(deviceInfo); - this.updateView(deviceInfo); - } + writeValue(obj: any): void {} - validate(): ValidationErrors | null { - return this.securityFormGroup.valid ? null : { - securityForm: { valid: false } - }; - } - - updateView(value: any) { - this.propagateChange(value); - } - - private updateValidators(type) { + private updateValidators(type): void { 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}); + this.securityFormGroup.get('mode')?.disable({emitEvent: false}); if (type === BrokerSecurityType.BASIC) { this.securityFormGroup.get('username').enable({emitEvent: false}); this.securityFormGroup.get('password').enable({emitEvent: false}); @@ -159,6 +126,16 @@ export class BrokerSecurityComponent extends PageComponent implements ControlVal this.securityFormGroup.get('pathToCACert').enable({emitEvent: false}); this.securityFormGroup.get('pathToPrivateKey').enable({emitEvent: false}); this.securityFormGroup.get('pathToClientCert').enable({emitEvent: false}); + if (this.extendCertificatesModel) { + const modeControl = this.securityFormGroup.get('mode'); + if (modeControl && !modeControl.value) { + modeControl.setValue(ModeType.NONE, {emitEvent: false}); + } + + modeControl?.enable({emitEvent: false}); + this.securityFormGroup.get('username').enable({emitEvent: false}); + this.securityFormGroup.get('password').enable({emitEvent: false}); + } } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html new file mode 100644 index 0000000000..ee097ae642 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html @@ -0,0 +1,125 @@ + +
+
+
gateway.server-url
+
+ + + + warning + + +
+
+
+
+ gateway.timeout +
+
+ + + + warning + + +
+
+
+
gateway.security
+
+ + + {{ version.name }} + + +
+
+
+
+ gateway.scan-period +
+
+ + + + warning + + +
+
+
+
+ gateway.sub-check-period +
+
+ + + + warning + + +
+
+
+ + + {{ 'gateway.enable-subscription' | translate }} + + +
+
+ + + {{ 'gateway.show-map' | translate }} + + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss new file mode 100644 index 0000000000..416f368279 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.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/server-config/server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts new file mode 100644 index 0000000000..3466387f3c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts @@ -0,0 +1,106 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, inject, Input, + OnDestroy, OnInit +} from '@angular/core'; +import { + ControlContainer, + ControlValueAccessor, + FormBuilder, FormGroup, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + noLeadTrailSpacesRegex, + SecurityType, + ServerSecurityTypes +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration'; + +@Component({ + selector: 'tb-server-config', + templateUrl: './server-config.component.html', + styleUrls: ['./server-config.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ServerConfigComponent), + multi: true + }, + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + ] +}) +export class ServerConfigComponent implements OnInit, ControlValueAccessor, OnDestroy { + @Input() controlKey = 'server'; + + serverSecurityTypes = ServerSecurityTypes; + serverConfigFormGroup: UntypedFormGroup; + + get parentFormGroup(): FormGroup { + return this.parentContainer.control as FormGroup; + } + + private parentContainer = inject(ControlContainer); + + constructor(private fb: FormBuilder) { + this.serverConfigFormGroup = this.fb.group({ + name: ['', []], + url: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + timeoutInMillis: [1000, [Validators.required, Validators.min(1000)]], + scanPeriodInMillis: [1000, [Validators.required, Validators.min(1000)]], + enableSubscriptions: [true, []], + subCheckPeriodInMillis: [10, [Validators.required, Validators.min(10)]], + showMap: [false, []], + security: [SecurityType.BASIC128, []], + identity: [{}, [Validators.required]] + }); + } + + ngOnInit(): void { + this.addSelfControl(); + } + + ngOnDestroy(): void { + this.removeSelfControl(); + } + + registerOnChange(fn: any): void {} + + registerOnTouched(fn: any): void {} + + writeValue(obj: any): void {} + + private addSelfControl(): void { + this.parentFormGroup.addControl(this.controlKey, this.serverConfigFormGroup); + } + + private removeSelfControl(): void { + this.parentFormGroup.removeControl(this.controlKey); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html new file mode 100644 index 0000000000..7ce7fb94f0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html @@ -0,0 +1,92 @@ + +
+
+
+
+ + + + +
{{ valueTitle(keyControl.get('value').value) }}
+
+
+ +
+
gateway.type
+
+ + + +
+ + + + {{ valueTypes.get(keyControl.get('type').value)?.name | translate}} + +
+
+ + + + {{ valueTypes.get(valueType).name | translate }} + +
+
+
+
+
+
gateway.value
+ + + + warning + + +
+
+
+
+
+ +
+
+
+ +
+
+ +
+ {{ 'gateway.no-value' }} +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss new file mode 100644 index 0000000000..687025e729 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss @@ -0,0 +1,52 @@ +/** + * 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 { + + .title-container { + max-width: 11vw; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap + } + + .key-panel { + height: 250px; + overflow: auto; + } + + .tb-form-panel { + .mat-mdc-icon-button { + width: 56px; + height: 56px; + padding: 16px; + color: rgba(0, 0, 0, 0.54); + } + } + + .see-example { + width: 32px; + height: 32px; + margin: 4px; + } +} + +:host ::ng-deep { + .mat-mdc-form-field-icon-suffix { + display: flex; + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts new file mode 100644 index 0000000000..e163d463ed --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts @@ -0,0 +1,144 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, + forwardRef, + OnDestroy, + OnInit, +} from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormArray, + UntypedFormBuilder, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { + MappingDataKey, + MappingValueType, + mappingValueTypesMap, + noLeadTrailSpacesRegex +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'tb-type-value-panel', + templateUrl: './type-value-panel.component.html', + styleUrls: ['./type-value-panel.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TypeValuePanelComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TypeValuePanelComponent), + multi: true + } + ] +}) +export class TypeValuePanelComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { + valueTypeKeys = Object.values(MappingValueType); + valueTypeEnum = MappingValueType; + valueTypes = mappingValueTypesMap; + dataKeyType: DataKeyType; + valueListFormArray: UntypedFormArray; + errorText = ''; + + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(private fb: UntypedFormBuilder) {} + + ngOnInit(): void { + this.valueListFormArray = this.fb.array([]); + this.valueListFormArray.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateView(value); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + trackByKey(_: number, keyControl: AbstractControl): any { + return keyControl; + } + + addKey(): void { + const dataKeyFormGroup = this.fb.group({ + type: [MappingValueType.STRING, []], + value: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] + }); + this.valueListFormArray.push(dataKeyFormGroup); + } + + deleteKey($event: Event, index: number): void { + if ($event) { + $event.stopPropagation(); + } + this.valueListFormArray.removeAt(index); + this.valueListFormArray.markAsDirty(); + } + + valueTitle(value: any): string { + if (isDefinedAndNotNull(value)) { + if (typeof value === 'object') { + return JSON.stringify(value); + } + return value; + } + return ''; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(deviceInfoArray: Array): void { + for (const deviceInfo of deviceInfoArray) { + const dataKeyFormGroup = this.fb.group({ + type: [deviceInfo.type, []], + value: [deviceInfo.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] + }); + this.valueListFormArray.push(dataKeyFormGroup); + } + } + + validate(): ValidationErrors | null { + return this.valueListFormArray.valid ? null : { + valueListForm: { valid: false } + }; + } + + updateView(value: any): void { + this.propagateChange(value); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html new file mode 100644 index 0000000000..37f7b1422e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html @@ -0,0 +1,63 @@ + +
+
+
+ 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/workers-config-control/workers-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts new file mode 100644 index 0000000000..45e3aef7e8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts @@ -0,0 +1,94 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + inject, + Input, + OnDestroy, + OnInit +} from '@angular/core'; +import { + ControlContainer, + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'tb-workers-config-control', + templateUrl: './workers-config-control.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + SharedModule, + ], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => WorkersConfigControlComponent), + multi: true + } + ] +}) +export class WorkersConfigControlComponent implements ControlValueAccessor, OnInit, OnDestroy { + @Input() controlKey = 'workers'; + + workersConfigFormGroup: UntypedFormGroup; + + get parentFormGroup(): FormGroup { + return this.parentContainer.control as FormGroup; + } + + private parentContainer = inject(ControlContainer); + + constructor(private fb: FormBuilder) { + this.workersConfigFormGroup = this.fb.group({ + maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], + maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], + }); + } + + ngOnInit(): void { + this.addSelfControl(); + } + + ngOnDestroy(): void { + this.removeSelfControl(); + } + + registerOnChange(fn: any): void {} + + registerOnTouched(fn: any): void {} + + writeValue(obj: any): void {} + + private addSelfControl(): void { + this.parentFormGroup.addControl(this.controlKey, this.workersConfigFormGroup); + } + + private removeSelfControl(): void { + this.parentFormGroup.removeControl(this.controlKey); + } +} 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 78ceaa566f..a202c85fcb 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 @@ -615,6 +615,124 @@ + +
+
+
+ gateway.device-node +
+
+
+ + + + {{ SourceTypeTranslationsMap.get(type) | translate }} + + + + + + + warning + +
+
+
+
+
+ + +
+
gateway.attributes
+
+ + + {{ attribute }} + + + + + + +
+
+
+
gateway.timeseries
+
+ + + {{ telemetry }} + + + + + + +
+
+
+
gateway.attribute-updates
+
+ + + {{ attribute }} + + + + + + +
+
+
+
gateway.rpc-methods
+
+ + + {{ attribute }} + + + + + + +
+
+
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 db98bc07cb..f70fba0e80 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 @@ -33,8 +33,8 @@ } .mat-mdc-dialog-content { - max-height: 670px; - height: 670px; + max-height: 75vh; + height: 75vh; } .ellipsis-chips-container { @@ -73,4 +73,8 @@ .mat-mdc-form-field-icon-suffix { display: flex; } + + .device-config { + padding-left: 10px; + } } 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 d01050b52e..00e5f2f2ad 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 @@ -19,7 +19,6 @@ 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 { @@ -35,8 +34,9 @@ import { MappingKeysPanelTitleTranslationsMap, MappingKeysType, MappingType, - MappingTypeTranslationsMap, + MappingTypeTranslationsMap, MappingValue, noLeadTrailSpacesRegex, + OPCUaSourceTypes, QualityTypes, QualityTypeTranslationsMap, RequestType, @@ -49,16 +49,15 @@ 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'; import { TranslateService } from '@ngx-translate/core'; +import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration'; @Component({ selector: 'tb-mapping-dialog', templateUrl: './mapping-dialog.component.html', - styleUrls: ['./mapping-dialog.component.scss'], - providers: [], + styleUrls: ['./mapping-dialog.component.scss'] }) -export class MappingDialogComponent extends DialogComponent> implements OnDestroy { +export class MappingDialogComponent extends DialogComponent implements OnDestroy { mappingForm: UntypedFormGroup; @@ -72,6 +71,8 @@ export class MappingDialogComponent extends DialogComponent; + OPCUaSourceTypesEnum = OPCUaSourceTypes; sourceTypesEnum = SourceTypes; SourceTypeTranslationsMap = SourceTypeTranslationsMap; @@ -102,7 +103,7 @@ export class MappingDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: MappingInfo, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, private fb: FormBuilder, private popoverService: TbPopoverService, private renderer: Renderer2, @@ -125,6 +126,22 @@ export class MappingDialogComponent extends DialogComponent { + return this.mappingForm.get('attributes').value?.map(value => value.key) || []; + } + + get opcTelemetry(): Array { + return this.mappingForm.get('timeseries').value?.map(value => value.key) || []; + } + + get opcRpcMethods(): Array { + return this.mappingForm.get('rpc_methods').value?.map(value => value.method) || []; + } + + get opcAttributesUpdates(): Array { + return this.mappingForm.get('attributes_updates')?.value?.map(value => value.key) || []; + } + get converterType(): ConvertorType { return this.mappingForm.get('converter').get('type').value; } @@ -155,109 +172,17 @@ 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}); - }) - } - - if (this.data.mappingType === MappingType.REQUESTS) { - this.mappingForm.addControl('requestType', this.fb.control(RequestType.CONNECT_REQUEST, [])); - this.mappingForm.addControl('requestValue', this.fb.group({ - connectRequests: this.fb.group({ - topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceInfo: [{}, []] - }), - disconnectRequests: this.fb.group({ - topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceInfo: [{}, []] - }), - attributeRequests: this.fb.group({ - topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceInfo: this.fb.group({ - deviceNameExpressionSource: [SourceTypes.MSG, []], - deviceNameExpression: ['', [Validators.required]], - }), - attributeNameExpressionSource: [SourceTypes.MSG, []], - attributeNameExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - retain: [false, []] - }), - attributeUpdates: this.fb.group({ - deviceNameFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - attributeFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - retain: [true, []] - }), - serverSideRpc: this.fb.group({ - type: [ServerSideRPCType.TWO_WAY, []], - deviceNameFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - methodFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - requestTopicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicQoS: [0, []], - responseTimeout: [10000, [Validators.required, Validators.min(1)]], - }) - })); - 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(); - }); - this.mappingForm.get('requestValue.serverSideRpc.type').valueChanges.pipe( - 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}); - } - }); - this.mappingForm.patchValue(this.prepareFormValueData()); + switch (this.data.mappingType) { + case MappingType.DATA: + this.mappingForm = this.fb.group({}); + this.createDataMappingForm(); + break; + case MappingType.REQUESTS: + this.mappingForm = this.fb.group({}); + this.createRequestMappingForm(); + break; + case MappingType.OPCUA: + this.createOPCUAMappingForm(); } } @@ -278,7 +203,7 @@ export class MappingDialogComponent extends DialogComponent { + dataKeysPanelPopover.tbComponentRef.instance.keysDataApplied.pipe(takeUntil(this.destroy$)).subscribe((keysData) => { dataKeysPanelPopover.hide(); keysControl.patchValue(keysData); keysControl.markAsDirty(); }); - dataKeysPanelPopover.tbHideStart.subscribe(() => { + dataKeysPanelPopover.tbHideStart.pipe(takeUntil(this.destroy$)).subscribe(() => { this.keysPopupClosed = true; }); } } - private prepareMappingData(): {[key: string]: any} { + private prepareMappingData(): {[key: string]: unknown} { const formValue = this.mappingForm.value; - if (this.data.mappingType === MappingType.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(): {[key: string]: any} { - if (this.data.value && Object.keys(this.data.value).length) { - if (this.data.mappingType === MappingType.DATA) { - const { converter, topicFilter, subscriptionQos } = this.data.value; + switch (this.data.mappingType) { + case MappingType.DATA: + const { converter, topicFilter, subscriptionQos } = formValue; return { topicFilter, subscriptionQos, converter: { type: converter.type, - [converter.type]: { ...converter } + ...converter[converter.type] } }; - } else { + case MappingType.REQUESTS: return { - requestType: this.data.value.requestType, - requestValue: { - [this.data.value.requestType]: this.data.value.requestValue - } + requestType: formValue.requestType, + requestValue: formValue.requestValue[formValue.requestType] }; + default: + return formValue; + } + } + + private prepareFormValueData(): {[key: string]: unknown} { + if (this.data.value && Object.keys(this.data.value).length) { + switch (this.data.mappingType) { + case MappingType.DATA: + const { converter, topicFilter, subscriptionQos } = this.data.value; + return { + topicFilter, + subscriptionQos, + converter: { + type: converter.type, + [converter.type]: { ...converter } + } + }; + case MappingType.REQUESTS: + return { + requestType: this.data.value.requestType, + requestValue: { + [this.data.value.requestType]: this.data.value.requestValue + } + }; + default: + return this.data.value; } } - return this.data.value; + } + + private createDataMappingForm(): void { + this.mappingForm.addControl('topicFilter', + this.fb.control('', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)])); + this.mappingForm.addControl('subscriptionQos', this.fb.control(0)); + this.mappingForm.addControl('converter', this.fb.group({ + type: [ConvertorType.JSON, []], + json: this.fb.group({ + deviceInfo: [{}, []], + attributes: [[], []], + timeseries: [[], []] + }), + bytes: this.fb.group({ + deviceInfo: [{}, []], + attributes: [[], []], + timeseries: [[], []] + }), + custom: this.fb.group({ + extension: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + extensionConfig: [{}, []] + }), + })); + 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}); + }) + } + + private createRequestMappingForm(): void { + this.mappingForm.addControl('requestType', this.fb.control(RequestType.CONNECT_REQUEST, [])); + this.mappingForm.addControl('requestValue', this.fb.group({ + connectRequests: this.fb.group({ + topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + deviceInfo: [{}, []] + }), + disconnectRequests: this.fb.group({ + topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + deviceInfo: [{}, []] + }), + attributeRequests: this.fb.group({ + topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + deviceInfo: this.fb.group({ + deviceNameExpressionSource: [SourceTypes.MSG, []], + deviceNameExpression: ['', [Validators.required]], + }), + attributeNameExpressionSource: [SourceTypes.MSG, []], + attributeNameExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + retain: [false, []] + }), + attributeUpdates: this.fb.group({ + deviceNameFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + attributeFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + retain: [true, []] + }), + serverSideRpc: this.fb.group({ + type: [ServerSideRPCType.TWO_WAY, []], + deviceNameFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + methodFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + requestTopicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + responseTopicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + responseTopicQoS: [0, []], + responseTimeout: [10000, [Validators.required, Validators.min(1)]], + }) + })); + 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(); + }); + this.mappingForm.get('requestValue.serverSideRpc.type').valueChanges.pipe( + 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}); + } + }); + this.mappingForm.patchValue(this.prepareFormValueData()); + } + + private createOPCUAMappingForm(): void { + this.mappingForm = this.fb.group({ + deviceNodeSource: [OPCUaSourceTypes.PATH, []], + deviceNodePattern: ['', [Validators.required]], + deviceInfo: [{}, []], + attributes: [[], []], + timeseries: [[], []], + rpc_methods: [[], []], + attributes_updates: [[], []] + }); + this.mappingForm.patchValue(this.prepareFormValueData()); } } 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 3a7f5403b9..308f9d1a9c 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 @@ -156,7 +156,10 @@ {{ initialConnector?.type ? gatewayConnectorDefaultTypes.get(initialConnector.type) : '' }} {{ 'gateway.configuration' | translate }} - + {{ 'gateway.basic' | translate }} @@ -219,144 +222,41 @@ - - - -
-
-
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/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 4f1b6b72a9..5f329ce66b 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 @@ -14,15 +14,27 @@ /// limitations under the License. /// -import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, NgZone, ViewChild } from '@angular/core'; +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Input, + NgZone, + QueryList, + ViewChild, + ViewChildren +} from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { + FormArray, FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, + UntypedFormArray, UntypedFormControl, ValidatorFn, Validators @@ -30,7 +42,7 @@ import { import { EntityId } from '@shared/models/id/entity-id'; import { AttributeService } from '@core/http/attribute.service'; import { TranslateService } from '@ngx-translate/core'; -import { forkJoin, Observable, of, Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, forkJoin, Observable, of, 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'; @@ -42,7 +54,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { DialogService } from '@core/services/dialog.service'; import { WidgetContext } from '@home/models/widget-component.models'; -import { camelCase, deepClone, generateSecret, isEqual, isString } from '@core/utils'; +import { camelCase, deepClone, generateSecret, isEqual, isObject, isString } from '@core/utils'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { DatasourceType, widgetType } from '@shared/models/widget.models'; @@ -51,20 +63,22 @@ import { EntityType } from '@shared/models/entity-type.models'; import { AddConnectorConfigData, ConnectorConfigurationModes, + ConnectorMapping, ConnectorType, GatewayConnector, GatewayConnectorDefaultTypesTranslatesMap, GatewayLogLevel, MappingType, - MqttVersions, noLeadTrailSpacesRegex, - PortLimits + RequestMappingData, + RequestType, } from './gateway-widget.models'; 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 { distinctUntilChanged, filter, take, takeUntil, tap } from 'rxjs/operators'; import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; +import { MatTab } from '@angular/material/tabs'; export class ForceErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -88,6 +102,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie @ViewChild('nameInput') nameInput: ElementRef; @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChildren(MatTab) tabs: QueryList; pageLink: PageLink; @@ -97,8 +112,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions']; - mqttVersions = MqttVersions; - gatewayConnectorDefaultTypes = GatewayConnectorDefaultTypesTranslatesMap; connectorConfigurationModes = ConnectorConfigurationModes; @@ -113,12 +126,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie mappingTypes = MappingType; - portLimits = PortLimits; - mode: ConnectorConfigurationModes = this.connectorConfigurationModes.BASIC; initialConnector: GatewayConnector; + private tabsCountSubject = new BehaviorSubject(0); + private inactiveConnectors: Array; private attributeDataSource: AttributeDatasource; @@ -182,20 +195,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.disable(); } - get portErrorTooltip(): string { - if (this.connectorForm.get('basicConfig.broker.port').hasError('required')) { - return this.translate.instant('gateway.port-required'); - } else if ( - this.connectorForm.get('basicConfig.broker.port').hasError('min') || - this.connectorForm.get('basicConfig.broker.port').hasError('max') - ) { - return this.translate.instant('gateway.port-limits-error', - {min: PortLimits.MIN, max: PortLimits.MAX}); - } - return ''; - } - - ngAfterViewInit() { + ngAfterViewInit(): void { this.connectorForm.get('type').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(type => { @@ -229,7 +229,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const mode = this.connectorForm.get('mode').value; if ( !isEqual(config, basicConfig?.value) && - type === ConnectorType.MQTT && + (type === ConnectorType.MQTT || type === ConnectorType.OPCUA) && mode === ConnectorConfigurationModes.ADVANCED ) { this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false}); @@ -269,37 +269,18 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } }); } + this.observeModeChange(); + this.observeTabsChanges(); } - private uniqNameRequired(): ValidatorFn { - return (c: UntypedFormControl) => { - const newName = c.value.trim().toLowerCase(); - const found = this.dataSource.data.find((connectorAttr) => { - const connectorData = connectorAttr.value; - return connectorData.name.toLowerCase() === newName; - }); - if (found) { - if (this.initialConnector && this.initialConnector.name.toLowerCase() === newName) { - return null; - } - return { - duplicateName: { - valid: false - } - }; - } - return null; - }; - } - - ngOnDestroy() { + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); super.ngOnDestroy(); } saveConnector(): void { - const value = this.connectorForm.value; + const value = this.connectorForm.get('type').value === ConnectorType.MQTT ? this.getMappedMQTTValue() : this.connectorForm.value; value.configuration = camelCase(value.name) + '.json'; delete value.basicConfig; if (value.type !== ConnectorType.GRPC) { @@ -360,6 +341,20 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie }); } + private getMappedMQTTValue(): GatewayConnector { + const value = this.connectorForm.value; + return { + ...value, + configurationJson: { + ...value.configurationJson, + broker: { + ...value.basicConfig.broker, + ...value.basicConfig.workers, + } + } + } + } + private updateData(reload: boolean = false): void { this.pageLink.sortOrder.property = this.sort.active; this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; @@ -581,12 +576,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie 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.updateConnector(value); this.generate('basicConfig.broker.clientId'); this.saveConnector(); } @@ -634,29 +624,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } } - private addMQTTConfigControls(): void { - const configControl = this.fb.group({}); - const brokerGroup = this.fb.group({ - name: ['', []], - host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], - version: [5, []], - clientId: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], - maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], - security: [{}, [Validators.required]] - }); - configControl.addControl('broker', brokerGroup); - configControl.addControl('dataMapping', this.fb.control([], Validators.required)); - configControl.addControl('requestsMapping', this.fb.control({})); - if (this.connectorForm.get('basicConfig')) { - this.connectorForm.setControl('basicConfig', configControl, {emitEvent: false}); - } else { - this.connectorForm.addControl('basicConfig', configControl, {emitEvent: false}); - } - this.createBasicConfigWatcher(); - } - private createBasicConfigWatcher(): void { if (this.basicConfigSub) { this.basicConfigSub.unsubscribe(); @@ -669,7 +636,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const mode = this.connectorForm.get('mode').value; if ( !isEqual(config, configJson?.value) && - type === ConnectorType.MQTT && + (type === ConnectorType.MQTT || type === ConnectorType.OPCUA) && mode === ConnectorConfigurationModes.BASIC ) { const newConfig = { ...configJson.value, ...config }; @@ -691,6 +658,47 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie return of(true); } + private observeTabsChanges(): void { + this.tabs.changes + .pipe( + tap(() => this.tabsCountSubject.next(this.tabs.length)), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + private observeModeChange(): void { + this.connectorForm.get('mode').valueChanges + .pipe( + distinctUntilChanged(), + filter(Boolean), + tap(mode => this.updateConnector({...this.initialConnector, basicConfig: this.initialConnector?.configurationJson || {}, mode })), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + private uniqNameRequired(): ValidatorFn { + return (c: UntypedFormControl) => { + const newName = c.value.trim().toLowerCase(); + const found = this.dataSource.data.find((connectorAttr) => { + const connectorData = connectorAttr.value; + return connectorData.name.toLowerCase() === newName; + }); + if (found) { + if (this.initialConnector && this.initialConnector.name.toLowerCase() === newName) { + return null; + } + return { + duplicateName: { + valid: false + } + }; + } + return null; + }; + } + private setFormValue(connector: GatewayConnector): void { if (this.connectorForm.disabled) { this.connectorForm.enable(); @@ -708,14 +716,115 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.initialConnector = connector; - if (connector.type === ConnectorType.MQTT) { - this.addMQTTConfigControls(); + this.updateConnector(connector); + } + + private updateConnector(connector: GatewayConnector): void { + switch (connector.type) { + case ConnectorType.MQTT: + this.patchToMQTT(connector); + this.createBasicConfigWatcher(); + break; + case ConnectorType.OPCUA: + this.patchToOPCUA(connector); + this.createBasicConfigWatcher(); + break; + default: + this.connectorForm.patchValue({...connector, mode: null}); + this.connectorForm.markAsPristine(); + } + } + + private isTabsInitialized(tabsCount: number, isAdvanced?: boolean): boolean { + return isAdvanced ? tabsCount === 2 : tabsCount > 2; + } + + private patchToOPCUA(connector: GatewayConnector): void { + const connectorBase = {...connector, basicConfig: {}}; + + if (!connector.mode || connector.mode === ConnectorConfigurationModes.BASIC) { + if (!connector.mode) { + this.connectorForm.get('mode').patchValue(ConnectorConfigurationModes.BASIC, {emitEvent: false}); + } + this.connectorForm.patchValue(connectorBase, {emitEvent: false}); + this.tabsCountSubject.pipe(filter(count => this.isTabsInitialized(count)), take(1)).subscribe(() => { + (this.connectorForm.get('basicConfig.mapping') as FormArray)?.clear(); + this.connectorForm.patchValue(connector); + this.pushDataAsFormArrays('basicConfig.mapping', connector.basicConfig.mapping); + this.connectorForm.markAsPristine(); + }) } else { - this.connectorForm.setControl('basicConfig', this.fb.group({}), {emitEvent: false}); + this.updateInAdvanced(connector); } + } - this.connectorForm.patchValue(connector, {emitEvent: false}); - this.connectorForm.markAsPristine(); + private updateInAdvanced(connector: GatewayConnector): void { + this.connectorForm.patchValue({...connector, basicConfig: {}}, {emitEvent: false}); + this.tabsCountSubject.pipe(filter(count => this.isTabsInitialized(count, true)), take(1)).subscribe(() => { + this.connectorForm.patchValue({...connector, basicConfig: {}}); + this.connectorForm.markAsPristine(); + }) + } + + private pushDataAsFormArrays(controlKey: string, data: ConnectorMapping[]): void { + const control: UntypedFormArray = this.connectorForm.get(controlKey) as FormArray; + + if (control && data?.length) { + data.forEach((mapping: ConnectorMapping) => control.push(this.fb.control(mapping))); + } + } + + private patchToMQTT(connector: GatewayConnector): void { + if (!connector.mode || connector.mode === ConnectorConfigurationModes.BASIC) { + if (!connector.mode) { + this.connectorForm.get('mode').patchValue(ConnectorConfigurationModes.BASIC, {emitEvent: false}); + } + this.connectorForm.patchValue({...connector, basicConfig: {}}, {emitEvent: false}); + + this.tabsCountSubject.pipe(filter(count => this.isTabsInitialized(count)), take(1)).subscribe(() => { + const editedConnector = { + ...connector, + basicConfig: { + ...connector.basicConfig, + workers: { + maxNumberOfWorkers: connector.basicConfig?.broker?.maxNumberOfWorkers, + maxMessageNumberPerWorker: connector.basicConfig?.broker?.maxMessageNumberPerWorker, + }, + requestsMapping: [], + } + }; + + (this.connectorForm.get('basicConfig.dataMapping') as FormArray)?.clear(); + (this.connectorForm.get('basicConfig.requestsMapping') as FormArray)?.clear(); + this.connectorForm.patchValue(editedConnector); + this.pushDataAsFormArrays('basicConfig.dataMapping', editedConnector.basicConfig.dataMapping); + this.pushDataAsFormArrays('basicConfig.requestsMapping', + Array.isArray(connector.basicConfig.requestsMapping) + ? connector.basicConfig.requestsMapping + : this.getRequestDataArray(connector.basicConfig.requestsMapping) + ); + this.connectorForm.markAsPristine(); + }) + } else { + this.updateInAdvanced(connector); + } + } + + private getRequestDataArray(value: Record): RequestMappingData[] { + const mappingConfigs = []; + + if (isObject(value)) { + Object.keys(value).forEach((configKey: string) => { + for (let mapping of value[configKey]) { + mappingConfigs.push({ + requestType: configKey, + requestValue: mapping + }); + } + }); + } + + return mappingConfigs; } private setClientData(data: PageData): void { 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 0e143cff36..c32baf8d66 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 @@ -17,7 +17,6 @@ import { ResourcesService } from '@core/services/resources.service'; import { Observable } from 'rxjs'; import { ValueTypeData } from '@shared/models/constants'; -import { Validators } from '@angular/forms'; export const noLeadTrailSpacesRegex: RegExp = /^(?! )[\S\s]*(? | RequestMappingData[]; + server?: ServerConfig; + broker?: BrokerConfig; + workers?: { + maxNumberOfWorkers: number; + maxMessageNumberPerWorker: number; + }; +} + +interface DeviceInfo { + deviceNameExpression: string; + deviceNameExpressionSource: string; + deviceProfileExpression: string; + deviceProfileExpressionSource: string; +} + +interface Attribute { + key: string; + type: string; + value: string; +} + +interface Timeseries { + key: string; + type: string; + value: string; +} + +interface RpcArgument { + type: string; + value: number; +} + +interface RpcMethod { + method: string; + arguments: RpcArgument[]; +} + +interface AttributesUpdate { + key: string; + type: string; + value: string; +} + +interface Converter { + type: ConvertorType; + deviceNameJsonExpression: string; + deviceTypeJsonExpression: string; + sendDataOnlyOnChange: boolean; + timeout: number; + attributes: Attribute[]; + timeseries: Timeseries[]; +} + +export interface ConverterConnectorMapping { + topicFilter: string; + subscriptionQos?: string; + converter: Converter; +} + +export interface DeviceConnectorMapping { + deviceNodePattern: string; + deviceNodeSource: string; + deviceInfo: DeviceInfo; + attributes: Attribute[]; + timeseries: Timeseries[]; + rpc_methods: RpcMethod[]; + attributes_updates: AttributesUpdate[]; } export enum ConnectorType { @@ -335,6 +457,12 @@ export interface MappingDataKey { value: any, type: MappingValueType } + +export interface RpcMethodsMapping { + method: string, + arguments: Array +} + export interface MappingInfo { mappingType: MappingType, value: {[key: string]: any}, @@ -352,6 +480,12 @@ export enum BrokerSecurityType { CERTIFICATES = 'certificates' } +export enum ModeType { + NONE = 'None', + SIGN = 'Sign', + SIGNANDENCRYPT = 'SignAndEncrypt' +} + export const BrokerSecurityTypeTranslationsMap = new Map( [ [BrokerSecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'], @@ -368,19 +502,22 @@ export const MqttVersions = [ export enum MappingType { DATA = 'data', - REQUESTS = 'requests' + REQUESTS = 'requests', + OPCUA = 'OPCua' } export const MappingTypeTranslationsMap = new Map( [ [MappingType.DATA, 'gateway.data-mapping'], - [MappingType.REQUESTS, 'gateway.requests-mapping'] + [MappingType.REQUESTS, 'gateway.requests-mapping'], + [MappingType.OPCUA, 'gateway.data-mapping'] ] ); export const MappingHintTranslationsMap = new Map( [ [MappingType.DATA, 'gateway.data-mapping-hint'], + [MappingType.OPCUA, 'gateway.opcua-data-mapping-hint'], [MappingType.REQUESTS, 'gateway.requests-mapping-hint'] ] ); @@ -415,19 +552,42 @@ export enum SourceTypes { CONST = 'constant' } +export enum OPCUaSourceTypes { + PATH = 'path', + IDENTIFIER = 'identifier', + CONST = 'constant' +} + export enum DeviceInfoType { FULL = 'full', PARTIAL = 'partial' } -export const SourceTypeTranslationsMap = new Map( +export const SourceTypeTranslationsMap = new Map( [ [SourceTypes.MSG, 'gateway.source-type.msg'], [SourceTypes.TOPIC, 'gateway.source-type.topic'], [SourceTypes.CONST, 'gateway.source-type.const'], + [OPCUaSourceTypes.PATH, 'gateway.source-type.path'], + [OPCUaSourceTypes.IDENTIFIER, 'gateway.source-type.identifier'], + [OPCUaSourceTypes.CONST, 'gateway.source-type.const'] ] ); +export interface RequestMappingData { + requestType: RequestType; + requestValue: RequestDataItem; +} + +export interface RequestDataItem { + type: string; + details: string; + requestType: RequestType; + methodFilter?: string; + attributeFilter?: string; + topicFilter?: string; +} + export enum RequestType { CONNECT_REQUEST = 'connectRequests', DISCONNECT_REQUEST = 'disconnectRequests', @@ -449,14 +609,18 @@ export const RequestTypesTranslationsMap = new Map( export enum MappingKeysType { ATTRIBUTES = 'attributes', TIMESERIES = 'timeseries', - CUSTOM = 'extensionConfig' + CUSTOM = 'extensionConfig', + RPC_METHODS = 'rpc_methods', + ATTRIBUTES_UPDATES = 'attributes_updates' } export const MappingKeysPanelTitleTranslationsMap = new Map( [ [MappingKeysType.ATTRIBUTES, 'gateway.attributes'], [MappingKeysType.TIMESERIES, 'gateway.timeseries'], - [MappingKeysType.CUSTOM, 'gateway.keys'] + [MappingKeysType.CUSTOM, 'gateway.keys'], + [MappingKeysType.ATTRIBUTES_UPDATES, 'gateway.attribute-updates'], + [MappingKeysType.RPC_METHODS, 'gateway.rpc-methods'] ] ); @@ -464,7 +628,9 @@ export const MappingKeysAddKeyTranslationsMap = new Map [ [MappingKeysType.ATTRIBUTES, 'gateway.add-attribute'], [MappingKeysType.TIMESERIES, 'gateway.add-timeseries'], - [MappingKeysType.CUSTOM, 'gateway.add-key'] + [MappingKeysType.CUSTOM, 'gateway.add-key'], + [MappingKeysType.ATTRIBUTES_UPDATES, 'gateway.add-attribute-update'], + [MappingKeysType.RPC_METHODS, 'gateway.add-rpc-method'] ] ); @@ -472,7 +638,9 @@ export const MappingKeysDeleteKeyTranslationsMap = new Map => resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); @@ -540,3 +709,15 @@ export const DataConversionTranslationsMap = new Map( [ConvertorType.CUSTOM, 'gateway.custom-hint'] ] ); + +export enum SecurityType { + BASIC128 = 'Basic128Rsa15', + BASIC256 = 'Basic256', + BASIC256SHA = 'Basic256Sha256' +} + +export const ServerSecurityTypes = [ + { value: 'Basic128Rsa15', name: 'Basic128RSA15' }, + { value: 'Basic256', name: 'Basic256' }, + { value: 'Basic256Sha256', name: 'Basic256SHA256' } +]; 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 a473df40bb..5dffc4b8d9 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 @@ -87,11 +87,7 @@ import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-wi import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component'; import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; -import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table.component'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; -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'; import { StatusWidgetComponent } from '@home/components/widget/lib/indicator/status-widget.component'; import { LatestChartComponent } from '@home/components/widget/lib/chart/latest-chart.component'; @@ -103,6 +99,17 @@ import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobi import { LabelCardWidgetComponent } from '@home/components/widget/lib/cards/label-card-widget.component'; import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards/label-value-card-widget.component'; +import { GatewayHelpLinkPipe } from '@home/pipes'; +import { + DeviceInfoTableComponent, + MappingDataKeysPanelComponent, + MappingTableComponent, + ServerConfigComponent, + TypeValuePanelComponent, + BrokerConfigControlComponent, + WorkersConfigControlComponent, +} from '@home/components/widget/lib/gateway/connectors-configuration'; + @NgModule({ declarations: [ @@ -133,7 +140,7 @@ import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards MappingDialogComponent, DeviceInfoTableComponent, MappingDataKeysPanelComponent, - BrokerSecurityComponent, + TypeValuePanelComponent, GatewayLogsComponent, GatewayStatisticsComponent, GatewayServiceRPCComponent, @@ -177,7 +184,11 @@ import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards SharedModule, RpcWidgetsModule, HomePageWidgetsModule, - SharedHomeComponentsModule + SharedHomeComponentsModule, + GatewayHelpLinkPipe, + BrokerConfigControlComponent, + WorkersConfigControlComponent, + ServerConfigComponent, ], exports: [ EntitiesTableWidgetComponent, @@ -206,7 +217,7 @@ import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards MappingDialogComponent, DeviceInfoTableComponent, MappingDataKeysPanelComponent, - BrokerSecurityComponent, + TypeValuePanelComponent, GatewayLogsComponent, GatewayServiceRPCConnectorComponent, GatewayServiceRPCConnectorTemplatesComponent, diff --git a/ui-ngx/src/app/modules/home/pipes/gateway-help-link/gateway-help-link.pipe.ts b/ui-ngx/src/app/modules/home/pipes/gateway-help-link/gateway-help-link.pipe.ts new file mode 100644 index 0000000000..0fec377860 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pipes/gateway-help-link/gateway-help-link.pipe.ts @@ -0,0 +1,39 @@ +/// +/// 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 { Pipe, PipeTransform } from '@angular/core'; +import { + MappingValueType, + OPCUaSourceTypes, + SourceTypes +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +@Pipe({ + name: 'getGatewayHelpLink', + standalone: true, +}) +export class GatewayHelpLinkPipe implements PipeTransform { + transform(field: string, sourceType: SourceTypes | OPCUaSourceTypes, sourceTypes?: Array ): string { + if (!sourceTypes || sourceTypes?.includes(OPCUaSourceTypes.PATH)) { + if (sourceType !== OPCUaSourceTypes.CONST) { + return `widget/lib/gateway/${field}-${sourceType}_fn`; + } else { + return; + } + } + return 'widget/lib/gateway/expressions_fn'; + } +} diff --git a/ui-ngx/src/app/modules/home/pipes/index.ts b/ui-ngx/src/app/modules/home/pipes/index.ts new file mode 100644 index 0000000000..1cb3ce312a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pipes/index.ts @@ -0,0 +1,17 @@ +/// +/// 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. +/// + +export * from './gateway-help-link/gateway-help-link.pipe'; diff --git a/ui-ngx/src/app/shared/validators/form-array.validators.ts b/ui-ngx/src/app/shared/validators/form-array.validators.ts new file mode 100644 index 0000000000..d7358d58d6 --- /dev/null +++ b/ui-ngx/src/app/shared/validators/form-array.validators.ts @@ -0,0 +1,24 @@ +/// +/// 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 { UntypedFormArray, ValidatorFn } from '@angular/forms'; + +export function validateArrayIsNotEmpty(): ValidatorFn { + return (formArray: UntypedFormArray): { formArrayIsEmpty: boolean } => { + const length = formArray.length; + return length ? null : { formArrayIsEmpty: true }; + }; +} diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-identifier_fn.md new file mode 100644 index 0000000000..7989fd7357 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-identifier_fn.md @@ -0,0 +1,54 @@ +An **Identifier** type is a unique ID assigned to a node within the OPC-UA server. It is used to directly reference specific nodes without navigating through the namespace hierarchy. +The **Identifier** type in the OPC-UA connector configuration can be used in various forms to uniquely reference nodes in the OPC-UA server's address space. Identifiers can be of different types, such as numeric (i), string (s), byte string (b), and GUID (g). Below is an explanation of each identifier type with examples. + +- ##### Numeric Identifier (`i`) + A **numeric identifier** uses an integer value to uniquely reference a node in the OPC-UA server. + ###### Example: + Gateway expects that the node exist and the value of “**ns=2;i=1235**” node is **21.34**. + + _Expression:_ + + **`${ns=2;i=1235}`** + + _Converted data:_ + + **`21.34`** + +- ##### String Identifier (`s`) + A **string identifier** uses a string value to uniquely reference a node in the OPC-UA server. + ###### Example: + Gateway expects that the node exist and the value of “**ns=3;s=TemperatureSensor**” node is **21.34**. + + _Expression:_ + + **`${ns=3;s=TemperatureSensor}`** + + _Converted data:_ + + **`21.34`** + +- ##### Byte String Identifier (`b`) + A **byte string identifier** uses a byte string to uniquely reference a node in the OPC-UA server. This is useful for binary data that can be converted to a byte string. + ###### Example: + Gateway expects that the node exist and the value of “**ns=4;b=Q2xpZW50RGF0YQ==**” node is **21.34**. + + _Expression:_ + + **`${ns=4;b=Q2xpZW50RGF0YQ==}`** + + _Converted data:_ + + **`21.34`** + +- ##### GUID Identifier (`g`) + A **GUID identifier** uses a globally unique identifier (GUID) to uniquely reference a node in the OPC-UA server. + ###### Example: + Gateway expects that the node exist and the value of “**ns=1;g=550e8400-e29b-41d4-a716-446655440000**” node is **21.34**. + + _Expression:_ + + **`${ns=1;g=550e8400-e29b-41d4-a716-446655440000}`** + + _Converted data:_ + + **`21.34`** diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-path_fn.md new file mode 100644 index 0000000000..48ae478273 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes-path_fn.md @@ -0,0 +1,36 @@ +A **Path** type refers to the hierarchical address within the OPC-UA server's namespace. It is used to navigate to specific +nodes in the server. + +The path for the attribute value can be absolute or relative. + +### Absolute Path +An **absolute path** specifies the full hierarchical address from the root of the OPC-UA server's namespace to the target node. + +###### Example: +Gateway expects that the node exist and the value of Root\\.Objects\\.TempSensor\\.Temperature is 23.54. + +_Expression:_ + +**`${Root\.Objects\.TempSensor\.Temperature}`** + +_Converted data:_ + +**`23.54`** + +### Relative Path +A **relative path** specifies the address relative to a predefined starting point in the OPC-UA server's namespace. +###### Example: +Gateway expects that the node exist and the value of “Root\\.Objects\\.TempSensor\\.Temperature” is 23.56. + +_Expression:_ + +**`${Temperature}`** + +_Converted data:_ + +**`23.56`** + +Additionally, you can use the node browser name to ensure that your path cannot be altered by the user or server. + +###### Example: +**`Root\.0:Objects\.3:Simulation`** diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-identifier_fn.md new file mode 100644 index 0000000000..0ffb3d8cd1 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-identifier_fn.md @@ -0,0 +1,46 @@ +An **Identifier** type is a unique ID assigned to a node within the OPC-UA server. It is used to directly reference +specific nodes without navigating through the namespace hierarchy. + +The **Identifier** type in the OPC-UA connector configuration can be used in various forms to uniquely reference nodes +in the OPC-UA server's address space. Identifiers can be of different types, such as numeric (`i`), string (`s`), +byte string (`b`), and GUID (`g`). Below is an explanation of each identifier type with examples. + +- ##### Numeric Identifier (`i`) + A **numeric identifier** uses an integer value to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist. + + _Expression:_ + + **`ns=2;i=1236`** + +- ##### String Identifier (`s`) + A **string identifier** uses a string value to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist. + + _Expression:_ + + **`ns=3;s=TemperatureSensor`** + +- ##### Byte String Identifier (`b`) + A **byte string identifier** uses a byte string to uniquely reference a node in the OPC-UA server. This is useful for binary data that can be converted to a byte string. + + ###### Example: + Gateway expects that the node exist. + + _Expression:_ + + **`ns=4;b=Q2xpZW50RGF0YQ==`** + +- ##### GUID Identifier (`g`) + A **GUID identifier** uses a globally unique identifier (GUID) to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist. + + _Expression:_ + + **`ns=1;g=550e8400-e29b-41d4-a716-446655440000`** diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-path_fn.md new file mode 100644 index 0000000000..f710c0fc72 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_updates-path_fn.md @@ -0,0 +1,34 @@ +A **Path** type refers to the hierarchical address within the OPC-UA server's namespace. It is used to navigate to +specific nodes in the server. + +The path for device name can be absolute or relative. + +### Absolute Path +An **absolute path** specifies the full hierarchical address from the root of the OPC-UA server's namespace to +the target node. + +###### Example: +Gateway expects that the node exist and the value of **Root\\.Objects\\.TempSensor\\.Version** is “**1.0.3**”. + +_Expression:_ + +**`Root\.Objects\.TempSensor\.Version`** + +In this example, the attribute update request will write the received data to the configured node above. + +### Relative Path +A **relative path** specifies the address relative to a predefined starting point in the OPC-UA server's namespace. + +###### Example: +Gateway expects that the node exist and the value of **Root\\.Objects\\.TempSensor\\.Name** is “**TH-101**”. + +_Device Node expression:_ + +**`Root\.Objects\.TempSensor`** + +_Expression:_ + +**`.Version`** + +In this example, **the gateway will search for the child node "Name" in the device node (parent node) +"Root\\.Objects\\.TempSensor"** and will write received data on it. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-identifier_fn.md new file mode 100644 index 0000000000..6ad440978e --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-identifier_fn.md @@ -0,0 +1,34 @@ +## Device Node Field + +### Identifier: + +Device Node field value is used to identify the node for the current device. + +An Identifier type is a unique ID assigned to a node within the OPC-UA server. It is used to directly reference specific nodes without navigating through the namespace hierarchy. +The Identifier type in the OPC-UA connector configuration can be used in various forms to uniquely reference nodes in the OPC-UA server's address space. Identifiers can be of different types, such as numeric (`i`), string (`s`), byte string (`b`), and GUID (`g`). Below is an explanation of each identifier type with examples. + +- ##### Numeric Identifier (`i`) +A numeric identifier uses an integer value to uniquely reference a node in the OPC-UA server. +###### Example: +`ns=2;i=1234` +In this example, `ns=2` specifies the namespace index `2`, and `i=1234` specifies the numeric identifier `1234` for the node. + +- ##### String Identifier (`s`) +A string identifier uses a string value to uniquely reference a node in the OPC-UA server. +###### Example: +`ns=3;s=TemperatureSensor` +Here, `ns=3` specifies the namespace index `3`, and `s=TemperatureSensor` specifies the string identifier for the node. + +- ##### Byte String Identifier (`b`) +An byte string identifier uses a byte string to uniquely reference a node in the OPC-UA server. This is useful for binary data that can be converted to a byte string. +###### Example: +`ns=4;b=Q2xpZW50RGF0YQ==` +In this example, `ns=4` specifies the namespace index `4`, and `b=Q2xpZW50RGF0YQ==` specifies the byte string identifier for the node (base64 encoded). + +- ##### GUID Identifier (`g`) +A GUID identifier uses a globally unique identifier (GUID) to uniquely reference a node in the OPC-UA server. +###### Example: +`ns=1;g=550e8400-e29b-41d4-a716-446655440000` +Here, `ns=1` specifies the namespace index `1`, and `g=550e8400-e29b-41d4-a716-446655440000` specifies the GUID for the node. + +By using these different identifier types, you can accurately and uniquely reference nodes in the OPC-UA server's address space, regardless of the format of the node identifiers. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-path_fn.md new file mode 100644 index 0000000000..73dde417de --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/device-node-path_fn.md @@ -0,0 +1,20 @@ +## Device Node Field +### Path: + +Device Node field value is used to identify the node for the current device. The connector will use this node as the parent node of the device. + +A Path type refers to the hierarchical address within the OPC-UA server's namespace. It is used to navigate to specific nodes in the server. + +The path for Device node can be only absolute. + +An absolute path specifies the full hierarchical address from the root of the OPC-UA server's namespace to the target node. + +###### Examples: + +- `Root\.Objects\.TempSensor` + +In this example, the `Value` specifies the full path to the `TempSensor` node located in the `Objects` namespace, starting from the root. + +Additionally, you can use the node browser name to ensure that your path cannot be altered by the user or server. + +- `Root\.0:Objects\.3:Simulation` diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md new file mode 100644 index 0000000000..f3c5987582 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md @@ -0,0 +1,67 @@ +## Device Name Field + +Device name field is used for looking the device name in some variable. + +An **Identifier** type is a unique ID assigned to a node within the OPC-UA server. It is used to directly reference +specific nodes without navigating through the namespace hierarchy. + +**Identifier** type in the OPC-UA connector configuration can be used in various forms to uniquely reference nodes in +the OPC-UA server's address space. Identifiers can be of different types, such as numeric (`**i**`), string (`**s**`), +byte string (`**b**`), and GUID (`**g**`). Below is an explanation of each identifier type with examples. + +- ##### **Numeric Identifier (`i`)** + + A **numeric identifier** uses an integer value to uniquely reference a node in the OPC-UA server. + + ###### **Example:** + + Gateway expects that the node exist and the value of **ns=2;i=1234** node is “**TH-101**”. + + _Expression:_ + + **`Device ${ns=2;i=1234}`** + + In this example, created device on platform will have “**Device TH-101**” name. + +- ##### **String Identifier (`s`)** + + A **string identifier** uses a string value to uniquely reference a node in the OPC-UA server. + + ###### **Example:** + + Gateway expects that the node exist and the value of **ns=3;s=TemperatureSensor** node is “**TH-101**”. + + _Expression:_ + + **`Device ${ns=3;s=TemperatureSensor}`** + + In this example, created device on platform will have “**Device TH-101**” name. + +- ##### **Byte String Identifier (`b`)** + + A **byte string identifier** uses a byte string to uniquely reference a node in the OPC-UA server. + This is useful for binary data that can be converted to a byte string. + + ###### **Example:** + + Gateway expects that the node exist and the value of **ns=4;b=Q2xpZW50RGF0YQ==** node is “**TH-101**”. + + _Expression:_ + + **`Device ${ns=4;b=Q2xpZW50RGF0YQ==}`** + + In this example, created device on platform will have “**Device TH-101**” name. + +- ##### GUID Identifier (**`g`**) + + A **GUID identifier** uses a globally unique identifier (GUID) to uniquely reference a node in the OPC-UA server. + + ###### **Example:** + + Gateway expects that the node exist and the value of **ns=1;g=550e8400-e29b-41d4-a716-446655440000** node is “**TH-101**”. + + _Expression:_ + + Device ${ns=1;g=550e8400-e29b-41d4-a716-446655440000} + + In this example, created device on platform will have “**Device TH-101**” name. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md new file mode 100644 index 0000000000..731fbd66fe --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md @@ -0,0 +1,41 @@ +## Device Name Field + +Device name field is used for looking the device name in some variable. + +A **Path** type refers to the hierarchical address within the OPC-UA server's namespace. It is used to navigate to +specific nodes in the server. + +The path for device name can be absolute or relative. + +### Absolute Path +An **absolute path** specifies the full hierarchical address from the root of the OPC-UA server's namespace to the +target node. + +##### **Example:** + +Gateway expects that the node exist and the value of **Root\\.Objects\\.TempSensor\\.Name** is “**TH-101**”. + +_Expression:_ + +`**Device ${Root\.Objects\.TempSensor\.Name}**` + +In this example, created device on platform will have “**Device TH-101**” name. + +### Relative Path +A relative path specifies the address relative to a predefined starting point in the OPC-UA server's namespace. + +##### **Example:** + +Gateway expects that the node exist and the value of **Root\\.Objects\\.TempSensor\\.Name** is “**TH-101**”. + +_Device Node expression:_ + +`**Root\.Objects\.TempSensor**` + +_Expression:_ + +`**Device ${Name}**` + +In this example, **the gateway will search for the child node "Name" in the device node (parent node) +"Root\\.Objects\\.TempSensor"** and the created device on the platform will be named "**Device TH-101**". + diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-identifier_fn.md new file mode 100644 index 0000000000..eb8f4e44bf --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-identifier_fn.md @@ -0,0 +1,60 @@ +## Profile Name Field + +Profile name field is used for looking the device profile name in some variable. + +An **Identifier** type is a unique ID assigned to a node within the OPC-UA server. It is used to directly reference +specific nodes without navigating through the namespace hierarchy. + +The **Identifier** type in the OPC-UA connector configuration can be used in various forms to uniquely reference nodes +in the OPC-UA server's address space. Identifiers can be of different types, such as numeric (**`i`**), string (**`s`**), +byte string (**`b`**), and GUID (**`g`**). Below is an explanation of each identifier type with examples. + +- ##### Numeric Identifier (**`i`**) + A **numeric identifier** uses an integer value to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist and the value of **ns=2;i=1235** node is “**thermostat**”. + + _Expression:_ + + **`Device ${ns=2;i=1235}`** + + In this example, created device on platform will have “**thermostat**” profile name. + +- ##### String Identifier (**`s`**) + A **string identifier** uses a string value to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist and the value of **ns=3;s=TemperatureSensorType** node is “**thermostat**”. + + _Expression:_ + + **`Device ${ns=3;s=TemperatureSensorType}`** + + In this example, created device on platform will have “**thermostat**” profile name. + +- ##### Byte String Identifier (**`b`**) + A **byte string identifier** uses a byte string to uniquely reference a node in the OPC-UA server. This is useful for + binary data that can be converted to a byte string. + + ###### Example: + Gateway expects that the node exist and the value of **ns=4;b=Q2xpZW50RGF0YQ==** node is “**thermostat**”. + + _Expression:_ + + **`Device ${ns=4;b=Q2xpZW50RGF0YQ==}`** + + In this example, created device on platform will have “**thermostat**” profile name. + +- ##### GUID Identifier (**`g`**) + A **GUID identifier** uses a globally unique identifier (GUID) to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist and the value of **ns=1;g=550e8400-e29b-41d4-a716-446655440000** node is + “**thermostat**”. + + _Expression:_ + + **`Device ${ns=1;g=550e8400-e29b-41d4-a716-446655440000}`** + + In this example, created device on platform will have “**thermostat**” profile name. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md new file mode 100644 index 0000000000..fb8c35dee6 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md @@ -0,0 +1,38 @@ +## Profile Name Field + +The profile name field is used for looking the device profile name in some variable. + +A **Path** type refers to the hierarchical address within the OPC-UA server's namespace. It is used to navigate to +specific nodes in the server. + +The path for device name can be absolute or relative. + +### Absolute Path +An absolute path specifies the full hierarchical address from the root of the OPC-UA server's namespace to the target +node. + +##### Example: +Gateway expects that the node exist and the value of **Root\\.Objects\\.TempSensor\\.Name** is “**thermostat**”. + +_Expression:_ + +**`Device ${Root\.Objects\.TempSensor\.Type}`** + +In this example, created device on platform will have “**thermostat**” profile name. + +### Relative Path +A relative path specifies the address relative to a predefined starting point in the OPC-UA server's namespace. + +##### Example: +Gateway expects that the node exist and the value of Root\\.Objects\\.TempSensor\\.Type is “thermostat”. + +_Device Node expression:_ + +**`Root\.Objects\.TempSensor`** + +_Expression:_ + +**`Device ${Type}`** + +In this example, **the gateway will search for the child node "Name" in the device node (parent node) +"Root\\.Objects\\.TempSensor"** and the created device on the platform will have "thermostat" profile name. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-identifier_fn.md new file mode 100644 index 0000000000..f144dcac52 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-identifier_fn.md @@ -0,0 +1,62 @@ +An **Identifier** type is a unique ID assigned to a node within the OPC-UA server. It is used to directly reference +specific nodes without navigating through the namespace hierarchy. + +The **Identifier** type in the OPC-UA connector configuration can be used in various forms to uniquely reference nodes in +the OPC-UA server's address space. Identifiers can be of different types, such as numeric (`i`), string (`s`), +byte string (`b`), and GUID (`g`). Below is an explanation of each identifier type with examples. + +- ##### Numeric Identifier (`i`) + A **numeric identifier** uses an integer value to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist and the value of “**ns=2;i=1235**” node is **21.34**. + + _Expression:_ + + **`${ns=2;i=1235}`** + + _Converted data:_ + + **`21.34`** + +- ##### String Identifier (`s`) + A **string identifier** uses a string value to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist and the value of “**ns=3;s=TemperatureSensor**” node is **21.34**. + + _Expression:_ + + **`${ns=3;s=TemperatureSensor}`** + + _Converted data:_ + + **`21.34`** + +- ##### Byte String Identifier (`b`) + A **byte string identifier** uses a byte string to uniquely reference a node in the OPC-UA server. This is useful for binary data that can be converted to a byte string. + + ###### Example: + Gateway expects that the node exist and the value of “**ns=4;b=Q2xpZW50RGF0YQ==**” node is **21.34**. + + _Expression:_ + + **`${ns=4;b=Q2xpZW50RGF0YQ==}`** + + _Converted data:_ + + **`21.34`** + +- ##### GUID Identifier (`g`) + A **GUID identifier** uses a globally unique identifier (GUID) to uniquely reference a node in the OPC-UA server. + + ###### Example: + Gateway expects that the node exist and the value of “**ns=1;g=550e8400-e29b-41d4-a716-446655440000**” node is **21.34**. + + _Expression:_ + + **`${ns=1;g=550e8400-e29b-41d4-a716-446655440000}`** + + _Converted data:_ + + **`21.34`** diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-path_fn.md new file mode 100644 index 0000000000..bb2bf59c4f --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/timeseries-path_fn.md @@ -0,0 +1,45 @@ +A **Path** type refers to the hierarchical address within the OPC-UA server's namespace. It is used to navigate to +specific nodes in the server. + +The path for the attribute value can be absolute or relative. + +### Absolute Path +An **absolute path** specifies the full hierarchical address from the root of the OPC-UA server's namespace to +the target node. + +###### Example: +Gateway expects that the node exist and the value of **Root\\.Objects\\.TempSensor\\.Temperature** is **23.54**. + +_Expression:_ + +**`${Root\.Objects\.TempSensor\.Temperature}`** + +_Converted data:_ + +**`23.54`** + +### Relative Path +A **relative path** specifies the address relative to a predefined starting point in the OPC-UA server's namespace. + +###### Example: +Gateway expects that the node exist and the value of “**Root\\.Objects\\.TempSensor\\.Temperature**” is **23.56**. + +_Device Node expression:_ + +**`Root\.Objects\.TempSensor`** + +_Expression:_ + +**`${Temperature}`** + +_Converted data:_ + +**`23.56`** + +In this example, **the gateway will search for the child node "Temperature" in the device node (parent node) +"Root\\.Objects\\.TempSensor"** and will send converted data to the device. + +Additionally, you can use the node browser name to ensure that your path cannot be altered by the user or server. + +###### Example: +**`Root\.0:Objects\.3:Simulation`** 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 4eb0679b92..45a8085628 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2756,11 +2756,19 @@ "gateway": { "add-entry": "Add configuration", "add-attribute": "Add attribute", + "add-attribute-update": "Add attribute update", "add-key": "Add key", "add-timeseries": "Add time series", "add-mapping": "Add mapping", + "arguments": "Arguments", + "add-rpc-method": "Add method", + "add-value": "Add argument", + "delete-value": "Delete value", + "delete-rpc-method": "Delete method", + "delete-attribute-update": "Add attribute update", "advanced": "Advanced", "attributes": "Attributes", + "attribute-updates": "Attribute updates", "attribute-filter": "Attribute filter", "attribute-filter-hint": "Filter for incoming attribute name from platform, supports regular expression.", "attribute-filter-required": "Attribute filter required.", @@ -2823,8 +2831,8 @@ "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 profile expression required." + "device-name-expression-required": "Device name expression is required.", + "device-profile-expression-required": "Device profile expression is required." }, "device-name-filter": "Device name filter", "device-name-filter-hint": "This field supports Regular expressions to filter incoming data by device name.", @@ -2863,13 +2871,20 @@ "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 time series data keys.", + "opcua-data-mapping-hint": "Data mapping provides the capability to parse and convert the data received from a OPCUA server into specific data keys.", "delete": "Delete configuration", "delete-attribute": "Delete attribute", "delete-key": "Delete key", "delete-timeseries": "Delete time series", "default": "Default", + "device-node": "Device node", + "device-node-required": "Device node required.", + "device-node-hint": "Path or identifier for device node on OPC UA server. Relative paths from it for attributes and time series can be used.", + "device-name": "Device name", + "device-profile": "Device profile", "download-tip": "Download configuration file", "drop-file": "Drop file here or", + "enable-subscription": "Enable subscription", "extension": "Extension", "extension-hint": "Put your converter classname in the field. Custom converter with such class should be in extension/mqtt folder.", "extension-required": "Extension is required.", @@ -2908,6 +2923,7 @@ "grpc-max-pings-without-data-required": "Max pings without data is required", "grpc-max-pings-without-data-min": "Max pings without data can not be less then 1", "grpc-max-pings-without-data-pattern": "Max pings without data is not valid", + "identity": "Identity", "inactivity-check-period-seconds": "Inactivity check period (in sec)", "inactivity-check-period-seconds-required": "Inactivity check period is required", "inactivity-check-period-seconds-min": "Inactivity check period can not be less then 1", @@ -2951,19 +2967,25 @@ "max-messages-queue-for-worker": "Max messages queue per worker", "max-messages-queue-for-worker-hint": "Maximal messages count that will be in the queue \nfor each converter worker.", "max-messages-queue-for-worker-required": "Max messages queue per worker is required.", + "method-name": "Method name", + "method-required": "Method name is required.", "min-pack-send-delay": "Min pack send delay (in ms)", "min-pack-send-delay-required": "Min pack send delay is required", "min-pack-send-delay-min": "Min pack send delay can not be less then 0", + "mode": "Mode", "mqtt-version": "MQTT version", "name": "Name", "name-required": "Name is required.", "no-attributes": "No attributes", + "no-attribute-updates": "No attribute updates", "no-connectors": "No connectors", "no-data": "No configurations", "no-gateway-found": "No gateway found.", "no-gateway-matching": " '{{item}}' not found.", "no-timeseries": "No time series", "no-keys": "No keys", + "no-value": "No arguments", + "no-rpc-methods": "No RPC methods", "path-hint": "The path is local to the gateway file system", "path-logs": "Path to log files", "path-logs-required": "Path is required.", @@ -3073,6 +3095,7 @@ "write-multiple-holding-registers": "16: Write Multiple Holding Registers", "json-value-invalid": "JSON value has an invalid format" }, + "rpc-methods": "RPC methods", "request" : { "connect-request": "Connect request", "disconnect-request": "Disconnect request", @@ -3102,6 +3125,10 @@ "without-response": "Without response", "other": "Other", "save-tip": "Save configuration file", + "scan-period": "Scan period (ms)", + "scan-period-error": "Scan period should be at least {{min}}(ms).", + "sub-check-period": "Subscription check period (ms)", + "sub-check-period-error": "Subscription check period should be at least {{min}}(ms).", "security": "Security", "security-type": "Security type", "security-types": { @@ -3114,8 +3141,12 @@ "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 time series in the converter output.", + "server": "Server", "server-port": "Server port", + "server-url": "Server endpoint url", + "server-url-required": "Server endpoint url is required.", "set": "Set", + "show-map": "Show map", "statistics": { "statistic": "Statistic", "statistics": "Statistics", @@ -3177,7 +3208,9 @@ "source-type": { "msg": "Extract from message", "topic": "Extract from topic", - "const": "Constant" + "const": "Constant", + "identifier": "Identifier", + "path": "Path" }, "workers-settings": "Workers settings", "thingsboard": "ThingsBoard", @@ -3195,6 +3228,8 @@ "thingsboard-port-required": "Port is required.", "tidy": "Tidy", "tidy-tip": "Tidy config JSON", + "timeout": "Timeout (ms)", + "timeout-error": "Timeout should be at least {{min}}(ms).", "title-connectors-json": "Connector {{typeName}} configuration", "type": "Type", "topic-filter": "Topic filter", @@ -3267,7 +3302,14 @@ "permit-without-calls": "Allow server to keep the GRPC connection alive even when there are no active RPC calls.", "memory": "Your data will be stored in the in-memory queue, it is a fastest but no persistence guarantee.", "file": "Your data will be stored in separated files and will be saved even after the gateway restart.", - "sqlite": "Your data will be stored in file based database. And will be saved even after the gateway restart." + "sqlite": "Your data will be stored in file based database. And will be saved even after the gateway restart.", + "opcua-timeout": "Timeout in seconds for connecting to OPC-UA server.", + "scan-period": "Period in milliseconds to rescan the server.", + "sub-check-period": "Period to check the subscriptions in the OPC-UA server.", + "enable-subscription": "If true - the gateway will subscribe to interesting nodes and wait for data update and if false - the gateway will rescan OPC-UA server every scanPeriodInMillis.", + "show-map": "Show nodes on scanning.", + "method-name": "Name of method on OPC-UA server.", + "arguments": "Arguments for the method (will be overwritten by arguments from the RPC request)." } }, "grid": { diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json index 91ee295648..97621deda8 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json @@ -4,49 +4,63 @@ "url": "localhost:4840/freeopcua/server/", "timeoutInMillis": 5000, "scanPeriodInMillis": 5000, - "disableSubscriptions": false, + "enableSubscriptions": true, "subCheckPeriodInMillis": 100, "showMap": false, "security": "Basic128Rsa15", "identity": { "type": "anonymous" + } + }, + "mapping": [{ + "deviceNodePattern": "Root\\.Objects\\.Device1", + "deviceNodeSource": "path", + "deviceInfo": { + "deviceNameExpression": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", + "deviceNameExpressionSource": "path", + "deviceProfileExpression": "Device", + "deviceProfileExpressionSource": "constant" }, - "mapping": [ + "attributes": [ { - "deviceNodePattern": "Root\\.Objects\\.Device1", - "deviceNamePattern": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", - "attributes": [ - { - "key": "temperature °C", - "path": "${ns=2;i=5}" - } - ], - "timeseries": [ + "key": "temperature °C", + "type": "path", + "value": "${ns=2;i=5}" + } + ], + "timeseries": [ + { + "key": "humidity", + "type": "path", + "value": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" + }, + { + "key": "batteryLevel", + "type": "path", + "value": "${Battery\\.batteryLevel}" + } + ], + "rpc_methods": [ + { + "method": "multiply", + "arguments": [ { - "key": "humidity", - "path": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" + "type": "integer", + "value": 2 }, { - "key": "batteryLevel", - "path": "${Battery\\.batteryLevel}" - } - ], - "rpc_methods": [ - { - "method": "multiply", - "arguments": [ - 2, - 4 - ] - } - ], - "attributes_updates": [ - { - "attributeOnThingsBoard": "deviceName", - "attributeOnDevice": "Root\\.Objects\\.Device1\\.serialNumber" + "type": "integer", + "value": 4 } ] } + ], + "attributes_updates": [ + { + "key": "deviceName", + "type": "path", + "value": "Root\\.Objects\\.Device1\\.serialNumber" + } ] - } -} \ No newline at end of file + }] +} From 4dd87d5fbbdb4a46574a6f918b7ff3658277b9e7 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 27 Jun 2024 16:55:21 +0300 Subject: [PATCH 02/16] refactoring --- .../mapping-table/mapping-table.component.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts index 75274c9099..f65616a0f9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts @@ -41,11 +41,15 @@ import { UntypedFormArray, } from '@angular/forms'; import { - ConnectorMapping, ConverterConnectorMapping, - ConvertorTypeTranslationsMap, DeviceConnectorMapping, + ConnectorMapping, + ConverterConnectorMapping, + ConvertorTypeTranslationsMap, + DeviceConnectorMapping, MappingInfo, MappingType, - MappingTypeTranslationsMap, MappingValue, RequestMappingData, + MappingTypeTranslationsMap, + MappingValue, + RequestMappingData, RequestType, RequestTypesTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; From baaca200171b7da65342b7c9245f2330d3e977c5 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 27 Jun 2024 17:16:13 +0300 Subject: [PATCH 03/16] fixed security --- .../security-config.component.ts | 58 ++++++++++++++----- .../server-config/server-config.component.ts | 1 + .../lib/gateway/gateway-widget.models.ts | 10 ++-- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts index 94f1eb8395..dccd9a6e32 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts @@ -26,13 +26,15 @@ import { Subject } from 'rxjs'; import { ControlValueAccessor, FormBuilder, + NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormGroup, + ValidationErrors, Validators } from '@angular/forms'; import { - BrokerSecurityType, - BrokerSecurityTypeTranslationsMap, + SecurityType, + SecurityTypeTranslationsMap, ModeType, noLeadTrailSpacesRegex } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -52,6 +54,11 @@ import { CommonModule } from '@angular/common'; useExisting: forwardRef(() => SecurityConfigComponent), multi: true }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SecurityConfigComponent), + multi: true + } ], standalone: true, imports:[ @@ -67,23 +74,25 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On @coerceBoolean() extendCertificatesModel = false; - BrokerSecurityType = BrokerSecurityType; + BrokerSecurityType = SecurityType; - securityTypes = Object.values(BrokerSecurityType); + securityTypes = Object.values(SecurityType); modeTypes = Object.values(ModeType); - SecurityTypeTranslationsMap = BrokerSecurityTypeTranslationsMap; + SecurityTypeTranslationsMap = SecurityTypeTranslationsMap; securityFormGroup: UntypedFormGroup; private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + constructor(private fb: FormBuilder) {} ngOnInit(): void { this.securityFormGroup = this.fb.group({ - type: [BrokerSecurityType.ANONYMOUS, []], + type: [SecurityType.ANONYMOUS, []], username: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], password: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], pathToCACert: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], @@ -93,11 +102,12 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On if (this.extendCertificatesModel) { this.securityFormGroup.addControl('mode', this.fb.control(ModeType.NONE, [])); } + 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); - }); + ).subscribe((type) => this.updateValidators(type)); } ngOnDestroy(): void { @@ -105,13 +115,31 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On this.destroy$.complete(); } - registerOnChange(fn: any): void {} + writeValue(deviceInfo: any) { + if (!deviceInfo.type) { + deviceInfo.type = SecurityType.ANONYMOUS; + } + this.securityFormGroup.reset(deviceInfo); + this.updateView(deviceInfo); + } + + validate(): ValidationErrors | null { + return this.securityFormGroup.valid ? null : { + securityForm: { valid: false } + }; + } - registerOnTouched(fn: any): void {} + updateView(value: any) { + this.propagateChange(value); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } - writeValue(obj: any): void {} + registerOnTouched(fn: any): void {} - private updateValidators(type): void { + private updateValidators(type: SecurityType): void { if (type) { this.securityFormGroup.get('username').disable({emitEvent: false}); this.securityFormGroup.get('password').disable({emitEvent: false}); @@ -119,10 +147,10 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On this.securityFormGroup.get('pathToPrivateKey').disable({emitEvent: false}); this.securityFormGroup.get('pathToClientCert').disable({emitEvent: false}); this.securityFormGroup.get('mode')?.disable({emitEvent: false}); - if (type === BrokerSecurityType.BASIC) { + if (type === SecurityType.BASIC) { this.securityFormGroup.get('username').enable({emitEvent: false}); this.securityFormGroup.get('password').enable({emitEvent: false}); - } else if (type === BrokerSecurityType.CERTIFICATES) { + } else if (type === SecurityType.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/server-config/server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts index 3466387f3c..ffbac95b9f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts @@ -84,6 +84,7 @@ export class ServerConfigComponent implements OnInit, ControlValueAccessor, OnDe ngOnInit(): void { this.addSelfControl(); + } ngOnDestroy(): void { 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 c32baf8d66..ae5e9a2309 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 @@ -474,7 +474,7 @@ export enum ConnectorConfigurationModes { ADVANCED = 'advanced' } -export enum BrokerSecurityType { +export enum SecurityType { ANONYMOUS = 'anonymous', BASIC = 'basic', CERTIFICATES = 'certificates' @@ -486,11 +486,11 @@ export enum ModeType { SIGNANDENCRYPT = 'SignAndEncrypt' } -export const BrokerSecurityTypeTranslationsMap = new Map( +export const SecurityTypeTranslationsMap = new Map( [ - [BrokerSecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'], - [BrokerSecurityType.BASIC, 'gateway.broker.security-types.basic'], - [BrokerSecurityType.CERTIFICATES, 'gateway.broker.security-types.certificates'] + [SecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'], + [SecurityType.BASIC, 'gateway.broker.security-types.basic'], + [SecurityType.CERTIFICATES, 'gateway.broker.security-types.certificates'] ] ); From f5ac049b0ac887f0ba5386228269297eb57130dc Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 1 Jul 2024 10:39:20 +0300 Subject: [PATCH 04/16] fixed mqtt mapping --- .../widget/lib/gateway/gateway-connectors.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5f329ce66b..86d150e933 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 @@ -348,8 +348,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie configurationJson: { ...value.configurationJson, broker: { - ...value.basicConfig.broker, - ...value.basicConfig.workers, + ...value.configurationJson.broker, + ...value.configurationJson.workers, } } } From d9e9253a63c22c6a09e6ac109184f349bc3b0d99 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 1 Jul 2024 15:49:27 +0300 Subject: [PATCH 05/16] index to public-api --- .../broker-config-control/broker-config-control.component.ts | 2 +- .../connectors-configuration/{index.ts => public-api.ts} | 0 .../server-config/server-config.component.ts | 2 +- .../widget/lib/gateway/dialog/mapping-dialog.component.ts | 2 +- .../modules/home/components/widget/lib/maps/map-widget2.ts | 2 +- .../widget/lib/maps/providers/{index.ts => public-api.ts} | 0 .../home/components/widget/widget-components.module.ts | 4 ++-- ui-ngx/src/app/modules/home/pipes/{index.ts => public-api.ts} | 0 8 files changed, 6 insertions(+), 6 deletions(-) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{index.ts => public-api.ts} (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/{index.ts => public-api.ts} (100%) rename ui-ngx/src/app/modules/home/pipes/{index.ts => public-api.ts} (100%) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts index d980bed4a7..f9e48e8601 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts @@ -41,7 +41,7 @@ import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { generateSecret } from '@core/utils'; -import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration'; +import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; @Component({ selector: 'tb-broker-config-control', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/index.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/index.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts index ffbac95b9f..0229951c9a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts @@ -35,7 +35,7 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; -import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration'; +import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; @Component({ selector: 'tb-server-config', 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 00e5f2f2ad..a2b6a67490 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,7 +50,7 @@ import { startWith, takeUntil } from 'rxjs/operators'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { TranslateService } from '@ngx-translate/core'; -import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration'; +import { MappingDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; @Component({ selector: 'tb-mapping-dialog', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index a46e6a8427..c9e4d872b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -29,7 +29,7 @@ import { import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; import { EntityDataPageLink } from '@shared/models/query/query.models'; -import { providerClass } from '@home/components/widget/lib/maps/providers'; +import { providerClass } from '@home/components/widget/lib/maps/providers/public-api'; import { isDefined, isDefinedAndNotNull, parseFunction } from '@core/utils'; import L from 'leaflet'; import { forkJoin, Observable, of } from 'rxjs'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/public-api.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/public-api.ts 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 5dffc4b8d9..e83a4df917 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 @@ -99,7 +99,7 @@ import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobi import { LabelCardWidgetComponent } from '@home/components/widget/lib/cards/label-card-widget.component'; import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards/label-value-card-widget.component'; -import { GatewayHelpLinkPipe } from '@home/pipes'; +import { GatewayHelpLinkPipe } from '@home/pipes/public-api'; import { DeviceInfoTableComponent, MappingDataKeysPanelComponent, @@ -108,7 +108,7 @@ import { TypeValuePanelComponent, BrokerConfigControlComponent, WorkersConfigControlComponent, -} from '@home/components/widget/lib/gateway/connectors-configuration'; +} from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; @NgModule({ declarations: diff --git a/ui-ngx/src/app/modules/home/pipes/index.ts b/ui-ngx/src/app/modules/home/pipes/public-api.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pipes/index.ts rename to ui-ngx/src/app/modules/home/pipes/public-api.ts From b91b317c39103f3702a7504990c9459a984c6839 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 4 Jul 2024 15:05:00 +0300 Subject: [PATCH 06/16] Changed aproach to valueAccessor --- .../broker-config-control.component.html | 2 +- .../broker-config-control.component.ts | 61 ++++--- .../general-config.component.html | 80 +++++++++ .../general-config.component.ts | 52 ++++++ .../mapping-table/mapping-table.component.ts | 96 +++++----- .../mqtt-basic-config.component.html | 24 +++ .../mqtt-basic-config.component.ts | 170 ++++++++++++++++++ .../opc-ua-basic-config.component.html | 13 ++ .../opc-ua-basic-config.component.ts | 139 ++++++++++++++ .../connectors-configuration/public-api.ts | 3 + .../security-config.component.ts | 43 ++--- .../server-config/server-config.component.ts | 62 ++++--- .../workers-config-control.component.ts | 57 +++--- .../gateway/gateway-connectors.component.html | 125 +++---------- .../gateway/gateway-connectors.component.ts | 158 +++------------- .../lib/gateway/gateway-widget.models.ts | 31 ++-- .../widget/widget-components.module.ts | 15 +- 17 files changed, 741 insertions(+), 390 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html index 4c2db19345..a6c3a620dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html @@ -81,6 +81,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts index f9e48e8601..893fb30fde 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts @@ -19,20 +19,20 @@ import { Component, forwardRef, inject, - Input, OnDestroy, - OnInit } from '@angular/core'; import { - ControlContainer, ControlValueAccessor, FormBuilder, - FormGroup, + NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormGroup, + ValidationErrors, + Validator, Validators } from '@angular/forms'; import { + BrokerConfig, MqttVersions, noLeadTrailSpacesRegex, PortLimits, @@ -42,6 +42,7 @@ import { CommonModule } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { generateSecret } from '@core/utils'; import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { Subject } from 'rxjs'; @Component({ selector: 'tb-broker-config-control', @@ -58,22 +59,24 @@ import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/con provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BrokerConfigControlComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => BrokerConfigControlComponent), + multi: true } ] }) -export class BrokerConfigControlComponent implements ControlValueAccessor, OnInit, OnDestroy { - @Input() controlKey = 'broker'; - +export class BrokerConfigControlComponent implements ControlValueAccessor, Validator, OnDestroy { brokerConfigFormGroup: UntypedFormGroup; mqttVersions = MqttVersions; portLimits = PortLimits; - get parentFormGroup(): FormGroup { - return this.parentContainer.control as FormGroup; - } + onChange!: (value: string) => void; + onTouched!: () => void; - private parentContainer = inject(ControlContainer); private translate = inject(TranslateService); + private destroy$ = new Subject(); constructor(private fb: FormBuilder) { this.brokerConfigFormGroup = this.fb.group({ @@ -82,9 +85,12 @@ export class BrokerConfigControlComponent implements ControlValueAccessor, OnIni port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], version: [5, []], clientId: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], - maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], - security: [{}, [Validators.required]] + security: [] + }); + + this.brokerConfigFormGroup.valueChanges.subscribe(value => { + this.onChange(value); + this.onTouched(); }); } @@ -101,29 +107,30 @@ export class BrokerConfigControlComponent implements ControlValueAccessor, OnIni return ''; } - ngOnInit(): void { - this.addSelfControl(); - } - ngOnDestroy(): void { - this.removeSelfControl(); + this.destroy$.next(); + this.destroy$.complete(); } generate(formControlName: string): void { this.brokerConfigFormGroup.get(formControlName)?.patchValue('tb_gw_' + generateSecret(5)); } - registerOnChange(fn: any): void {} - - registerOnTouched(fn: any): void {} + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - writeValue(obj: any): void {} + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } - private addSelfControl(): void { - this.parentFormGroup.addControl(this.controlKey, this.brokerConfigFormGroup); + writeValue(brokerConfig: BrokerConfig): void { + this.brokerConfigFormGroup.patchValue(brokerConfig, {emitEvent: false}); } - private removeSelfControl(): void { - this.parentFormGroup.removeControl(this.controlKey); + validate(): ValidationErrors | null { + return this.brokerConfigFormGroup.valid ? null : { + brokerConfigFormGroup: {valid: false} + }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html new file mode 100644 index 0000000000..9c5ee12e6c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html @@ -0,0 +1,80 @@ + +
+
+
gateway.name
+
+ + + + warning + + +
+
+
+
gateway.connectors-table-class
+
+ + + +
+
+
+
gateway.connectors-table-key
+
+ + + +
+
+
+
gateway.logs-configuration
+
+ + + {{ 'gateway.enable-remote-logging' | translate }} + + +
+
+
gateway.remote-logging-level
+
+ + + {{ logLevel }} + + +
+
+
+
+ + + {{ 'gateway.send-change-data' | translate }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts new file mode 100644 index 0000000000..320031a9fe --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts @@ -0,0 +1,52 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + inject, +} from '@angular/core'; +import { + ControlContainer, + FormGroup, +} from '@angular/forms'; +import { + ConnectorType, + GatewayLogLevel, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'tb-general-config', + templateUrl: './general-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + SharedModule, + ], +}) +export class GeneralConfigComponent { + gatewayLogLevel = Object.values(GatewayLogLevel); + connectorType = ConnectorType; + + get parentFormGroup(): FormGroup { + return this.parentContainer.control as FormGroup; + } + + private parentContainer = inject(ControlContainer); +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts index f65616a0f9..02fcfac1a0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts @@ -20,7 +20,6 @@ import { Component, ElementRef, forwardRef, - inject, Input, OnDestroy, OnInit, @@ -33,12 +32,14 @@ import { DialogService } from '@core/services/dialog.service'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, take, takeUntil } from 'rxjs/operators'; import { - ControlContainer, ControlValueAccessor, + FormArray, FormBuilder, - FormGroup, + NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormArray, + ValidationErrors, + Validator, } from '@angular/forms'; import { ConnectorMapping, @@ -57,7 +58,8 @@ import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { validateArrayIsNotEmpty } from '@shared/validators/form-array.validators'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'tb-mapping-table', @@ -69,16 +71,29 @@ import { validateArrayIsNotEmpty } from '@shared/validators/form-array.validator provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MappingTableComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MappingTableComponent), + multi: true } - ] + ], + standalone: true, + imports: [CommonModule, SharedModule] }) -export class MappingTableComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy { - @Input() controlKey = 'dataMapping'; - +export class MappingTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { @coerceBoolean() @Input() required = false; - parentContainer = inject(ControlContainer); + @Input() + set mappingType(value: MappingType) { + if (this.mappingTypeValue !== value) { + this.mappingTypeValue = value; + } + } + + @ViewChild('searchInput') searchInputField: ElementRef; + mappingTypeTranslationsMap = MappingTypeTranslationsMap; mappingTypeEnum = MappingType; displayedColumns = []; @@ -86,31 +101,18 @@ export class MappingTableComponent implements ControlValueAccessor, AfterViewIni textSearchMode = false; dataSource: MappingDatasource; hidePageSize = false; - activeValue = false; dirtyValue = false; - mappingTypeValue: MappingType; + mappingFormGroup: UntypedFormArray; + textSearch = this.fb.control('', {nonNullable: true}); get mappingType(): MappingType { return this.mappingTypeValue; } - get parentFormGroup(): FormGroup { - return this.parentContainer.control as FormGroup; - } - - @Input() - set mappingType(value: MappingType) { - if (this.mappingTypeValue !== value) { - this.mappingTypeValue = value; - } - } - - @ViewChild('searchInput') searchInputField: ElementRef; - - mappingFormGroup: UntypedFormArray; - textSearch = this.fb.control('', {nonNullable: true}); + onChange: (value: string) => void = () => {}; + onTouched: () => void = () => {}; private destroy$ = new Subject(); @@ -130,14 +132,14 @@ export class MappingTableComponent implements ControlValueAccessor, AfterViewIni takeUntil(this.destroy$) ).subscribe((value) => { this.updateTableData(value); + this.onChange(value); + this.onTouched(); }); - this.addSelfControl(); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); - this.removeSelfControl(); } ngAfterViewInit(): void { @@ -151,11 +153,24 @@ export class MappingTableComponent implements ControlValueAccessor, AfterViewIni }); } - registerOnChange(fn: any): void {} + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - registerOnTouched(fn: any): void {} + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } - writeValue(obj: any): void {} + writeValue(connectorMappings: ConnectorMapping[]): void { + (this.mappingFormGroup as FormArray).clear(); + this.pushDataAsFormArrays(connectorMappings) + } + + validate(): ValidationErrors | null { + return !this.required || this.mappingFormGroup.controls.length ? null : { + mappingFormGroup: {valid: false} + }; + } enterFilterMode(): void { this.textSearchMode = true; @@ -229,13 +244,19 @@ export class MappingTableComponent implements ControlValueAccessor, AfterViewIni }); } + private pushDataAsFormArrays(data: ConnectorMapping[]): void { + if (data?.length) { + data.forEach((mapping: ConnectorMapping) => this.mappingFormGroup.push(this.fb.control(mapping))); + } + } + private getMappingValue(value: ConnectorMapping): MappingValue { switch (this.mappingType) { case MappingType.DATA: return { topicFilter: (value as ConverterConnectorMapping).topicFilter, QoS: (value as ConverterConnectorMapping).subscriptionQos, - converter: this.translate.instant(ConvertorTypeTranslationsMap.get((value as ConverterConnectorMapping).converter.type)) + converter: this.translate.instant(ConvertorTypeTranslationsMap.get((value as ConverterConnectorMapping).converter?.type) || '') }; case MappingType.REQUESTS: let details; @@ -288,17 +309,6 @@ export class MappingTableComponent implements ControlValueAccessor, AfterViewIni ); } } - - private addSelfControl(): void { - this.parentFormGroup.addControl(this.controlKey, this.mappingFormGroup); - if (this.required) { - this.mappingFormGroup.addValidators(validateArrayIsNotEmpty()); - } - } - - private removeSelfControl(): void { - this.parentFormGroup.removeControl(this.controlKey); - } } export class MappingDatasource implements DataSource<{[key: string]: any}> { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html new file mode 100644 index 0000000000..8fecdd8618 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html @@ -0,0 +1,24 @@ + + + + + + + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts new file mode 100644 index 0000000000..a46d55fed4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts @@ -0,0 +1,170 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + Input, + OnDestroy, + TemplateRef, +} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, +} from '@angular/forms'; +import { + ConnectorBaseConfig, + ConnectorType, + MappingType, + RequestMappingData, + RequestType, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { + BrokerConfigControlComponent, + MappingTableComponent, + SecurityConfigComponent, + WorkersConfigControlComponent, + GeneralConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { isObject } from 'lodash'; + +@Component({ + selector: 'tb-mqtt-basic-config', + templateUrl: './mqtt-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + WorkersConfigControlComponent, + BrokerConfigControlComponent, + MappingTableComponent, + GeneralConfigComponent, + ], + styles: [` + :host { + height: 100%; + } + :host ::ng-deep { + .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { + height: 100%; + } + } + `], +}) + +export class MqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { + @Input() generalTabContent: TemplateRef; + + mappingTypes = MappingType; + basicFormGroup: FormGroup; + + onChange!: (value: string) => void; + onTouched!: () => void; + + protected readonly connectorType = ConnectorType; + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.basicFormGroup = this.fb.group({ + dataMapping: [], + requestsMapping: [], + broker: [], + workers: [], + }); + + this.basicFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => { + this.onChange(value); + this.onTouched(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(basicConfig: ConnectorBaseConfig): void { + const editedBase = { + ...basicConfig, + workers: { + maxNumberOfWorkers: basicConfig.broker?.maxNumberOfWorkers, + maxMessageNumberPerWorker: basicConfig.broker?.maxMessageNumberPerWorker, + }, + requestsMapping: Array.isArray(basicConfig.requestsMapping) + ? basicConfig.requestsMapping + : this.getRequestDataArray(basicConfig.requestsMapping), + }; + + this.basicFormGroup.patchValue(editedBase, {emitEvent: false}); + } + + validate(): ValidationErrors | null { + return this.basicFormGroup.valid ? null : { + basicFormGroup: {valid: false} + }; + } + + private getRequestDataArray(value: Record): RequestMappingData[] { + const mappingConfigs = []; + + if (isObject(value)) { + Object.keys(value).forEach((configKey: string) => { + for (let mapping of value[configKey]) { + mappingConfigs.push({ + requestType: configKey, + requestValue: mapping + }); + } + }); + } + + return mappingConfigs; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html new file mode 100644 index 0000000000..75c2b76f69 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html @@ -0,0 +1,13 @@ + + + + + + + + +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts new file mode 100644 index 0000000000..f32dbff598 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -0,0 +1,139 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + Input, + OnDestroy, + TemplateRef, +} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, +} from '@angular/forms'; +import { + ConnectorBaseConfig, + ConnectorType, + MappingType, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { + BrokerConfigControlComponent, + MappingTableComponent, + SecurityConfigComponent, + ServerConfigComponent, + WorkersConfigControlComponent, + GeneralConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'tb-opc-ua-basic-config', + templateUrl: './opc-ua-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OpcUaBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => OpcUaBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + WorkersConfigControlComponent, + BrokerConfigControlComponent, + MappingTableComponent, + GeneralConfigComponent, + ServerConfigComponent, + ], + styles: [` + :host { + height: 100%; + } + :host ::ng-deep { + .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { + height: 100%; + } + } + `], +}) + +export class OpcUaBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { + @Input() generalTabContent: TemplateRef; + + mappingTypes = MappingType; + basicFormGroup: FormGroup; + + onChange!: (value: string) => void; + onTouched!: () => void; + + protected readonly connectorType = ConnectorType; + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.basicFormGroup = this.fb.group({ + mapping: [], + server: [], + }); + + this.basicFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => { + this.onChange(value); + this.onTouched(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(basicConfig: ConnectorBaseConfig): void { + this.basicFormGroup.patchValue(basicConfig, {emitEvent: false}); + } + + validate(): ValidationErrors | null { + return this.basicFormGroup.valid ? null : { + basicFormGroup: {valid: false} + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts index bd0d4787d6..93776a85fc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts @@ -22,3 +22,6 @@ export * from './mapping-data-keys-panel/mapping-data-keys-panel.component'; export * from './type-value-panel/type-value-panel.component'; export * from './broker-config-control/broker-config-control.component'; export * from './workers-config-control/workers-config-control.component'; +export * from './opc-ua-basic-config/opc-ua-basic-config.component'; +export * from './mqtt-basic-config/mqtt-basic-config.component'; +export * from './general-config/general-config.component'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts index dccd9a6e32..73095927b7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts @@ -36,7 +36,8 @@ import { SecurityType, SecurityTypeTranslationsMap, ModeType, - noLeadTrailSpacesRegex + noLeadTrailSpacesRegex, + ConnectorSecurity } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { takeUntil } from 'rxjs/operators'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -75,18 +76,15 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On extendCertificatesModel = false; BrokerSecurityType = SecurityType; - securityTypes = Object.values(SecurityType); - modeTypes = Object.values(ModeType); - SecurityTypeTranslationsMap = SecurityTypeTranslationsMap; - securityFormGroup: UntypedFormGroup; - private destroy$ = new Subject(); + onChange!: (value: string) => void; + onTouched!: () => void; - private propagateChange = (v: any) => {}; + private destroy$ = new Subject(); constructor(private fb: FormBuilder) {} @@ -104,7 +102,10 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On } this.securityFormGroup.valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe((value) => this.updateView(value)); + ).subscribe((value) => { + this.onChange(value); + this.onTouched(); + }); this.securityFormGroup.get('type').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((type) => this.updateValidators(type)); @@ -115,30 +116,32 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On this.destroy$.complete(); } - writeValue(deviceInfo: any) { - if (!deviceInfo.type) { - deviceInfo.type = SecurityType.ANONYMOUS; + writeValue(securityInfo: ConnectorSecurity): void { + if (!securityInfo) { + const defaultSecurity = {type: SecurityType.ANONYMOUS}; + this.securityFormGroup.reset(defaultSecurity, {emitEvent: false}); + } else { + if (!securityInfo.type) { + securityInfo.type = SecurityType.ANONYMOUS; + } + this.securityFormGroup.reset(securityInfo, {emitEvent: false}); } - this.securityFormGroup.reset(deviceInfo); - this.updateView(deviceInfo); } validate(): ValidationErrors | null { - return this.securityFormGroup.valid ? null : { + return this.securityFormGroup.get('type').value !== SecurityType.BASIC || this.securityFormGroup.valid ? null : { securityForm: { valid: false } }; } - updateView(value: any) { - this.propagateChange(value); + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; } - registerOnChange(fn: any): void { - this.propagateChange = fn; + registerOnTouched(fn: () => void): void { + this.onTouched = fn; } - registerOnTouched(fn: any): void {} - private updateValidators(type: SecurityType): void { if (type) { this.securityFormGroup.get('username').disable({emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts index 0229951c9a..5954918f4f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts @@ -17,25 +17,30 @@ import { ChangeDetectionStrategy, Component, - forwardRef, inject, Input, - OnDestroy, OnInit + forwardRef, + OnDestroy } from '@angular/core'; import { - ControlContainer, ControlValueAccessor, - FormBuilder, FormGroup, + FormBuilder, + NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormGroup, + ValidationErrors, + Validator, Validators } from '@angular/forms'; import { noLeadTrailSpacesRegex, SecurityType, + ServerConfig, ServerSecurityTypes } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-server-config', @@ -48,6 +53,11 @@ import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/con useExisting: forwardRef(() => ServerConfigComponent), multi: true }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ServerConfigComponent), + multi: true + } ], standalone: true, imports: [ @@ -56,17 +66,14 @@ import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/con SecurityConfigComponent, ] }) -export class ServerConfigComponent implements OnInit, ControlValueAccessor, OnDestroy { - @Input() controlKey = 'server'; - +export class ServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { serverSecurityTypes = ServerSecurityTypes; serverConfigFormGroup: UntypedFormGroup; - get parentFormGroup(): FormGroup { - return this.parentContainer.control as FormGroup; - } + onChange!: (value: string) => void; + onTouched!: () => void; - private parentContainer = inject(ControlContainer); + private destroy$ = new Subject(); constructor(private fb: FormBuilder) { this.serverConfigFormGroup = this.fb.group({ @@ -80,28 +87,35 @@ export class ServerConfigComponent implements OnInit, ControlValueAccessor, OnDe security: [SecurityType.BASIC128, []], identity: [{}, [Validators.required]] }); - } - - ngOnInit(): void { - this.addSelfControl(); + this.serverConfigFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.onChange(value); + this.onTouched(); + }); } ngOnDestroy(): void { - this.removeSelfControl(); + this.destroy$.next(); + this.destroy$.complete(); } - registerOnChange(fn: any): void {} - - registerOnTouched(fn: any): void {} + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - writeValue(obj: any): void {} + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } - private addSelfControl(): void { - this.parentFormGroup.addControl(this.controlKey, this.serverConfigFormGroup); + validate(): ValidationErrors | null { + return this.serverConfigFormGroup.valid ? null : { + serverConfigFormGroup: { valid: false } + }; } - private removeSelfControl(): void { - this.parentFormGroup.removeControl(this.controlKey); + writeValue(serverConfig: ServerConfig): void { + this.serverConfigFormGroup.patchValue(serverConfig, {emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts index 45e3aef7e8..f858328d5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts @@ -18,22 +18,21 @@ import { ChangeDetectionStrategy, Component, forwardRef, - inject, - Input, OnDestroy, - OnInit } from '@angular/core'; import { - ControlContainer, ControlValueAccessor, FormBuilder, - FormGroup, + NG_VALIDATORS, NG_VALUE_ACCESSOR, - UntypedFormGroup, + UntypedFormGroup, ValidationErrors, Validator, Validators } from '@angular/forms'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; +import { WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-workers-config-control', @@ -49,46 +48,54 @@ import { CommonModule } from '@angular/common'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => WorkersConfigControlComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => WorkersConfigControlComponent), + multi: true } ] }) -export class WorkersConfigControlComponent implements ControlValueAccessor, OnInit, OnDestroy { - @Input() controlKey = 'workers'; - +export class WorkersConfigControlComponent implements OnDestroy, ControlValueAccessor, Validator { workersConfigFormGroup: UntypedFormGroup; - get parentFormGroup(): FormGroup { - return this.parentContainer.control as FormGroup; - } + onChange!: (value: string) => void; + onTouched!: () => void; - private parentContainer = inject(ControlContainer); + private destroy$ = new Subject(); constructor(private fb: FormBuilder) { this.workersConfigFormGroup = this.fb.group({ maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], }); - } - ngOnInit(): void { - this.addSelfControl(); + this.workersConfigFormGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { + this.onChange(value); + this.onTouched(); + }); } ngOnDestroy(): void { - this.removeSelfControl(); + this.destroy$.next(); + this.destroy$.complete(); } - registerOnChange(fn: any): void {} - - registerOnTouched(fn: any): void {} + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - writeValue(obj: any): void {} + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } - private addSelfControl(): void { - this.parentFormGroup.addControl(this.controlKey, this.workersConfigFormGroup); + writeValue(workersConfig: WorkersConfig): void { + this.workersConfigFormGroup.patchValue(workersConfig, {emitEvent: false}); } - private removeSelfControl(): void { - this.parentFormGroup.removeControl(this.controlKey); + validate(): ValidationErrors | null { + return this.workersConfigFormGroup.valid ? null : { + workersConfigFormGroup: {valid: false} + }; } } 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 f453f0c2bc..cafb8d159b 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 @@ -173,110 +173,25 @@ class="no-data-found" translate> gateway.select-connector + + +
- - -
-
-
gateway.name
-
- - - - warning - - -
-
-
-
gateway.connectors-table-class
-
- - - -
-
-
-
gateway.connectors-table-key
-
- - - -
-
-
-
gateway.logs-configuration
-
- - - {{ 'gateway.enable-remote-logging' | translate }} - - -
-
-
gateway.remote-logging-level
-
- - - {{ logLevel }} - - -
-
-
-
- - - {{ 'gateway.send-change-data' | translate }} - - -
-
-
- - - - - - - -
- -
-
- -
- -
-
- -
- -
-
-
- - - - - -
- -
-
-
-
-
- + + + + + + + + + + + - -
+ +
- -
+
+
+
gateway.client-id
+
+ + + +
- -
+ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts index 893fb30fde..13f5c7c77e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts @@ -14,13 +14,7 @@ /// limitations under the License. /// -import { - ChangeDetectionStrategy, - Component, - forwardRef, - inject, - OnDestroy, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -72,13 +66,13 @@ export class BrokerConfigControlComponent implements ControlValueAccessor, Valid mqttVersions = MqttVersions; portLimits = PortLimits; - onChange!: (value: string) => void; - onTouched!: () => void; + private onChange: (value: string) => void; + private onTouched: () => void; - private translate = inject(TranslateService); private destroy$ = new Subject(); - constructor(private fb: FormBuilder) { + constructor(private fb: FormBuilder, + private translate: TranslateService) { this.brokerConfigFormGroup = this.fb.group({ name: ['', []], host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html deleted file mode 100644 index 9c5ee12e6c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.html +++ /dev/null @@ -1,80 +0,0 @@ - -
-
-
gateway.name
-
- - - - warning - - -
-
-
-
gateway.connectors-table-class
-
- - - -
-
-
-
gateway.connectors-table-key
-
- - - -
-
-
-
gateway.logs-configuration
-
- - - {{ 'gateway.enable-remote-logging' | translate }} - - -
-
-
gateway.remote-logging-level
-
- - - {{ logLevel }} - - -
-
-
-
- - - {{ 'gateway.send-change-data' | translate }} - - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts deleted file mode 100644 index 320031a9fe..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/general-config/general-config.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -/// -/// Copyright © 2016-2024 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { - ChangeDetectionStrategy, - Component, - inject, -} from '@angular/core'; -import { - ControlContainer, - FormGroup, -} from '@angular/forms'; -import { - ConnectorType, - GatewayLogLevel, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'tb-general-config', - templateUrl: './general-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], -}) -export class GeneralConfigComponent { - gatewayLogLevel = Object.values(GatewayLogLevel); - connectorType = ConnectorType; - - get parentFormGroup(): FormGroup { - return this.parentContainer.control as FormGroup; - } - - private parentContainer = inject(ControlContainer); -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts index 02fcfac1a0..7042e5ae38 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts @@ -25,7 +25,6 @@ import { OnInit, ViewChild, } from '@angular/core'; -import { PageLink } from '@shared/models/page/page-link'; import { TranslateService } from '@ngx-translate/core'; import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; @@ -33,7 +32,6 @@ import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, take, takeUntil } from 'rxjs/operators'; import { ControlValueAccessor, - FormArray, FormBuilder, NG_VALIDATORS, NG_VALUE_ACCESSOR, @@ -54,7 +52,7 @@ import { RequestType, RequestTypesTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { CollectionViewer, DataSource } from '@angular/cdk/collections'; +import { DataSource } from '@angular/cdk/collections'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -82,8 +80,10 @@ import { CommonModule } from '@angular/common'; imports: [CommonModule, SharedModule] }) export class MappingTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { + + @Input() @coerceBoolean() - @Input() required = false; + required = false; @Input() set mappingType(value: MappingType) { @@ -92,6 +92,10 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A } } + get mappingType(): MappingType { + return this.mappingTypeValue; + } + @ViewChild('searchInput') searchInputField: ElementRef; mappingTypeTranslationsMap = MappingTypeTranslationsMap; @@ -107,12 +111,8 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A mappingFormGroup: UntypedFormArray; textSearch = this.fb.control('', {nonNullable: true}); - get mappingType(): MappingType { - return this.mappingTypeValue; - } - - onChange: (value: string) => void = () => {}; - onTouched: () => void = () => {}; + private onChange: (value: string) => void = () => {}; + private onTouched: () => void = () => {}; private destroy$ = new Subject(); @@ -149,7 +149,7 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A takeUntil(this.destroy$) ).subscribe((text) => { const searchText = text.trim(); - this.updateTableData(this.mappingFormGroup.value, searchText.trim()) + this.updateTableData(this.mappingFormGroup.value, searchText.trim()); }); } @@ -162,8 +162,8 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A } writeValue(connectorMappings: ConnectorMapping[]): void { - (this.mappingFormGroup as FormArray).clear(); - this.pushDataAsFormArrays(connectorMappings) + this.mappingFormGroup.clear(); + this.pushDataAsFormArrays(connectorMappings); } validate(): ValidationErrors | null { @@ -213,12 +213,11 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A }); } - updateTableData(value: ConnectorMapping[], textSearch?: string): void { - let tableValue = - value.map((value: ConnectorMapping) => this.getMappingValue(value)); + private updateTableData(value: ConnectorMapping[], textSearch?: string): void { + let tableValue = value.map(mappingValue => this.getMappingValue(mappingValue)); if (textSearch) { - tableValue = tableValue.filter(value => - Object.values(value).some(val => + tableValue = tableValue.filter(mappingValue => + Object.values(mappingValue).some(val => val.toString().toLowerCase().includes(textSearch.toLowerCase()) ) ); @@ -259,7 +258,7 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A converter: this.translate.instant(ConvertorTypeTranslationsMap.get((value as ConverterConnectorMapping).converter?.type) || '') }; case MappingType.REQUESTS: - let details; + let details: string; if ((value as RequestMappingData).requestType === RequestType.ATTRIBUTE_UPDATE) { details = (value as RequestMappingData).requestValue.attributeFilter; } else if ((value as RequestMappingData).requestType === RequestType.SERVER_SIDE_RPC) { @@ -315,22 +314,17 @@ export class MappingDatasource implements DataSource<{[key: string]: any}> { private mappingSubject = new BehaviorSubject>([]); - private allMappings: Observable>; - constructor() {} - connect(collectionViewer: CollectionViewer): Observable> { + connect(): Observable> { return this.mappingSubject.asObservable(); } - disconnect(collectionViewer: CollectionViewer): void { + disconnect(): void { this.mappingSubject.complete(); } - loadMappings(mappings: Array<{[key: string]: any}>, pageLink?: PageLink, reload: boolean = false): void { - if (reload) { - this.allMappings = null; - } + loadMappings(mappings: Array<{[key: string]: any}>): void { this.mappingSubject.next(mappings); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts index c6a24956d6..bf4e087904 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts @@ -14,14 +14,7 @@ /// limitations under the License. /// -import { - ChangeDetectionStrategy, - Component, - forwardRef, - Input, - OnDestroy, - TemplateRef, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -33,7 +26,6 @@ import { } from '@angular/forms'; import { ConnectorBaseConfig, - ConnectorType, MappingType, RequestMappingData, RequestType, @@ -44,8 +36,7 @@ import { BrokerConfigControlComponent, MappingTableComponent, SecurityConfigComponent, - WorkersConfigControlComponent, - GeneralConfigComponent + WorkersConfigControlComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @@ -75,7 +66,6 @@ import { isObject } from 'lodash'; WorkersConfigControlComponent, BrokerConfigControlComponent, MappingTableComponent, - GeneralConfigComponent, ], styles: [` :host { @@ -90,15 +80,16 @@ import { isObject } from 'lodash'; }) export class MqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - @Input() generalTabContent: TemplateRef; + + @Input() + generalTabContent: TemplateRef; mappingTypes = MappingType; basicFormGroup: FormGroup; - onChange!: (value: string) => void; - onTouched!: () => void; + private onChange: (value: string) => void; + private onTouched: () => void; - protected readonly connectorType = ConnectorType; private destroy$ = new Subject(); constructor(private fb: FormBuilder) { @@ -157,7 +148,7 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator if (isObject(value)) { Object.keys(value).forEach((configKey: string) => { - for (let mapping of value[configKey]) { + for (const mapping of value[configKey]) { mappingConfigs.push({ requestType: configKey, requestValue: mapping diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts index 9c39aa7b13..d0552a0de4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -14,14 +14,7 @@ /// limitations under the License. /// -import { - ChangeDetectionStrategy, - Component, - forwardRef, - Input, - OnDestroy, - TemplateRef, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -43,8 +36,7 @@ import { MappingTableComponent, SecurityConfigComponent, ServerConfigComponent, - WorkersConfigControlComponent, - GeneralConfigComponent + WorkersConfigControlComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @@ -73,7 +65,6 @@ import { Subject } from 'rxjs'; WorkersConfigControlComponent, BrokerConfigControlComponent, MappingTableComponent, - GeneralConfigComponent, ServerConfigComponent, ], styles: [` @@ -89,7 +80,7 @@ import { Subject } from 'rxjs'; }) export class OpcUaBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - @Input() generalTabContent: TemplateRef; + @Input() generalTabContent: TemplateRef; mappingTypes = MappingType; basicFormGroup: FormGroup; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts index 93776a85fc..5e185ddf0a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts @@ -24,4 +24,3 @@ export * from './broker-config-control/broker-config-control.component'; export * from './workers-config-control/workers-config-control.component'; export * from './opc-ua-basic-config/opc-ua-basic-config.component'; export * from './mqtt-basic-config/mqtt-basic-config.component'; -export * from './general-config/general-config.component'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts index 73095927b7..38a66daafc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts @@ -68,21 +68,22 @@ import { CommonModule } from '@angular/common'; ] }) export class SecurityConfigComponent implements ControlValueAccessor, OnInit, OnDestroy { + @Input() - title: string = 'gateway.security'; + title = 'gateway.security'; @Input() @coerceBoolean() extendCertificatesModel = false; BrokerSecurityType = SecurityType; - securityTypes = Object.values(SecurityType); + securityTypes = Object.values(SecurityType) as SecurityType[]; modeTypes = Object.values(ModeType); SecurityTypeTranslationsMap = SecurityTypeTranslationsMap; securityFormGroup: UntypedFormGroup; - onChange!: (value: string) => void; - onTouched!: () => void; + private onChange: (value: string) => void; + private onTouched: () => void; private destroy$ = new Subject(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html index ee097ae642..fc103a4ad6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html @@ -58,7 +58,7 @@
- {{ version.name }} + {{ version.name }}
@@ -119,7 +119,7 @@ - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts index 5954918f4f..98a36182a6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts @@ -14,12 +14,7 @@ /// limitations under the License. /// -import { - ChangeDetectionStrategy, - Component, - forwardRef, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -32,9 +27,9 @@ import { } from '@angular/forms'; import { noLeadTrailSpacesRegex, - SecurityType, - ServerConfig, - ServerSecurityTypes + SecurityPolicy, + SecurityPolicyTypes, + ServerConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; @@ -67,7 +62,8 @@ import { takeUntil } from 'rxjs/operators'; ] }) export class ServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - serverSecurityTypes = ServerSecurityTypes; + + securityPolicyTypes = SecurityPolicyTypes; serverConfigFormGroup: UntypedFormGroup; onChange!: (value: string) => void; @@ -84,7 +80,7 @@ export class ServerConfigComponent implements ControlValueAccessor, Validator, O enableSubscriptions: [true, []], subCheckPeriodInMillis: [10, [Validators.required, Validators.min(10)]], showMap: [false, []], - security: [SecurityType.BASIC128, []], + security: [SecurityPolicy.BASIC128, []], identity: [{}, [Validators.required]] }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts index f858328d5e..9a37bdb07c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts @@ -57,10 +57,11 @@ import { takeUntil } from 'rxjs/operators'; ] }) export class WorkersConfigControlComponent implements OnDestroy, ControlValueAccessor, Validator { + workersConfigFormGroup: UntypedFormGroup; - onChange!: (value: string) => void; - onTouched!: () => void; + private onChange: (value: string) => void; + private onTouched: () => void; private destroy$ = new Subject(); 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 cafb8d159b..7896ac2516 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 @@ -156,9 +156,7 @@ {{ initialConnector?.type ? gatewayConnectorDefaultTypes.get(initialConnector.type) : '' }} {{ 'gateway.configuration' | translate }} - {{ 'gateway.basic' | translate }} @@ -173,19 +171,18 @@ class="no-data-found" translate> gateway.select-connector - - -
- - - + - + + + + @@ -215,3 +212,68 @@
+ +
+
+
gateway.name
+
+ + + + warning + + +
+
+
+
gateway.connectors-table-class
+
+ + + +
+
+
+
gateway.connectors-table-key
+
+ + + +
+
+
+
gateway.logs-configuration
+
+ + + {{ 'gateway.enable-remote-logging' | translate }} + + +
+
+
gateway.remote-logging-level
+
+ + + {{ logLevel }} + + +
+
+
+
+ + + {{ 'gateway.send-change-data' | translate }} + + +
+
+
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 8c1555cfb4..e2c3aa8b65 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,20 +21,12 @@ import { ElementRef, Input, NgZone, - ViewChild, + OnDestroy, + ViewChild } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { - FormBuilder, - FormControl, - FormGroup, - FormGroupDirective, - NgForm, - UntypedFormControl, - ValidatorFn, - Validators -} from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, 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'; @@ -72,7 +64,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; export class ForceErrorStateMatcher implements ErrorStateMatcher { - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + isErrorState(control: FormControl | null): boolean { return (control && control.invalid); } } @@ -83,7 +75,7 @@ export class ForceErrorStateMatcher implements ErrorStateMatcher { providers: [{ provide: ErrorStateMatcher, useClass: ForceErrorStateMatcher }], styleUrls: ['./gateway-connectors.component.scss'] }) -export class GatewayConnectorComponent extends PageComponent implements AfterViewInit { +export class GatewayConnectorComponent extends PageComponent implements AfterViewInit, OnDestroy { @Input() ctx: WidgetContext; @@ -98,6 +90,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie connectorType = ConnectorType; + allowBasicConfig = new Set([ + ConnectorType.MQTT, + ConnectorType.OPCUA + ]); + + gatewayLogLevel = Object.values(GatewayLogLevel); + dataSource: MatTableDataSource; displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions']; @@ -137,7 +136,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie onDataUpdated: () => this.ctx.ngZone.run(() => { this.onDataUpdated(); }), - onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + onDataUpdateError: (_, e) => this.ctx.ngZone.run(() => { this.onDataUpdateError(e); }) } @@ -196,7 +195,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie defaultConfig[0].value); this.cd.detectChanges(); } - }) + }); } }); @@ -214,11 +213,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const basicConfig = this.connectorForm.get('basicConfig'); const type = this.connectorForm.get('type').value; const mode = this.connectorForm.get('mode').value; - if ( - !isEqual(config, basicConfig?.value) && - (type === ConnectorType.MQTT || type === ConnectorType.OPCUA) && - mode === ConnectorConfigurationModes.ADVANCED - ) { + if (!isEqual(config, basicConfig?.value) && this.allowBasicConfig.has(type) && mode === ConnectorConfigurationModes.ADVANCED) { this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false}); } }); @@ -350,7 +345,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie ...value.configurationJson.workers, } } - } + }; } private updateData(reload: boolean = false): void { @@ -607,11 +602,15 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const wasEnabled = this.activeConnectors.includes(key); if (wasEnabled) { const index = this.activeConnectors.indexOf(key); - if (index !== -1) this.activeConnectors.splice(index, 1); + if (index !== -1) { + this.activeConnectors.splice(index, 1); + } this.inactiveConnectors.push(key); } else { const index = this.inactiveConnectors.indexOf(key); - if (index !== -1) this.inactiveConnectors.splice(index, 1); + if (index !== -1) { + this.inactiveConnectors.splice(index, 1); + } this.activeConnectors.push(key); } } @@ -689,12 +688,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie const configJson = this.connectorForm.get('configurationJson'); const type = this.connectorForm.get('type').value; const mode = this.connectorForm.get('mode').value; - if ( - !isEqual(config, configJson?.value) && - (type === ConnectorType.MQTT || type === ConnectorType.OPCUA) && - mode === ConnectorConfigurationModes.BASIC - ) { - const newConfig = { ...configJson.value, ...config }; + if (!isEqual(config, configJson?.value) && this.allowBasicConfig.has(type) && mode === ConnectorConfigurationModes.BASIC) { + const newConfig = {...configJson.value, ...config}; this.connectorForm.get('configurationJson').patchValue(newConfig, {emitEvent: false}); } }); 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 64e18ab1fe..e734358908 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 @@ -18,7 +18,7 @@ import { ResourcesService } from '@core/services/resources.service'; import { Observable } from 'rxjs'; import { ValueTypeData } from '@shared/models/constants'; -export const noLeadTrailSpacesRegex: RegExp = /^(?! )[\S\s]*(?([ [6, 'gateway.rpc.write-single-holding-register'], [15, 'gateway.rpc.write-multiple-coils'], [16, 'gateway.rpc.write-multiple-holding-registers'] -]) +]); export enum BACnetRequestTypes { WriteProperty = 'writeProperty', @@ -330,8 +331,8 @@ export enum BACnetRequestTypes { export const BACnetRequestTypesTranslates = new Map([ [BACnetRequestTypes.WriteProperty, 'gateway.rpc.write-property'], - [BACnetRequestTypes.ReadProperty, "gateway.rpc.read-property"] -]) + [BACnetRequestTypes.ReadProperty, 'gateway.rpc.read-property'] +]); export enum BACnetObjectTypes { BinaryInput = 'binaryInput', @@ -349,7 +350,7 @@ export const BACnetObjectTypesTranslates = new Map([ [BACnetObjectTypes.BinaryInput, 'gateway.rpc.binary-input'], [BACnetObjectTypes.BinaryValue, 'gateway.rpc.binary-value'], [BACnetObjectTypes.AnalogValue, 'gateway.rpc.analog-value'] -]) +]); export enum BLEMethods { WRITE = 'write', @@ -361,7 +362,7 @@ export const BLEMethodsTranslates = new Map([ [BLEMethods.WRITE, 'gateway.rpc.write'], [BLEMethods.READ, 'gateway.rpc.read'], [BLEMethods.SCAN, 'gateway.rpc.scan'], -]) +]); export enum CANByteOrders { LITTLE = 'LITTLE', @@ -374,18 +375,18 @@ export enum SocketMethodProcessings { export const SocketMethodProcessingsTranslates = new Map([ [SocketMethodProcessings.WRITE, 'gateway.rpc.write'] -]) +]); export enum SNMPMethods { SET = 'set', - MULTISET = "multiset", - GET = "get", - BULKWALK = "bulkwalk", - TABLE = "table", - MULTIGET = "multiget", - GETNEXT = "getnext", - BULKGET = "bulkget", - WALKS = "walk" + MULTISET = 'multiset', + GET = 'get', + BULKWALK = 'bulkwalk', + TABLE = 'table', + MULTIGET = 'multiget', + GETNEXT = 'getnext', + BULKGET = 'bulkget', + WALKS = 'walk' } export const SNMPMethodsTranslations = new Map([ @@ -398,7 +399,7 @@ export const SNMPMethodsTranslations = new Map([ [SNMPMethods.GETNEXT, 'gateway.rpc.get-next'], [SNMPMethods.BULKGET, 'gateway.rpc.bulk-get'], [SNMPMethods.WALKS, 'gateway.rpc.walk'] -]) +]); export enum HTTPMethods { CONNECT = 'CONNECT', @@ -427,8 +428,8 @@ export interface RPCTemplateConfig { } export interface SaveRPCTemplateData { - config: RPCTemplateConfig, - templates: Array + config: RPCTemplateConfig; + templates: Array; } export interface LogLink { @@ -445,33 +446,33 @@ export interface GatewayLogData { } export interface AddConnectorConfigData { - dataSourceData: Array + dataSourceData: Array; } export interface CreatedConnectorConfigData { - type: ConnectorType, - name: string, - logLevel: GatewayLogLevel, - useDefaults: boolean, - sendDataOnlyOnChange: boolean, - configurationJson?: {[key: string]: any} + type: ConnectorType; + name: string; + logLevel: GatewayLogLevel; + useDefaults: boolean; + sendDataOnlyOnChange: boolean; + configurationJson?: {[key: string]: any}; } export interface MappingDataKey { - key: string, - value: any, - type: MappingValueType + key: string; + value: any; + type: MappingValueType; } export interface RpcMethodsMapping { - method: string, - arguments: Array + method: string; + arguments: Array; } export interface MappingInfo { - mappingType: MappingType, - value: {[key: string]: any}, - buttonTitle: string + mappingType: MappingType; + value: {[key: string]: any}; + buttonTitle: string; } export enum ConnectorConfigurationModes { @@ -727,14 +728,14 @@ export const DataConversionTranslationsMap = new Map( ] ); -export enum SecurityType { +export enum SecurityPolicy { BASIC128 = 'Basic128Rsa15', BASIC256 = 'Basic256', BASIC256SHA = 'Basic256Sha256' } -export const ServerSecurityTypes = [ - { value: 'Basic128Rsa15', name: 'Basic128RSA15' }, - { value: 'Basic256', name: 'Basic256' }, - { value: 'Basic256Sha256', name: 'Basic256SHA256' } +export const SecurityPolicyTypes = [ + { value: SecurityPolicy.BASIC128, name: 'Basic128RSA15' }, + { value: SecurityPolicy.BASIC256, name: 'Basic256' }, + { value: SecurityPolicy.BASIC256SHA, name: 'Basic256SHA256' } ]; 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 3123010b76..17578adc86 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 @@ -111,7 +111,6 @@ import { TypeValuePanelComponent, BrokerConfigControlComponent, WorkersConfigControlComponent, - GeneralConfigComponent, MqttBasicConfigComponent, OpcUaBasicConfigComponent, } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; @@ -194,7 +193,6 @@ import { BrokerConfigControlComponent, WorkersConfigControlComponent, ServerConfigComponent, - GeneralConfigComponent, MqttBasicConfigComponent, MappingTableComponent, OpcUaBasicConfigComponent, From bbb2b1683e3518d21db24599338946036cd9a8d5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 5 Jul 2024 15:19:23 +0300 Subject: [PATCH 10/16] UI: Refactoring gateway connectors configuration --- .../type-value-panel.component.html | 2 +- .../type-value-panel.component.ts | 16 ++---- .../dialog/mapping-dialog.component.ts | 50 +++++++++---------- .../lib/gateway/gateway-widget.models.ts | 8 +-- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html index 7ce7fb94f0..a31fe4ff94 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html @@ -32,7 +32,7 @@
gateway.type
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts index e163d463ed..32d0b2bddb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts @@ -14,12 +14,7 @@ /// limitations under the License. /// -import { - Component, - forwardRef, - OnDestroy, - OnInit, -} from '@angular/core'; +import { Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -31,7 +26,6 @@ import { Validator, Validators } from '@angular/forms'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { isDefinedAndNotNull } from '@core/utils'; import { MappingDataKey, @@ -60,12 +54,10 @@ import { Subject } from 'rxjs'; ] }) export class TypeValuePanelComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { - valueTypeKeys = Object.values(MappingValueType); - valueTypeEnum = MappingValueType; + + valueTypeKeys: MappingValueType[] = Object.values(MappingValueType); valueTypes = mappingValueTypesMap; - dataKeyType: DataKeyType; valueListFormArray: UntypedFormArray; - errorText = ''; private destroy$ = new Subject(); private propagateChange = (v: any) => {}; @@ -138,7 +130,7 @@ export class TypeValuePanelComponent implements ControlValueAccessor, Validator, }; } - updateView(value: any): void { + private updateView(value: any): void { this.propagateChange(value); } } 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 a2b6a67490..3812fa161e 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 @@ -22,6 +22,8 @@ import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { + Attribute, + AttributesUpdate, ConvertorType, ConvertorTypeTranslationsMap, DataConversionTranslationsMap, @@ -34,16 +36,19 @@ import { MappingKeysPanelTitleTranslationsMap, MappingKeysType, MappingType, - MappingTypeTranslationsMap, MappingValue, + MappingTypeTranslationsMap, + MappingValue, noLeadTrailSpacesRegex, OPCUaSourceTypes, QualityTypes, QualityTypeTranslationsMap, RequestType, RequestTypesTranslationsMap, + RpcMethod, ServerSideRPCType, SourceTypes, - SourceTypeTranslationsMap + SourceTypeTranslationsMap, + Timeseries } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { Subject } from 'rxjs'; import { startWith, takeUntil } from 'rxjs/operators'; @@ -66,17 +71,17 @@ export class MappingDialogComponent extends DialogComponent; OPCUaSourceTypesEnum = OPCUaSourceTypes; sourceTypesEnum = SourceTypes; SourceTypeTranslationsMap = SourceTypeTranslationsMap; - requestTypes = Object.values(RequestType); + requestTypes: RequestType[] = Object.values(RequestType); RequestTypeEnum = RequestType; RequestTypesTranslationsMap = RequestTypesTranslationsMap; @@ -92,12 +97,8 @@ export class MappingDialogComponent extends DialogComponent(); constructor(protected store: Store, @@ -116,30 +117,30 @@ export class MappingDialogComponent extends DialogComponent { if (this.converterType) { - return this.mappingForm.get('converter').get(this.converterType).value.attributes.map(value => value.key); + return this.mappingForm.get('converter').get(this.converterType).value.attributes.map((value: Attribute) => value.key); } } get converterTelemetry(): Array { if (this.converterType) { - return this.mappingForm.get('converter').get(this.converterType).value.timeseries.map(value => value.key); + return this.mappingForm.get('converter').get(this.converterType).value.timeseries.map((value: Timeseries) => value.key); } } get opcAttributes(): Array { - return this.mappingForm.get('attributes').value?.map(value => value.key) || []; + return this.mappingForm.get('attributes').value?.map((value: Attribute) => value.key) || []; } get opcTelemetry(): Array { - return this.mappingForm.get('timeseries').value?.map(value => value.key) || []; + return this.mappingForm.get('timeseries').value?.map((value: Timeseries) => value.key) || []; } get opcRpcMethods(): Array { - return this.mappingForm.get('rpc_methods').value?.map(value => value.method) || []; + return this.mappingForm.get('rpc_methods').value?.map((value: RpcMethod) => value.method) || []; } get opcAttributesUpdates(): Array { - return this.mappingForm.get('attributes_updates')?.value?.map(value => value.key) || []; + return this.mappingForm.get('attributes_updates')?.value?.map((value: AttributesUpdate) => value.key) || []; } get converterType(): ConvertorType { @@ -197,7 +198,6 @@ export class MappingDialogComponent extends DialogComponent Date: Fri, 5 Jul 2024 15:34:50 +0300 Subject: [PATCH 11/16] fix --- .../widget/lib/gateway/gateway-connectors.component.ts | 2 +- 1 file changed, 1 insertion(+), 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 e2c3aa8b65..14a8c15c00 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 @@ -552,7 +552,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie value.basicConfig = value.configurationJson; this.updateConnector(value); this.generate('basicConfig.broker.clientId'); - this.saveConnector(); + setTimeout(() => this.saveConnector()); } }); } From 6773eb0b8de8e702c572160c5eafdf54823f385d Mon Sep 17 00:00:00 2001 From: mpetrov Date: Fri, 5 Jul 2024 17:53:34 +0300 Subject: [PATCH 12/16] min width for server inputs fix --- .../server-config.component.html | 168 +++++++++--------- .../server-config.component.scss | 6 + 2 files changed, 91 insertions(+), 83 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html index fc103a4ad6..feff4d0a2d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html @@ -16,93 +16,95 @@ -->
-
-
gateway.server-url
-
- - - - warning - - +
+
+
gateway.server-url
+
+ + + + warning + + +
-
-
-
- gateway.timeout -
-
- - - - warning - - +
+
+ gateway.timeout +
+
+ + + + warning + + +
-
-
-
gateway.security
-
- - - {{ version.name }} - - +
+
gateway.security
+
+ + + {{ version.name }} + + +
-
-
-
- gateway.scan-period -
-
- - - - warning - - -
-
-
-
- gateway.sub-check-period +
+
+ gateway.scan-period +
+
+ + + + warning + + +
-
- - - - warning - - +
+
+ gateway.sub-check-period +
+
+ + + + warning + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss index 416f368279..815e92da90 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss @@ -17,4 +17,10 @@ width: 100%; height: 100%; display: block; + + .server-settings { + ::ng-deep .fixed-title-width { + min-width: 254px !important; + } + } } From 6c49cd0090256a2eb8da60292ff9008b25cd5c7c Mon Sep 17 00:00:00 2001 From: mpetrov Date: Fri, 5 Jul 2024 18:50:46 +0300 Subject: [PATCH 13/16] md files minor adjustments --- .../help/en_US/widget/lib/gateway/name-field-identifier_fn.md | 2 +- .../assets/help/en_US/widget/lib/gateway/name-field-path_fn.md | 2 +- .../help/en_US/widget/lib/gateway/profile-name-path_fn.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md index f3c5987582..0550afb4c1 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-identifier_fn.md @@ -62,6 +62,6 @@ byte string (`**b**`), and GUID (`**g**`). Below is an explanation of each ident _Expression:_ - Device ${ns=1;g=550e8400-e29b-41d4-a716-446655440000} + **`Device ${ns=1;g=550e8400-e29b-41d4-a716-446655440000}`** In this example, created device on platform will have “**Device TH-101**” name. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md index 731fbd66fe..80e3a65192 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/name-field-path_fn.md @@ -37,5 +37,5 @@ _Expression:_ `**Device ${Name}**` In this example, **the gateway will search for the child node "Name" in the device node (parent node) -"Root\\.Objects\\.TempSensor"** and the created device on the platform will be named "**Device TH-101**". +"Root\\.Objects\\.TempSensor"** and created device on the platform will be named "**Device TH-101**". diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md index fb8c35dee6..78fa2a028e 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/profile-name-path_fn.md @@ -35,4 +35,4 @@ _Expression:_ **`Device ${Type}`** In this example, **the gateway will search for the child node "Name" in the device node (parent node) -"Root\\.Objects\\.TempSensor"** and the created device on the platform will have "thermostat" profile name. +"Root\\.Objects\\.TempSensor"** and created device on the platform will have "thermostat" profile name. From 50c93521b401cd0033e91a5de0d7e236508d4cdf Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 8 Jul 2024 14:34:17 +0300 Subject: [PATCH 14/16] ui fixes --- .../mqtt-basic-config.component.html | 17 +++++++++++++++++ .../opc-ua-basic-config.component.html | 17 +++++++++++++++++ .../server-config/server-config.component.html | 10 +++++----- .../server-config/server-config.component.scss | 11 ++++++----- .../dialog/mapping-dialog.component.html | 2 +- .../dialog/mapping-dialog.component.scss | 6 ++++++ ui-ngx/src/form.scss | 3 +++ 7 files changed, 55 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html index 8fecdd8618..9849923218 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html @@ -1,3 +1,20 @@ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html index 75c2b76f69..1089fd114c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html @@ -1,3 +1,20 @@ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html index feff4d0a2d..09ef9aa1ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html @@ -18,7 +18,7 @@
-
gateway.server-url
+
gateway.server-url
@@ -35,7 +35,7 @@
-
+
gateway.timeout
@@ -55,7 +55,7 @@
-
gateway.security
+
gateway.security
@@ -65,7 +65,7 @@
-
+
gateway.scan-period
@@ -86,7 +86,7 @@
-
+
gateway.sub-check-period
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss index 815e92da90..17ffb27bd8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss @@ -17,10 +17,11 @@ width: 100%; height: 100%; display: block; +} - .server-settings { - ::ng-deep .fixed-title-width { - min-width: 254px !important; - } - } +.server-conf-field-title { + width: 30%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } 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 a202c85fcb..35ac90f671 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 @@ -630,7 +630,7 @@ - + Date: Mon, 8 Jul 2024 14:36:55 +0300 Subject: [PATCH 15/16] Update form.scss --- ui-ngx/src/form.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 57e0b96fe5..c9580bcf74 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -238,9 +238,6 @@ &-230 { min-width: 230px; } - &-250 { - min-width: 250px; - } } [class^="fixed-title-width"] { @media #{$mat-xs} { From 0fefeb655f07397f13fb6034c8aa0e1e0dd88a7e Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 8 Jul 2024 14:42:18 +0300 Subject: [PATCH 16/16] ui fixes --- .../server-config/server-config.component.html | 10 +++++----- .../server-config/server-config.component.scss | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html index 09ef9aa1ff..43b15de06c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html @@ -18,7 +18,7 @@
-
gateway.server-url
+
gateway.server-url
@@ -35,7 +35,7 @@
-
+
gateway.timeout
@@ -55,7 +55,7 @@
-
gateway.security
+
gateway.security
@@ -65,7 +65,7 @@
-
+
gateway.scan-period
@@ -86,7 +86,7 @@
-
+
gateway.sub-check-period
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss index 17ffb27bd8..64e886ffef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss @@ -17,11 +17,12 @@ width: 100%; height: 100%; display: block; -} -.server-conf-field-title { - width: 30%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + .server-conf-field-title { + min-width: 250px; + width: 30%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } }