32 changed files with 2689 additions and 574 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,101 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
|
||||
|
Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
|
||||
|
NOTICE: All information contained herein is, and remains |
||||
|
the property of ThingsBoard, Inc. and its suppliers, |
||||
|
if any. The intellectual and technical concepts contained |
||||
|
herein are proprietary to ThingsBoard, Inc. |
||||
|
and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
patents in process, and are protected by trade secret or copyright law. |
||||
|
|
||||
|
Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
unless prior written permission is obtained from COMPANY. |
||||
|
|
||||
|
Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
explicitly covering such access. |
||||
|
|
||||
|
The copyright notice above does not evidence any actual or intended publication |
||||
|
or disclosure of this source code, which includes |
||||
|
information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
|
||||
|
--> |
||||
|
<div mat-dialog-content tb-toast fxLayout="column" toastTarget="dockerCommandDialogContent"> |
||||
|
<mat-button-toggle-group [formControl]="protocolCtrl" class="protocol-toggle"> |
||||
|
<mat-button-toggle value="mqtt" aria-label="mqtt"> |
||||
|
MQTT |
||||
|
</mat-button-toggle> |
||||
|
<mat-button-toggle value="coap" aria-label="coap"> |
||||
|
COAP |
||||
|
</mat-button-toggle> |
||||
|
<mat-button-toggle value="http" aria-label="html"> |
||||
|
HTTP |
||||
|
</mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
<span class="help-title">In order to publish telemetry data to (<a [href]="helpLink" target="_blank">ThingsBoard device</a>) with credentials of the current device you can use the following commands.</span> |
||||
|
<div class="mat-content" fxLayout="column"> |
||||
|
<div class="mat-content" fxLayout="column" *ngIf="protocolCtrl.value === 'mqtt'"> |
||||
|
<span>MQTT </span> |
||||
|
<span>1. Setup: <span [innerHTML]="mqttSetup"></span></span> |
||||
|
<span class="protocol-send-command">2. Send command:</span> |
||||
|
<div fxLayout="row wrap" fxLayoutAlign="start center"> |
||||
|
<pre class="tb-highlight" fxFlex><code class="pre-wrap">{{ mqttCode }}</code></pre> |
||||
|
<button mat-icon-button |
||||
|
color="primary" |
||||
|
ngxClipboard |
||||
|
cbContent="{{ mqttCode }}" |
||||
|
(cbOnSuccess)="onDockerCodeCopied()" |
||||
|
matTooltip="{{ 'device.transportCommandCopiedMessage' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="mat-content" fxLayout="column" *ngIf="protocolCtrl.value === 'coap'"> |
||||
|
<span>COAP </span> |
||||
|
<span>1. Setup: <span [innerHTML]="coapSetup"></span></span> |
||||
|
<span class="protocol-send-command">2. Send command: (based on CoAP cli)</span> |
||||
|
<div fxLayout="row wrap" fxLayoutAlign="start center"> |
||||
|
<pre class="tb-highlight" fxFlex><code class="pre-wrap">{{ coapCode }}</code></pre> |
||||
|
<button mat-icon-button |
||||
|
color="primary" |
||||
|
ngxClipboard |
||||
|
cbContent="{{ coapCode }}" |
||||
|
(cbOnSuccess)="onDockerCodeCopied()" |
||||
|
matTooltip="{{ 'device.transportCommandCopiedMessage' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
</button> |
||||
|
<pre class="tb-highlight" fxFlex="100"><code |
||||
|
class="pre-wrap">JSON example: {{'[{"temperature": 40}, {"isRainy": true}]'}}</code></pre> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="mat-content" fxLayout="column" *ngIf="protocolCtrl.value === 'http'"> |
||||
|
<span>HTTP </span> |
||||
|
<span>1. Setup: <span [innerHTML]="httpSetup"></span></span> |
||||
|
<span class="protocol-send-command">2. Send command:</span> |
||||
|
<div fxLayout="row wrpa"fxLayoutAlign="start center"> |
||||
|
<pre class="tb-highlight" fxFlex><code class="pre-wrap">{{ httpCode }}</code></pre> |
||||
|
<button mat-icon-button |
||||
|
color="primary" |
||||
|
ngxClipboard |
||||
|
cbContent="{{ httpCode }}" |
||||
|
(cbOnSuccess)="onDockerCodeCopied()" |
||||
|
matTooltip="{{ 'device.transportCommandCopiedMessage' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
* |
||||
|
* Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
* |
||||
|
* NOTICE: All information contained herein is, and remains |
||||
|
* the property of ThingsBoard, Inc. and its suppliers, |
||||
|
* if any. The intellectual and technical concepts contained |
||||
|
* herein are proprietary to ThingsBoard, Inc. |
||||
|
* and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
* patents in process, and are protected by trade secret or copyright law. |
||||
|
* |
||||
|
* Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
* unless prior written permission is obtained from COMPANY. |
||||
|
* |
||||
|
* Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
* managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
* explicitly covering such access. |
||||
|
* |
||||
|
* The copyright notice above does not evidence any actual or intended publication |
||||
|
* or disclosure of this source code, which includes |
||||
|
* information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
* ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
* OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
* THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
* AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
* THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
* DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
* OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
*/ |
||||
|
:host { |
||||
|
|
||||
|
mat-button-toggle-group, .protocol-title { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.protocol-toggle { |
||||
|
width: fit-content; |
||||
|
} |
||||
|
|
||||
|
.help-title { |
||||
|
padding-bottom: 5px; |
||||
|
} |
||||
|
|
||||
|
.protocol-send-command { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.pre-wrap { |
||||
|
white-space: pre-wrap; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,113 @@ |
|||||
|
///
|
||||
|
/// ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
|
||||
|
///
|
||||
|
/// Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved.
|
||||
|
///
|
||||
|
/// NOTICE: All information contained herein is, and remains
|
||||
|
/// the property of ThingsBoard, Inc. and its suppliers,
|
||||
|
/// if any. The intellectual and technical concepts contained
|
||||
|
/// herein are proprietary to ThingsBoard, Inc.
|
||||
|
/// and its suppliers and may be covered by U.S. and Foreign Patents,
|
||||
|
/// patents in process, and are protected by trade secret or copyright law.
|
||||
|
///
|
||||
|
/// Dissemination of this information or reproduction of this material is strictly forbidden
|
||||
|
/// unless prior written permission is obtained from COMPANY.
|
||||
|
///
|
||||
|
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
|
||||
|
/// managers or contractors who have executed Confidentiality and Non-disclosure agreements
|
||||
|
/// explicitly covering such access.
|
||||
|
///
|
||||
|
/// The copyright notice above does not evidence any actual or intended publication
|
||||
|
/// or disclosure of this source code, which includes
|
||||
|
/// information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
|
||||
|
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
|
||||
|
/// OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
|
||||
|
/// THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
|
||||
|
/// AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
|
||||
|
/// THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
|
||||
|
/// DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
|
||||
|
/// OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { FormControl } from '@angular/forms'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
||||
|
import { helpBaseUrl } from '@shared/models/constants'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-device-example-command', |
||||
|
templateUrl: './device-example-command.component.html', |
||||
|
styleUrls: ['./device-example-command.component.scss'] |
||||
|
}) |
||||
|
|
||||
|
export class DeviceExampleCommandComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
token: string; |
||||
|
|
||||
|
helpLink: string = helpBaseUrl + '/docs/reference/protocols/'; |
||||
|
|
||||
|
mqttCode: string; |
||||
|
|
||||
|
coapCode: string; |
||||
|
|
||||
|
httpCode: string; |
||||
|
|
||||
|
mqttSetup: string; |
||||
|
|
||||
|
coapSetup: string; |
||||
|
|
||||
|
httpSetup: string; |
||||
|
|
||||
|
protocolCtrl: FormControl; |
||||
|
|
||||
|
constructor(protected router: Router, |
||||
|
protected store: Store<AppState>, |
||||
|
private translate: TranslateService) { |
||||
|
} |
||||
|
|
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const HOST = window.location.hostname; |
||||
|
this.mqttCode = `mosquitto_pub -d -q 1 -h ${HOST} -t "v1/devices/me/telemetry" -u "${this.token}" -m "{"temperature":42}"`; |
||||
|
this.coapCode = `cat telemetry-data.json | coap post coap://${HOST}/api/v1/${this.token}/telemetry`; |
||||
|
this.httpCode = `curl -v -X POST --data "{"temperature":42,"humidity":73}" http://${HOST}/api/v1/${this.token}/telemetry --header "Content-Type:application/json"`; |
||||
|
|
||||
|
this.protocolCtrl = new FormControl('mqtt'); |
||||
|
const mqttLink = '<a href="https://thingsboard.io/docs/getting-started-guides/helloworld/?connectdevice=mqtt-windows"> Mqtt Guide</a>'; |
||||
|
|
||||
|
// @ts-ignore
|
||||
|
const platform = window.navigator?.userAgentData?.platform || window.navigator.platform, |
||||
|
macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], |
||||
|
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; |
||||
|
if (macosPlatforms.indexOf(platform) !== -1) { |
||||
|
this.mqttSetup = 'brew install mosquitto-clients'; |
||||
|
this.coapSetup = 'npm install coap-cli -g'; |
||||
|
this.httpSetup = 'brew install curl'; |
||||
|
} else if (windowsPlatforms.indexOf(platform) !== -1) { |
||||
|
this.mqttSetup = mqttLink; |
||||
|
this.coapSetup = 'npm install coap-cli -g'; |
||||
|
this.httpSetup = 'not required, available by default in windows 10+'; |
||||
|
} else if (/Linux/.test(platform)) { |
||||
|
this.mqttSetup = 'sudo apt-get install mosquitto-clients'; |
||||
|
this.coapSetup = 'npm install coap-cli -g'; |
||||
|
this.httpSetup = 'sudo apt-get install curl'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onDockerCodeCopied() { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{ |
||||
|
message: this.translate.instant('gateway.command-copied-message'), |
||||
|
type: 'success', |
||||
|
target: 'dockerCommandDialogContent', |
||||
|
duration: 1200, |
||||
|
verticalPosition: 'bottom', |
||||
|
horizontalPosition: 'left' |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
@ -1,55 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2022 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 mat-dialog-content tb-toast fxLayout="column" toastTarget="dockerCommandDialogContent"> |
|
||||
<span style="padding-bottom: 15px;">In order to run ThingsBoard IoT gateway (<a [href]="helpLink" target="_blank">ThingsBoard IoT gateway</a>) in docker with credentials for this device you can use the following commands.</span> |
|
||||
<div fxFlex fxLayout="row" style="justify-content: space-between"> |
|
||||
<mat-button-toggle-group [formControl]="selectedOSControll" aria-label="Favorite Color" style="width: max-content"> |
|
||||
<mat-button-toggle [value]="osTypes.windows"><span translate>gateway.windows </span></mat-button-toggle> |
|
||||
<mat-button-toggle [value]="osTypes.linux"><span translate>gateway.linux-macos </span></mat-button-toggle> |
|
||||
</mat-button-toggle-group> |
|
||||
<div class="tb-help" [tb-help]="helpLink"></div> |
|
||||
</div> |
|
||||
<div class="mat-content" fxLayout="column" *ngIf="selectedOSControll.value !== osTypes.windows"> |
|
||||
<div fxLayout="row" fxLayoutAlign="start center"> |
|
||||
<pre class="tb-highlight" fxFlex><code style="white-space: pre-wrap;">{{ linuxCode }}</code></pre> |
|
||||
<button mat-icon-button |
|
||||
color="primary" |
|
||||
ngxClipboard |
|
||||
cbContent="{{ linuxCode }}" |
|
||||
(cbOnSuccess)="onDockerCodeCopied()" |
|
||||
matTooltip="{{ 'gateway.copy-command' | translate }}" |
|
||||
matTooltipPosition="above"> |
|
||||
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="mat-content" fxLayout="column" *ngIf="selectedOSControll.value === osTypes.windows"> |
|
||||
<div fxLayout="row" fxLayoutAlign="start center"> |
|
||||
<pre class="tb-highlight" fxFlex><code style="white-space: pre-wrap;">{{ windowsCode }}</code></pre> |
|
||||
<button mat-icon-button |
|
||||
color="primary" |
|
||||
ngxClipboard |
|
||||
cbContent="{{ windowsCode }}" |
|
||||
(cbOnSuccess)="onDockerCodeCopied()" |
|
||||
matTooltip="{{ 'gateway.copy-command' | translate }}" |
|
||||
matTooltipPosition="above"> |
|
||||
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
@ -1,116 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2022 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, Input, OnInit} from '@angular/core'; |
|
||||
import { |
|
||||
FormControl, |
|
||||
} from '@angular/forms'; |
|
||||
import {Router} from "@angular/router"; |
|
||||
import {Store} from "@ngrx/store"; |
|
||||
import {AppState} from "@core/core.state"; |
|
||||
import {TranslateService} from "@ngx-translate/core"; |
|
||||
import {ActionNotificationShow} from "@core/notification/notification.actions"; |
|
||||
import {DeviceService} from "@core/http/device.service"; |
|
||||
import {helpBaseUrl} from "@shared/models/constants"; |
|
||||
|
|
||||
enum OsType { |
|
||||
linux = 'linux', |
|
||||
macos = 'macos', |
|
||||
windows = 'win' |
|
||||
} |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-gateway-command', |
|
||||
templateUrl: './device-gateway-command.component.html', |
|
||||
styleUrls: [] |
|
||||
}) |
|
||||
|
|
||||
export class DeviceGatewayCommandComponent implements OnInit { |
|
||||
|
|
||||
@Input() |
|
||||
token: string; |
|
||||
|
|
||||
@Input() |
|
||||
deviceId: string; |
|
||||
|
|
||||
linuxCode: string; |
|
||||
windowsCode: string; |
|
||||
selectedOSControll: FormControl; |
|
||||
osTypes = OsType; |
|
||||
helpLink: string = helpBaseUrl + '/docs/iot-gateway/install/docker-installation/'; |
|
||||
|
|
||||
constructor(protected router: Router, |
|
||||
protected store: Store<AppState>, |
|
||||
private translate: TranslateService, |
|
||||
private deviceService: DeviceService) { |
|
||||
} |
|
||||
|
|
||||
|
|
||||
ngOnInit(): void { |
|
||||
const HOST = window.location.hostname; |
|
||||
if (this.deviceId) { |
|
||||
this.deviceService.getDeviceCredentials(this.deviceId).subscribe(credentials=>{ |
|
||||
this.token = credentials.credentialsId; |
|
||||
this.createRunCode(HOST); |
|
||||
}) |
|
||||
} |
|
||||
this.selectedOSControll = new FormControl(''); |
|
||||
// @ts-ignore
|
|
||||
const platform = window.navigator?.userAgentData?.platform || window.navigator.platform, |
|
||||
macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], |
|
||||
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; |
|
||||
if (macosPlatforms.indexOf(platform) !== -1) { |
|
||||
this.selectedOSControll.setValue(OsType.macos); |
|
||||
} else if (windowsPlatforms.indexOf(platform) !== -1) { |
|
||||
this.selectedOSControll.setValue(OsType.windows); |
|
||||
} else if (/Linux/.test(platform)) { |
|
||||
this.selectedOSControll.setValue(OsType.linux); |
|
||||
} |
|
||||
this.createRunCode(HOST); |
|
||||
} |
|
||||
|
|
||||
createRunCode(HOST) { |
|
||||
this.linuxCode = "docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v " + |
|
||||
"~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --name tb-gateway -e host=" + |
|
||||
HOST + |
|
||||
" -e port=1883 -e accessToken=" + |
|
||||
this.token + |
|
||||
" --restart always thingsboard/tb-gateway"; |
|
||||
this.windowsCode = "docker run -it -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config -v " + |
|
||||
"%HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs " + |
|
||||
"--name tb-gateway -e host=" + |
|
||||
HOST + |
|
||||
" -e port=1883 -e accessToken=" + |
|
||||
this.token + |
|
||||
" --restart always thingsboard/tb-gateway"; |
|
||||
} |
|
||||
|
|
||||
goToDocumentationPage() { |
|
||||
window.open(this.helpLink, '_blank'); |
|
||||
} |
|
||||
|
|
||||
onDockerCodeCopied() { |
|
||||
this.store.dispatch(new ActionNotificationShow( |
|
||||
{ |
|
||||
message: this.translate.instant('gateway.command-copied-message'), |
|
||||
type: 'success', |
|
||||
target: 'dockerCommandDialogContent', |
|
||||
duration: 1200, |
|
||||
verticalPosition: 'bottom', |
|
||||
horizontalPosition: 'left' |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
@ -1,41 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2022 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. |
|
||||
|
|
||||
--> |
|
||||
<form style="min-width: 400px; position: relative;"> |
|
||||
<mat-toolbar color="primary"> |
|
||||
<h2 translate>gateway.command</h2> |
|
||||
<span fxFlex></span> |
|
||||
<button mat-button mat-icon-button |
|
||||
(click)="close()" |
|
||||
type="button"> |
|
||||
<mat-icon class="material-icons">close</mat-icon> |
|
||||
</button> |
|
||||
</mat-toolbar> |
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|
||||
</mat-progress-bar> |
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|
||||
<tb-gateway-command [token]="data.credentials.credentialsId"></tb-gateway-command> |
|
||||
<div mat-dialog-actions fxLayoutAlign="end center"> |
|
||||
<button mat-button color="primary" |
|
||||
type="button" |
|
||||
cdkFocusInitial |
|
||||
[disabled]="(isLoading$ | async)" |
|
||||
(click)="close()"> |
|
||||
{{ 'action.ok' | translate }} |
|
||||
</button> |
|
||||
</div> |
|
||||
</form> |
|
||||
@ -1,58 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2022 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, OnInit} from '@angular/core'; |
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; |
|
||||
import {Store} from '@ngrx/store'; |
|
||||
import {AppState} from '@core/core.state'; |
|
||||
import {Router} from '@angular/router'; |
|
||||
import {DialogComponent} from '@app/shared/components/dialog.component'; |
|
||||
import {TranslateService} from '@ngx-translate/core'; |
|
||||
import {Device, DeviceCredentials} from "@shared/models/device.models"; |
|
||||
|
|
||||
export interface GatewayCommandDialogData { |
|
||||
device: Device, |
|
||||
credentials: DeviceCredentials |
|
||||
} |
|
||||
|
|
||||
enum OsType { |
|
||||
linux = 'linux', |
|
||||
macos = 'macos', |
|
||||
windows = 'win' |
|
||||
} |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-gateway-command-dialog', |
|
||||
templateUrl: './gateway-command-dialog.component.html', |
|
||||
styleUrls: [] |
|
||||
}) |
|
||||
export class GatewayCommandDialogComponent extends DialogComponent<GatewayCommandDialogComponent> implements OnInit { |
|
||||
|
|
||||
constructor(protected router: Router, |
|
||||
protected store: Store<AppState>, |
|
||||
@Inject(MAT_DIALOG_DATA) public data: GatewayCommandDialogData, |
|
||||
public dialogRef: MatDialogRef<GatewayCommandDialogComponent, boolean>,) { |
|
||||
super(store, router, dialogRef); |
|
||||
} |
|
||||
|
|
||||
ngOnInit() { |
|
||||
} |
|
||||
|
|
||||
close(): void { |
|
||||
this.dialogRef.close(); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,130 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2022 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 { |
|
||||
DateEntityTableColumn, |
|
||||
EntityTableColumn, |
|
||||
EntityTableConfig |
|
||||
} from '@home/models/entity/entities-table-config.models'; |
|
||||
import {EntityType, EntityTypeResource} from '@shared/models/entity-type.models'; |
|
||||
import {TranslateService} from '@ngx-translate/core'; |
|
||||
import {DatePipe} from '@angular/common'; |
|
||||
import {Direction} from '@shared/models/page/sort-order'; |
|
||||
import {MatDialog} from '@angular/material/dialog'; |
|
||||
import {TimePageLink} from '@shared/models/page/page-link'; |
|
||||
import {Observable} from 'rxjs'; |
|
||||
import {emptyPageData, PageData} from '@shared/models/page/page-data'; |
|
||||
import {UtilsService} from '@core/services/utils.service'; |
|
||||
import {DeviceService} from "@core/http/device.service"; |
|
||||
import {AttributeService} from "@core/http/attribute.service"; |
|
||||
import {Device} from "@shared/models/device.models"; |
|
||||
import { |
|
||||
GatewayCommandDialogComponent, |
|
||||
GatewayCommandDialogData |
|
||||
} from "@home/components/gateway/gateway-command-dialog.component"; |
|
||||
import {ActionNotificationShow} from "@core/notification/notification.actions"; |
|
||||
import {Store} from "@ngrx/store"; |
|
||||
import {AppState} from "@core/core.state"; |
|
||||
import {map} from "rxjs/operators"; |
|
||||
import {EntityService} from "@core/http/entity.service"; |
|
||||
|
|
||||
export class GatewayListTableConfig extends EntityTableConfig<Device, TimePageLink> { |
|
||||
|
|
||||
constructor(protected store: Store<AppState>, |
|
||||
private deviceService: DeviceService, |
|
||||
private attributeService: AttributeService, |
|
||||
private entityService: EntityService, |
|
||||
private datePipe: DatePipe, |
|
||||
private translate: TranslateService, |
|
||||
private utils: UtilsService, |
|
||||
private dialog: MatDialog, |
|
||||
updateOnInit = true) { |
|
||||
super(); |
|
||||
this.loadDataOnInit = updateOnInit; |
|
||||
this.tableTitle = 'Gateway list'; |
|
||||
this.useTimePageLink = false; |
|
||||
this.pageMode = false; |
|
||||
this.displayPagination = false; |
|
||||
this.detailsPanelEnabled = false; |
|
||||
this.selectionEnabled = false; |
|
||||
this.searchEnabled = false; |
|
||||
this.addEnabled = false; |
|
||||
this.entitiesDeleteEnabled = false; |
|
||||
this.actionsColumnTitle = ''; |
|
||||
this.entityTranslations = { |
|
||||
noEntities: 'gateway.no-gateway-found', |
|
||||
search: 'gateway.gateway-search' |
|
||||
}; |
|
||||
this.entityResources = {} as EntityTypeResource<Device>; |
|
||||
|
|
||||
this.entitiesFetchFunction = () => this.fetchGateways(); |
|
||||
|
|
||||
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
|
||||
|
|
||||
this.columns.push( |
|
||||
new DateEntityTableColumn<Device>('createdTime', 'gateway.created-time', this.datePipe, '150px')); |
|
||||
|
|
||||
this.columns.push( |
|
||||
new EntityTableColumn<Device>('entityName', 'gateway.gateway-name', '20%', |
|
||||
(entity => this.utils.customTranslation(entity.name, entity.name)) |
|
||||
) |
|
||||
); |
|
||||
|
|
||||
this.cellActionDescriptors.push( |
|
||||
{ |
|
||||
name: this.translate.instant('gateway.command'), |
|
||||
icon: 'vpn_key', |
|
||||
isEnabled: () => true, |
|
||||
onAction: ($event, entity) => this.showGatewayDockerCommand(entity) |
|
||||
} |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
showGatewayDockerCommand(entity: Device) { |
|
||||
this.deviceService.getDeviceCredentials(entity.id.id).subscribe(credentials => { |
|
||||
this.dialog.open<GatewayCommandDialogComponent, GatewayCommandDialogData, boolean> |
|
||||
(GatewayCommandDialogComponent, |
|
||||
{ |
|
||||
disableClose: true, |
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|
||||
data: { |
|
||||
credentials: credentials, |
|
||||
device: entity |
|
||||
} |
|
||||
}).afterClosed().subscribe( |
|
||||
(res) => { |
|
||||
if (res) { |
|
||||
this.updateData(); |
|
||||
} |
|
||||
} |
|
||||
); |
|
||||
}, error => { |
|
||||
const messageToShow = `<div>${error}</div>`; |
|
||||
this.store.dispatch(new ActionNotificationShow({message: messageToShow, type: 'error'})); |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
fetchGateways(): Observable<PageData<Device>> { |
|
||||
return this.entityService.getEntitiesByNameFilter(EntityType.DEVICE, "", -1).pipe( |
|
||||
map((array: Array<Device>) => { |
|
||||
const pageData = emptyPageData<Device>(); |
|
||||
pageData.data = array.filter(device => device.additionalInfo?.gateway); |
|
||||
pageData.totalPages = 1; |
|
||||
pageData.totalElements = array.length; |
|
||||
return pageData; |
|
||||
})) |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2022 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. |
|
||||
|
|
||||
--> |
|
||||
<tb-entities-table [entitiesTableConfig]="gatewayListTableConfig"></tb-entities-table> |
|
||||
@ -1,40 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2022 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
:host { |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
display: block; |
|
||||
.tb-table-widget { |
|
||||
.table-container { |
|
||||
position: relative; |
|
||||
} |
|
||||
.mat-table { |
|
||||
.mat-row { |
|
||||
&.invisible { |
|
||||
visibility: hidden; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
span.no-data-found { |
|
||||
position: absolute; |
|
||||
top: 60px; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@ -1,63 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2022 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, OnInit} from '@angular/core'; |
|
||||
import {UtilsService} from '@core/services/utils.service'; |
|
||||
import {TranslateService} from '@ngx-translate/core'; |
|
||||
import {DeviceService} from '@core/http/device.service'; |
|
||||
import {AttributeService} from '@core/http/attribute.service'; |
|
||||
import {GatewayListTableConfig} from "@home/components/gateway/gateway-list-table-config"; |
|
||||
import {DatePipe} from "@angular/common"; |
|
||||
import {MatDialog} from "@angular/material/dialog"; |
|
||||
import {Store} from "@ngrx/store"; |
|
||||
import {AppState} from "@core/core.state"; |
|
||||
import {EntityService} from "@core/http/entity.service"; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-gateway-list', |
|
||||
templateUrl: './gateway-list.component.html', |
|
||||
styleUrls: ['./gateway-list.component.scss'] |
|
||||
}) |
|
||||
|
|
||||
|
|
||||
export class GatewayListComponent implements OnInit { |
|
||||
gatewayListTableConfig: GatewayListTableConfig; |
|
||||
|
|
||||
constructor( |
|
||||
protected store: Store<AppState>, |
|
||||
private utils: UtilsService, |
|
||||
private translate: TranslateService, |
|
||||
private datePipe: DatePipe, |
|
||||
private deviceService: DeviceService, |
|
||||
private entityService: EntityService, |
|
||||
private attributeService: AttributeService, |
|
||||
private dialog: MatDialog, |
|
||||
) { |
|
||||
} |
|
||||
|
|
||||
ngOnInit(): void { |
|
||||
this.gatewayListTableConfig = new GatewayListTableConfig( |
|
||||
this.store, |
|
||||
this.deviceService, |
|
||||
this.attributeService, |
|
||||
this.entityService, |
|
||||
this.datePipe, |
|
||||
this.translate, |
|
||||
this.utils, |
|
||||
this.dialog |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,70 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
|
||||
|
Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
|
||||
|
NOTICE: All information contained herein is, and remains |
||||
|
the property of ThingsBoard, Inc. and its suppliers, |
||||
|
if any. The intellectual and technical concepts contained |
||||
|
herein are proprietary to ThingsBoard, Inc. |
||||
|
and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
patents in process, and are protected by trade secret or copyright law. |
||||
|
|
||||
|
Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
unless prior written permission is obtained from COMPANY. |
||||
|
|
||||
|
Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
explicitly covering such access. |
||||
|
|
||||
|
The copyright notice above does not evidence any actual or intended publication |
||||
|
or disclosure of this source code, which includes |
||||
|
information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
|
||||
|
--> |
||||
|
<div mat-dialog-content tb-toast fxLayout="column" toastTarget="dockerCommandDialogContent"> |
||||
|
<span style="padding-bottom: 15px;">In order to run ThingsBoard IoT gateway (<a [href]="helpLink" target="_blank">ThingsBoard IoT gateway</a>) in docker with credentials for this device you can use the following commands.</span> |
||||
|
<div fxFlex fxLayout="row" style="justify-content: space-between"> |
||||
|
<mat-button-toggle-group [formControl]="selectedOSCControl" aria-label="Favorite Color" style="width: max-content; flex-grow: 0"> |
||||
|
<mat-button-toggle [value]="osTypes.windows"><span translate>gateway.windows </span></mat-button-toggle> |
||||
|
<mat-button-toggle [value]="osTypes.linux"><span translate>gateway.linux-macos </span></mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
<div class="tb-help" [tb-help]="helpLink"></div> |
||||
|
</div> |
||||
|
<div class="mat-content" fxLayout="column" *ngIf="selectedOSCControl.value !== osTypes.windows"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<pre class="tb-highlight" fxFlex><code style="white-space: pre-wrap;">{{ linuxCode }}</code></pre> |
||||
|
<button mat-icon-button |
||||
|
color="primary" |
||||
|
ngxClipboard |
||||
|
cbContent="{{ linuxCode }}" |
||||
|
(cbOnSuccess)="onDockerCodeCopied()" |
||||
|
matTooltip="{{ 'gateway.copy-command' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="mat-content" fxLayout="column" *ngIf="selectedOSCControl.value === osTypes.windows"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<pre class="tb-highlight" fxFlex><code style="white-space: pre-wrap;">{{ windowsCode }}</code></pre> |
||||
|
<button mat-icon-button |
||||
|
color="primary" |
||||
|
ngxClipboard |
||||
|
cbContent="{{ windowsCode }}" |
||||
|
(cbOnSuccess)="onDockerCodeCopied()" |
||||
|
matTooltip="{{ 'gateway.copy-command' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,125 @@ |
|||||
|
///
|
||||
|
/// ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
|
||||
|
///
|
||||
|
/// Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved.
|
||||
|
///
|
||||
|
/// NOTICE: All information contained herein is, and remains
|
||||
|
/// the property of ThingsBoard, Inc. and its suppliers,
|
||||
|
/// if any. The intellectual and technical concepts contained
|
||||
|
/// herein are proprietary to ThingsBoard, Inc.
|
||||
|
/// and its suppliers and may be covered by U.S. and Foreign Patents,
|
||||
|
/// patents in process, and are protected by trade secret or copyright law.
|
||||
|
///
|
||||
|
/// Dissemination of this information or reproduction of this material is strictly forbidden
|
||||
|
/// unless prior written permission is obtained from COMPANY.
|
||||
|
///
|
||||
|
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
|
||||
|
/// managers or contractors who have executed Confidentiality and Non-disclosure agreements
|
||||
|
/// explicitly covering such access.
|
||||
|
///
|
||||
|
/// The copyright notice above does not evidence any actual or intended publication
|
||||
|
/// or disclosure of this source code, which includes
|
||||
|
/// information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
|
||||
|
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
|
||||
|
/// OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
|
||||
|
/// THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
|
||||
|
/// AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
|
||||
|
/// THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
|
||||
|
/// DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
|
||||
|
/// OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { FormControl } from '@angular/forms'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
||||
|
import { DeviceService } from '@core/http/device.service'; |
||||
|
import { helpBaseUrl } from '@shared/models/constants'; |
||||
|
|
||||
|
enum OsType { |
||||
|
linux = 'linux', |
||||
|
macos = 'macos', |
||||
|
windows = 'win' |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-gateway-command', |
||||
|
templateUrl: './device-gateway-command.component.html', |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
|
||||
|
export class DeviceGatewayCommandComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
token: string; |
||||
|
|
||||
|
@Input() |
||||
|
deviceId: string; |
||||
|
|
||||
|
linuxCode: string; |
||||
|
windowsCode: string; |
||||
|
selectedOSCControl: FormControl; |
||||
|
osTypes = OsType; |
||||
|
helpLink: string = helpBaseUrl + '/docs/iot-gateway/install/docker-installation/'; |
||||
|
|
||||
|
constructor(protected router: Router, |
||||
|
protected store: Store<AppState>, |
||||
|
private translate: TranslateService, |
||||
|
private deviceService: DeviceService) { |
||||
|
} |
||||
|
|
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const HOST = window.location.hostname; |
||||
|
if (this.deviceId) { |
||||
|
this.deviceService.getDeviceCredentials(this.deviceId).subscribe(credentials => { |
||||
|
this.token = credentials.credentialsId; |
||||
|
this.createRunCode(HOST); |
||||
|
}); |
||||
|
} |
||||
|
this.selectedOSCControl = new FormControl(''); |
||||
|
// @ts-ignore
|
||||
|
const platform = window.navigator?.userAgentData?.platform || window.navigator.platform, |
||||
|
macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], |
||||
|
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; |
||||
|
if (macosPlatforms.indexOf(platform) !== -1) { |
||||
|
this.selectedOSCControl.setValue(OsType.macos); |
||||
|
} else if (windowsPlatforms.indexOf(platform) !== -1) { |
||||
|
this.selectedOSCControl.setValue(OsType.windows); |
||||
|
} else if (/Linux/.test(platform)) { |
||||
|
this.selectedOSCControl.setValue(OsType.linux); |
||||
|
} |
||||
|
this.createRunCode(HOST); |
||||
|
} |
||||
|
|
||||
|
createRunCode(HOST) { |
||||
|
this.linuxCode = 'docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ' + |
||||
|
'~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --name tb-gateway -e host=' + |
||||
|
HOST + |
||||
|
' -e port=1883 -e accessToken=' + |
||||
|
this.token + |
||||
|
' --restart always thingsboard/tb-gateway'; |
||||
|
this.windowsCode = 'docker run -it -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config -v ' + |
||||
|
'%HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs ' + |
||||
|
'--name tb-gateway -e host=' + |
||||
|
HOST + |
||||
|
' -e port=1883 -e accessToken=' + |
||||
|
this.token + |
||||
|
' --restart always thingsboard/tb-gateway'; |
||||
|
} |
||||
|
|
||||
|
onDockerCodeCopied() { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{ |
||||
|
message: this.translate.instant('gateway.command-copied-message'), |
||||
|
type: 'success', |
||||
|
target: 'dockerCommandDialogContent', |
||||
|
duration: 1200, |
||||
|
verticalPosition: 'bottom', |
||||
|
horizontalPosition: 'left' |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,683 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
|
||||
|
Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
|
||||
|
NOTICE: All information contained herein is, and remains |
||||
|
the property of ThingsBoard, Inc. and its suppliers, |
||||
|
if any. The intellectual and technical concepts contained |
||||
|
herein are proprietary to ThingsBoard, Inc. |
||||
|
and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
patents in process, and are protected by trade secret or copyright law. |
||||
|
|
||||
|
Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
unless prior written permission is obtained from COMPANY. |
||||
|
|
||||
|
Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
explicitly covering such access. |
||||
|
|
||||
|
The copyright notice above does not evidence any actual or intended publication |
||||
|
or disclosure of this source code, which includes |
||||
|
information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="gatewayConfigGroup" style="height: 100%" fxLayout="column"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2 translate>gateway.gateway-configuration</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
type="button" |
||||
|
(click)="cancel()" |
||||
|
*ngIf="dialogRef"> |
||||
|
<mat-icon class="material-icons" style="color:#000;">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-tab-group style="max-height: calc(100% - 115px)"> |
||||
|
<mat-tab label="{{'gateway.thingsboard-general' | translate}}"> |
||||
|
<div formGroupName="thingsboard" fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-accordion multi> |
||||
|
<mat-expansion-panel expanded="true" disabled style="padding-top: 20px"> |
||||
|
<div fxLayout="row wrap"> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="remoteConfiguration"> |
||||
|
{{ 'gateway.remote-configuration' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="remoteShell"> |
||||
|
{{ 'gateway.remote-shell' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.thingsboard-host</mat-label> |
||||
|
<input matInput formControlName="host"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.host').hasError('required')"> |
||||
|
{{ 'gateway.thingsboard-host-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.thingsboard-port</mat-label> |
||||
|
<input matInput formControlName="port" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('required')"> |
||||
|
{{'gateway.thingsboard-port-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('min')"> |
||||
|
{{'gateway.thingsboard-port-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('max')"> |
||||
|
{{'gateway.thingsboard-port-max' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('pattern')"> |
||||
|
{{'gateway.thingsboard-port-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
|
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
<mat-expansion-panel expanded="true" disabled> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title translate class="expansion-panel-header">security.security</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<div fxLayout="row wrap" formGroupName="security"> |
||||
|
<mat-button-toggle-group class="security-toggle-group" formControlName="type" fxFlex="100"> |
||||
|
<mat-button-toggle *ngFor="let securityType of securityTypes | keyvalue" [value]="securityType.key"> |
||||
|
{{ securityType.value | translate }} |
||||
|
</mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
<mat-form-field |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value.toLowerCase().includes('accesstoken')" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>security.access-token</mat-label> |
||||
|
<input matInput formControlName="accessToken"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.accessToken').hasError('required')"> |
||||
|
{{'security.access-token-required' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>security.clientId</mat-label> |
||||
|
<input matInput formControlName="clientId"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.clientId').hasError('required')"> |
||||
|
{{'security.clientId-required' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>security.username</mat-label> |
||||
|
<input matInput formControlName="username"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.username').hasError('required')"> |
||||
|
{{'security.username-required' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>security.password</mat-label> |
||||
|
<input matInput formControlName="password"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.password').hasError('required')"> |
||||
|
{{'security.password-required' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-file-input |
||||
|
fxFlex="100" |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value.toLowerCase().includes('tls')" |
||||
|
formControlName="caCert" |
||||
|
label="{{'security.ca-cert-required' | translate}}" |
||||
|
[allowedExtensions]="'pem, cert, key'" |
||||
|
[accept]="'.pem, application/pem,.cert, application/cert, .key,application/key'" |
||||
|
dropLabel="{{'gateway.drop-file' | translate}}"> |
||||
|
</tb-file-input> |
||||
|
<tb-file-input |
||||
|
fxFlex="100" |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'tlsPrivateKey'" |
||||
|
formControlName="cert" |
||||
|
label="{{'security.cert' | translate}}" |
||||
|
[allowedExtensions]="'pem, cert, key'" |
||||
|
[accept]="'.pem, application/pem,.cert, application/cert, .key,application/key'" |
||||
|
dropLabel="{{'gateway.drop-file' | translate}}"> |
||||
|
</tb-file-input> |
||||
|
<tb-file-input |
||||
|
fxFlex="100" |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'tlsPrivateKey'" |
||||
|
formControlName="privateKey" |
||||
|
label="{{'security.private-key' | translate}}" |
||||
|
[allowedExtensions]="'pem, cert, key'" |
||||
|
[accept]="'.pem, application/pem,.cert, application/cert, .key,application/key'" |
||||
|
dropLabel="{{'gateway.drop-file' | translate}}"> |
||||
|
</tb-file-input> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{'gateway.logs.logs' | translate}}"> |
||||
|
<div formGroupName="logs" fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-accordion multi> |
||||
|
<mat-expansion-panel expanded="true" disabled style="padding-top: 20px"> |
||||
|
<div fxLayout="row wrap"> |
||||
|
<mat-form-field fxFlex="100" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.logs.date-format</mat-label> |
||||
|
<input matInput formControlName="dateFormat"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('logs.dateFormat').hasError('required')"> |
||||
|
{{'gateway.logs.date-format-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="100" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.logs.log-format</mat-label> |
||||
|
<textarea matInput formControlName="logFormat" rows="2"></textarea> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('logs.logFormat').hasError('required')"> |
||||
|
{{'gateway.logs.log-format-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<label fxFlex="100" class="logs-label" translate>gateway.logs.log-type</label> |
||||
|
<mat-button-toggle-group class="logs-toggle-group" #logging="matButtonToggleGroup" value="remote" style="margin-bottom: 15px"> |
||||
|
<mat-button-toggle value="remote" |
||||
|
aria-label="Remote logging"> {{'gateway.logs.remote' | translate}}</mat-button-toggle> |
||||
|
<mat-button-toggle value="local" |
||||
|
aria-label="Local logging">{{'gateway.logs.local' | translate}} </mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
<div fxFlex="100" fxLayout="column" formGroupName="remote" *ngIf="logging.value === 'remote'"> |
||||
|
<mat-slide-toggle color="primary" formControlName="enabled"> |
||||
|
{{ 'gateway.logs.remote-logs' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-form-field class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.logs.level</mat-label> |
||||
|
<mat-select formControlName="logLevel"> |
||||
|
<mat-option *ngFor="let logLevel of gatewayLogLevel" [value]="logLevel">{{logLevel}}</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxFlex="100" fxLayout="row wrap" formGroupName="local" *ngIf="logging.value === 'local'"> |
||||
|
<mat-button-toggle-group class="security-toggle-group" [formControl]="logSelector" fxFlex="100" |
||||
|
name="logSelector"> |
||||
|
<mat-button-toggle *ngFor="let logConfig of localLogsConfigs" [value]="logConfig" |
||||
|
class="first-capital"> |
||||
|
{{ logConfig }} |
||||
|
</mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
<div fxFlex="100" fxLayout="row wrap" [formGroup]="getLogFormGroup(logSelector.value)"> |
||||
|
<mat-form-field class="mat-block tb-value-type" fxFlex="calc(50%-15px)"> |
||||
|
<mat-label translate>gateway.logs.level</mat-label> |
||||
|
<mat-select formControlName="logLevel"> |
||||
|
<mat-option *ngFor="let logLevel of gatewayLogLevel" [value]="logLevel">{{logLevel}}</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block tb-value-type" fxFlex="calc(50%-15px)"> |
||||
|
<mat-label translate>gateway.logs.file-path</mat-label> |
||||
|
<input matInput formControlName="filePath"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.filePath').hasError('required')"> |
||||
|
{{'gateway.logs.file-path-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block tb-value-type" fxFlex="calc(35%-30px)"> |
||||
|
<mat-label translate>gateway.logs.saving-period</mat-label> |
||||
|
<input matInput formControlName="savingTime" type="number"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.savingTime').hasError('required')"> |
||||
|
{{'gateway.logs.saving-period-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.savingTime').hasError('min')"> |
||||
|
{{'gateway.logs.saving-period-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block tb-value-type" fxFlex="calc(15%-30px)"> |
||||
|
<mat-label translate></mat-label> |
||||
|
<mat-select formControlName="savingPeriod"> |
||||
|
<mat-option *ngFor="let period of logSavingPeriods | keyvalue" [value]="period.key"> |
||||
|
{{period.value | translate}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block tb-value-type" fxFlex="calc(50%-30px)"> |
||||
|
<mat-label translate>gateway.logs.backup-count</mat-label> |
||||
|
<input matInput formControlName="backupCount" type="number"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('required')"> |
||||
|
{{'gateway.logs.backup-count-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('min')"> |
||||
|
{{'gateway.logs.backup-count-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{'gateway.storage' | translate}}"> |
||||
|
<div formGroupName="storage" fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-accordion> |
||||
|
<mat-expansion-panel expanded="true" disabled> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title translate class="expansion-panel-header">gateway.storage</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<div fxLayout="row wrap"> |
||||
|
<mat-button-toggle-group class="security-toggle-group" formControlName="type" fxFlex="100"> |
||||
|
<mat-button-toggle *ngFor="let storageType of storageTypes | keyvalue" [value]="storageType.key"> |
||||
|
{{ storageType.value | translate }} |
||||
|
</mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'memory'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-read-record-count</mat-label> |
||||
|
<input matInput formControlName="read_records_count"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.read_records_count').hasError('required')"> |
||||
|
{{'gateway.storage-read-record-count-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.read_records_count').hasError('min')"> |
||||
|
{{'gateway.storage-read-record-count-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.read_records_count').hasError('pattern')"> |
||||
|
{{'gateway.storage-read-record-count-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'memory'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-max-records</mat-label> |
||||
|
<input matInput formControlName="max_records_count"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_count').hasError('required')"> |
||||
|
{{'gateway.storage-max-records-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_count').hasError('min')"> |
||||
|
{{'gateway.storage-max-records-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_count').hasError('pattern')"> |
||||
|
{{'gateway.storage-max-records-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'file'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-data-folder-path</mat-label> |
||||
|
<input matInput formControlName="data_folder_path"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.data_folder_path').hasError('required')"> |
||||
|
{{'gateway.storage-data-folder-path-required' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'file'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-max-files</mat-label> |
||||
|
<input matInput formControlName="max_file_count"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_file_count').hasError('required')"> |
||||
|
{{'gateway.storage-max-files-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_file_count').hasError('min')"> |
||||
|
{{'gateway.storage-max-files-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_file_count').hasError('pattern')"> |
||||
|
{{'gateway.storage-max-files-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'file'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-max-read-record-count</mat-label> |
||||
|
<input matInput formControlName="max_read_records_count"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_read_records_count').hasError('required')"> |
||||
|
{{'gateway.storage-max-read-record-count-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_read_records_count').hasError('min')"> |
||||
|
{{'gateway.storage-max-read-record-count-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_read_records_count').hasError('pattern')"> |
||||
|
{{'gateway.storage-max-read-record-count-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'file'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-max-file-records</mat-label> |
||||
|
<input matInput formControlName="max_records_per_file"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_per_file').hasError('required')"> |
||||
|
{{'gateway.storage-max-records-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_per_file').hasError('min')"> |
||||
|
{{'gateway.storage-max-records-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_per_file').hasError('pattern')"> |
||||
|
{{'gateway.storage-max-records-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'sqlite'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.storage-path</mat-label> |
||||
|
<input matInput formControlName="data_file_path"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.data_file_path').hasError('required')"> |
||||
|
{{'gateway.storage-path-required' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'sqlite'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.messages-ttl-check-in-hours</mat-label> |
||||
|
<input matInput formControlName="messages_ttl_check_in_hours"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_check_in_hours').hasError('required')"> |
||||
|
{{'gateway.messages-ttl-check-in-hours-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_check_in_hours').hasError('min')"> |
||||
|
{{'gateway.messages-ttl-check-in-hours-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_check_in_hours').hasError('pattern')"> |
||||
|
{{'gateway.messages-ttl-check-in-hours-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="gatewayConfigGroup.get('storage.type').value === 'sqlite'" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type" style="flex-grow: 0"> |
||||
|
<mat-label translate>gateway.messages-ttl-in-days</mat-label> |
||||
|
<input matInput formControlName="messages_ttl_in_days"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_in_days').hasError('required')"> |
||||
|
{{'gateway.messages-ttl-in-days-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_in_days').hasError('min')"> |
||||
|
{{'gateway.messages-ttl-in-days-min' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_in_days').hasError('pattern')"> |
||||
|
{{'gateway.messages-ttl-in-days-pattern' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{'gateway.grpc' | translate}}"> |
||||
|
<div formGroupName="grpc" fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-accordion multi> |
||||
|
<mat-expansion-panel expanded="true" disabled style="padding-top: 20px"> |
||||
|
<div fxLayout="row wrap"> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="enabled"> |
||||
|
{{ 'gateway.grpc' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="keepalivePermitWithoutCalls"> |
||||
|
{{ 'gateway.permit-without-calls' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.server-port</mat-label> |
||||
|
<input matInput formControlName="serverPort" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('required')"> |
||||
|
{{'gateway.thingsboard-port-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('min')"> |
||||
|
{{'gateway.thingsboard-port-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('max')"> |
||||
|
{{'gateway.thingsboard-port-max' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('pattern')"> |
||||
|
{{'gateway.thingsboard-port-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.grpc-keep-alive-timeout</mat-label> |
||||
|
<input matInput formControlName="keepAliveTimeoutMs" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeoutMs').hasError('required')"> |
||||
|
{{'gateway.grpc-keep-alive-timeout-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeoutMs').hasError('min')"> |
||||
|
{{'gateway.grpc-keep-alive-timeout-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeoutMs').hasError('pattern')"> |
||||
|
{{'gateway.grpc-keep-alive-timeout-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.grpc-keep-alive</mat-label> |
||||
|
<input matInput formControlName="keepAliveTimeMs" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeMs').hasError('required')"> |
||||
|
{{'gateway.grpc-keep-alive-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeMs').hasError('min')"> |
||||
|
{{'gateway.grpc-keep-alive-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeMs').hasError('pattern')"> |
||||
|
{{'gateway.grpc-keep-alive-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.grpc-min-time-between-pings</mat-label> |
||||
|
<input matInput formControlName="minTimeBetweenPingsMs" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minTimeBetweenPingsMs').hasError('required')"> |
||||
|
{{'gateway.grpc-min-time-between-pings-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minTimeBetweenPingsMs').hasError('min')"> |
||||
|
{{'gateway.grpc-min-time-between-pings-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minTimeBetweenPingsMs').hasError('pattern')"> |
||||
|
{{'gateway.grpc-min-time-between-pings-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.grpc-max-pings-without-data</mat-label> |
||||
|
<input matInput formControlName="maxPingsWithoutData" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.maxPingsWithoutData').hasError('required')"> |
||||
|
{{'gateway.grpc-max-pings-without-data-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.maxPingsWithoutData').hasError('min')"> |
||||
|
{{'gateway.grpc-max-pings-without-data-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.maxPingsWithoutData').hasError('pattern')"> |
||||
|
{{'gateway.grpc-max-pings-without-data-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.grpc-min-ping-interval-without-data</mat-label> |
||||
|
<input matInput formControlName="minPingIntervalWithoutDataMs" type="number"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('required')"> |
||||
|
{{'gateway.grpc-min-ping-interval-without-data-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('min')"> |
||||
|
{{'gateway.grpc-min-ping-interval-without-data-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('pattern')"> |
||||
|
{{'gateway.grpc-min-ping-interval-without-data-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{'gateway.statistics.statistics' | translate}}"> |
||||
|
<div formGroupName="thingsboard" fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-accordion multi> |
||||
|
<mat-expansion-panel expanded="true" disabled style="padding-top: 20px"> |
||||
|
<div fxLayout="row wrap" formGroupName="statistics"> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="enable"> |
||||
|
{{ 'gateway.statistics.statistics' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.statistics.send-period</mat-label> |
||||
|
<input matInput formControlName="statsSendPeriodInSeconds" type="number"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('required')"> |
||||
|
{{'gateway.statistics.send-period-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('min')"> |
||||
|
{{'gateway.statistics.send-period-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('pattern')"> |
||||
|
{{'gateway.statistics.send-period-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.statistics.check-connectors-configuration</mat-label> |
||||
|
<input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.checkConnectorsConfigurationInSeconds').hasError('required')"> |
||||
|
{{'gateway.statistics.check-connectors-configuration-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.checkConnectorsConfigurationInSeconds').hasError('min')"> |
||||
|
{{'gateway.statistics.check-connectors-configuration-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.checkConnectorsConfigurationInSeconds').hasError('pattern')"> |
||||
|
{{'gateway.statistics.check-connectors-configuration-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
<mat-expansion-panel expanded="true" disabled> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title translate class="expansion-panel-header">gateway.statistics.statistics</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<div fxLayout="column" formGroupName="statistics"> |
||||
|
<div fxLayout="row" formArrayName="commands" |
||||
|
*ngFor="let commandControl of commandFormArray().controls; let $index = index" |
||||
|
style="margin-bottom: 15px"> |
||||
|
<mat-card fxLayout="row wrap" style="margin-bottom: 15px" [formGroupName]="$index"> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.statistics.attribute-name</mat-label> |
||||
|
<input matInput formControlName="attributeName"/> |
||||
|
<mat-error |
||||
|
*ngIf="commandControl.get('attributeName').hasError('required')"> |
||||
|
{{'gateway.statistics.attribute-name-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.statistics.timeout</mat-label> |
||||
|
<input matInput formControlName="timeout" type="number"/> |
||||
|
<mat-error |
||||
|
*ngIf="commandControl.get('timeout').hasError('required')"> |
||||
|
{{'gateway.statistics.timeout-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="commandControl.get('timeout').hasError('min')"> |
||||
|
{{'gateway.statistics.timeout-min' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="commandControl.get('timeout').hasError('pattern')"> |
||||
|
{{'gateway.statistics.timeout-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type" style="flex-grow: 0"> |
||||
|
<mat-label translate>gateway.statistics.command</mat-label> |
||||
|
<input matInput formControlName="command"/> |
||||
|
<mat-error |
||||
|
*ngIf="commandControl.get('command').hasError('required')"> |
||||
|
{{'gateway.statistics.command-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</mat-card> |
||||
|
<button mat-icon-button (click)="removeCommandControl($index)" |
||||
|
[disabled]="!gatewayConfigGroup.get('thingsboard.remoteConfiguration').value" |
||||
|
matTooltip="{{ 'gateway.statistics.remove' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<button mat-stroked-button color="primary" |
||||
|
style="width: fit-content;" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
[disabled]="!gatewayConfigGroup.get('thingsboard.remoteConfiguration').value" |
||||
|
(click)="addCommand()"> |
||||
|
{{ 'gateway.statistics.add' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{'gateway.other' | translate}}"> |
||||
|
<div formGroupName="thingsboard" fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-accordion multi> |
||||
|
<mat-expansion-panel expanded="true" disabled formGroupName="checkingDeviceActivity"> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title class="expansion-panel-header"> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="checkDeviceInactivity"> |
||||
|
{{ 'gateway.checking-device-activity' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<div fxLayout="row wrap"> |
||||
|
<mat-form-field |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.inactivity-timeout-seconds</mat-label> |
||||
|
<input matInput formControlName="inactivityTimeoutSeconds" type="number"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('required')"> |
||||
|
{{'gateway.inactivity-timeout-seconds-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('min')"> |
||||
|
{{'gateway.inactivity-timeout-seconds-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value" |
||||
|
fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.inactivity-check-period-seconds</mat-label> |
||||
|
<input matInput formControlName="inactivityCheckPeriodSeconds"/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('required')"> |
||||
|
{{'gateway.inactivity-check-period-seconds-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('min')"> |
||||
|
{{'gateway.inactivity-check-period-seconds-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
<mat-expansion-panel expanded="true" disabled > |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title translate class="expansion-panel-header">gateway.advanced</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<div fxLayout="row wrap"> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.min-pack-send-delay</mat-label> |
||||
|
<input matInput formControlName="minPackSendDelayMS" type="number" min="0"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('required')"> |
||||
|
{{ 'gateway.min-pack-send-delay-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('min')"> |
||||
|
{{ 'gateway.min-pack-send-delay-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)" class="mat-block tb-value-type"> |
||||
|
<mat-label translate>gateway.mqtt-qos</mat-label> |
||||
|
<input matInput formControlName="qos" type="number" min="0" max="2"/> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.qos').hasError('required')"> |
||||
|
{{ 'gateway.mqtt-qos-required' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.qos').hasError('min')"> |
||||
|
{{ 'gateway.mqtt-qos-range' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.qos').hasError('max')"> |
||||
|
{{ 'gateway.mqtt-qos-range' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
|
||||
|
</mat-accordion> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
</mat-tab-group> |
||||
|
<div fxFlex></div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="gatewayConfigGroup.invalid || !gatewayConfigGroup.dirty" |
||||
|
cdkFocusInitial |
||||
|
(click)="saveConfig()"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
|
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,166 @@ |
|||||
|
/** |
||||
|
* ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
* |
||||
|
* Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
* |
||||
|
* NOTICE: All information contained herein is, and remains |
||||
|
* the property of ThingsBoard, Inc. and its suppliers, |
||||
|
* if any. The intellectual and technical concepts contained |
||||
|
* herein are proprietary to ThingsBoard, Inc. |
||||
|
* and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
* patents in process, and are protected by trade secret or copyright law. |
||||
|
* |
||||
|
* Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
* unless prior written permission is obtained from COMPANY. |
||||
|
* |
||||
|
* Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
* managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
* explicitly covering such access. |
||||
|
* |
||||
|
* The copyright notice above does not evidence any actual or intended publication |
||||
|
* or disclosure of this source code, which includes |
||||
|
* information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
* ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
* OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
* THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
* AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
* THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
* DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
* OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
*/ |
||||
|
:host { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: block; |
||||
|
|
||||
|
.mat-toolbar { |
||||
|
background: transparent; |
||||
|
color: rgba(0, 0, 0, .87) !important; |
||||
|
} |
||||
|
|
||||
|
.mat-content { |
||||
|
.expansion-panel-header { |
||||
|
font-weight: 600; |
||||
|
color: rgba(0, 0, 0, .87) !important; |
||||
|
} |
||||
|
|
||||
|
mat-slide-toggle { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
mat-form-field { |
||||
|
margin-right: 15px; |
||||
|
} |
||||
|
|
||||
|
.block-title { |
||||
|
font-size: 20px; |
||||
|
font-weight: 400; |
||||
|
padding-top: 16px; |
||||
|
} |
||||
|
|
||||
|
.hover-cursor { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.security-toggle-group { |
||||
|
padding-bottom: 0; |
||||
|
background: transparent !important; |
||||
|
margin-bottom: 15px; |
||||
|
border: none; |
||||
|
|
||||
|
.mat-button-toggle { |
||||
|
border-radius: 15px; |
||||
|
border: 1px solid rgba(0, 0, 0, 0); |
||||
|
background: rgba(0, 0, 0, .05); |
||||
|
margin: 5px; |
||||
|
color: rgba(0, 0, 0, .2); |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-checked { |
||||
|
border-radius: 15px; |
||||
|
border: 1px solid #305680; |
||||
|
background: white; |
||||
|
color: #305680; |
||||
|
} |
||||
|
|
||||
|
::ng-deep span { |
||||
|
text-transform: capitalize; |
||||
|
line-height: 20px; |
||||
|
font-size: 13px; |
||||
|
padding: 5px 25px; |
||||
|
} |
||||
|
|
||||
|
::ng-deep .mat-button-toggle-ripple { |
||||
|
border-radius: 15px; |
||||
|
} |
||||
|
|
||||
|
::ng-deep .mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { |
||||
|
opacity: 0 !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.logs-label { |
||||
|
font-weight: 500; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.logs-toggle-group { |
||||
|
border: none; |
||||
|
border-radius: 15px; |
||||
|
background: rgba(0, 0, 0, .05); |
||||
|
color: rgba(0, 0, 0, .2); |
||||
|
font-weight: 500; |
||||
|
|
||||
|
.mat-button-toggle { |
||||
|
border: none; |
||||
|
background: rgba(0, 0, 0, .0); |
||||
|
color: rgba(0, 0, 0, .2); |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-checked { |
||||
|
border-radius: 15px; |
||||
|
color: white; |
||||
|
background: #305680; |
||||
|
} |
||||
|
|
||||
|
::ng-deep span { |
||||
|
line-height: 20px; |
||||
|
font-size: 13px; |
||||
|
padding: 5px 25px; |
||||
|
} |
||||
|
|
||||
|
::ng-deep .mat-button-toggle-ripple { |
||||
|
border-radius: 15px; |
||||
|
} |
||||
|
|
||||
|
::ng-deep .mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { |
||||
|
opacity: 0 !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.first-capital { |
||||
|
text-transform: capitalize; |
||||
|
} |
||||
|
|
||||
|
::ng-deep.mat-tab-label-active { |
||||
|
color: white; |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
::ng-deep.mat-tab-label, ::ng-deep.mat-tab-label-active { |
||||
|
min-width: 50px !important; |
||||
|
padding: 3px !important; |
||||
|
margin: 3px !important; |
||||
|
flex-grow: 1; |
||||
|
} |
||||
|
|
||||
|
::ng-deep.mat-ink-bar { |
||||
|
height: 100%; |
||||
|
z-index: -10; |
||||
|
border-radius: 5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,521 @@ |
|||||
|
///
|
||||
|
/// ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
|
||||
|
///
|
||||
|
/// Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved.
|
||||
|
///
|
||||
|
/// NOTICE: All information contained herein is, and remains
|
||||
|
/// the property of ThingsBoard, Inc. and its suppliers,
|
||||
|
/// if any. The intellectual and technical concepts contained
|
||||
|
/// herein are proprietary to ThingsBoard, Inc.
|
||||
|
/// and its suppliers and may be covered by U.S. and Foreign Patents,
|
||||
|
/// patents in process, and are protected by trade secret or copyright law.
|
||||
|
///
|
||||
|
/// Dissemination of this information or reproduction of this material is strictly forbidden
|
||||
|
/// unless prior written permission is obtained from COMPANY.
|
||||
|
///
|
||||
|
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
|
||||
|
/// managers or contractors who have executed Confidentiality and Non-disclosure agreements
|
||||
|
/// explicitly covering such access.
|
||||
|
///
|
||||
|
/// The copyright notice above does not evidence any actual or intended publication
|
||||
|
/// or disclosure of this source code, which includes
|
||||
|
/// information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
|
||||
|
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
|
||||
|
/// OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
|
||||
|
/// THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
|
||||
|
/// AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
|
||||
|
/// THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
|
||||
|
/// DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
|
||||
|
/// OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { base64toObj, base64toString, objToBase64, stringToBase64 } from '@core/utils'; |
||||
|
import { AttributeService } from '@core/http/attribute.service'; |
||||
|
import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { |
||||
|
GatewayRemoteConfigurationDialogComponent, |
||||
|
GatewayRemoteConfigurationDialogData |
||||
|
} from '@shared/components/dialog/gateway-remote-configuration-dialog'; |
||||
|
import { DeviceService } from '@core/http/device.service'; |
||||
|
|
||||
|
export enum StorageTypes { |
||||
|
MEMORY = 'memory', |
||||
|
FILE = 'file', |
||||
|
SQLITE = 'sqlite' |
||||
|
} |
||||
|
|
||||
|
export enum GatewayLogLevel { |
||||
|
none = 'NONE', |
||||
|
critical = 'CRITICAL', |
||||
|
error = 'ERROR', |
||||
|
warning = 'WARNING', |
||||
|
info = 'INFO', |
||||
|
debug = 'DEBUG' |
||||
|
} |
||||
|
|
||||
|
export enum LogSavingPeriod { |
||||
|
days = 'd', |
||||
|
hours = 'h', |
||||
|
minutes = 'm', |
||||
|
seconds = 's' |
||||
|
} |
||||
|
|
||||
|
export enum LocalLogsConfigs { |
||||
|
service = 'service', |
||||
|
connector = 'connector', |
||||
|
converter = 'converter', |
||||
|
tb_connection = 'tb_connection', |
||||
|
storage = 'storage', |
||||
|
extension = 'extension' |
||||
|
} |
||||
|
|
||||
|
export const logSavingPeriodTranslations = new Map<LogSavingPeriod, string>( |
||||
|
[ |
||||
|
[LogSavingPeriod.days, 'gateway.logs.days'], |
||||
|
[LogSavingPeriod.hours, 'gateway.logs.hours'], |
||||
|
[LogSavingPeriod.minutes, 'gateway.logs.minutes'], |
||||
|
[LogSavingPeriod.seconds, 'gateway.logs.seconds'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export const storageTypesTranslations = new Map<StorageTypes, string>( |
||||
|
[ |
||||
|
[StorageTypes.MEMORY, 'gateway.storage-types.memory-storage'], |
||||
|
[StorageTypes.FILE, 'gateway.storage-types.file-storage'], |
||||
|
[StorageTypes.SQLITE, 'gateway.storage-types.sqlite'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export enum SecurityTypes { |
||||
|
ACCESS_TOKEN = 'accessToken', |
||||
|
USERNAME_PASSWORD = 'usernamePassword', |
||||
|
TLS_ACCESS_TOKEN = 'tlsAccessToken', |
||||
|
TLS_PRIVATE_KEY = 'tlsPrivateKey' |
||||
|
} |
||||
|
|
||||
|
export const securityTypesTranslationsMap = new Map<SecurityTypes, string>( |
||||
|
[ |
||||
|
[SecurityTypes.ACCESS_TOKEN, 'gateway.security-types.access-token'], |
||||
|
[SecurityTypes.USERNAME_PASSWORD, 'gateway.security-types.username-password'], |
||||
|
[SecurityTypes.TLS_ACCESS_TOKEN, 'gateway.security-types.tls-access-token'], |
||||
|
[SecurityTypes.TLS_PRIVATE_KEY, 'gateway.security-types.tls-private-key'], |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-gateway-configuration', |
||||
|
templateUrl: './gateway-configuration.component.html', |
||||
|
styleUrls: ['./gateway-configuration.component.scss'] |
||||
|
}) |
||||
|
export class GatewayConfigurationComponent implements OnInit { |
||||
|
|
||||
|
gatewayConfigGroup: FormGroup; |
||||
|
|
||||
|
storageTypes = storageTypesTranslations; |
||||
|
|
||||
|
logSavingPeriods = logSavingPeriodTranslations; |
||||
|
|
||||
|
securityTypes = securityTypesTranslationsMap; |
||||
|
|
||||
|
gatewayLogLevel = Object.values(GatewayLogLevel); |
||||
|
|
||||
|
localLogsConfigs = Object.values(LocalLogsConfigs); |
||||
|
|
||||
|
@Input() |
||||
|
device: EntityId; |
||||
|
|
||||
|
@Input() |
||||
|
dialogRef: MatDialogRef<any>; |
||||
|
|
||||
|
logSelector: FormControl; |
||||
|
|
||||
|
|
||||
|
constructor(protected router: Router, |
||||
|
protected store: Store<AppState>, |
||||
|
protected fb: FormBuilder, |
||||
|
protected attributeService: AttributeService, |
||||
|
protected deviceService: DeviceService, |
||||
|
public dialog: MatDialog) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.gatewayConfigGroup = this.fb.group({ |
||||
|
thingsboard: this.fb.group({ |
||||
|
host: ['thingsboard.cloud', [Validators.required]], |
||||
|
port: [1883, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
remoteShell: [false, []], |
||||
|
remoteConfiguration: [true, []], |
||||
|
statistics: this.fb.group({ |
||||
|
enable: [true, []], |
||||
|
statsSendPeriodInSeconds: [3600, [Validators.required, Validators.min(0), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
checkConnectorsConfigurationInSeconds: [60, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
handleDeviceRenaming: [false, []], |
||||
|
commands: this.fb.array([], []) |
||||
|
}), |
||||
|
maxPayloadSizeBytes: [1024, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
minPackSendDelayMS: [200, [Validators.required, Validators.min(0), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
minPackSizeToSend: [500, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
handleDeviceRenaming: [true, []], |
||||
|
checkingDeviceActivity: this.fb.group({ |
||||
|
checkDeviceInactivity: [false, []], |
||||
|
inactivityTimeoutSeconds: [200, [Validators.min(1)]], |
||||
|
inactivityCheckPeriodSeconds: [500, [Validators.min(1)]] |
||||
|
}), |
||||
|
security: this.fb.group({ |
||||
|
type: [SecurityTypes.ACCESS_TOKEN, [Validators.required]], |
||||
|
accessToken: [null, [Validators.required]], |
||||
|
clientId: [null, []], |
||||
|
username: [null, []], |
||||
|
password: [null, []], |
||||
|
caCert: [null, []], |
||||
|
cert: [null, []], |
||||
|
privateKey: [null, []], |
||||
|
}), |
||||
|
qos: [1, [Validators.min(0), Validators.max(2), Validators.required]] |
||||
|
}), |
||||
|
storage: this.fb.group({ |
||||
|
type: [StorageTypes.MEMORY, [Validators.required]], |
||||
|
read_records_count: [100, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]], |
||||
|
max_records_count: [100000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]], |
||||
|
data_folder_path: ['./data/', []], |
||||
|
max_file_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
max_read_records_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
max_records_per_file: [10000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
data_file_path: ['./data/data.db', []], |
||||
|
messages_ttl_check_in_hours: [1, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
messages_ttl_in_days: [7, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
|
||||
|
}), |
||||
|
grpc: this.fb.group({ |
||||
|
enabled: [false, []], |
||||
|
serverPort: [9595, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
keepAliveTimeMs: [10000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
keepAliveTimeoutMs: [5000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
keepalivePermitWithoutCalls: [true, []], |
||||
|
maxPingsWithoutData: [0, [Validators.required, Validators.min(0), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
minTimeBetweenPingsMs: [10000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
minPingIntervalWithoutDataMs: [5000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
}), |
||||
|
connectors: this.fb.array([]), |
||||
|
logs: this.fb.group({ |
||||
|
dateFormat: ['%Y-%m-%d %H:%M:%S', [Validators.required]], |
||||
|
logFormat: ['%(asctime)s - |%(levelname)s| - [%(filename)s] - %(module)s - %(funcName)s - %(lineno)d - %(message)s', |
||||
|
[Validators.required]], |
||||
|
type: ['remote', [Validators.required]], |
||||
|
remote: this.fb.group({ |
||||
|
enabled: [false], |
||||
|
logLevel: [GatewayLogLevel.info, [Validators.required]], |
||||
|
}), |
||||
|
local: this.fb.group({}) |
||||
|
}) |
||||
|
}); |
||||
|
|
||||
|
this.gatewayConfigGroup.get('thingsboard.remoteConfiguration').valueChanges.subscribe(enabled => { |
||||
|
if (!enabled) { |
||||
|
this.openConfigurationConfirmDialog(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.logSelector = this.fb.control(LocalLogsConfigs.service); |
||||
|
|
||||
|
for (const localLogsConfigsKey of Object.keys(LocalLogsConfigs)) { |
||||
|
this.addLocalLogConfig(localLogsConfigsKey, {}); |
||||
|
} |
||||
|
|
||||
|
const checkingDeviceActivityGroup = this.gatewayConfigGroup.get('thingsboard.checkingDeviceActivity') as FormGroup; |
||||
|
checkingDeviceActivityGroup.get('checkDeviceInactivity').valueChanges.subscribe(enabled => { |
||||
|
checkingDeviceActivityGroup.updateValueAndValidity(); |
||||
|
if (enabled) { |
||||
|
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').setValidators([Validators.min(1), Validators.required]); |
||||
|
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').setValidators([Validators.min(1), Validators.required]); |
||||
|
} else { |
||||
|
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').clearValidators(); |
||||
|
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').setErrors(null); |
||||
|
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').clearValidators(); |
||||
|
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').setErrors(null); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const securityGroup = this.gatewayConfigGroup.get('thingsboard.security') as FormGroup; |
||||
|
securityGroup.get('type').valueChanges.subscribe(type => { |
||||
|
this.removeAllSecurityValidators(); |
||||
|
if (type === SecurityTypes.ACCESS_TOKEN) { |
||||
|
securityGroup.get('accessToken').addValidators([Validators.required]); |
||||
|
securityGroup.get('accessToken').updateValueAndValidity(); |
||||
|
} else if (type === SecurityTypes.TLS_PRIVATE_KEY) { |
||||
|
securityGroup.get('caCert').addValidators([Validators.required]); |
||||
|
securityGroup.get('caCert').updateValueAndValidity(); |
||||
|
securityGroup.get('privateKey').addValidators([Validators.required]); |
||||
|
securityGroup.get('privateKey').updateValueAndValidity(); |
||||
|
securityGroup.get('cert').addValidators([Validators.required]); |
||||
|
securityGroup.get('cert').updateValueAndValidity(); |
||||
|
} else if (type === SecurityTypes.TLS_ACCESS_TOKEN) { |
||||
|
securityGroup.get('accessToken').addValidators([Validators.required]); |
||||
|
securityGroup.get('accessToken').updateValueAndValidity(); |
||||
|
securityGroup.get('caCert').addValidators([Validators.required]); |
||||
|
securityGroup.get('caCert').updateValueAndValidity(); |
||||
|
} else if (type === SecurityTypes.USERNAME_PASSWORD) { |
||||
|
securityGroup.get('clientId').addValidators([Validators.required]); |
||||
|
securityGroup.get('clientId').updateValueAndValidity(); |
||||
|
securityGroup.get('username').addValidators([Validators.required]); |
||||
|
securityGroup.get('username').updateValueAndValidity(); |
||||
|
securityGroup.get('password').addValidators([Validators.required]); |
||||
|
securityGroup.get('password').updateValueAndValidity(); |
||||
|
} |
||||
|
securityGroup.updateValueAndValidity(); |
||||
|
}); |
||||
|
|
||||
|
const storageGroup = this.gatewayConfigGroup.get('storage') as FormGroup; |
||||
|
storageGroup.get('type').valueChanges.subscribe(type => { |
||||
|
this.removeAllStorageValidators(); |
||||
|
if (type === StorageTypes.MEMORY) { |
||||
|
storageGroup.get('read_records_count').addValidators( |
||||
|
[Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); |
||||
|
storageGroup.get('max_records_count').addValidators( |
||||
|
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); |
||||
|
} else if (type === StorageTypes.FILE) { |
||||
|
storageGroup.get('data_folder_path').addValidators([Validators.required]); |
||||
|
storageGroup.get('max_file_count').addValidators( |
||||
|
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); |
||||
|
storageGroup.get('max_read_records_count').addValidators( |
||||
|
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); |
||||
|
storageGroup.get('max_records_per_file').addValidators( |
||||
|
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); |
||||
|
} else if (type === StorageTypes.SQLITE) { |
||||
|
storageGroup.get('data_file_path').addValidators([Validators.required]); |
||||
|
storageGroup.get('messages_ttl_check_in_hours').addValidators( |
||||
|
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); |
||||
|
storageGroup.get('messages_ttl_in_days').addValidators( |
||||
|
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.fetchConfigAttribute(this.device); |
||||
|
} |
||||
|
|
||||
|
fetchConfigAttribute(entityId: EntityId) { |
||||
|
this.attributeService.getEntityAttributes(entityId, AttributeScope.SHARED_SCOPE, |
||||
|
['configuration', 'RemoteLoggingLevel']).subscribe(attributes => { |
||||
|
if (attributes.length) { |
||||
|
const configuration = attributes.find(attribute => attribute.key === 'configuration').value; |
||||
|
const remoteLoggingLevel = attributes.find(attribute => attribute.key === 'RemoteLoggingLevel').value; |
||||
|
if (configuration) { |
||||
|
const configObj = base64toObj(configuration).thingsboard; |
||||
|
configObj.logs = this.logsBase64ToObj(configObj.logs); |
||||
|
if (configObj.thingsboard.statistics && configObj.thingsboard.statistics.commands) { |
||||
|
for (const command of Object.values(configObj.thingsboard.statistics.commands)) { |
||||
|
this.addCommand(command); |
||||
|
} |
||||
|
delete configObj.thingsboard.statistics.commands; |
||||
|
} |
||||
|
this.gatewayConfigGroup.patchValue(configObj, {emitEvent: false}); |
||||
|
this.gatewayConfigGroup.markAsDirty(); |
||||
|
if (!configObj.thingsboard.remoteConfiguration) { |
||||
|
this.gatewayConfigGroup.disable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
if (remoteLoggingLevel) { |
||||
|
const remoteLogsFormGroup = this.gatewayConfigGroup.get('logs.remote'); |
||||
|
remoteLogsFormGroup.patchValue({ |
||||
|
enabled: remoteLoggingLevel !== GatewayLogLevel.none, |
||||
|
logLevel: remoteLoggingLevel |
||||
|
}, {emitEvent: false}); |
||||
|
remoteLogsFormGroup.markAsDirty(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
logsBase64ToObj(logs64: string) { |
||||
|
const logsString = base64toString(logs64).split('"').join(''); |
||||
|
const logsObject = {}; |
||||
|
logsString.split('logger_').forEach((split, index) => { |
||||
|
if (index > 0) { |
||||
|
const key = split.split(']')[0]; |
||||
|
if (LocalLogsConfigs[key]) { |
||||
|
const logLevel = split.split('=')[1].replace('}}handlers', ''); |
||||
|
logsObject[key] = { |
||||
|
logLevel |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
logsString.split('handler_').forEach((split, index) => { |
||||
|
if (index > 0) { |
||||
|
const key = split.split('Handler]')[0]; |
||||
|
if (LocalLogsConfigs[key]) { |
||||
|
const args = split.split('args=(')[1].split(','); |
||||
|
logsObject[key].filePath = args[0].replace(`/${key}.log`, ''); |
||||
|
logsObject[key].savingPeriod = args[1].replace(' ', ''); |
||||
|
logsObject[key].savingTime = +args[2]; |
||||
|
logsObject[key].backupCount = +args[3]; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
const formatArgs = logsString.split('format=')[1].split('}}datefmt='); |
||||
|
const logFormat = formatArgs[0]; |
||||
|
const dateFormat = formatArgs[1]; |
||||
|
|
||||
|
return {local: logsObject, logFormat, dateFormat}; |
||||
|
} |
||||
|
|
||||
|
addCommand(command?): void { |
||||
|
const data = command || {}; |
||||
|
const commandsFormArray = this.commandFormArray(); |
||||
|
const commandFormGroup = this.fb.group({ |
||||
|
attributeName: [data.attributeName || null, [Validators.required]], |
||||
|
command: [data.command || null, [Validators.required]], |
||||
|
timeout: [data.timeout || null, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], |
||||
|
}); |
||||
|
commandsFormArray.push(commandFormGroup); |
||||
|
} |
||||
|
|
||||
|
addLocalLogConfig(name, config): void { |
||||
|
const localLogsFormGroup = this.gatewayConfigGroup.get('logs.local') as FormGroup; |
||||
|
const configGroup = this.fb.group({ |
||||
|
logLevel: [config.logLevel || GatewayLogLevel.info, [Validators.required]], |
||||
|
filePath: [config.filePath || './data', [Validators.required]], |
||||
|
backupCount: [config.backupCount || 7, [Validators.required, Validators.min(0)]], |
||||
|
savingTime: [config.savingTime || 3, [Validators.required, Validators.min(0)]], |
||||
|
savingPeriod: [config.savingPeriod || LogSavingPeriod.days, [Validators.required]] |
||||
|
}); |
||||
|
localLogsFormGroup.addControl(name, configGroup); |
||||
|
} |
||||
|
|
||||
|
getLogFormGroup(value: string): FormGroup { |
||||
|
return this.gatewayConfigGroup.get(`logs.local.${value}`) as FormGroup; |
||||
|
} |
||||
|
|
||||
|
commandFormArray(): FormArray { |
||||
|
return this.gatewayConfigGroup.get('thingsboard.statistics.commands') as FormArray; |
||||
|
} |
||||
|
|
||||
|
removeCommandControl(index: number): void { |
||||
|
this.commandFormArray().removeAt(index); |
||||
|
} |
||||
|
|
||||
|
removeAllSecurityValidators(): void { |
||||
|
const securityGroup = this.gatewayConfigGroup.get('thingsboard.security') as FormGroup; |
||||
|
for (const controlsKey in securityGroup.controls) { |
||||
|
if (controlsKey !== 'type') { |
||||
|
securityGroup.controls[controlsKey].clearValidators(); |
||||
|
securityGroup.controls[controlsKey].setErrors(null); |
||||
|
securityGroup.controls[controlsKey].updateValueAndValidity(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
removeAllStorageValidators(): void { |
||||
|
const storageGroup = this.gatewayConfigGroup.get('storage') as FormGroup; |
||||
|
for (const storageKey in storageGroup.controls) { |
||||
|
if (storageKey !== 'type') { |
||||
|
storageGroup.controls[storageKey].clearValidators(); |
||||
|
storageGroup.controls[storageKey].setErrors(null); |
||||
|
storageGroup.controls[storageKey].updateValueAndValidity(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
removeEmpty(obj) { |
||||
|
return Object.fromEntries( |
||||
|
Object.entries(obj) |
||||
|
.filter(([_, v]) => v != null) |
||||
|
.map(([k, v]) => [k, v === Object(v) ? this.removeEmpty(v) : v]) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
generateLogsFile(logsObj): string { |
||||
|
const line = '[loggers]}}' + |
||||
|
'keys=root, service, connector, converter, tb_connection, storage, extension}}' + |
||||
|
'[handlers]}}' + |
||||
|
'keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}' + |
||||
|
'[formatters]}}' + |
||||
|
'keys=LogFormatter}}' + |
||||
|
'[logger_root]}}' + |
||||
|
'level=ERROR}}' + |
||||
|
'handlers=consoleHandler}}' + |
||||
|
'[logger_database]}}' + |
||||
|
'level=INFO}}' + |
||||
|
'handlers=databaseHandler}}' + |
||||
|
'formatter=LogFormatter}}' + |
||||
|
'qualname=database}}'; |
||||
|
|
||||
|
let loggerLine = ''; |
||||
|
let handlerLine = ''; |
||||
|
|
||||
|
for (const key of Object.keys(logsObj.local)) { |
||||
|
loggerLine += `[logger_${key}]}}` + |
||||
|
`level=${logsObj.local[key].logLevel}}}` + |
||||
|
`handlers=${key}Handler}}` + |
||||
|
`formatter=LogFormatter}}` + |
||||
|
`qualname=${key}}}`; |
||||
|
handlerLine += `[handler_${key}Handler]}}` + |
||||
|
`level=${logsObj.local[key].logLevel}}}` + |
||||
|
`class=thingsboard_gateway.tb_utility.tb_logger.TimedRotatingFileHandler}}` + |
||||
|
`formatter=LogFormatter}}` + |
||||
|
`args=("${logsObj.local[key].filePath}/${key}.log", ${logsObj.local[key].savingPeriod},` + |
||||
|
` ${logsObj.local[key].savingTime}, ${logsObj.local[key].backupCount},)}}`; |
||||
|
} |
||||
|
|
||||
|
const logerEnding = `[formatter_LogFormatter]}}` + |
||||
|
`format="${logsObj.logFormat}"}}` + |
||||
|
`datefmt="${logsObj.dateFormat}"`; |
||||
|
|
||||
|
return line + loggerLine + handlerLine + logerEnding; |
||||
|
} |
||||
|
|
||||
|
saveConfig(): void { |
||||
|
const value = this.removeEmpty(this.gatewayConfigGroup.value); |
||||
|
const attributes = []; |
||||
|
attributes.push({ |
||||
|
key: 'RemoteLoggingLevel', |
||||
|
value: value.logs.remote.logLevel |
||||
|
}); |
||||
|
delete value.connectors; |
||||
|
value.logs = stringToBase64(this.generateLogsFile(value.logs)); |
||||
|
const configuration = objToBase64({thingsboard: value}); |
||||
|
attributes.push({ |
||||
|
key: 'configuration', |
||||
|
value: configuration |
||||
|
}); |
||||
|
this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, attributes).subscribe(_ => { |
||||
|
if (this.dialogRef) { |
||||
|
this.dialogRef.close(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
if (this.dialogRef) { |
||||
|
this.dialogRef.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private openConfigurationConfirmDialog(): void { |
||||
|
this.deviceService.getDevice(this.device.id).subscribe(gateway => { |
||||
|
this.dialog.open<GatewayRemoteConfigurationDialogComponent, GatewayRemoteConfigurationDialogData> |
||||
|
(GatewayRemoteConfigurationDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
gatewayName: gateway.name |
||||
|
} |
||||
|
}).afterClosed().subscribe( |
||||
|
(res) => { |
||||
|
if (!res) { |
||||
|
this.gatewayConfigGroup.get('thingsboard.remoteConfiguration').setValue(true, {emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
|
||||
|
Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
|
||||
|
NOTICE: All information contained herein is, and remains |
||||
|
the property of ThingsBoard, Inc. and its suppliers, |
||||
|
if any. The intellectual and technical concepts contained |
||||
|
herein are proprietary to ThingsBoard, Inc. |
||||
|
and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
patents in process, and are protected by trade secret or copyright law. |
||||
|
|
||||
|
Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
unless prior written permission is obtained from COMPANY. |
||||
|
|
||||
|
Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
explicitly covering such access. |
||||
|
|
||||
|
The copyright notice above does not evidence any actual or intended publication |
||||
|
or disclosure of this source code, which includes |
||||
|
information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
|
||||
|
--> |
||||
|
<form style="height: 100%" fxLayout="column" [formGroup]="gatewayConnectorsGroup"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2 translate>gateway.connectors</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
type="button" |
||||
|
(click)="cancel()" |
||||
|
*ngIf="dialogRef"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<div fxLayout="row" fxLayoutGap="15px" fxFlex style="height: calc(100% - 35px)"> |
||||
|
<div fxFlex="30" fxLayout="column" style="overflow: auto"> |
||||
|
<mat-accordion multi> |
||||
|
<mat-expansion-panel expanded="true" |
||||
|
formArrayName="connectors" |
||||
|
*ngFor="let connectorControl of connectorsFormArray().controls; let $index = index" |
||||
|
[ngClass]="{'mat-card-selected': $index === selectedConnector}"> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title class="expansion-panel-header"> |
||||
|
{{generatePanelTitle(connectorControl)}} |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<div fxLayout="row"> |
||||
|
<mat-card fxLayout="row wrap" style="margin-bottom: 15px" [formGroupName]="$index"> |
||||
|
<mat-slide-toggle color="primary" fxFlex="100" formControlName="active"> |
||||
|
{{ (connectorControl.get('active').value ? 'gateway.connectors-active' : 'gateway.connectors-inactive') | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)"> |
||||
|
<mat-label translate>gateway.connector-name</mat-label> |
||||
|
<input matInput formControlName="name" required/> |
||||
|
<mat-error |
||||
|
*ngIf="connectorControl.get('name').hasError('required')"> |
||||
|
{{'gateway.connector-name-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)"> |
||||
|
<mat-label translate>gateway.connector-configuration</mat-label> |
||||
|
<input matInput formControlName="configuration" required/> |
||||
|
<mat-error |
||||
|
*ngIf="connectorControl.get('configuration').hasError('required')"> |
||||
|
{{'gateway.connector-configuration-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)"> |
||||
|
<mat-label translate>gateway.connector-type</mat-label> |
||||
|
<mat-select formControlName="type" required> |
||||
|
<mat-option *ngFor="let type of gatewayConnectorDefaultTypes" [value]="type">{{type}}</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error |
||||
|
*ngIf="connectorControl.get('type').hasError('required')"> |
||||
|
{{'gateway.connector-type-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex="calc(50%-15px)"> |
||||
|
<mat-label translate>gateway.connector-key</mat-label> |
||||
|
<input matInput formControlName="key" required/> |
||||
|
<mat-error |
||||
|
*ngIf="connectorControl.get('key').hasError('required')"> |
||||
|
{{'gateway.connector-key-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</mat-card> |
||||
|
<div fxLayout="column"> |
||||
|
<button mat-icon-button (click)="removeConnector($index)" |
||||
|
matTooltip="{{ 'gateway.statistics.remove' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
<button mat-icon-button (click)="selectConnector($index)" |
||||
|
matTooltip="{{ 'gateway.select-connector' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>code</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
<button mat-raised-button color="primary" |
||||
|
style="margin-top: 15px" |
||||
|
type="button" |
||||
|
(click)="addConnector(true)"> |
||||
|
{{ 'gateway.connector-add' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
<tb-json-object-edit |
||||
|
fxFlex |
||||
|
fxLayout="column" |
||||
|
required |
||||
|
*ngIf="selectedConnector !== undefined" |
||||
|
label="{{ 'gateway.connector-json' | translate }}" |
||||
|
[formControl]="getJsonControl(selectedConnector)"> |
||||
|
</tb-json-object-edit> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="primary" |
||||
|
class="action-btns" |
||||
|
type="button" |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
class="action-btns" |
||||
|
type="button" |
||||
|
[disabled]="gatewayConnectorsGroup.invalid || !gatewayConnectorsGroup.dirty" |
||||
|
(click)="saveConnectors()"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
|
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
* |
||||
|
* Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
* |
||||
|
* NOTICE: All information contained herein is, and remains |
||||
|
* the property of ThingsBoard, Inc. and its suppliers, |
||||
|
* if any. The intellectual and technical concepts contained |
||||
|
* herein are proprietary to ThingsBoard, Inc. |
||||
|
* and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
* patents in process, and are protected by trade secret or copyright law. |
||||
|
* |
||||
|
* Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
* unless prior written permission is obtained from COMPANY. |
||||
|
* |
||||
|
* Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
* managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
* explicitly covering such access. |
||||
|
* |
||||
|
* The copyright notice above does not evidence any actual or intended publication |
||||
|
* or disclosure of this source code, which includes |
||||
|
* information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
* ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
* OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
* THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
* AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
* THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
* DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
* OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
*/ |
||||
|
:host { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: block; |
||||
|
|
||||
|
.mat-toolbar { |
||||
|
background: transparent; |
||||
|
color: rgba(0, 0, 0, .87) !important; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-form-field { |
||||
|
margin-right: 15px; |
||||
|
} |
||||
|
|
||||
|
mat-expansion-panel { |
||||
|
border: 1px solid #305680; |
||||
|
} |
||||
|
|
||||
|
mat-card { |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.mat-card-selected { |
||||
|
background-color: white; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-slide-toggle { |
||||
|
margin: 15px; |
||||
|
} |
||||
|
|
||||
|
::ng-deep tb-json-object-edit > div { |
||||
|
flex-grow: 1; |
||||
|
} |
||||
|
|
||||
|
.mat-accordion .mat-expansion-panel { |
||||
|
border-radius: 5px; |
||||
|
} |
||||
|
|
||||
|
.action-btns { |
||||
|
margin: 10px 10px 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,195 @@ |
|||||
|
///
|
||||
|
/// ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
|
||||
|
///
|
||||
|
/// Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved.
|
||||
|
///
|
||||
|
/// NOTICE: All information contained herein is, and remains
|
||||
|
/// the property of ThingsBoard, Inc. and its suppliers,
|
||||
|
/// if any. The intellectual and technical concepts contained
|
||||
|
/// herein are proprietary to ThingsBoard, Inc.
|
||||
|
/// and its suppliers and may be covered by U.S. and Foreign Patents,
|
||||
|
/// patents in process, and are protected by trade secret or copyright law.
|
||||
|
///
|
||||
|
/// Dissemination of this information or reproduction of this material is strictly forbidden
|
||||
|
/// unless prior written permission is obtained from COMPANY.
|
||||
|
///
|
||||
|
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
|
||||
|
/// managers or contractors who have executed Confidentiality and Non-disclosure agreements
|
||||
|
/// explicitly covering such access.
|
||||
|
///
|
||||
|
/// The copyright notice above does not evidence any actual or intended publication
|
||||
|
/// or disclosure of this source code, which includes
|
||||
|
/// information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
|
||||
|
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
|
||||
|
/// OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
|
||||
|
/// THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
|
||||
|
/// AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
|
||||
|
/// THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
|
||||
|
/// DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
|
||||
|
/// OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
|
||||
|
///
|
||||
|
|
||||
|
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { AttributeService } from '@core/http/attribute.service'; |
||||
|
import { DeviceService } from '@core/http/device.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { forkJoin } from 'rxjs'; |
||||
|
import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; |
||||
|
|
||||
|
|
||||
|
export interface gatewayConnector { |
||||
|
name: string; |
||||
|
type: string; |
||||
|
configuration: string; |
||||
|
jsonConfiguration: string; |
||||
|
key: string; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-gateway-connector', |
||||
|
templateUrl: './gateway-connectors.component.html', |
||||
|
styleUrls: ['./gateway-connectors.component.scss'] |
||||
|
}) |
||||
|
export class GatewayConnectorComponent implements OnInit { |
||||
|
|
||||
|
gatewayConnectorsGroup: FormGroup; |
||||
|
|
||||
|
gatewayConnectorDefaultTypes: Array<string> = |
||||
|
['mqtt', |
||||
|
'modbus', |
||||
|
'grpc', |
||||
|
'opcua', |
||||
|
'opcua_asyncio', |
||||
|
'ble', |
||||
|
'request', |
||||
|
'can', |
||||
|
'bacnet', |
||||
|
'odbc', |
||||
|
'rest', |
||||
|
'snmp', |
||||
|
'ftp', |
||||
|
'socket', |
||||
|
'xmpp', |
||||
|
'ocpp' |
||||
|
]; |
||||
|
|
||||
|
selectedConnector: number; |
||||
|
|
||||
|
@Input() |
||||
|
device: EntityId; |
||||
|
|
||||
|
@Input() |
||||
|
dialogRef: MatDialogRef<any>; |
||||
|
|
||||
|
logSelector: FormControl; |
||||
|
|
||||
|
|
||||
|
constructor(protected router: Router, |
||||
|
protected store: Store<AppState>, |
||||
|
protected fb: FormBuilder, |
||||
|
protected translate: TranslateService, |
||||
|
protected attributeService: AttributeService, |
||||
|
protected deviceService: DeviceService, |
||||
|
private cd: ChangeDetectorRef, |
||||
|
public dialog: MatDialog) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.gatewayConnectorsGroup = this.fb.group({ |
||||
|
connectors: this.fb.array([], [Validators.required]) |
||||
|
}); |
||||
|
this.getConnectorsData(); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
if (this.dialogRef) { |
||||
|
this.dialogRef.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getConnectorsData(): void { |
||||
|
forkJoin([ |
||||
|
this.attributeService.getEntityAttributes(this.device, AttributeScope.SHARED_SCOPE,['implementedConnectors']), |
||||
|
this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE,['implementedConnectors']), |
||||
|
this.attributeService.getEntityAttributes(this.device, AttributeScope.SHARED_SCOPE,['connectorTypes']) |
||||
|
]).subscribe(attributes=>{ |
||||
|
if (attributes[0].length) { |
||||
|
attributes[0][0].value.forEach(connector=>this.addConnector(true, connector)); |
||||
|
} |
||||
|
if (attributes[1].length) { |
||||
|
attributes[1][0].value.forEach(connector=>this.addConnector(false, connector)); |
||||
|
} |
||||
|
if(attributes[2].length) { |
||||
|
attributes[1][0].value.forEach(type=> { |
||||
|
if (this.gatewayConnectorDefaultTypes.indexOf(type) === -1) { |
||||
|
this.gatewayConnectorDefaultTypes.push(type); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
this.cd.detectChanges(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
saveConnectors(): void { |
||||
|
const connectors = this.gatewayConnectorsGroup.value.connectors; |
||||
|
const activeConnectors = connectors.filter(connector=>connector.active); |
||||
|
const inactiveConnectors = connectors.filter(connector=>!connector.active); |
||||
|
forkJoin([ |
||||
|
this.attributeService.saveEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, |
||||
|
[{key: 'implementedConnectors', value: inactiveConnectors}]), |
||||
|
this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, |
||||
|
[{key: 'implementedConnectors', value: activeConnectors}]) |
||||
|
]).subscribe(_=> { |
||||
|
if (this.dialogRef) { |
||||
|
this.dialogRef.close(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
connectorsFormArray(): FormArray { |
||||
|
return this.gatewayConnectorsGroup.get('connectors') as FormArray; |
||||
|
} |
||||
|
|
||||
|
addConnector(active?: boolean,connector?: gatewayConnector) { |
||||
|
const newConnector = this.fb.group({ |
||||
|
active: [!!active], |
||||
|
name: [connector?.name || '', [Validators.required]], |
||||
|
type: [connector?.type || '', [Validators.required]], |
||||
|
configuration: [connector?.configuration || '', [Validators.required]], |
||||
|
jsonConfiguration: [connector?.jsonConfiguration || {}, [Validators.required]], |
||||
|
key: [connector?.key || 'auto', [Validators.required]], |
||||
|
}); |
||||
|
const connectorsFormArray = this.connectorsFormArray(); |
||||
|
connectorsFormArray.push(newConnector); |
||||
|
this.selectConnector(connectorsFormArray.length-1); |
||||
|
} |
||||
|
|
||||
|
removeConnector(index: number): void { |
||||
|
this.connectorsFormArray().removeAt(index); |
||||
|
} |
||||
|
|
||||
|
selectConnector(index: number): void { |
||||
|
this.selectedConnector = index; |
||||
|
} |
||||
|
|
||||
|
getJsonControl(selectedConnector: number): FormControl { |
||||
|
return this.connectorsFormArray().at(selectedConnector).get('jsonConfiguration') as FormControl; |
||||
|
} |
||||
|
|
||||
|
generatePanelTitle(connectorControl): string{ |
||||
|
const connectorValues = connectorControl.value; |
||||
|
const activeTxt = this.translate.instant(connectorValues.active ? 'gateway.connectors-active' : 'gateway.connectors-inactive'); |
||||
|
let title = `${connectorValues.name} | ${connectorValues.type} `; |
||||
|
if (connectorValues.type === 'grpc') { |
||||
|
title += `| ${connectorValues.key} `; |
||||
|
} |
||||
|
title += `| ${activeTxt}`; |
||||
|
return title; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL |
||||
|
|
||||
|
Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. |
||||
|
|
||||
|
NOTICE: All information contained herein is, and remains |
||||
|
the property of ThingsBoard, Inc. and its suppliers, |
||||
|
if any. The intellectual and technical concepts contained |
||||
|
herein are proprietary to ThingsBoard, Inc. |
||||
|
and its suppliers and may be covered by U.S. and Foreign Patents, |
||||
|
patents in process, and are protected by trade secret or copyright law. |
||||
|
|
||||
|
Dissemination of this information or reproduction of this material is strictly forbidden |
||||
|
unless prior written permission is obtained from COMPANY. |
||||
|
|
||||
|
Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, |
||||
|
managers or contractors who have executed Confidentiality and Non-disclosure agreements |
||||
|
explicitly covering such access. |
||||
|
|
||||
|
The copyright notice above does not evidence any actual or intended publication |
||||
|
or disclosure of this source code, which includes |
||||
|
information that is confidential and/or proprietary, and is a trade secret, of COMPANY. |
||||
|
ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, |
||||
|
OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT |
||||
|
THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, |
||||
|
AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. |
||||
|
THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION |
||||
|
DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, |
||||
|
OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. |
||||
|
|
||||
|
--> |
||||
|
<form style="width: 600px; position: relative;"> |
||||
|
<mat-toolbar color="warn"> |
||||
|
<mat-icon>warning</mat-icon> |
||||
|
<h2 translate>gateway.configuration-delete-dialog-header</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 tb-toast toastTarget="activationLinkDialogContent"> |
||||
|
<div class="mat-content" fxLayout="column"> |
||||
|
<span innerHTML="{{'gateway.configuration-delete-dialog-body' | translate}}" > </span> |
||||
|
<mat-form-field class="mat-block tb-value-type" style="flex-grow: 0"> |
||||
|
<mat-label translate>gateway.configuration-delete-dialog-input</mat-label> |
||||
|
<input matInput [formControl]="gatewayForm" required/> |
||||
|
<mat-error |
||||
|
*ngIf="gatewayForm.hasError('required')"> |
||||
|
{{'gateway.configuration-delete-dialog-input-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="warn" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
(click)="close()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-button color="warn" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
[disabled]="gatewayForm.value !== gatewayName" |
||||
|
(click)="turnOff()"> |
||||
|
{{ 'gateway.configuration-delete-dialog-confirm' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,76 @@ |
|||||
|
///
|
||||
|
/// ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
|
||||
|
///
|
||||
|
/// Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved.
|
||||
|
///
|
||||
|
/// NOTICE: All information contained herein is, and remains
|
||||
|
/// the property of ThingsBoard, Inc. and its suppliers,
|
||||
|
/// if any. The intellectual and technical concepts contained
|
||||
|
/// herein are proprietary to ThingsBoard, Inc.
|
||||
|
/// and its suppliers and may be covered by U.S. and Foreign Patents,
|
||||
|
/// patents in process, and are protected by trade secret or copyright law.
|
||||
|
///
|
||||
|
/// Dissemination of this information or reproduction of this material is strictly forbidden
|
||||
|
/// unless prior written permission is obtained from COMPANY.
|
||||
|
///
|
||||
|
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
|
||||
|
/// managers or contractors who have executed Confidentiality and Non-disclosure agreements
|
||||
|
/// explicitly covering such access.
|
||||
|
///
|
||||
|
/// The copyright notice above does not evidence any actual or intended publication
|
||||
|
/// or disclosure of this source code, which includes
|
||||
|
/// information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
|
||||
|
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
|
||||
|
/// OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
|
||||
|
/// THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
|
||||
|
/// AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
|
||||
|
/// THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
|
||||
|
/// DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
|
||||
|
/// OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Inject, OnInit } 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 } from '@angular/forms'; |
||||
|
|
||||
|
export interface GatewayRemoteConfigurationDialogData { |
||||
|
gatewayName: string; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-activation-link-dialog', |
||||
|
templateUrl: './gateway-remote-configuration-dialog.html' |
||||
|
}) |
||||
|
|
||||
|
export class GatewayRemoteConfigurationDialogComponent extends DialogComponent<GatewayRemoteConfigurationDialogComponent, |
||||
|
boolean> implements OnInit { |
||||
|
|
||||
|
gatewayName: string; |
||||
|
|
||||
|
gatewayForm: FormControl; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: GatewayRemoteConfigurationDialogData, |
||||
|
public dialogRef: MatDialogRef<GatewayRemoteConfigurationDialogComponent, boolean>, |
||||
|
private fb: FormBuilder) { |
||||
|
super(store, router, dialogRef); |
||||
|
this.gatewayName = this.data.gatewayName; |
||||
|
this.gatewayForm = this.fb.control(null); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(); |
||||
|
} |
||||
|
|
||||
|
turnOff(): void { |
||||
|
this.dialogRef.close(true); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue