committed by
GitHub
16 changed files with 1874 additions and 105 deletions
@ -0,0 +1,54 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-toolbar color="primary"> |
|||
<h2 translate>gateway.rpc.save-template</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="close()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<div mat-dialog-content style="width: 600px" class="mat-content" fxLayout="column"> |
|||
<mat-form-field class="mat-block tb-value-type" style="flex-grow: 0"> |
|||
<mat-label translate>gateway.rpc.template-name</mat-label> |
|||
<input matInput [formControl]="templateNameCtrl" required/> |
|||
<mat-error |
|||
*ngIf="templateNameCtrl.hasError('required')"> |
|||
{{ 'gateway.rpc.template-name-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<div class="mat-mdc-form-field-error" |
|||
style="margin-top: -15px; padding-left: 10px; font-size: 14px;" |
|||
*ngIf="validateDuplicateName(templateNameCtrl)"> |
|||
{{ 'gateway.rpc.template-name-duplicate' | translate }} |
|||
</div> |
|||
</div> |
|||
<div mat-dialog-actions fxLayoutAlign="end center"> |
|||
<button mat-button |
|||
type="button" |
|||
(click)="close()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button color="primary" |
|||
type="button" |
|||
[disabled]="!templateNameCtrl.valid" |
|||
(click)="save()"> |
|||
{{ 'action.save' | translate }} |
|||
</button> |
|||
</div> |
|||
@ -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<GatewayServiceRPCConnectorTemplateDialogComponent, boolean> { |
|||
|
|||
config: { |
|||
[key: string]: any; |
|||
}; |
|||
templates: Array<RPCTemplate>; |
|||
templateNameCtrl: FormControl; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: SaveRPCTemplateData, |
|||
public dialogRef: MatDialogRef<GatewayServiceRPCConnectorTemplateDialogComponent, boolean>, |
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<div class="mat-subtitle-1 title">{{ 'gateway.rpc.templates-title' | translate }}</div> |
|||
<mat-expansion-panel hideToggle *ngFor="let template of rpcTemplates"> |
|||
<mat-expansion-panel-header> |
|||
<mat-panel-title> |
|||
{{template.name}} |
|||
</mat-panel-title> |
|||
<mat-panel-description> |
|||
<button mat-icon-button matTooltip="Delete" (click)="deleteTemplate($event, template)"> |
|||
<mat-icon class="material-icons">delete</mat-icon> |
|||
</button> |
|||
<button mat-icon-button matTooltip="Use" (click)="applyTemplate($event, template)"> |
|||
<mat-icon class="material-icons">play_arrow</mat-icon> |
|||
</button> |
|||
</mat-panel-description> |
|||
</mat-expansion-panel-header> |
|||
|
|||
<ng-container |
|||
*ngFor="let config of template.config | keyvalue : originalOrder" |
|||
[ngTemplateOutlet]="RPCTemplateRef" |
|||
[ngTemplateOutletContext]="{ $implicit: config, innerValue: false }"> |
|||
</ng-container> |
|||
<ng-template #RPCTemplateRef let-config let-innerValue='innerValue'> |
|||
<div [fxLayout]="isObject(config.value) ? 'column': 'row'" |
|||
[fxLayoutAlign]="!isObject(config.value) ? 'space-between center' : ''" |
|||
[ngStyle]="{'padding-left': innerValue ? '16px': '0'}" |
|||
class="rpc-params-row"> |
|||
<div class="template-key"> |
|||
{{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}} |
|||
</div> |
|||
<div *ngIf="!isObject(config.value) else RPCObjectRow" |
|||
[ngClass]="{'boolean-true': config.value === true, |
|||
'boolean-false': config.value === false }"> |
|||
{{config.value}}</div> |
|||
<ng-template #RPCObjectRow> |
|||
<ng-container |
|||
*ngFor="let subConfig of config.value | keyvalue : originalOrder" |
|||
[ngTemplateOutlet]="RPCTemplateRef" |
|||
[ngTemplateOutletContext]="{ $implicit: subConfig, innerValue: true }"> |
|||
</ng-container> |
|||
</ng-template> |
|||
</div> |
|||
</ng-template> |
|||
</mat-expansion-panel> |
|||
@ -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; |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
@ -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<any> = new EventEmitter(); |
|||
|
|||
@Output() |
|||
useTemplate: EventEmitter<any> = new EventEmitter(); |
|||
|
|||
@Input() |
|||
rpcTemplates: Array<RPCTemplate>; |
|||
|
|||
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(() => { |
|||
}) |
|||
} |
|||
} |
|||
@ -0,0 +1,542 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<div fxLayout="column" class="command-form" [formGroup]="commandForm"> |
|||
<div |
|||
class="mat-subtitle-1 title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div> |
|||
<ng-template [ngIf]="connectorType"> |
|||
<ng-container [ngSwitch]="connectorType"> |
|||
<ng-template [ngSwitchCase]="ConnectorType.MQTT"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label> |
|||
<input matInput formControlName="methodFilter" |
|||
placeholder="echo"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.requestTopicExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="requestTopicExpression" |
|||
placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="margin" (click)="$event.stopPropagation()" [formControl]="isMQTTWithResponse"> |
|||
{{ 'gateway.rpc.withResponse' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-form-field *ngIf="isMQTTWithResponse.value"> |
|||
<mat-label>{{ 'gateway.rpc.responseTopicExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="responseTopicExpression" |
|||
placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/> |
|||
</mat-form-field> |
|||
<mat-form-field *ngIf="isMQTTWithResponse.value"> |
|||
<mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label> |
|||
<input matInput formControlName="responseTimeout" type="number" |
|||
placeholder="10000" min="10" step="1"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="valueExpression" |
|||
placeholder="${params}"/> |
|||
</mat-form-field> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.MODBUS"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.tag' | translate }}</mat-label> |
|||
<input matInput formControlName="tag" placeholder="setValue"/> |
|||
</mat-form-field> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50" class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.type' | translate }}</mat-label> |
|||
<mat-select formControlName="type"> |
|||
<mat-option *ngFor="let type of modbusCommandTypes" [value]="type"> |
|||
{{ type }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="50" class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.functionCode' | translate }}</mat-label> |
|||
<mat-select formControlName="functionCode"> |
|||
<mat-option *ngFor="let code of codesArray" [value]="code"> |
|||
{{ modbusCodesTranslate.get(code) | translate}} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
</div> |
|||
<mat-form-field *ngIf="commandForm.get('functionCode').value>4"> |
|||
<mat-label>{{ 'gateway.rpc.value' | translate }}</mat-label> |
|||
<input matInput formControlName="value" placeholder="value"/> |
|||
</mat-form-field> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label> |
|||
<input matInput formControlName="address" type="number" min="0" |
|||
placeholder="1" step="1"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.objectsCount' | translate }}</mat-label> |
|||
<input matInput formControlName="objectsCount" type="number" min="0" |
|||
placeholder="31" step="1"/> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.BACNET"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label> |
|||
<input matInput formControlName="method" placeholder="set_state"/> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.requestType' | translate }}</mat-label> |
|||
<mat-select formControlName="requestType"> |
|||
<mat-option *ngFor="let type of bACnetRequestTypes" [value]="type"> |
|||
{{bACnetRequestTypesTranslates.get(type) | translate}} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.requestTimeout' | translate }}</mat-label> |
|||
<input matInput formControlName="requestTimeout" type="number" |
|||
min="10" step="1" placeholder="1000"/> |
|||
</mat-form-field> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50" class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.objectType' | translate }}</mat-label> |
|||
<mat-select formControlName="objectType"> |
|||
<mat-option *ngFor="let type of bACnetObjectTypes" [value]="type"> |
|||
{{bACnetObjectTypesTranslates.get(type) | translate}} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.identifier' | translate }}</mat-label> |
|||
<input matInput formControlName="identifier" type="number" |
|||
min="1" step="1" placeholder="1"/> |
|||
</mat-form-field> |
|||
</div> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.propertyId' | translate }}</mat-label> |
|||
<input matInput formControlName="propertyId" placeholder="presentValue"/> |
|||
</mat-form-field> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.BLE"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label> |
|||
<input matInput formControlName="methodRPC" placeholder="rpcMethod1"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.characteristicUUID' | translate }}</mat-label> |
|||
<input matInput formControlName="characteristicUUID" placeholder="00002A00-0000-1000-8000-00805F9B34FB"/> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.methodProcessing' | translate }}</mat-label> |
|||
<mat-select formControlName="methodProcessing"> |
|||
<mat-option *ngFor="let type of bLEMethods" [value]="type"> |
|||
{{bLEMethodsTranslates.get(type) | translate}} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="mat-slide" formControlName="withResponse"> |
|||
{{ 'gateway.rpc.withResponse' | translate }} |
|||
</mat-slide-toggle> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.CAN"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label> |
|||
<input matInput formControlName="method" placeholder="sendSameData"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.nodeID' | translate }}</mat-label> |
|||
<input matInput formControlName="nodeID" type="number" placeholder="4" min="0" step="1"/> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="isExtendedID"> |
|||
{{ 'gateway.rpc.isExtendedID' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="isFD"> |
|||
{{ 'gateway.rpc.isFD' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="bitrateSwitch"> |
|||
{{ 'gateway.rpc.bitrateSwitch' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.dataLength' | translate }}</mat-label> |
|||
<input matInput formControlName="dataLength" type="number" placeholder="2" min="1" step="1"/> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block" fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.dataByteorder' | translate }}</mat-label> |
|||
<mat-select formControlName="dataByteorder"> |
|||
<mat-option *ngFor="let order of cANByteOrders" [value]="order"> |
|||
{{ order | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.dataBefore' | translate }}</mat-label> |
|||
<input matInput formControlName="dataBefore" placeholder="00AA"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.dataAfter' | translate }}</mat-label> |
|||
<input matInput formControlName="dataAfter" placeholder="0102"/> |
|||
</mat-form-field> |
|||
</div> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.dataInHEX' | translate }}</mat-label> |
|||
<input matInput formControlName="dataInHEX" |
|||
placeholder="aa bb cc dd ee ff aa bb aa bb cc d ee ff"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.dataExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="dataExpression" |
|||
placeholder="userSpeed if maxAllowedSpeed > userSpeed else maxAllowedSpeed"/> |
|||
</mat-form-field> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.FTP"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label> |
|||
<input matInput formControlName="methodFilter" placeholder="read"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="valueExpression" placeholder="${params}"/> |
|||
</mat-form-field> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.OCPP"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label> |
|||
<input matInput formControlName="methodRPC" placeholder="rpc1"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="valueExpression" placeholder="${params}"/> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> |
|||
{{ 'gateway.rpc.withResponse' | translate }} |
|||
</mat-slide-toggle> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.SOCKET"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label> |
|||
<input matInput formControlName="methodRPC" placeholder="rpcMethod1"/> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.methodProcessing' | translate }}</mat-label> |
|||
<mat-select formControlName="methodProcessing"> |
|||
<mat-option *ngFor="let method of socketMethodProcessings" [value]="method"> |
|||
{{ SocketMethodProcessingsTranslates.get(method) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.encoding' | translate }}</mat-label> |
|||
<input matInput formControlName="encoding" placeholder="{{socketEncodings[0]}}"/> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> |
|||
{{ 'gateway.rpc.withResponse' | translate }} |
|||
</mat-slide-toggle> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.XMPP"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label> |
|||
<input matInput formControlName="methodRPC" placeholder="rpc1"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="valueExpression" placeholder="${params}"/> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> |
|||
{{ 'gateway.rpc.withResponse' | translate }} |
|||
</mat-slide-toggle> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.SNMP"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.requestFilter' | translate }}</mat-label> |
|||
<input matInput formControlName="requestFilter" placeholder="setData"/> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label> |
|||
<mat-select formControlName="method"> |
|||
<mat-option *ngFor="let method of sNMPMethods" [value]="method"> |
|||
{{ SNMPMethodsTranslations.get(method) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> |
|||
{{ 'gateway.rpc.withResponse' | translate }} |
|||
</mat-slide-toggle> |
|||
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="oid"> |
|||
<span class="fields-label">{{ 'gateway.rpc.oids' | translate }}*</span> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" |
|||
*ngFor="let control of getFormArrayControls('oid'); let i = index"> |
|||
<mat-form-field class="tb-inline-field" appearance="outline" fxFlex subscriptSizing="dynamic"> |
|||
<input matInput [formControl]="control" required/> |
|||
</mat-form-field> |
|||
<mat-icon style="cursor:pointer;" |
|||
fxFlex="30px" |
|||
(click)="removeSNMPoid(i)" |
|||
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete |
|||
</mat-icon> |
|||
</div> |
|||
<button mat-raised-button |
|||
fxFlexAlign="start" |
|||
(click)="addSNMPoid()"> |
|||
{{ 'gateway.rpc.add-oid' | translate }} |
|||
</button> |
|||
</fieldset> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.REST"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label> |
|||
<input matInput formControlName="methodFilter" placeholder="post_attributes"/> |
|||
</mat-form-field> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field class="mat-block" fxFlex="33"> |
|||
<mat-label>{{ 'gateway.rpc.httpMethod' | translate }}</mat-label> |
|||
<mat-select formControlName="httpMethod"> |
|||
<mat-option *ngFor="let method of hTTPMethods" [value]="method"> |
|||
{{ method }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'gateway.rpc.requestUrlExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="requestUrlExpression" |
|||
placeholder="http://127.0.0.1:5000/my_devices"/> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="33"> |
|||
<mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label> |
|||
<input matInput formControlName="responseTimeout" type="number" |
|||
step="1" min="10" placeholder="10"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'gateway.rpc.timeout' | translate }}</mat-label> |
|||
<input matInput formControlName="timeout" type="number" |
|||
step="1" min="10" placeholder="1000"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'gateway.rpc.tries' | translate }}</mat-label> |
|||
<input matInput formControlName="tries" type="number" |
|||
step="1" min="1" placeholder="3"/> |
|||
</mat-form-field> |
|||
</div> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="valueExpression" placeholder="${params}"/> |
|||
</mat-form-field> |
|||
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders"> |
|||
<span class="fields-label">{{ 'gateway.rpc.httpHeaders' | translate }}</span> |
|||
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length"> |
|||
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"> |
|||
<span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span> |
|||
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span> |
|||
<span fxFlex="30px"></span> |
|||
</div> |
|||
<mat-divider></mat-divider> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" |
|||
*ngFor="let control of getFormArrayControls('httpHeaders'); let i = index"> |
|||
<ng-container [formGroupName]="i"> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput formControlName="headerName"/> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput formControlName="value" placeholder="application/json"/> |
|||
</mat-form-field> |
|||
<mat-icon style="cursor:pointer;" |
|||
fxFlex="30px" |
|||
(click)="removeHTTPHeader(i)" |
|||
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete |
|||
</mat-icon> |
|||
</ng-container> |
|||
</div> |
|||
</div> |
|||
<button mat-raised-button |
|||
fxFlexAlign="start" |
|||
(click)="addHTTPHeader()"> |
|||
{{ 'gateway.rpc.add-header' | translate }} |
|||
</button> |
|||
</fieldset> |
|||
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="security"> |
|||
<span class="fields-label">{{ 'gateway.rpc.security' | translate }}</span> |
|||
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('security').length"> |
|||
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"> |
|||
<span fxFlex class="title">{{ 'gateway.rpc.security-name' | translate }}</span> |
|||
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span> |
|||
<span fxFlex="30px"></span> |
|||
</div> |
|||
<mat-divider></mat-divider> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" |
|||
*ngFor="let control of getFormArrayControls('security'); let i = index"> |
|||
<ng-container [formGroupName]="i"> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput formControlName="securityName"/> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput formControlName="value" placeholder="anonymous"/> |
|||
</mat-form-field> |
|||
<mat-icon style="cursor:pointer;" |
|||
fxFlex="30px" |
|||
(click)="removeHTTPSecurity(i)" |
|||
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete |
|||
</mat-icon> |
|||
</ng-container> |
|||
</div> |
|||
</div> |
|||
<button mat-raised-button |
|||
fxFlexAlign="start" |
|||
(click)="addHTTPSecurity()"> |
|||
{{ 'gateway.rpc.add-security' | translate }} |
|||
</button> |
|||
</fieldset> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.REQUEST"> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label> |
|||
<input matInput formControlName="methodFilter" placeholder="echo"/> |
|||
</mat-form-field> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field class="mat-block" fxFlex="33"> |
|||
<mat-label>{{ 'gateway.rpc.httpMethod' | translate }}</mat-label> |
|||
<mat-select formControlName="httpMethod"> |
|||
<mat-option *ngFor="let method of hTTPMethods" [value]="method"> |
|||
{{ method }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'gateway.rpc.requestUrlExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="requestUrlExpression" placeholder="http://127.0.0.1:5000/my_devices"/> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="33"> |
|||
<mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label> |
|||
<input matInput formControlName="responseTimeout" type="number" |
|||
step="1" min="10" placeholder="10"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'gateway.rpc.timeout' | translate }}</mat-label> |
|||
<input matInput formControlName="timeout" type="number" |
|||
step="1" min="10" placeholder="10"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'gateway.rpc.tries' | translate }}</mat-label> |
|||
<input matInput formControlName="tries" type="number" |
|||
step="1" min="1" placeholder="1"/> |
|||
</mat-form-field> |
|||
</div> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.requestValueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="requestValueExpression" placeholder="${params}"/> |
|||
</mat-form-field> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.rpc.responseValueExpression' | translate }}</mat-label> |
|||
<input matInput formControlName="responseValueExpression" placeholder="${temp}"/> |
|||
</mat-form-field> |
|||
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders"> |
|||
<span class="fields-label">{{ 'gateway.rpc.httpHeaders' | translate }}</span> |
|||
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length"> |
|||
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"> |
|||
<span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span> |
|||
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span> |
|||
<span fxFlex="30px"></span> |
|||
</div> |
|||
<mat-divider></mat-divider> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" |
|||
*ngFor="let control of getFormArrayControls('httpHeaders'); let i = index"> |
|||
<ng-container [formGroupName]="i"> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput formControlName="headerName" placeholder="{{ 'gateway.rpc.set' | translate }}"/> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput formControlName="value"/> |
|||
</mat-form-field> |
|||
<mat-icon style="cursor:pointer;" |
|||
fxFlex="30px" |
|||
(click)="removeHTTPHeader(i)" |
|||
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete |
|||
</mat-icon> |
|||
</ng-container> |
|||
</div> |
|||
</div> |
|||
<button mat-raised-button |
|||
fxFlexAlign="start" |
|||
(click)="addHTTPHeader()"> |
|||
{{ 'gateway.rpc.add-header' | translate }} |
|||
</button> |
|||
</fieldset> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.OPCUA_ASYNCIO"> |
|||
<ng-container *ngTemplateOutlet="OPCUAForm"></ng-container> |
|||
</ng-template> |
|||
<ng-template [ngSwitchCase]="ConnectorType.OPCUA" #OPCUAForm> |
|||
<mat-form-field > |
|||
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label> |
|||
<input matInput formControlName="method" placeholder="multiply"/> |
|||
</mat-form-field> |
|||
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="arguments"> |
|||
<span class="fields-label">{{ 'gateway.rpc.arguments' | translate }}</span> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" |
|||
*ngFor="let control of getFormArrayControls('arguments'); let i = index"> |
|||
<mat-form-field appearance="outline" fxFlex> |
|||
<input matInput [formControl]="control" required/> |
|||
</mat-form-field> |
|||
<mat-icon style="cursor:pointer;" |
|||
fxFlex="30px" |
|||
(click)="removeOCPUAArguments(i)" |
|||
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete |
|||
</mat-icon> |
|||
</div> |
|||
<button mat-raised-button |
|||
fxFlexAlign="start" |
|||
(click)="addOCPUAArguments()"> |
|||
{{ 'gateway.rpc.add-argument' | translate }} |
|||
</button> |
|||
</fieldset> |
|||
</ng-template> |
|||
<ng-template ngSwitchDefault> |
|||
<mat-form-field> |
|||
<mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label> |
|||
<input matInput formControlName="command"/> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label>{{ 'widget-config.datasource-parameters' | translate }}</mat-label> |
|||
<input matInput formControlName="params" type="JSON" tb-json-to-string/> |
|||
<mat-icon class="material-icons-outlined" aria-hidden="false" aria-label="help-icon" |
|||
matIconSuffix style="cursor:pointer;" |
|||
(click)="openEditJSONDialog($event)" |
|||
matTooltip="{{ 'gateway.rpc-command-edit-params' | translate }}">edit |
|||
</mat-icon> |
|||
<mat-error *ngIf="commandForm.get('params').hasError('invalidJSON')"> |
|||
{{ 'gateway.rpc.json-value-invalid' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</ng-template> |
|||
</ng-container> |
|||
</ng-template> |
|||
<div class="template-actions" fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px"> |
|||
<button mat-raised-button |
|||
(click)="save()" |
|||
[disabled]="commandForm.invalid"> |
|||
{{ 'gateway.rpc-command-save-template' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
color="primary" |
|||
(click)="sendCommand.emit()" |
|||
[disabled]="commandForm.invalid"> |
|||
{{ 'gateway.rpc-command-send' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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]*(?<! )$/; |
|||
|
|||
@Component({ |
|||
selector: 'tb-gateway-service-rpc-connector', |
|||
templateUrl: './gateway-service-rpc-connector.component.html', |
|||
styleUrls: ['./gateway-service-rpc-connector.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => GatewayServiceRPCConnectorComponent), |
|||
multi: true |
|||
} |
|||
] |
|||
}) |
|||
export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor { |
|||
|
|||
@Input() |
|||
connectorType: ConnectorType; |
|||
|
|||
@Output() |
|||
sendCommand: EventEmitter<RPCCommand> = new EventEmitter(); |
|||
|
|||
@Output() |
|||
saveTemplate: EventEmitter<RPCTemplateConfig> = new EventEmitter(); |
|||
|
|||
commandForm: FormGroup; |
|||
isMQTTWithResponse: FormControl; |
|||
codesArray: Array<number> = [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, JsonObjectEditDialogData, object>(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}); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue