From 6ee6ccaa368ad27ddc1ac216ffed80c1a01f00f5 Mon Sep 17 00:00:00 2001 From: Maksym Dudnik Date: Thu, 14 Dec 2023 13:21:57 +0200 Subject: [PATCH] connectors templates --- ...service-rpc-connector-template-dialog.html | 54 ++++++ ...y-service-rpc-connector-template-dialog.ts | 62 ++++++ ...ice-rpc-connector-templates.component.html | 19 +- ...ice-rpc-connector-templates.component.scss | 5 + ...rvice-rpc-connector-templates.component.ts | 69 ++++--- ...teway-service-rpc-connector.component.html | 136 ++++++------- ...gateway-service-rpc-connector.component.ts | 181 ++++++++++++++++-- .../gateway-service-rpc.component.html | 7 +- .../gateway/gateway-service-rpc.component.ts | 141 ++++++++++++-- .../lib/gateway/gateway-widget.models.ts | 15 +- .../widget/widget-components.module.ts | 5 + .../assets/locale/locale.constant-en_US.json | 69 +++---- 12 files changed, 584 insertions(+), 179 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html new file mode 100644 index 0000000000..6227ee1049 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html @@ -0,0 +1,54 @@ + + +

gateway.rpc.save-template

+ + +
+
+ + gateway.rpc.template-name + + + {{ 'gateway.rpc.template-name-required' | translate }} + + +
+ {{ 'gateway.rpc.template-name-duplicate' | translate }} +
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts new file mode 100644 index 0000000000..63f492519c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts @@ -0,0 +1,62 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormControl, UntypedFormControl, Validators } from '@angular/forms'; +import { RPCTemplate, SaveRPCTemplateData } from '@home/components/widget/lib/gateway/gateway-widget.models'; + +@Component({ + selector: 'gateway-service-rpc-connector-template-dialog', + templateUrl: './gateway-service-rpc-connector-template-dialog.html' +}) + +export class GatewayServiceRPCConnectorTemplateDialogComponent extends DialogComponent { + + config: { + [key: string]: any; + }; + templates: Array; + templateNameCtrl: FormControl; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: SaveRPCTemplateData, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + this.config = this.data.config; + this.templates = this.data.templates; + this.templateNameCtrl = this.fb.control('', [Validators.required]) + } + + validateDuplicateName(c: UntypedFormControl) { + const name = c.value.trim(); + return !!this.templates.find((template) => template.name === name); + }; + + close(): void { + this.dialogRef.close(); + } + + save(): void { + this.dialogRef.close(this.templateNameCtrl.value); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html index 3c6b410ac2..0554ca0b57 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -22,13 +22,10 @@ {{template.name}} - - - @@ -37,13 +34,15 @@ + [ngTemplateOutletContext]="{ $implicit: config, innerValue: false }"> - +
-
{{config.key}}
+ [ngStyle]="{'padding-left': innerValue ? '16px': '0'}"> +
+ {{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}} +
@@ -52,7 +51,7 @@ + [ngTemplateOutletContext]="{ $implicit: subConfig, innerValue: true }">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss index 33ce5fa6d8..4c28959be2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss @@ -50,6 +50,11 @@ background-color: rgba(25, 128, 56, 0.08); } + mat-expansion-panel { + margin-top: 10px; + overflow: visible; + } + .mat-expansion-panel-header-description { flex-direction: row-reverse; align-items: center; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts index 26cdf24dd1..357dd0504b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -15,9 +15,16 @@ /// import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { ConnectorType, RPCTemplate } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { TranslateService } from '@ngx-translate/core'; +import { + ConnectorType, + RPCTemplate +} from '@home/components/widget/lib/gateway/gateway-widget.models'; import { KeyValue } from '@angular/common'; +import { EntityType } from '@shared/models/entity-type.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeService } from '@core/http/attribute.service'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'tb-gateway-service-rpc-connector-templates', @@ -29,52 +36,52 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { @Input() connectorType: ConnectorType; + @Input() + ctx: WidgetContext; + @Output() saveTemplate: EventEmitter = new EventEmitter(); - rpcTemplates: Array = []; + @Output() + useTemplate: EventEmitter = new EventEmitter(); - constructor(private translate: TranslateService) { - this.rpcTemplates.push( - { - name: 'Test Template', - config: { - fieldString: 'string', - fieldNumber: 666, - fieldBool: true, - fieldArray: [111, 222, 333, "String", "444"], - fieldObj: { - subKey1: 'dasd', - subKey2: 666, - } - } - } - ) + @Input() + rpcTemplates: Array; + + constructor(private attributeService: AttributeService) { } - ngOnInit() { - } - public useTemplate($event: Event): void { + public applyTemplate($event: Event, template: RPCTemplate): void { $event.stopPropagation(); - console.log("useTemplate") + this.useTemplate.emit(template); } - public copyTemplate($event: Event): void { + public deleteTemplate($event: Event, template: RPCTemplate): void { $event.stopPropagation(); - console.log("copyTemplate") + const index = this.rpcTemplates.findIndex(data => { + return data.name == template.name; + }) + this.rpcTemplates.splice(index, 1); + const key = `${this.connectorType}_template`; + this.attributeService.saveEntityAttributes( + { + id: this.ctx.defaultSubscription.targetDeviceId, + entityType: EntityType.DEVICE + } + , AttributeScope.SERVER_SCOPE, [{ + key, + value: this.rpcTemplates + }]).subscribe(() => { + }) } - public deleteTemplate($event: Event): void { - $event.stopPropagation(); - console.log("deleteTemplate") - } - - public originalOrder = (a: KeyValue, b: KeyValue): number => { + public originalOrder = (a, b): number => { return 0; } + public isObject(value: any) { return value !== null && typeof value === 'object' && !Array.isArray(value); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html index 9bab9508a5..c706244c31 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -16,7 +16,8 @@ -->
-
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
+
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
@@ -26,27 +27,26 @@ placeholder="echo"/> - {{ 'gateway.rpc.request-topic-expression' | translate }} + {{ 'gateway.rpc.requestTopicExpression' | translate }} - {{ 'gateway.rpc.response-topic-expression' | translate }} + {{ 'gateway.rpc.responseTopicExpression' | translate }} - {{ 'gateway.rpc.response-timeout' | translate }} + {{ 'gateway.rpc.responseTimeout' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - {{ 'gateway.rpc.tag' | translate }} @@ -62,7 +62,7 @@ - {{ 'gateway.rpc.function-code' | translate }} + {{ 'gateway.rpc.functionCode' | translate }} {{ code }} @@ -77,20 +77,19 @@ placeholder="1" step="1"/> - {{ 'gateway.rpc.objects-count' | translate }} + {{ 'gateway.rpc.objectsCount' | translate }}
- - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.request-type' | translate }} + {{ 'gateway.rpc.requestType' | translate }} {{bACnetRequestTypesTranslates.get(type) | translate}} @@ -98,13 +97,13 @@ - {{ 'gateway.rpc.request-timeout' | translate }} + {{ 'gateway.rpc.requestTimeout' | translate }}
- {{ 'gateway.rpc.object-type' | translate }} + {{ 'gateway.rpc.objectType' | translate }} {{bACnetObjectTypesTranslates.get(type) | translate}} @@ -118,22 +117,21 @@
- {{ 'gateway.rpc.property-id' | translate }} + {{ 'gateway.rpc.propertyId' | translate }}
- - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.characteristic-uuid' | translate }} + {{ 'gateway.rpc.characteristicUUID' | translate }} - {{ 'gateway.rpc.method-processing' | translate }} + {{ 'gateway.rpc.methodProcessing' | translate }} {{bLEMethodsTranslates.get(type) | translate}} @@ -141,35 +139,34 @@ - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.node-id' | translate }} + {{ 'gateway.rpc.nodeID' | translate }} - {{ 'gateway.rpc.is-extended-id' | translate }} + {{ 'gateway.rpc.isExtendedID' | translate }} - {{ 'gateway.rpc.is-fd' | translate }} + {{ 'gateway.rpc.isFD' | translate }} - {{ 'gateway.rpc.bitrate-switch' | translate }} + {{ 'gateway.rpc.bitrateSwitch' | translate }}
- {{ 'gateway.rpc.data-length' | translate }} + {{ 'gateway.rpc.dataLength' | translate }} - {{ 'gateway.rpc.data-byte-order' | translate }} + {{ 'gateway.rpc.dataByteorder' | translate }} {{ order | translate }} @@ -179,59 +176,55 @@
- {{ 'gateway.rpc.data-before' | translate }} + {{ 'gateway.rpc.dataBefore' | translate }} - {{ 'gateway.rpc.data-after' | translate }} + {{ 'gateway.rpc.dataAfter' | translate }}
- {{ 'gateway.rpc.data-in-hex' | translate }} + {{ 'gateway.rpc.dataInHEX' | translate }} - {{ 'gateway.rpc.data-expression' | translate }} + {{ 'gateway.rpc.dataExpression' | translate }}
- - - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.method-processing' | translate }} + {{ 'gateway.rpc.methodProcessing' | translate }} {{ SocketMethodProcessingsTranslates.get(method) | translate }} @@ -247,27 +240,25 @@ - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.request-filter' | translate }} + {{ 'gateway.rpc.requestFilter' | translate }} @@ -298,15 +289,14 @@ - - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }}
- {{ 'gateway.rpc.http-method' | translate }} + {{ 'gateway.rpc.HTTPMethod' | translate }} {{ method }} @@ -314,14 +304,14 @@ - {{ 'gateway.rpc.request-url' | translate }} + {{ 'gateway.rpc.requestUrlExpression' | translate }}
- {{ 'gateway.rpc.response-timeout' | translate }} + {{ 'gateway.rpc.responseTimeout' | translate }} @@ -337,11 +327,11 @@
- {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }}
- {{ 'gateway.rpc.http-headers' | translate }} + {{ 'gateway.rpc.httpHeaders' | translate }}
{{ 'gateway.rpc.header-name' | translate }} @@ -405,15 +395,14 @@
- - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }}
- {{ 'gateway.rpc.method' | translate }} + {{ 'gateway.rpc.httpMethod' | translate }} {{ method }} @@ -421,13 +410,13 @@ - {{ 'gateway.rpc.request-url' | translate }} + {{ 'gateway.rpc.requestUrlExpression' | translate }}
- {{ 'gateway.rpc.response-timeout' | translate }} + {{ 'gateway.rpc.responseTimeout' | translate }} @@ -443,15 +432,15 @@
- {{ 'gateway.rpc.request-value-expression' | translate }} + {{ 'gateway.rpc.requestValueExpression' | translate }} - {{ 'gateway.rpc.response-value-expression' | translate }} + {{ 'gateway.rpc.responseValueExpression' | translate }}
- {{ 'gateway.rpc.http-headers' | translate }} + {{ 'gateway.rpc.httpHeaders' | translate }}
{{ 'gateway.rpc.header-name' | translate }} @@ -483,7 +472,6 @@
- @@ -513,11 +501,27 @@ + + {{'dsadas'}} + + {{ 'gateway.statistics.command' | translate }} + + + + {{ 'widget-config.datasource-parameters' | translate }} + + edit + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index ce2c850741..7a2f91907e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -14,27 +14,57 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { - BACnetObjectTypes, BACnetObjectTypesTranslates, - BACnetRequestTypes, BACnetRequestTypesTranslates, BLEMethods, BLEMethodsTranslates, + ControlValueAccessor, + FormArray, + FormBuilder, + FormControl, + FormGroup, + NG_VALUE_ACCESSOR, + Validators +} from '@angular/forms'; +import { + BACnetObjectTypes, + BACnetObjectTypesTranslates, + BACnetRequestTypes, + BACnetRequestTypesTranslates, + BLEMethods, + BLEMethodsTranslates, CANByteOrders, - ConnectorType, GatewayConnectorDefaultTypesTranslates, HTTPMethods, + ConnectorType, + GatewayConnectorDefaultTypesTranslates, + HTTPMethods, ModbusCommandTypes, RPCCommand, - SNMPMethods, SNMPMethodsTranslations, + RPCTemplateConfig, + SNMPMethods, + SNMPMethodsTranslations, SocketEncodings, - SocketMethodProcessings, SocketMethodProcessingsTranslates + SocketMethodProcessings, + SocketMethodProcessingsTranslates } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { TranslateService } from "@ngx-translate/core"; +import { MatDialog } from '@angular/material/dialog'; +import { + JsonObjectEditDialogComponent, + JsonObjectEditDialogData +} from '@shared/components/dialog/json-object-edit-dialog.component'; +import { jsonRequired } from '@shared/components/json-object-edit.component'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-gateway-service-rpc-connector', templateUrl: './gateway-service-rpc-connector.component.html', - styleUrls: ['./gateway-service-rpc-connector.component.scss'] + styleUrls: ['./gateway-service-rpc-connector.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => GatewayServiceRPCConnectorComponent), + multi: true + } + ] }) -export class GatewayServiceRPCConnectorComponent implements OnInit { +export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor { @Input() connectorType: ConnectorType; @@ -42,6 +72,9 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { @Output() sendCommand: EventEmitter = new EventEmitter(); + @Output() + saveTemplate: EventEmitter = new EventEmitter(); + commandForm: FormGroup; codesArray: Array = [1, 2, 3, 4, 5, 6, 15, 16]; @@ -74,14 +107,41 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { '(\\#[-a-z\\d_]*)?$', // fragment locator 'i' ); + private propagateChange = (v: any) => { + } constructor(private fb: FormBuilder, - private translate: TranslateService) { + private dialog: MatDialog,) { } ngOnInit() { - this.commandForm = this.connectorParamsFormGroupByType(this.connectorType) + this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); + this.commandForm.valueChanges.subscribe(value => { + const httpHeaders = {}; + const security = {}; + switch (this.connectorType) { + case ConnectorType.REST: + value.httpHeaders.forEach(data => { + httpHeaders[data.headerName] = data.value; + }) + value.httpHeaders = httpHeaders; + value.security.forEach(data => { + security[data.securityName] = data.value; + }) + value.security = security; + break; + case ConnectorType.REQUEST: + value.httpHeaders.forEach(data => { + httpHeaders[data.headerName] = data.value; + }) + value.httpHeaders = httpHeaders; + break; + } + if (this.commandForm.valid) { + this.propagateChange({...this.commandForm.value,...value}); + } + }) } connectorParamsFormGroupByType(type: ConnectorType): FormGroup { @@ -206,6 +266,13 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { method: [null, [Validators.required]], arguments: this.fb.array([]), }) + break; + default: + formGroup = this.fb.group({ + command: [null, [Validators.required]], + params: ['{}', [jsonRequired]], + }) + } return formGroup; } @@ -213,7 +280,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { addSNMPoid(value: string = null) { const oidsFA = this.commandForm.get('oid') as FormArray; if (oidsFA) { - oidsFA.push(this.fb.control(value, [Validators.required])); + oidsFA.push(this.fb.control(value, [Validators.required]), {emitEvent: false}); } } @@ -229,7 +296,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { value: [value.value, [Validators.required]] }) if (headerFA) { - headerFA.push(formGroup); + headerFA.push(formGroup, {emitEvent: false}); } } @@ -245,7 +312,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { value: [value.value, [Validators.required]] }) if (securityFA) { - securityFA.push(formGroup); + securityFA.push(formGroup, {emitEvent: false}); } } @@ -261,7 +328,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { addOCPUAArguments(value: string = null) { const oidsFA = this.commandForm.get('arguments') as FormArray; if (oidsFA) { - oidsFA.push(this.fb.control(value)); + oidsFA.push(this.fb.control(value), {emitEvent: false}); } } @@ -269,4 +336,86 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { const oidsFA = this.commandForm.get('arguments') as FormArray; oidsFA.removeAt(index); } + + openEditJSONDialog($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(JsonObjectEditDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + jsonValue: JSON.parse(this.commandForm.get('params').value), + required: true + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.commandForm.get('params').setValue(JSON.stringify(res)); + } + } + ); + } + + save() { + this.saveTemplate.emit(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + clearFromArrayByName(name: string) { + const formArray = this.commandForm.get(name) as FormArray; + while (formArray.length !== 0) { + formArray.removeAt(0) + } + } + + writeValue(value: RPCTemplateConfig): void { + if (typeof value == "object") { + value = deepClone(value); + switch (this.connectorType) { + case ConnectorType.SNMP: + this.clearFromArrayByName("oids"); + value.oids.forEach(value => { + this.addSNMPoid(value) + }) + delete value.oids; + break; + case ConnectorType.REQUEST: + this.clearFromArrayByName("httpHeaders"); + value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { + this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) + }) + delete value.httpHeaders; + break; + case ConnectorType.REST: + this.clearFromArrayByName("httpHeaders"); + this.clearFromArrayByName("security"); + value.security && Object.entries(value.security).forEach(securityHeader => { + this.addHTTPSecurity({securityName: securityHeader[0], value: securityHeader[1] as string}) + }) + delete value.security; + value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { + this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) + }) + delete value.httpHeaders; + break; + case ConnectorType.OPCUA: + case ConnectorType.OPCUA_ASYNCIO: + this.clearFromArrayByName("arguments"); + value.arguments.forEach(value => { + this.addOCPUAArguments(value) + }) + delete value.arguments; + break; + } + this.commandForm.patchValue(value, {onlySelf: false}); + } + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html index 2397d1bcdd..da9ea07ebd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html @@ -41,7 +41,8 @@ - +
@@ -52,4 +53,6 @@
- + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index a3c96f7d49..484f8b16b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -14,17 +14,28 @@ /// limitations under the License. /// -import { OnInit, Component, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { WidgetContext } from '@home/models/widget-component.models'; import { ContentType } from '@shared/models/constants'; -import { - JsonObjectEditDialogComponent, - JsonObjectEditDialogData -} from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; -import { ConnectorType, RPCCommand } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { + ConnectorType, + RPCCommand, + RPCTemplate, + RPCTemplateConfig, + SaveRPCTemplateData +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { + GatewayServiceRPCConnectorTemplateDialogComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog'; +import { AttributeService } from '@core/http/attribute.service'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DatasourceType, widgetType } from '@shared/models/widget.models'; +import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { UtilsService } from '@core/services/utils.service'; @Component({ selector: 'tb-gateway-service-rpc', @@ -58,9 +69,27 @@ export class GatewayServiceRPCComponent implements OnInit { ]; public connectorType: ConnectorType; + public templates: Array = []; + + private subscription: IWidgetSubscription; + private subscriptionOptions: WidgetSubscriptionOptions = { + callbacks: { + onDataUpdated: () => this.ctx.ngZone.run(() => { + this.updateTemplates() + }), + onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + this.onDataUpdateError(e); + }), + dataLoading: () => { + } + } + }; constructor(private fb: FormBuilder, - private dialog: MatDialog) { + private dialog: MatDialog, + private utils: UtilsService, + private cd: ChangeDetectorRef, + private attributeService: AttributeService) { this.commandForm = this.fb.group({ command: [null, [Validators.required]], time: [60, [Validators.required, Validators.min(1)]], @@ -75,6 +104,17 @@ export class GatewayServiceRPCComponent implements OnInit { this.commandForm.get('command').setValue(this.RPCCommands[0]); } else { this.connectorType = this.ctx.stateController.getStateParams().connector_rpc.value.type; + const subscriptionInfo = [{ + type: DatasourceType.entity, + entityType: EntityType.DEVICE, + entityId: this.ctx.defaultSubscription.targetDeviceId, + entityName: 'Connector', + attributes: [{name: `${this.connectorType}_template`}] + }]; + this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.latest, subscriptionInfo, + this.subscriptionOptions, false, true).subscribe(subscription => { + this.subscription = subscription; + }) } } @@ -82,7 +122,9 @@ export class GatewayServiceRPCComponent implements OnInit { this.resultTime = null; const formValues = value || this.commandForm.value; const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_'; - this.ctx.controlApi.sendTwoWayCommand(commandPrefix + formValues.command.toLowerCase(), formValues.params, formValues.time).subscribe({ + const command = !this.isConnector ? formValues.command.toLowerCase() : this.getCommandFromParamsByType(formValues.params); + const params = formValues.params; + this.ctx.controlApi.sendTwoWayCommand(commandPrefix + command, params, formValues.time).subscribe({ next: resp => { this.resultTime = new Date().getTime(); this.commandForm.get('result').setValue(JSON.stringify(resp)) @@ -95,23 +137,86 @@ export class GatewayServiceRPCComponent implements OnInit { }); } - openEditJSONDialog($event: Event) { - if ($event) { - $event.stopPropagation(); + getCommandFromParamsByType(params: RPCTemplateConfig) { + switch (this.connectorType) { + case ConnectorType.MQTT: + case ConnectorType.FTP: + case ConnectorType.SNMP: + case ConnectorType.REST: + case ConnectorType.REQUEST: + return params.methodFilter; + case ConnectorType.MODBUS: + return params.tag; + case ConnectorType.BACNET: + case ConnectorType.CAN: + case ConnectorType.OPCUA: + case ConnectorType.OPCUA_ASYNCIO: + return params.method; + case ConnectorType.BLE: + case ConnectorType.OCPP: + case ConnectorType.SOCKET: + case ConnectorType.XMPP: + return params.methodRPC; + default: + return params.command; } - this.dialog.open(JsonObjectEditDialogComponent, { + } + + saveTemplate() { + this.dialog.open + (GatewayServiceRPCConnectorTemplateDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - jsonValue: JSON.parse(this.commandForm.get('params').value), - required: true - } + data: {config: this.commandForm.value.params, templates: this.templates} }).afterClosed().subscribe( (res) => { - if (res) { - this.commandForm.get('params').setValue(JSON.stringify(res)); + const templateAttribute: RPCTemplate = { + name: res, + config: this.commandForm.value.params } + const templatesArray = this.templates; + const existingIndex = templatesArray.findIndex(template=>{ + return template.name == templateAttribute.name; + }) + if (existingIndex > -1 ){ + templatesArray.splice(existingIndex, 1) + } + templatesArray.push(templateAttribute) + const key = `${this.connectorType}_template`; + this.attributeService.saveEntityAttributes( + { + id: this.ctx.defaultSubscription.targetDeviceId, + entityType: EntityType.DEVICE + } + , AttributeScope.SERVER_SCOPE, [{ + key, + value: templatesArray + }]).subscribe(() => { + }) } ); } + + useTemplate($event) { + this.commandForm.get('params').patchValue($event.config); + } + + private updateTemplates() { + this.templates = this.subscription.data[0].data[0][1].length ? + JSON.parse(this.subscription.data[0].data[0][1]) : []; + if (this.templates.length && this.commandForm.get('params').value == "{}") { + this.commandForm.get('params').patchValue(this.templates[0].config); + } + this.cd.detectChanges(); + } + + private onDataUpdateError(e: any) { + const exceptionData = this.utils.parseException(e); + let errorText = exceptionData.name; + if (exceptionData.message) { + errorText += ': ' + exceptionData.message; + } + console.error(errorText); + } + } 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 4b09667a6b..b8f8c996b3 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 @@ -261,10 +261,17 @@ export enum SocketEncodings { } export interface RPCTemplate { - name: string; - config: { - [key: string]: any; - }; + name?: string; + config: RPCTemplateConfig; +} + +export interface RPCTemplateConfig { + [key: string]: any; +} + +export interface SaveRPCTemplateData { + config: RPCTemplateConfig, + templates: Array } export interface LogLink { 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 b4b688848e..d4828a0190 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 @@ -74,6 +74,9 @@ import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component'; import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component'; import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/doughnut-widget.component'; +import { + GatewayServiceRPCConnectorTemplateDialogComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog'; @NgModule({ declarations: @@ -107,6 +110,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough DeviceGatewayCommandComponent, GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, + GatewayServiceRPCConnectorTemplateDialogComponent, ValueCardWidgetComponent, AggregatedValueCardWidgetComponent, CountWidgetComponent, @@ -154,6 +158,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough DeviceGatewayCommandComponent, GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, + GatewayServiceRPCConnectorTemplateDialogComponent, ValueCardWidgetComponent, AggregatedValueCardWidgetComponent, CountWidgetComponent, 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 294173e231..da5fbf824a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2776,54 +2776,54 @@ "rpc": { "title": "{{type}} Connector RPC parameters", "templates-title": "Connector RPC Templates", - "method-filter": "Method filter", - "request-topic-expression": "Request topic expression", - "response-topic-expression": "Response topic expression", - "response-timeout": "Response Time", - "value-expression": "Value Expression", + "methodFilter": "Method filter", + "requestTopicExpression": "Request topic expression", + "responseTopicExpression": "Response topic expression", + "responseTimeout": "Response Time", + "valueExpression": "Value Expression", "tag": "Tag", "type": "Type", - "function-code": "Function Code", - "objects-count": "Objects Count", + "functionCode": "Function Code", + "objectsCount": "Objects Count", "address": "Address", "method": "Method", - "request-type": "Request Type", - "request-timeout": "Request Timeout", - "object-type": "Object type", + "requestType": "Request Type", + "requestTimeout": "Request Timeout", + "objectType": "Object type", "identifier": "Identifier", - "property-id": "Property ID", - "method-rpc": "Method RPC name", - "with-response": "With Response", - "characteristic-uuid": "Characteristic UUID", - "method-processing": "Method Processing", - "node-id": "Node ID", - "is-extended-id": "Is Extended ID", - "is-fd": "Is FD", - "bitrate-switch": "Bitrate Switch", - "data-in-hex": "Data In HEX", - "data-length": "Data Length", - "data-byte-order": "Data Byte Order", - "data-before": "Data Before", - "data-after": "Data After", - "data-expression": "Data Expression", + "propertyId": "Property ID", + "methodRPC": "Method RPC name", + "withResponse": "With Response", + "characteristicUUID": "Characteristic UUID", + "methodProcessing": "Method Processing", + "nodeID": "Node ID", + "isExtendedID": "Is Extended ID", + "isFD": "Is FD", + "bitrateSwitch": "Bitrate Switch", + "dataInHEX": "Data In HEX", + "dataLength": "Data Length", + "dataByteorder": "Data Byte Order", + "dataBefore": "Data Before", + "dataAfter": "Data After", + "dataExpression": "Data Expression", "encoding": "Encoding", "oid": "OID", "add-oid": "Add OID", "add-header": "Add header", "add-security": "Add security", "remove": "Remove", - "request-filter": "Request Filter", - "request-url": "Request URL Expression", - "http-method": "HTTP Method", + "requestFilter": "Request Filter", + "requestUrlExpression": "Request URL Expression", + "HTTPMethod": "HTTP Method", "timeout": "Timeout", "tries": "Tries", - "http-headers": "HTTP Headers", + "httpHeaders": "HTTP Headers", "header-name": "Header name", "security-name": "Security name", "value": "Value", "security": "Security", - "response-value-expression": "Response Value Expression", - "request-value-expression": "Request Value Expression", + "responseValueExpression": "Response Value Expression", + "requestValueExpression": "Request Value Expression", "arguments": "Arguments", "add-argument": "Add argument", "write-property": "Write property", @@ -2843,7 +2843,12 @@ "multi-get": "Multiget", "get-next": "Get next", "bulk-get": "Bulk get", - "walk": "Walk" + "walk": "Walk", + "save-template": "Save template", + "template-name": "Template name", + "template-name-required": "Template name is required.", + "template-name-duplicate": "The name is already used." + }, "other": "Other",