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 9fa1f7b5f2..18b2c2d862 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 @@ -40,6 +40,7 @@ import { DatasourceType, widgetType } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { EntityType } from '@shared/models/entity-type.models'; import { + ConnectorType, GatewayConnector, GatewayConnectorDefaultTypesTranslates, GatewayLogLevel @@ -198,10 +199,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie saveConnector(): void { const value = this.connectorForm.value; value.configuration = camelCase(value.name) + '.json'; - if (value.type !== 'grpc') { + if (value.type !== ConnectorType.GRPC) { delete value.key; } - if (value.type !== 'custom') { + if (value.type !== ConnectorType.CUSTOM) { delete value.class; } value.ts = new Date().getTime(); @@ -309,7 +310,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private clearOutConnectorForm(): void { this.connectorForm.setValue({ name: '', - type: 'mqtt', + type: ConnectorType.MQTT, logLevel: GatewayLogLevel.INFO, key: 'auto', class: '', 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 new file mode 100644 index 0000000000..3c6b410ac2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -0,0 +1,60 @@ + +
{{ 'gateway.rpc.templates-title' | translate }}
+ + + + {{template.name}} + + + + + + + + + + + +
+
{{config.key}}
+
+ {{config.value}}
+ + + + +
+
+
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 new file mode 100644 index 0000000000..33ce5fa6d8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss @@ -0,0 +1,79 @@ +/** + * 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. + */ +:host { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + padding: 0; + + .title { + font-weight: 500; + } + + .template-key { + color: rgba(0, 0, 0, 0.38); + height: 32px; + line-height: 32px; + } + + .boolean-true, .boolean-false { + border-radius: 3px; + height: 32px; + line-height: 32px; + padding: 0 12px; + width: fit-content; + font-size: 14px; + text-transform: capitalize; + } + + .boolean-false { + color: #d12730; + background-color: rgba(209, 39, 48, 0.08); + } + + .boolean-true { + color: #198038; + background-color: rgba(25, 128, 56, 0.08); + } + + .mat-expansion-panel-header-description { + flex-direction: row-reverse; + align-items: center; + margin-right: 0; + + & > mat-icon { + margin-left: 15px; + color: rgba(0, 0, 0, 0.38); + } + } + + .mat-expansion-panel-header { + padding: 0 0 0 12px; + + &.mat-expansion-panel-header.mat-expanded { + height: 48px; + } + + .mat-content.mat-content-hide-toggle { + margin-right: 0; + } + } + + +} + + 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 new file mode 100644 index 0000000000..26cdf24dd1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -0,0 +1,81 @@ +/// +/// 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, 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 { KeyValue } from '@angular/common'; + +@Component({ + selector: 'tb-gateway-service-rpc-connector-templates', + templateUrl: './gateway-service-rpc-connector-templates.component.html', + styleUrls: ['./gateway-service-rpc-connector-templates.component.scss'] +}) +export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { + + @Input() + connectorType: ConnectorType; + + @Output() + saveTemplate: EventEmitter = new EventEmitter(); + + rpcTemplates: Array = []; + + 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, + } + } + } + ) + } + + + ngOnInit() { + + } + + public useTemplate($event: Event): void { + $event.stopPropagation(); + console.log("useTemplate") + } + + public copyTemplate($event: Event): void { + $event.stopPropagation(); + console.log("copyTemplate") + } + + public deleteTemplate($event: Event): void { + $event.stopPropagation(); + console.log("deleteTemplate") + } + + public originalOrder = (a: KeyValue, b: KeyValue): 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 new file mode 100644 index 0000000000..9bab9508a5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -0,0 +1,531 @@ + +
+
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
+ + + + + {{ 'gateway.rpc.method-filter' | translate }} + + + + {{ 'gateway.rpc.request-topic-expression' | translate }} + + + + {{ 'gateway.rpc.response-topic-expression' | translate }} + + + + {{ 'gateway.rpc.response-timeout' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + + + + {{ 'gateway.rpc.tag' | translate }} + + +
+ + {{ 'gateway.rpc.type' | translate }} + + + {{ type }} + + + + + {{ 'gateway.rpc.function-code' | translate }} + + + {{ code }} + + + +
+
+ + {{ 'gateway.rpc.address' | translate }} + + + + {{ 'gateway.rpc.objects-count' | translate }} + + +
+
+ + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.request-type' | translate }} + + + {{bACnetRequestTypesTranslates.get(type) | translate}} + + + + + {{ 'gateway.rpc.request-timeout' | translate }} + + +
+ + {{ 'gateway.rpc.object-type' | translate }} + + + {{bACnetObjectTypesTranslates.get(type) | translate}} + + + + + {{ 'gateway.rpc.identifier' | translate }} + + +
+ + {{ 'gateway.rpc.property-id' | translate }} + + +
+ + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.characteristic-uuid' | translate }} + + + + {{ 'gateway.rpc.method-processing' | translate }} + + + {{bLEMethodsTranslates.get(type) | translate}} + + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.node-id' | translate }} + + + + {{ 'gateway.rpc.is-extended-id' | translate }} + + + {{ 'gateway.rpc.is-fd' | translate }} + + + {{ 'gateway.rpc.bitrate-switch' | translate }} + +
+ + {{ 'gateway.rpc.data-length' | translate }} + + + + {{ 'gateway.rpc.data-byte-order' | translate }} + + + {{ order | translate }} + + + +
+
+ + {{ 'gateway.rpc.data-before' | translate }} + + + + {{ 'gateway.rpc.data-after' | translate }} + + +
+ + {{ 'gateway.rpc.data-in-hex' | translate }} + + + + {{ 'gateway.rpc.data-expression' | translate }} + + +
+ + + + + {{ 'gateway.rpc.method-filter' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.method-processing' | translate }} + + + {{ SocketMethodProcessingsTranslates.get(method) | translate }} + + + + + {{ 'gateway.rpc.encoding' | translate }} + + + {{ encoding }} + + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.request-filter' | translate }} + + + + {{ 'gateway.rpc.method' | translate }} + + + {{ SNMPMethodsTranslations.get(method) | translate }} + + + +
+ {{ 'gateway.rpc.oids' | translate }}* +
+ + + + delete + +
+ +
+
+ + + + {{ 'gateway.rpc.method-filter' | translate }} + + +
+ + {{ 'gateway.rpc.http-method' | translate }} + + + {{ method }} + + + + + {{ 'gateway.rpc.request-url' | translate }} + + +
+
+ + {{ 'gateway.rpc.response-timeout' | translate }} + + + + {{ 'gateway.rpc.timeout' | translate }} + + + + {{ 'gateway.rpc.tries' | translate }} + + +
+ + {{ 'gateway.rpc.value-expression' | translate }} + + +
+ {{ 'gateway.rpc.http-headers' | translate }} +
+
+ {{ 'gateway.rpc.header-name' | translate }} + {{ 'gateway.rpc.value' | translate }} + +
+ +
+ + + + + + + + delete + + +
+
+ +
+
+ {{ 'gateway.rpc.security' | translate }} +
+
+ {{ 'gateway.rpc.security-name' | translate }} + {{ 'gateway.rpc.value' | translate }} + +
+ +
+ + + + + + + + delete + + +
+
+ +
+
+ + + + {{ 'gateway.rpc.method-filter' | translate }} + + +
+ + {{ 'gateway.rpc.method' | translate }} + + + {{ method }} + + + + + {{ 'gateway.rpc.request-url' | translate }} + + +
+
+ + {{ 'gateway.rpc.response-timeout' | translate }} + + + + {{ 'gateway.rpc.timeout' | translate }} + + + + {{ 'gateway.rpc.tries' | translate }} + + +
+ + {{ 'gateway.rpc.request-value-expression' | translate }} + + + + {{ 'gateway.rpc.response-value-expression' | translate }} + + +
+ {{ 'gateway.rpc.http-headers' | translate }} +
+
+ {{ 'gateway.rpc.header-name' | translate }} + {{ 'gateway.rpc.value' | translate }} + +
+ +
+ + + + + + + + delete + + +
+
+ +
+
+ + + + + + + {{ 'gateway.rpc.method' | translate }} + + +
+ {{ 'gateway.rpc.arguments' | translate }} +
+ + + + + delete + +
+ +
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss new file mode 100644 index 0000000000..65abdb50b3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss @@ -0,0 +1,85 @@ +/** + * 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. + */ +:host { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + padding: 0; + + .title { + font-weight: 500; + } + + .command-form { + flex-wrap: nowrap; + + & > button { + margin-top: 10px; + } + } + + .mat-mdc-slide-toggle.margin { + margin-bottom: 10px; + margin-left: 10px; + } + + .fields { + + ::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { + padding-top: 6px; + padding-bottom: 6px; + min-height: auto; + } + + ::ng-deep .mat-mdc-form-field-subscript-wrapper { + display: none; + } + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading, + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch, + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing { + border-color: #e0e0e0; + } + + .fields-label { + font-weight: 500; + } + } + + .border { + padding: 16px; + margin-bottom: 10px; + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + + .title { + color: rgba(0, 0, 0, .54); + } + + .mat-icon { + color: rgba(0, 0, 0, .38); + } + + .mat-divider { + margin-left: -16px; + margin-right: -16px; + margin-bottom: 16px; + } + } + +} + 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 new file mode 100644 index 0000000000..ce2c850741 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -0,0 +1,272 @@ +/// +/// 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, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { + BACnetObjectTypes, BACnetObjectTypesTranslates, + BACnetRequestTypes, BACnetRequestTypesTranslates, BLEMethods, BLEMethodsTranslates, + CANByteOrders, + ConnectorType, GatewayConnectorDefaultTypesTranslates, HTTPMethods, + ModbusCommandTypes, + RPCCommand, + SNMPMethods, SNMPMethodsTranslations, + SocketEncodings, + SocketMethodProcessings, SocketMethodProcessingsTranslates +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { TranslateService } from "@ngx-translate/core"; + +@Component({ + selector: 'tb-gateway-service-rpc-connector', + templateUrl: './gateway-service-rpc-connector.component.html', + styleUrls: ['./gateway-service-rpc-connector.component.scss'] +}) +export class GatewayServiceRPCConnectorComponent implements OnInit { + + @Input() + connectorType: ConnectorType; + + @Output() + sendCommand: EventEmitter = new EventEmitter(); + + commandForm: FormGroup; + + codesArray: Array = [1, 2, 3, 4, 5, 6, 15, 16]; + + readonly ConnectorType = ConnectorType; + + modbusCommandTypes = Object.values(ModbusCommandTypes) as ModbusCommandTypes[]; + bACnetRequestTypes = Object.values(BACnetRequestTypes) as BACnetRequestTypes[]; + bACnetObjectTypes = Object.values(BACnetObjectTypes) as BACnetObjectTypes[]; + bLEMethods = Object.values(BLEMethods) as BLEMethods[]; + cANByteOrders = Object.values(CANByteOrders) as CANByteOrders[]; + socketMethodProcessings = Object.values(SocketMethodProcessings) as SocketMethodProcessings[]; + socketEncodings = Object.values(SocketEncodings) as SocketEncodings[]; + sNMPMethods = Object.values(SNMPMethods) as SNMPMethods[]; + hTTPMethods = Object.values(HTTPMethods) as HTTPMethods[]; + + bACnetRequestTypesTranslates = BACnetRequestTypesTranslates; + bACnetObjectTypesTranslates = BACnetObjectTypesTranslates; + bLEMethodsTranslates = BLEMethodsTranslates; + SocketMethodProcessingsTranslates = SocketMethodProcessingsTranslates; + SNMPMethodsTranslations = SNMPMethodsTranslations; + gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslates; + + urlPattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR IP (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', // fragment locator + 'i' + ); + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + } + + + ngOnInit() { + this.commandForm = this.connectorParamsFormGroupByType(this.connectorType) + } + + connectorParamsFormGroupByType(type: ConnectorType): FormGroup { + let formGroup: FormGroup; + + switch (type) { + case ConnectorType.MQTT: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + requestTopicExpression: [null, [Validators.required]], + responseTopicExpression: [null, []], + responseTimeout: [null, [Validators.min(10), Validators.pattern("^[0-9]*$")]], + valueExpression: [null, [Validators.required]], + }) + break; + case ConnectorType.MODBUS: + formGroup = this.fb.group({ + tag: [null, [Validators.required]], + type: [null, [Validators.required]], + functionCode: [null, [Validators.required]], + address: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], + objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]] + }) + break; + case ConnectorType.BACNET: + formGroup = this.fb.group({ + method: [null, [Validators.required]], + requestType: [null, [Validators.required]], + requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + objectType: [null, []], + identifier: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + propertyId: [null, [Validators.required]] + }) + break; + case ConnectorType.BLE: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + characteristicUUID: [null, [Validators.required]], + methodProcessing: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.CAN: + formGroup = this.fb.group({ + method: [null, [Validators.required]], + nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], + isExtendedID: [false, []], + isFD: [false, []], + bitrateSwitch: [false, []], + dataLength: [null, [Validators.min(1), Validators.pattern("^[0-9]*$")]], + dataByteorder: [null, []], + dataBefore: [null, []], + dataAfter: [null, []], + dataInHEX: [null, []], + dataExpression: [null, []] + }) + break; + case ConnectorType.FTP: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + valueExpression: [null, [Validators.required]] + }) + break; + case ConnectorType.OCPP: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + valueExpression: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.SOCKET: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + methodProcessing: [null, [Validators.required]], + encoding: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.XMPP: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + valueExpression: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.SNMP: + formGroup = this.fb.group({ + requestFilter: [null, [Validators.required]], + method: [null, [Validators.required]], + oid: this.fb.array([], [Validators.required]) + }) + break; + case ConnectorType.REST: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + HTTPMethod: [null, [Validators.required]], + requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], + responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + valueExpression: [null, [Validators.required]], + httpHeaders: this.fb.array([]), + security: this.fb.array([]) + }) + break; + case ConnectorType.REQUEST: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + httpMethod: [null, [Validators.required]], + requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], + responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + requestValueExpression: [null, [Validators.required]], + responseValueExpression: [null, []], + httpHeaders: this.fb.array([]), + }) + break; + case ConnectorType.OPCUA: + case ConnectorType.OPCUA_ASYNCIO: + formGroup = this.fb.group({ + method: [null, [Validators.required]], + arguments: this.fb.array([]), + }) + } + return formGroup; + } + + addSNMPoid(value: string = null) { + const oidsFA = this.commandForm.get('oid') as FormArray; + if (oidsFA) { + oidsFA.push(this.fb.control(value, [Validators.required])); + } + } + + removeSNMPoid(index: number) { + const oidsFA = this.commandForm.get('oid') as FormArray; + oidsFA.removeAt(index); + } + + addHTTPHeader(value: { headerName: string, value: string } = {headerName: null, value: null}) { + const headerFA = this.commandForm.get('httpHeaders') as FormArray; + const formGroup = this.fb.group({ + headerName: [value.headerName, [Validators.required]], + value: [value.value, [Validators.required]] + }) + if (headerFA) { + headerFA.push(formGroup); + } + } + + removeHTTPHeader(index: number) { + const oidsFA = this.commandForm.get('httpHeaders') as FormArray; + oidsFA.removeAt(index); + } + + addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) { + const securityFA = this.commandForm.get('security') as FormArray; + const formGroup = this.fb.group({ + securityName: [value.securityName, [Validators.required]], + value: [value.value, [Validators.required]] + }) + if (securityFA) { + securityFA.push(formGroup); + } + } + + removeHTTPSecurity(index: number) { + const oidsFA = this.commandForm.get('security') as FormArray; + oidsFA.removeAt(index); + } + + getFormArrayControls(path: string) { + return (this.commandForm.get(path) as FormArray).controls as FormControl[]; + } + + addOCPUAArguments(value: string = null) { + const oidsFA = this.commandForm.get('arguments') as FormArray; + if (oidsFA) { + oidsFA.push(this.fb.control(value)); + } + } + + removeOCPUAArguments(index: number) { + const oidsFA = this.commandForm.get('arguments') as FormArray; + oidsFA.removeAt(index); + } +} 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 8a96f1849b..2397d1bcdd 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 @@ -15,51 +15,41 @@ limitations under the License. --> -
- - - {{ 'gateway.statistics.command' | translate }} - - - {{ command }} - - - - - {{ 'gateway.statistics.timeout-ms' | translate }} - - - {{ 'gateway.statistics.timeout-min' | translate }} - - - - - - {{ 'gateway.statistics.command' | translate }} - - - - {{ 'widget-config.datasource-parameters' | translate }} - - edit - - - - -
-
+
+
+ + + {{ 'gateway.statistics.command' | translate }} + + + {{ command }} + + + + + {{ 'gateway.statistics.timeout-ms' | translate }} + + + {{ 'gateway.statistics.timeout-min' | translate }} + + + + + + + +
+
{{ 'gateway.rpc-command-result' | translate }}
schedule {{ resultTime | date: 'yyyy/MM/dd HH:mm:ss' }}
- -
- + +
+ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss index 5dab1d91c1..00e9e8df63 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss @@ -16,10 +16,20 @@ :host { width: 100%; height: 100%; - overflow-x: auto; + overflow: auto; display: flex; - flex-direction: column; - padding: 0; + flex-direction: row; + padding: 0 5px; + + & > * { + height: 100%; + overflow: auto; + } + + & > tb-gateway-service-rpc-connector-templates:last-child { + margin-left: 10px; + } + .command-form { flex-wrap: nowrap; @@ -63,6 +73,13 @@ flex: 1; } } + + .border { + padding: 16px; + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + } } :host ::ng-deep { 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 d19624e257..a3c96f7d49 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,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, Input } from '@angular/core'; +import { OnInit, Component, Input } 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'; @@ -24,13 +24,14 @@ import { 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'; @Component({ selector: 'tb-gateway-service-rpc', templateUrl: './gateway-service-rpc.component.html', styleUrls: ['./gateway-service-rpc.component.scss'] }) -export class GatewayServiceRPCComponent implements AfterViewInit { +export class GatewayServiceRPCComponent implements OnInit { @Input() ctx: WidgetContext; @@ -56,19 +57,19 @@ export class GatewayServiceRPCComponent implements AfterViewInit { 'Reboot' ]; - private connectorType: string; + public connectorType: ConnectorType; constructor(private fb: FormBuilder, private dialog: MatDialog) { this.commandForm = this.fb.group({ - command: [null,[Validators.required]], + command: [null, [Validators.required]], time: [60, [Validators.required, Validators.min(1)]], params: ['{}', [jsonRequired]], result: [null] }); } - ngAfterViewInit() { + ngOnInit() { this.isConnector = this.ctx.settings.isConnector; if (!this.isConnector) { this.commandForm.get('command').setValue(this.RPCCommands[0]); @@ -77,17 +78,17 @@ export class GatewayServiceRPCComponent implements AfterViewInit { } } - sendCommand() { + sendCommand(value?: RPCCommand) { this.resultTime = null; - const formValues = this.commandForm.value; + 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({ + this.ctx.controlApi.sendTwoWayCommand(commandPrefix + formValues.command.toLowerCase(), formValues.params, formValues.time).subscribe({ next: resp => { - this.resultTime = new Date().getTime(); + this.resultTime = new Date().getTime(); this.commandForm.get('result').setValue(JSON.stringify(resp)) }, error: error => { - this.resultTime = new Date().getTime(); + this.resultTime = new Date().getTime(); console.error(error); this.commandForm.get('result').setValue(JSON.stringify(error.error)); } 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 d58c33079a..4b09667a6b 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 @@ -107,27 +107,166 @@ export interface GatewayConnector { key?: string; } +export enum ConnectorType { + MQTT = 'mqtt', + MODBUS = 'modbus', + GRPC = 'grpc', + OPCUA = 'opcua', + OPCUA_ASYNCIO = 'opcua_asyncio', + BLE = 'ble', + REQUEST = 'request', + CAN = 'can', + BACNET = 'bacnet', + ODBC = 'odbc', + REST = 'rest', + SNMP = 'snmp', + FTP = 'ftp', + SOCKET = 'socket', + XMPP = 'xmpp', + OCPP = 'ocpp', + CUSTOM = 'custom' +} -export const GatewayConnectorDefaultTypesTranslates = new Map([ - ['mqtt', 'MQTT'], - ['modbus', 'MODBUS'], - ['grpc', 'GRPC'], - ['opcua', 'OPCUA'], - ['opcua_asyncio', 'OPCUA ASYNCIO'], - ['ble', 'BLE'], - ['request', 'REQUEST'], - ['can', 'CAN'], - ['bacnet', 'BACNET'], - ['odbc', 'ODBC'], - ['rest', 'REST'], - ['snmp', 'SNMP'], - ['ftp', 'FTP'], - ['socket', 'SOCKET'], - ['xmpp', 'XMPP'], - ['ocpp', 'OCPP'], - ['custom', 'CUSTOM'] +export const GatewayConnectorDefaultTypesTranslates = new Map([ + [ConnectorType.MQTT, 'MQTT'], + [ConnectorType.MODBUS, 'MODBUS'], + [ConnectorType.GRPC, 'GRPC'], + [ConnectorType.OPCUA, 'OPCUA'], + [ConnectorType.OPCUA_ASYNCIO, 'OPCUA ASYNCIO'], + [ConnectorType.BLE, 'BLE'], + [ConnectorType.REQUEST, 'REQUEST'], + [ConnectorType.CAN, 'CAN'], + [ConnectorType.BACNET, 'BACNET'], + [ConnectorType.ODBC, 'ODBC'], + [ConnectorType.REST, 'REST'], + [ConnectorType.SNMP, 'SNMP'], + [ConnectorType.FTP, 'FTP'], + [ConnectorType.SOCKET, 'SOCKET'], + [ConnectorType.XMPP, 'XMPP'], + [ConnectorType.OCPP, 'OCPP'], + [ConnectorType.CUSTOM, 'CUSTOM'] ]); +export interface RPCCommand { + command: string, + params: any, + time: number +} + + +export enum ModbusCommandTypes { + Bits = 'bits', + Bit = 'bit', + String = 'string', + Bytes = 'bytes', + Int8 = '8int', + Uint8 = '8uint', + Int16 = '16int', + Uint16 = '16uint', + Float16 = '16float', + Int32 = '32int', + Uint32 = '32uint', + Float32 = '32float', + Int64 = '64int', + Uint64 = '64uint', + Float64 = '64float' +} + +export enum BACnetRequestTypes { + WriteProperty = 'writeProperty', + ReadProperty = 'readProperty' +} + +export const BACnetRequestTypesTranslates = new Map([ + [BACnetRequestTypes.WriteProperty, 'gateway.rpc.write-property'], + [BACnetRequestTypes.ReadProperty, "gateway.rpc.read-property"] +]) + +export enum BACnetObjectTypes { + BinaryOutput = 'binaryOutput', + AnalogOutput = 'analogOutput', + BinaryValue = 'binaryValue' +} + +export const BACnetObjectTypesTranslates = new Map([ + [BACnetObjectTypes.AnalogOutput, 'gateway.rpc.analog-output'], + [BACnetObjectTypes.BinaryOutput, 'gateway.rpc.binary-output'], + [BACnetObjectTypes.BinaryValue, 'gateway.rpc.binary-value'], +]) + +export enum BLEMethods { + WRITE = 'write', + READ = 'read', + SCAN = 'scan' +} + +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', + BIG = 'BIG' +} + +export enum SocketMethodProcessings { + WRITE = 'write' +} + +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" +} + +export const SNMPMethodsTranslations = new Map([ + [SNMPMethods.SET, 'gateway.rpc.set'], + [SNMPMethods.MULTISET, 'gateway.rpc.multiset'], + [SNMPMethods.GET, 'gateway.rpc.get'], + [SNMPMethods.BULKWALK, 'gateway.rpc.bulk-walk'], + [SNMPMethods.TABLE, 'gateway.rpc.table'], + [SNMPMethods.MULTIGET, 'gateway.rpc.multi-get'], + [SNMPMethods.GETNEXT, 'gateway.rpc.get-next'], + [SNMPMethods.BULKGET, 'gateway.rpc.bul-kget'], + [SNMPMethods.WALKS, 'gateway.rpc.walk'] +]) + +export enum HTTPMethods { + CONNECT = 'CONNECT', + DELETE = 'DELETE', + GET = 'GET', + HEAD = 'HEAD', + OPTIONS = 'OPTIONS', + PATCH = 'PATCH', + POST = 'POST', + PUT = 'PUT', + TRACE = 'TRACE' + +} + +export enum SocketEncodings { + UTF_8 = 'utf-8' +} + +export interface RPCTemplate { + name: string; + config: { + [key: string]: any; + }; +} + export interface LogLink { name: string; key: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 858856fb4a..b4b688848e 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 @@ -22,7 +22,9 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarm/alarms-table-widget.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; -import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entity/entities-hierarchy-widget.component'; +import { + EntitiesHierarchyWidgetComponent +} from '@home/components/widget/lib/entity/entities-hierarchy-widget.component'; import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module'; import { DateRangeNavigatorPanelComponent, @@ -47,6 +49,12 @@ import { GatewayConnectorComponent } from '@home/components/widget/lib/gateway/g import { GatewayLogsComponent } from '@home/components/widget/lib/gateway/gateway-logs.component'; import { GatewayStatisticsComponent } from '@home/components/widget/lib/gateway/gateway-statistics.component'; import { GatewayServiceRPCComponent } from '@home/components/widget/lib/gateway/gateway-service-rpc.component'; +import { + GatewayServiceRPCConnectorComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector.component'; +import { + GatewayServiceRPCConnectorTemplatesComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component'; import { DeviceGatewayCommandComponent } from '@home/components/widget/lib/gateway/device-gateway-command.component'; import { GatewayConfigurationComponent } from '@home/components/widget/lib/gateway/gateway-configuration.component'; import { @@ -94,6 +102,8 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough GatewayLogsComponent, GatewayStatisticsComponent, GatewayServiceRPCComponent, + GatewayServiceRPCConnectorComponent, + GatewayServiceRPCConnectorTemplatesComponent, DeviceGatewayCommandComponent, GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, @@ -115,46 +125,48 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough HomePageWidgetsModule, SharedHomeComponentsModule ], - exports: [ - EntitiesTableWidgetComponent, - AlarmsTableWidgetComponent, - TimeseriesTableWidgetComponent, - EntitiesHierarchyWidgetComponent, - EdgesOverviewWidgetComponent, - RpcWidgetsModule, - HomePageWidgetsModule, - DateRangeNavigatorWidgetComponent, - JsonInputWidgetComponent, - MultipleInputWidgetComponent, - TripAnimationComponent, - PhotoCameraInputWidgetComponent, - GatewayFormComponent, - NavigationCardsWidgetComponent, - NavigationCardWidgetComponent, - QrCodeWidgetComponent, - MarkdownWidgetComponent, - LegendComponent, - FlotWidgetComponent, - GatewayConnectorComponent, - GatewayLogsComponent, - GatewayStatisticsComponent, - GatewayServiceRPCComponent, - DeviceGatewayCommandComponent, - GatewayConfigurationComponent, - GatewayRemoteConfigurationDialogComponent, - ValueCardWidgetComponent, - AggregatedValueCardWidgetComponent, - CountWidgetComponent, - BatteryLevelWidgetComponent, - WindSpeedDirectionWidgetComponent, - SignalStrengthWidgetComponent, - ValueChartCardWidgetComponent, - ProgressBarWidgetComponent, - LiquidLevelWidgetComponent, - DoughnutWidgetComponent - ], + exports: [ + EntitiesTableWidgetComponent, + AlarmsTableWidgetComponent, + TimeseriesTableWidgetComponent, + EntitiesHierarchyWidgetComponent, + EdgesOverviewWidgetComponent, + RpcWidgetsModule, + HomePageWidgetsModule, + DateRangeNavigatorWidgetComponent, + JsonInputWidgetComponent, + MultipleInputWidgetComponent, + TripAnimationComponent, + PhotoCameraInputWidgetComponent, + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent, + QrCodeWidgetComponent, + MarkdownWidgetComponent, + LegendComponent, + FlotWidgetComponent, + GatewayConnectorComponent, + GatewayLogsComponent, + GatewayServiceRPCConnectorComponent, + GatewayServiceRPCConnectorTemplatesComponent, + GatewayStatisticsComponent, + GatewayServiceRPCComponent, + DeviceGatewayCommandComponent, + GatewayConfigurationComponent, + GatewayRemoteConfigurationDialogComponent, + ValueCardWidgetComponent, + AggregatedValueCardWidgetComponent, + CountWidgetComponent, + BatteryLevelWidgetComponent, + WindSpeedDirectionWidgetComponent, + SignalStrengthWidgetComponent, + ValueChartCardWidgetComponent, + ProgressBarWidgetComponent, + LiquidLevelWidgetComponent, + DoughnutWidgetComponent + ], providers: [ - {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } + {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule} ] }) export class WidgetComponentsModule { 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 fcf42dc0e2..294173e231 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2681,6 +2681,7 @@ "connectors-table-actions": "Actions", "connectors-table-key": "Key", "connectors-table-class": "Class", + "rpc-command-save-template": "Save Template", "rpc-command-send": "Send", "rpc-command-result": "Response", "rpc-command-edit-params": "Edit parameters", @@ -2772,6 +2773,79 @@ "remove-entry": "Remove configuration", "remote-shell": "Remote shell", "remote-configuration": "Remote Configuration", + "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", + "tag": "Tag", + "type": "Type", + "function-code": "Function Code", + "objects-count": "Objects Count", + "address": "Address", + "method": "Method", + "request-type": "Request Type", + "request-timeout": "Request Timeout", + "object-type": "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", + "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", + "timeout": "Timeout", + "tries": "Tries", + "http-headers": "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", + "arguments": "Arguments", + "add-argument": "Add argument", + "write-property": "Write property", + "read-property": "Read property", + "analog-output": "Analog output", + "binary-output": "Binary output", + "binary-value": "Binary value", + "write": "Write", + "read": "Read", + "scan": "Scan", + "oids": "OIDS", + "set": "Set", + "multiset": "Multiset", + "get": "Get", + "bulk-walk": "Bulk walk", + "table": "Table", + "multi-get": "Multiget", + "get-next": "Get next", + "bulk-get": "Bulk get", + "walk": "Walk" + + }, "other": "Other", "save-tip": "Save configuration file", "security-type": "Security type",