diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss
index 2245de8b12..87fe898568 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss
@@ -104,8 +104,3 @@
background-color: rgb(25, 128, 56);
}
}
-
-:host ::ng-deep tb-json-object-edit > div {
- flex-grow: 1;
-}
-
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 8071a2f924..21d232f2f1 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
@@ -213,10 +214,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();
@@ -325,7 +326,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
this.initialConnector = null;
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-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..d8b028361d
--- /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..5c70e1f233
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts
@@ -0,0 +1,63 @@
+///
+/// Copyright © 2016-2024 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Component, Inject } 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.templateNameCtrl.setValue(this.templateNameCtrl.value.trim());
+ if (this.templateNameCtrl.valid) 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
new file mode 100644
index 0000000000..c0f6997a36
--- /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}}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{!innerValue ? ('gateway.rpc.' + config.key | translate) : 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..7e5c4af1f1
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss
@@ -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.
+ */
+: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 {
+ margin-top: 10px;
+ overflow: visible;
+ }
+
+ .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;
+ }
+ }
+
+ .rpc-params-row {
+ overflow: hidden;
+ white-space: nowrap;
+
+ & :not(:first-child) {
+ white-space: pre;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+}
+
+
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..4392b35f2d
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts
@@ -0,0 +1,82 @@
+///
+/// Copyright © 2016-2024 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import {
+ ConnectorType,
+ RPCTemplate
+} from '@home/components/widget/lib/gateway/gateway-widget.models';
+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 { isLiteralObject } from '@app/core/utils';
+
+@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;
+
+ @Input()
+ ctx: WidgetContext;
+
+ @Output()
+ saveTemplate: EventEmitter = new EventEmitter();
+
+ @Output()
+ useTemplate: EventEmitter = new EventEmitter();
+
+ @Input()
+ rpcTemplates: Array;
+
+ public readonly originalOrder = (): number => 0;
+ public readonly isObject = (value: any) => isLiteralObject(value);
+
+ constructor(private attributeService: AttributeService) {
+ }
+
+ ngOnInit() {
+ }
+
+ public applyTemplate($event: Event, template: RPCTemplate): void {
+ $event.stopPropagation();
+ this.useTemplate.emit(template);
+ }
+
+ public deleteTemplate($event: Event, template: RPCTemplate): void {
+ $event.stopPropagation();
+ 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(() => {
+ })
+ }
+}
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..2d992187f6
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html
@@ -0,0 +1,542 @@
+
+
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..a0af50aee8
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss
@@ -0,0 +1,67 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+: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{
+ .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..69b785d133
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts
@@ -0,0 +1,428 @@
+///
+/// Copyright © 2016-2024 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
+import {
+ ControlValueAccessor,
+ FormArray,
+ FormBuilder,
+ FormControl,
+ FormGroup,
+ NG_VALUE_ACCESSOR,
+ Validators
+} from '@angular/forms';
+import {
+ BACnetObjectTypes,
+ BACnetObjectTypesTranslates,
+ BACnetRequestTypes,
+ BACnetRequestTypesTranslates,
+ BLEMethods,
+ BLEMethodsTranslates,
+ CANByteOrders,
+ ConnectorType,
+ GatewayConnectorDefaultTypesTranslates,
+ HTTPMethods,
+ ModbusCodesTranslate,
+ ModbusCommandTypes,
+ RPCCommand,
+ RPCTemplateConfig,
+ SNMPMethods,
+ SNMPMethodsTranslations,
+ SocketEncodings,
+ SocketMethodProcessings,
+ SocketMethodProcessingsTranslates
+} from '@home/components/widget/lib/gateway/gateway-widget.models';
+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';
+
+export const noLeadTrailSpacesRegex: RegExp = /^(?! )[\S\s]*(? GatewayServiceRPCConnectorComponent),
+ multi: true
+ }
+ ]
+})
+export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor {
+
+ @Input()
+ connectorType: ConnectorType;
+
+ @Output()
+ sendCommand: EventEmitter = new EventEmitter();
+
+ @Output()
+ saveTemplate: EventEmitter = new EventEmitter();
+
+ commandForm: FormGroup;
+ isMQTTWithResponse: FormControl;
+ codesArray: Array = [1, 2, 3, 4, 5, 6, 15, 16];
+ 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;
+ modbusCodesTranslate = ModbusCodesTranslate;
+
+ urlPattern = /^[-a-zA-Zd_$:{}?~+=\/.0-9-]*$/;
+ numbersOnlyPattern = /^[0-9]*$/;
+ hexOnlyPattern = /^[0-9A-Fa-f ]+$/;
+
+ private propagateChange = (v: any) => {
+ }
+
+ constructor(private fb: FormBuilder,
+ private dialog: MatDialog,) {
+ }
+
+ ngOnInit() {
+ 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});
+ }
+ });
+ this.isMQTTWithResponse = this.fb.control(false);
+ }
+
+ connectorParamsFormGroupByType(type: ConnectorType): FormGroup {
+ let formGroup: FormGroup;
+
+ switch (type) {
+ case ConnectorType.MQTT:
+ formGroup = this.fb.group({
+ methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ responseTopicExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]],
+ responseTimeout: [null, [Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
+ valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ })
+ break;
+ case ConnectorType.MODBUS:
+ formGroup = this.fb.group({
+ tag: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ type: [null, [Validators.required]],
+ functionCode: [null, [Validators.required]],
+ value: [null, []],
+ address: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]],
+ objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]]
+ })
+ const valueForm = formGroup.get('value');
+ formGroup.get('functionCode').valueChanges.subscribe(value => {
+ if (value > 4) {
+ valueForm.addValidators([Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]);
+ } else {
+ valueForm.clearValidators();
+ valueForm.setValue(null);
+ }
+ valueForm.updateValueAndValidity();
+ })
+ break;
+ case ConnectorType.BACNET:
+ formGroup = this.fb.group({
+ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ requestType: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
+ objectType: [null, []],
+ identifier: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]],
+ propertyId: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]]
+ })
+ break;
+ case ConnectorType.BLE:
+ formGroup = this.fb.group({
+ methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ characteristicUUID: ["00002A00-0000-1000-8000-00805F9B34FB", [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ methodProcessing: [null, [Validators.required]],
+ withResponse: [false, []]
+ })
+ break;
+ case ConnectorType.CAN:
+ formGroup = this.fb.group({
+ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]],
+ isExtendedID: [false, []],
+ isFD: [false, []],
+ bitrateSwitch: [false, []],
+ dataLength: [null, [Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]],
+ dataByteorder: [null, []],
+ dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]],
+ dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]],
+ dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]],
+ dataExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]]
+ })
+ break;
+ case ConnectorType.FTP:
+ formGroup = this.fb.group({
+ methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]]
+ })
+ break;
+ case ConnectorType.OCPP:
+ formGroup = this.fb.group({
+ methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ withResponse: [false, []]
+ })
+ break;
+ case ConnectorType.SOCKET:
+ formGroup = this.fb.group({
+ methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ methodProcessing: [null, [Validators.required]],
+ encoding: [SocketEncodings.UTF_8, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ withResponse: [false, []]
+ })
+ break;
+ case ConnectorType.XMPP:
+ formGroup = this.fb.group({
+ methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ withResponse: [false, []]
+ })
+ break;
+ case ConnectorType.SNMP:
+ formGroup = this.fb.group({
+ requestFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ method: [null, [Validators.required]],
+ withResponse: [false, []],
+ oid: this.fb.array([], [Validators.required])
+ })
+ break;
+ case ConnectorType.REST:
+ formGroup = this.fb.group({
+ methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ httpMethod: [null, [Validators.required]],
+ requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]],
+ responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
+ timeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
+ tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]],
+ valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ httpHeaders: this.fb.array([]),
+ security: this.fb.array([])
+ })
+ break;
+ case ConnectorType.REQUEST:
+ formGroup = this.fb.group({
+ methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ httpMethod: [null, [Validators.required]],
+ requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]],
+ responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
+ timeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
+ tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]],
+ requestValueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ responseValueExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]],
+ httpHeaders: this.fb.array([]),
+ })
+ break;
+ case ConnectorType.OPCUA:
+ case ConnectorType.OPCUA_ASYNCIO:
+ formGroup = this.fb.group({
+ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ arguments: this.fb.array([]),
+ })
+ break;
+ default:
+ formGroup = this.fb.group({
+ command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
+ params: [{}, [jsonRequired]],
+ })
+ }
+ return formGroup;
+ }
+
+ addSNMPoid(value: string = null) {
+ const oidsFA = this.commandForm.get('oid') as FormArray;
+ if (oidsFA) {
+ oidsFA.push(this.fb.control(value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false});
+ }
+ }
+
+ 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, Validators.pattern(noLeadTrailSpacesRegex)]],
+ value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]]
+ })
+ if (headerFA) {
+ headerFA.push(formGroup, {emitEvent: false});
+ }
+ }
+
+ 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, Validators.pattern(noLeadTrailSpacesRegex)]],
+ value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]]
+ })
+ if (securityFA) {
+ securityFA.push(formGroup, {emitEvent: false});
+ }
+ }
+
+ 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, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false});
+ }
+ }
+
+ removeOCPUAArguments(index: number) {
+ 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: this.commandForm.get('params').value,
+ required: true
+ }
+ }).afterClosed().subscribe(
+ (res) => {
+ if (res) {
+ this.commandForm.get('params').setValue(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 33ddef3f81..8368698087 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,46 @@
limitations under the License.
-->
-