Browse Source

Added Flow to edit Gateway Config as JSON in Advanced config tab

pull/11462/head
mpetrov 2 years ago
parent
commit
0599ea0647
  1. 2
      application/src/main/data/json/tenant/dashboards/gateways.json
  2. 8
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.html
  3. 23
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.scss
  4. 95
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.ts
  5. 221
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html
  6. 16
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.scss
  7. 676
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts
  8. 64
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.html
  9. 64
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.scss
  10. 377
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts
  11. 148
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts
  12. 16
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  13. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  14. 10
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

2
application/src/main/data/json/tenant/dashboards/gateways.json

@ -200,7 +200,7 @@
"useShowWidgetActionFunction": null, "useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;", "showWidgetActionFunction": "return true;",
"type": "customPretty", "type": "customPretty",
"customHtml": "<mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2 translate>gateway.gateway-configuration</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n</mat-toolbar>\n<mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n</mat-progress-bar>\n<div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n<tb-gateway-configuration\n class=\"gateway-config\"\n mat-dialog-content\n [device]=\"device\"\n [dialogRef]=\"dialogRef\">\n</tb-gateway-configuration>", "customHtml": "<mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n</mat-progress-bar>\n<tb-gateway-configuration\n class=\"gateway-config\"\n mat-dialog-content\n [device]=\"device\"\n [dialogRef]=\"dialogRef\">\n</tb-gateway-configuration>",
"customCss": ".gateway-config {\n width: 800px !important;\n padding: 0 !important;\n min-height: 75vh;\n max-width: 100%;\n display: grid !important;\n}\n\n@media screen and (max-width: 599px) {\n .mat-mdc-dialog-content {\n max-height: calc(100% - 60px) !important;\n }\n}", "customCss": ".gateway-config {\n width: 800px !important;\n padding: 0 !important;\n min-height: 75vh;\n max-width: 100%;\n display: grid !important;\n}\n\n@media screen and (max-width: 599px) {\n .mat-mdc-dialog-content {\n max-height: calc(100% - 60px) !important;\n }\n}",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.device = additionalParams.entity.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.device = additionalParams.entity.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n",
"customResources": [], "customResources": [],

8
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.html

@ -0,0 +1,8 @@
<tb-json-object-edit
fillHeight="true"
class="tb-flex config-container"
fxLayout="column"
jsonRequired
label="{{ 'gateway.configuration' | translate }}"
[formControl]="advancedFormControl"
/>

23
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.scss

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.config-container {
height: calc(100% - 60px);
padding: 8px;
}
}

95
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.ts

@ -0,0 +1,95 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, OnDestroy } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validators
} from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
@Component({
selector: 'tb-gateway-advanced-configuration',
templateUrl: './gateway-advanced-configuration.component.html',
styleUrls: ['./gateway-advanced-configuration.component.scss'],
standalone: true,
imports: [
CommonModule,
SharedModule,
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GatewayAdvancedConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => GatewayAdvancedConfigurationComponent),
multi: true
}
],
})
export class GatewayAdvancedConfigurationComponent implements OnDestroy, ControlValueAccessor, Validators {
advancedFormControl: FormControl;
private onChange: (value: unknown) => void;
private onTouched: () => void;
private destroy$ = new Subject<void>();
constructor(private fb: FormBuilder) {
this.advancedFormControl = this.fb.control('');
this.advancedFormControl.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
this.onChange(value);
this.onTouched();
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: (value: unknown) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
writeValue(basicConfig: unknown): void {
this.advancedFormControl.reset(basicConfig, {emitEvent: false});
}
validate(): ValidationErrors | null {
return this.advancedFormControl.valid ? null : {
advancedFormControl: {valid: false}
};
}
}

221
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html → ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html

@ -15,10 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<mat-toolbar color="primary" *ngIf="!dialogRef"> <mat-tab-group class="tab-group-block" [formGroup]="basicFormGroup" [class.dialog-mode]="dialogMode">
<h2 translate>gateway.gateway-configuration</h2>
</mat-toolbar>
<mat-tab-group class="tab-group-block" [formGroup]="gatewayConfigGroup">
<mat-tab label="{{ 'gateway.general' | translate }}"> <mat-tab label="{{ 'gateway.general' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<div formGroupName="thingsboard" class="mat-content mat-padding configuration-block"> <div formGroupName="thingsboard" class="mat-content mat-padding configuration-block">
@ -42,23 +39,23 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.host' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.host' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.host').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.host').hasError('required')">
{{ 'gateway.thingsboard-host-required' | translate }} {{ 'gateway.thingsboard-host-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.thingsboard-port</mat-label> <mat-label translate>gateway.thingsboard-port</mat-label>
<input matInput formControlName="port" type="number" min="0"/> <input matInput formControlName="port" type="number" min="0"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.port').hasError('required')">
{{ 'gateway.thingsboard-port-required' | translate }} {{ 'gateway.thingsboard-port-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.port').hasError('min')">
{{ 'gateway.thingsboard-port-min' | translate }} {{ 'gateway.thingsboard-port-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('max')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.port').hasError('max')">
{{ 'gateway.thingsboard-port-max' | translate }} {{ 'gateway.thingsboard-port-max' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.port').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.port').hasError('pattern')">
{{ 'gateway.thingsboard-port-pattern' | translate }} {{ 'gateway.thingsboard-port-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -76,17 +73,17 @@
</tb-toggle-option> </tb-toggle-option>
</tb-toggle-select> </tb-toggle-select>
<mat-form-field appearance="outline" <mat-form-field appearance="outline"
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value.toLowerCase().includes('accesstoken')"> *ngIf="basicFormGroup.get('thingsboard.security.type').value.toLowerCase().includes('accesstoken')">
<mat-label translate>security.access-token</mat-label> <mat-label translate>security.access-token</mat-label>
<input matInput formControlName="accessToken"/> <input matInput formControlName="accessToken"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.accessToken').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.security.accessToken').hasError('required')">
{{ 'security.access-token-required' | translate }} {{ 'security.access-token-required' | translate }}
</mat-error> </mat-error>
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.accessToken').value" *ngIf="basicFormGroup.get('thingsboard.security.accessToken').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.accessToken').value" [copyText]="basicFormGroup.get('thingsboard.security.accessToken').value"
tooltipText="{{ 'device.copy-access-token' | translate }}" tooltipText="{{ 'device.copy-access-token' | translate }}"
tooltipPosition="above" tooltipPosition="above"
icon="content_copy"> icon="content_copy">
@ -97,18 +94,18 @@
</mat-form-field> </mat-form-field>
<section> <section>
<div class="tb-form-row no-border no-padding tb-standard-fields" <div class="tb-form-row no-border no-padding tb-standard-fields"
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'"> *ngIf="basicFormGroup.get('thingsboard.security.type').value === 'usernamePassword'">
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>security.clientId</mat-label> <mat-label translate>security.clientId</mat-label>
<input matInput formControlName="clientId"/> <input matInput formControlName="clientId"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.clientId').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.security.clientId').hasError('required')">
{{ 'security.clientId-required' | translate }} {{ 'security.clientId-required' | translate }}
</mat-error> </mat-error>
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.clientId').value" *ngIf="basicFormGroup.get('thingsboard.security.clientId').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.clientId').value" [copyText]="basicFormGroup.get('thingsboard.security.clientId').value"
tooltipText="{{ 'gateway.copy-client-id' | translate }}" tooltipText="{{ 'gateway.copy-client-id' | translate }}"
tooltipPosition="above" tooltipPosition="above"
icon="content_copy"> icon="content_copy">
@ -120,14 +117,14 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>security.username</mat-label> <mat-label translate>security.username</mat-label>
<input matInput formControlName="username"/> <input matInput formControlName="username"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.security.username').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.security.username').hasError('required')">
{{ 'security.username-required' | translate }} {{ 'security.username-required' | translate }}
</mat-error> </mat-error>
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.username').value" *ngIf="basicFormGroup.get('thingsboard.security.username').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.username').value" [copyText]="basicFormGroup.get('thingsboard.security.username').value"
tooltipText="{{ 'gateway.copy-username' | translate }}" tooltipText="{{ 'gateway.copy-username' | translate }}"
tooltipPosition="above" tooltipPosition="above"
icon="content_copy"> icon="content_copy">
@ -138,14 +135,14 @@
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 100%" <mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 100%"
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'"> *ngIf="basicFormGroup.get('thingsboard.security.type').value === 'usernamePassword'">
<mat-label translate>gateway.password</mat-label> <mat-label translate>gateway.password</mat-label>
<input matInput formControlName="password"/> <input matInput formControlName="password"/>
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.password').value" *ngIf="basicFormGroup.get('thingsboard.security.password').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.password').value" [copyText]="basicFormGroup.get('thingsboard.security.password').value"
tooltipText="{{ 'gateway.copy-password' | translate }}" tooltipText="{{ 'gateway.copy-password' | translate }}"
tooltipPosition="above" tooltipPosition="above"
icon="content_copy"> icon="content_copy">
@ -156,13 +153,13 @@
</mat-form-field> </mat-form-field>
</section> </section>
<tb-error style="margin-top: -12px; display: block;" fxFlex="100" <tb-error style="margin-top: -12px; display: block;" fxFlex="100"
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'" *ngIf="basicFormGroup.get('thingsboard.security.type').value === 'usernamePassword'"
[error]="gatewayConfigGroup.get('thingsboard.security').hasError('atLeastOne') ? [error]="basicFormGroup.get('thingsboard.security').hasError('atLeastOne') ?
('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error> ('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error>
<tb-file-input <tb-file-input
fxFlex="100" fxFlex="100"
hint="{{ 'gateway.hints.ca-cert' | translate }}" hint="{{ 'gateway.hints.ca-cert' | translate }}"
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value.toLowerCase().includes('tls')" *ngIf="basicFormGroup.get('thingsboard.security.type').value.toLowerCase().includes('tls')"
formControlName="caCert" formControlName="caCert"
label="{{ 'security.ca-cert' | translate }}" label="{{ 'security.ca-cert' | translate }}"
[allowedExtensions]="'pem, cert, key'" [allowedExtensions]="'pem, cert, key'"
@ -182,7 +179,7 @@
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label translate>gateway.logs.date-format</mat-label> <mat-label translate>gateway.logs.date-format</mat-label>
<input matInput formControlName="dateFormat"/> <input matInput formControlName="dateFormat"/>
<mat-error *ngIf="gatewayConfigGroup.get('logs.dateFormat').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('logs.dateFormat').hasError('required')">
{{ 'gateway.logs.date-format-required' | translate }} {{ 'gateway.logs.date-format-required' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -192,7 +189,7 @@
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label translate>gateway.logs.log-format</mat-label> <mat-label translate>gateway.logs.log-format</mat-label>
<textarea matInput formControlName="logFormat" rows="2"></textarea> <textarea matInput formControlName="logFormat" rows="2"></textarea>
<mat-error *ngIf="gatewayConfigGroup.get('logs.logFormat').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('logs.logFormat').hasError('required')">
{{ 'gateway.logs.log-format-required' | translate }} {{ 'gateway.logs.log-format-required' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -233,7 +230,7 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.logs.file-path</mat-label> <mat-label translate>gateway.logs.file-path</mat-label>
<input matInput formControlName="filePath"/> <input matInput formControlName="filePath"/>
<mat-error *ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.filePath').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('logs.local.' + logSelector.value + '.filePath').hasError('required')">
{{ 'gateway.logs.file-path-required' | translate }} {{ 'gateway.logs.file-path-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -244,11 +241,11 @@
<mat-label translate>gateway.logs.saving-period</mat-label> <mat-label translate>gateway.logs.saving-period</mat-label>
<input matInput formControlName="savingTime" type="number" min="0"/> <input matInput formControlName="savingTime" type="number" min="0"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.savingTime').hasError('required')"> *ngIf="basicFormGroup.get('logs.local.' + logSelector.value + '.savingTime').hasError('required')">
{{ 'gateway.logs.saving-period-required' | translate }} {{ 'gateway.logs.saving-period-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.savingTime').hasError('min')"> *ngIf="basicFormGroup.get('logs.local.' + logSelector.value + '.savingTime').hasError('min')">
{{ 'gateway.logs.saving-period-min' | translate }} {{ 'gateway.logs.saving-period-min' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -264,11 +261,11 @@
<mat-label translate>gateway.logs.backup-count</mat-label> <mat-label translate>gateway.logs.backup-count</mat-label>
<input matInput formControlName="backupCount" type="number" min="0"/> <input matInput formControlName="backupCount" type="number" min="0"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('required')"> *ngIf="basicFormGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('required')">
{{ 'gateway.logs.backup-count-required' | translate }} {{ 'gateway.logs.backup-count-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('min')"> *ngIf="basicFormGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('min')">
{{ 'gateway.logs.backup-count-min' | translate }} {{ 'gateway.logs.backup-count-min' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -292,19 +289,19 @@
{{ storageTypesTranslationMap.get(storageType) | translate }} {{ storageTypesTranslationMap.get(storageType) | translate }}
</tb-toggle-option> </tb-toggle-option>
</tb-toggle-select> </tb-toggle-select>
<div class="tb-form-panel-hint">{{ 'gateway.hints.' + gatewayConfigGroup.get('storage.type').value | translate }}</div> <div class="tb-form-panel-hint">{{ 'gateway.hints.' + basicFormGroup.get('storage.type').value | translate }}</div>
<ng-container [ngSwitch]="gatewayConfigGroup.get('storage.type').value"> <ng-container [ngSwitch]="basicFormGroup.get('storage.type').value">
<section *ngSwitchCase="StorageTypes.MEMORY" class="tb-form-row no-border no-padding tb-standard-fields column-xs"> <section *ngSwitchCase="StorageTypes.MEMORY" class="tb-form-row no-border no-padding tb-standard-fields column-xs">
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-read-record-count</mat-label> <mat-label translate>gateway.storage-read-record-count</mat-label>
<input type="number" matInput formControlName="read_records_count"/> <input type="number" matInput formControlName="read_records_count"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.read_records_count').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.read_records_count').hasError('required')">
{{ 'gateway.storage-read-record-count-required' | translate }} {{ 'gateway.storage-read-record-count-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.read_records_count').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.read_records_count').hasError('min')">
{{ 'gateway.storage-read-record-count-min' | translate }} {{ 'gateway.storage-read-record-count-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.read_records_count').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.read_records_count').hasError('pattern')">
{{ 'gateway.storage-read-record-count-pattern' | translate }} {{ 'gateway.storage-read-record-count-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -314,13 +311,13 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-records</mat-label> <mat-label translate>gateway.storage-max-records</mat-label>
<input type="number" matInput formControlName="max_records_count"/> <input type="number" matInput formControlName="max_records_count"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_count').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.max_records_count').hasError('required')">
{{ 'gateway.storage-max-records-required' | translate }} {{ 'gateway.storage-max-records-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_count').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.max_records_count').hasError('min')">
{{ 'gateway.storage-max-records-min' | translate }} {{ 'gateway.storage-max-records-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_count').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.max_records_count').hasError('pattern')">
{{ 'gateway.storage-max-records-pattern' | translate }} {{ 'gateway.storage-max-records-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -333,7 +330,7 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-data-folder-path</mat-label> <mat-label translate>gateway.storage-data-folder-path</mat-label>
<input matInput formControlName="data_folder_path"/> <input matInput formControlName="data_folder_path"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.data_folder_path').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.data_folder_path').hasError('required')">
{{ 'gateway.storage-data-folder-path-required' | translate }} {{ 'gateway.storage-data-folder-path-required' | translate }}
</mat-error> </mat-error>
<mat-icon class="mat-form-field-infix pointer-event suffix-icon" aria-hidden="false" <mat-icon class="mat-form-field-infix pointer-event suffix-icon" aria-hidden="false"
@ -345,13 +342,13 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-files</mat-label> <mat-label translate>gateway.storage-max-files</mat-label>
<input matInput type="number" formControlName="max_file_count"/> <input matInput type="number" formControlName="max_file_count"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_file_count').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.max_file_count').hasError('required')">
{{ 'gateway.storage-max-files-required' | translate }} {{ 'gateway.storage-max-files-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_file_count').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.max_file_count').hasError('min')">
{{ 'gateway.storage-max-files-min' | translate }} {{ 'gateway.storage-max-files-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_file_count').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.max_file_count').hasError('pattern')">
{{ 'gateway.storage-max-files-pattern' | translate }} {{ 'gateway.storage-max-files-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -363,13 +360,13 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-read-record-count</mat-label> <mat-label translate>gateway.storage-max-read-record-count</mat-label>
<input matInput type="number" formControlName="max_read_records_count"/> <input matInput type="number" formControlName="max_read_records_count"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_read_records_count').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.max_read_records_count').hasError('required')">
{{ 'gateway.storage-max-read-record-count-required' | translate }} {{ 'gateway.storage-max-read-record-count-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_read_records_count').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.max_read_records_count').hasError('min')">
{{ 'gateway.storage-max-read-record-count-min' | translate }} {{ 'gateway.storage-max-read-record-count-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_read_records_count').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.max_read_records_count').hasError('pattern')">
{{ 'gateway.storage-max-read-record-count-pattern' | translate }} {{ 'gateway.storage-max-read-record-count-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -379,13 +376,13 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-file-records</mat-label> <mat-label translate>gateway.storage-max-file-records</mat-label>
<input matInput type="number" formControlName="max_records_per_file"/> <input matInput type="number" formControlName="max_records_per_file"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_per_file').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.max_records_per_file').hasError('required')">
{{ 'gateway.storage-max-records-required' | translate }} {{ 'gateway.storage-max-records-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_per_file').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.max_records_per_file').hasError('min')">
{{ 'gateway.storage-max-records-min' | translate }} {{ 'gateway.storage-max-records-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.max_records_per_file').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.max_records_per_file').hasError('pattern')">
{{ 'gateway.storage-max-records-pattern' | translate }} {{ 'gateway.storage-max-records-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -399,7 +396,7 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-path</mat-label> <mat-label translate>gateway.storage-path</mat-label>
<input matInput formControlName="data_file_path"/> <input matInput formControlName="data_file_path"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.data_file_path').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.data_file_path').hasError('required')">
{{ 'gateway.storage-path-required' | translate }} {{ 'gateway.storage-path-required' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -409,13 +406,13 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.messages-ttl-check-in-hours</mat-label> <mat-label translate>gateway.messages-ttl-check-in-hours</mat-label>
<input matInput type="number" formControlName="messages_ttl_check_in_hours"/> <input matInput type="number" formControlName="messages_ttl_check_in_hours"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_check_in_hours').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.messages_ttl_check_in_hours').hasError('required')">
{{ 'gateway.messages-ttl-check-in-hours-required' | translate }} {{ 'gateway.messages-ttl-check-in-hours-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_check_in_hours').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.messages_ttl_check_in_hours').hasError('min')">
{{ 'gateway.messages-ttl-check-in-hours-min' | translate }} {{ 'gateway.messages-ttl-check-in-hours-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_check_in_hours').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.messages_ttl_check_in_hours').hasError('pattern')">
{{ 'gateway.messages-ttl-check-in-hours-pattern' | translate }} {{ 'gateway.messages-ttl-check-in-hours-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -426,13 +423,13 @@
<mat-form-field appearance="outline" class="mat-block"> <mat-form-field appearance="outline" class="mat-block">
<mat-label translate>gateway.messages-ttl-in-days</mat-label> <mat-label translate>gateway.messages-ttl-in-days</mat-label>
<input matInput type="number" formControlName="messages_ttl_in_days"/> <input matInput type="number" formControlName="messages_ttl_in_days"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_in_days').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('storage.messages_ttl_in_days').hasError('required')">
{{ 'gateway.messages-ttl-in-days-required' | translate }} {{ 'gateway.messages-ttl-in-days-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_in_days').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('storage.messages_ttl_in_days').hasError('min')">
{{ 'gateway.messages-ttl-in-days-min' | translate }} {{ 'gateway.messages-ttl-in-days-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('storage.messages_ttl_in_days').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('storage.messages_ttl_in_days').hasError('pattern')">
{{ 'gateway.messages-ttl-in-days-pattern' | translate }} {{ 'gateway.messages-ttl-in-days-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -466,16 +463,16 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.server-port' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.server-port' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('grpc.serverPort').hasError('required')">
{{ 'gateway.thingsboard-port-required' | translate }} {{ 'gateway.thingsboard-port-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('grpc.serverPort').hasError('min')">
{{ 'gateway.thingsboard-port-min' | translate }} {{ 'gateway.thingsboard-port-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('max')"> <mat-error *ngIf="basicFormGroup.get('grpc.serverPort').hasError('max')">
{{ 'gateway.thingsboard-port-max' | translate }} {{ 'gateway.thingsboard-port-max' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.serverPort').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('grpc.serverPort').hasError('pattern')">
{{ 'gateway.thingsboard-port-pattern' | translate }} {{ 'gateway.thingsboard-port-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -485,13 +482,13 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-keep-alive-timeout' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.grpc-keep-alive-timeout' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeoutMs').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('grpc.keepAliveTimeoutMs').hasError('required')">
{{ 'gateway.grpc-keep-alive-timeout-required' | translate }} {{ 'gateway.grpc-keep-alive-timeout-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeoutMs').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('grpc.keepAliveTimeoutMs').hasError('min')">
{{ 'gateway.grpc-keep-alive-timeout-min' | translate }} {{ 'gateway.grpc-keep-alive-timeout-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeoutMs').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('grpc.keepAliveTimeoutMs').hasError('pattern')">
{{ 'gateway.grpc-keep-alive-timeout-pattern' | translate }} {{ 'gateway.grpc-keep-alive-timeout-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -503,13 +500,13 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-keep-alive' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.grpc-keep-alive' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeMs').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('grpc.keepAliveTimeMs').hasError('required')">
{{ 'gateway.grpc-keep-alive-required' | translate }} {{ 'gateway.grpc-keep-alive-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeMs').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('grpc.keepAliveTimeMs').hasError('min')">
{{ 'gateway.grpc-keep-alive-min' | translate }} {{ 'gateway.grpc-keep-alive-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.keepAliveTimeMs').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('grpc.keepAliveTimeMs').hasError('pattern')">
{{ 'gateway.grpc-keep-alive-pattern' | translate }} {{ 'gateway.grpc-keep-alive-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -519,13 +516,13 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-min-time-between-pings' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.grpc-min-time-between-pings' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minTimeBetweenPingsMs').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('grpc.minTimeBetweenPingsMs').hasError('required')">
{{ 'gateway.grpc-min-time-between-pings-required' | translate }} {{ 'gateway.grpc-min-time-between-pings-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minTimeBetweenPingsMs').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('grpc.minTimeBetweenPingsMs').hasError('min')">
{{ 'gateway.grpc-min-time-between-pings-min' | translate }} {{ 'gateway.grpc-min-time-between-pings-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minTimeBetweenPingsMs').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('grpc.minTimeBetweenPingsMs').hasError('pattern')">
{{ 'gateway.grpc-min-time-between-pings-pattern' | translate }} {{ 'gateway.grpc-min-time-between-pings-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -537,13 +534,13 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-max-pings-without-data' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.grpc-max-pings-without-data' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.maxPingsWithoutData').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('grpc.maxPingsWithoutData').hasError('required')">
{{ 'gateway.grpc-max-pings-without-data-required' | translate }} {{ 'gateway.grpc-max-pings-without-data-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.maxPingsWithoutData').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('grpc.maxPingsWithoutData').hasError('min')">
{{ 'gateway.grpc-max-pings-without-data-min' | translate }} {{ 'gateway.grpc-max-pings-without-data-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.maxPingsWithoutData').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('grpc.maxPingsWithoutData').hasError('pattern')">
{{ 'gateway.grpc-max-pings-without-data-pattern' | translate }} {{ 'gateway.grpc-max-pings-without-data-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -553,13 +550,13 @@
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-min-ping-interval-without-data' | translate }}">info_outlined matTooltip="{{ 'gateway.hints.grpc-min-ping-interval-without-data' | translate }}">info_outlined
</mat-icon> </mat-icon>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('required')">
{{ 'gateway.grpc-min-ping-interval-without-data-required' | translate }} {{ 'gateway.grpc-min-ping-interval-without-data-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('min')">
{{ 'gateway.grpc-min-ping-interval-without-data-min' | translate }} {{ 'gateway.grpc-min-ping-interval-without-data-min' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('pattern')"> <mat-error *ngIf="basicFormGroup.get('grpc.minPingIntervalWithoutDataMs').hasError('pattern')">
{{ 'gateway.grpc-min-ping-interval-without-data-pattern' | translate }} {{ 'gateway.grpc-min-ping-interval-without-data-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -580,15 +577,15 @@
<mat-label translate>gateway.statistics.send-period</mat-label> <mat-label translate>gateway.statistics.send-period</mat-label>
<input matInput formControlName="statsSendPeriodInSeconds" type="number" min="60"/> <input matInput formControlName="statsSendPeriodInSeconds" type="number" min="60"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('required')"> *ngIf="basicFormGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('required')">
{{ 'gateway.statistics.send-period-required' | translate }} {{ 'gateway.statistics.send-period-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('min')"> *ngIf="basicFormGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('min')">
{{ 'gateway.statistics.send-period-min' | translate }} {{ 'gateway.statistics.send-period-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('pattern')">
{{ 'gateway.statistics.send-period-pattern' | translate }} {{ 'gateway.statistics.send-period-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -643,7 +640,7 @@
</mat-form-field> </mat-form-field>
</section> </section>
<button mat-icon-button (click)="removeCommandControl($index, $event)" <button mat-icon-button (click)="removeCommandControl($index, $event)"
[disabled]="!gatewayConfigGroup.get('thingsboard.remoteConfiguration').value" [disabled]="!basicFormGroup.get('thingsboard.remoteConfiguration').value"
matTooltip="{{ 'gateway.statistics.remove' | translate }}" matTooltip="{{ 'gateway.statistics.remove' | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
@ -652,7 +649,7 @@
<button mat-stroked-button color="primary" <button mat-stroked-button color="primary"
style="width: fit-content;" style="width: fit-content;"
type="button" type="button"
[disabled]="!gatewayConfigGroup.get('thingsboard.remoteConfiguration').value" [disabled]="!basicFormGroup.get('thingsboard.remoteConfiguration').value"
(click)="addCommand()"> (click)="addCommand()">
{{ 'gateway.statistics.add' | translate }} {{ 'gateway.statistics.add' | translate }}
</button> </button>
@ -665,7 +662,7 @@
<ng-template matTabContent> <ng-template matTabContent>
<div formGroupName="thingsboard" class="mat-content mat-padding configuration-block"> <div formGroupName="thingsboard" class="mat-content mat-padding configuration-block">
<div class="tb-form-panel" formGroupName="checkingDeviceActivity" <div class="tb-form-panel" formGroupName="checkingDeviceActivity"
[class.no-padding-bottom]="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value"> [class.no-padding-bottom]="basicFormGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value">
<div tb-hint-tooltip-icon="{{ 'gateway.hints.check-device-activity' | translate }}" <div tb-hint-tooltip-icon="{{ 'gateway.hints.check-device-activity' | translate }}"
class="tb-form-row no-border no-padding"> class="tb-form-row no-border no-padding">
<mat-slide-toggle class="mat-slide" color="primary" formControlName="checkDeviceInactivity"> <mat-slide-toggle class="mat-slide" color="primary" formControlName="checkDeviceInactivity">
@ -673,20 +670,20 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<section class="tb-form-row no-border no-padding tb-standard-fields column-xs" <section class="tb-form-row no-border no-padding tb-standard-fields column-xs"
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value">
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.inactivity-timeout-seconds</mat-label> <mat-label translate>gateway.inactivity-timeout-seconds</mat-label>
<input matInput formControlName="inactivityTimeoutSeconds" type="number" min="0"/> <input matInput formControlName="inactivityTimeoutSeconds" type="number" min="0"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('required')"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('required')">
{{ 'gateway.inactivity-timeout-seconds-required' | translate }} {{ 'gateway.inactivity-timeout-seconds-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('min')"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('min')">
{{ 'gateway.inactivity-timeout-seconds-min' | translate }} {{ 'gateway.inactivity-timeout-seconds-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('pattern')">
{{ 'gateway.inactivity-timeout-seconds-pattern' | translate }} {{ 'gateway.inactivity-timeout-seconds-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -697,15 +694,15 @@
<mat-label translate>gateway.inactivity-check-period-seconds</mat-label> <mat-label translate>gateway.inactivity-check-period-seconds</mat-label>
<input matInput formControlName="inactivityCheckPeriodSeconds"/> <input matInput formControlName="inactivityCheckPeriodSeconds"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('required')"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('required')">
{{ 'gateway.inactivity-check-period-seconds-required' | translate }} {{ 'gateway.inactivity-check-period-seconds-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('min')"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('min')">
{{ 'gateway.inactivity-check-period-seconds-min' | translate }} {{ 'gateway.inactivity-check-period-seconds-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityCheckPeriodSeconds').hasError('pattern')">
{{ 'gateway.inactivity-check-period-seconds-pattern' | translate }} {{ 'gateway.inactivity-check-period-seconds-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -720,14 +717,14 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.min-pack-send-delay</mat-label> <mat-label translate>gateway.min-pack-send-delay</mat-label>
<input matInput formControlName="minPackSendDelayMS" type="number" min="0"/> <input matInput formControlName="minPackSendDelayMS" type="number" min="0"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.minPackSendDelayMS').hasError('required')">
{{ 'gateway.min-pack-send-delay-required' | translate }} {{ 'gateway.min-pack-send-delay-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.minPackSendDelayMS').hasError('min')">
{{ 'gateway.min-pack-send-delay-min' | translate }} {{ 'gateway.min-pack-send-delay-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.minPackSendDelayMS').hasError('pattern')">
{{ 'gateway.min-pack-send-delay-min-pattern' | translate }} {{ 'gateway.min-pack-send-delay-min-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -737,13 +734,13 @@
<mat-form-field appearance="outline" class="flex"> <mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.mqtt-qos</mat-label> <mat-label translate>gateway.mqtt-qos</mat-label>
<input matInput formControlName="qos" type="number" min="0" max="1"/> <input matInput formControlName="qos" type="number" min="0" max="1"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.qos').hasError('required')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.qos').hasError('required')">
{{ 'gateway.mqtt-qos-required' | translate }} {{ 'gateway.mqtt-qos-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.qos').hasError('min')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.qos').hasError('min')">
{{ 'gateway.mqtt-qos-range' | translate }} {{ 'gateway.mqtt-qos-range' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.qos').hasError('max')"> <mat-error *ngIf="basicFormGroup.get('thingsboard.qos').hasError('max')">
{{ 'gateway.mqtt-qos-range' | translate }} {{ 'gateway.mqtt-qos-range' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -756,15 +753,15 @@
<mat-label translate>gateway.statistics.check-connectors-configuration</mat-label> <mat-label translate>gateway.statistics.check-connectors-configuration</mat-label>
<input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/> <input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')"> *ngIf="basicFormGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')">
{{ 'gateway.statistics.check-connectors-configuration-required' | translate }} {{ 'gateway.statistics.check-connectors-configuration-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')"> *ngIf="basicFormGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')">
{{ 'gateway.statistics.check-connectors-configuration-min' | translate }} {{ 'gateway.statistics.check-connectors-configuration-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')">
{{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -772,15 +769,15 @@
<mat-label translate>gateway.statistics.max-payload-size-bytes</mat-label> <mat-label translate>gateway.statistics.max-payload-size-bytes</mat-label>
<input matInput formControlName="maxPayloadSizeBytes" type="number" min="0"/> <input matInput formControlName="maxPayloadSizeBytes" type="number" min="0"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('required')"> *ngIf="basicFormGroup.get('thingsboard.maxPayloadSizeBytes').hasError('required')">
{{ 'gateway.statistics.max-payload-size-bytes-required' | translate }} {{ 'gateway.statistics.max-payload-size-bytes-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('min')"> *ngIf="basicFormGroup.get('thingsboard.maxPayloadSizeBytes').hasError('min')">
{{ 'gateway.statistics.max-payload-size-bytes-min' | translate }} {{ 'gateway.statistics.max-payload-size-bytes-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.maxPayloadSizeBytes').hasError('pattern')">
{{ 'gateway.statistics.max-payload-size-bytes-pattern' | translate }} {{ 'gateway.statistics.max-payload-size-bytes-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -793,15 +790,15 @@
<mat-label translate>gateway.statistics.min-pack-size-to-send</mat-label> <mat-label translate>gateway.statistics.min-pack-size-to-send</mat-label>
<input matInput formControlName="minPackSizeToSend" type="number" min="0"/> <input matInput formControlName="minPackSizeToSend" type="number" min="0"/>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSizeToSend').hasError('required')"> *ngIf="basicFormGroup.get('thingsboard.minPackSizeToSend').hasError('required')">
{{ 'gateway.statistics.min-pack-size-to-send-required' | translate }} {{ 'gateway.statistics.min-pack-size-to-send-required' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSizeToSend').hasError('min')"> *ngIf="basicFormGroup.get('thingsboard.minPackSizeToSend').hasError('min')">
{{ 'gateway.statistics.min-pack-size-to-send-min' | translate }} {{ 'gateway.statistics.min-pack-size-to-send-min' | translate }}
</mat-error> </mat-error>
<mat-error <mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.minPackSizeToSend').hasError('pattern')"> *ngIf="basicFormGroup.get('thingsboard.minPackSizeToSend').hasError('pattern')">
{{ 'gateway.statistics.min-pack-size-to-send-pattern' | translate }} {{ 'gateway.statistics.min-pack-size-to-send-pattern' | translate }}
</mat-error> </mat-error>
<mat-icon matIconSuffix style="cursor:pointer;" <mat-icon matIconSuffix style="cursor:pointer;"
@ -814,17 +811,3 @@
</ng-template> </ng-template>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
<div class="actions">
<button mat-button color="primary"
type="button"
*ngIf="dialogRef"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="button"
[disabled]="gatewayConfigGroup.invalid || !gatewayConfigGroup.dirty"
(click)="saveConfig()">
{{ 'action.save' | translate }}
</button>
</div>

16
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.scss

@ -23,6 +23,13 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
max-height: 70vh;
}
.dialog-mode {
.configuration-block {
max-height: 60vh;
}
} }
.mat-toolbar { .mat-toolbar {
@ -62,15 +69,6 @@
} }
} }
.actions {
grid-row: 3;
padding: 8px;
display: flex;
gap: 8px;
justify-content: flex-end;
flex: 1;
}
mat-form-field { mat-form-field {
mat-error { mat-error {
display: none !important; display: none !important;

676
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts

@ -14,29 +14,37 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { import {
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
Input,
OnDestroy,
Output
} from '@angular/core';
import {
ControlValueAccessor,
FormArray, FormArray,
FormBuilder, FormBuilder,
FormControl, FormControl,
FormGroup, FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors, ValidationErrors,
ValidatorFn, ValidatorFn,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { AttributeService } from '@core/http/attribute.service';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { import {
GatewayRemoteConfigurationDialogComponent, GatewayRemoteConfigurationDialogComponent,
GatewayRemoteConfigurationDialogData GatewayRemoteConfigurationDialogData
} from '@home/components/widget/lib/gateway/gateway-remote-configuration-dialog'; } from '@home/components/widget/lib/gateway/gateway-remote-configuration-dialog';
import { DeviceService } from '@core/http/device.service'; import { DeviceService } from '@core/http/device.service';
import { Observable, of } from 'rxjs'; import { Subject } from 'rxjs';
import { mergeMap } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { import {
GatewayLogLevel, GatewayLogLevel,
GecurityTypesTranslationsMap, GecurityTypesTranslationsMap,
@ -46,51 +54,228 @@ import {
LogSavingPeriodTranslations, LogSavingPeriodTranslations,
SecurityTypes, SecurityTypes,
StorageTypes, StorageTypes,
StorageTypesTranslationMap StorageTypesTranslationMap,
} from './gateway-widget.models'; } from '../../gateway-widget.models';
import { deepTrim } from '@core/utils'; import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { coerceBoolean } from '@shared/decorators/coercion';
import {
GatewayConfigCommand,
GatewayConfigValue, LogConfig
} from '@home/components/widget/lib/gateway/configuration/models/gateway-configuration.models';
@Component({ @Component({
selector: 'tb-gateway-configuration', selector: 'tb-gateway-basic-configuration',
templateUrl: './gateway-configuration.component.html', templateUrl: './gateway-basic-configuration.component.html',
styleUrls: ['./gateway-configuration.component.scss'] styleUrls: ['./gateway-basic-configuration.component.scss'],
standalone: true,
imports: [
CommonModule,
SharedModule,
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GatewayBasicConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => GatewayBasicConfigurationComponent),
multi: true
}
],
}) })
export class GatewayConfigurationComponent implements OnInit { export class GatewayBasicConfigurationComponent implements OnDestroy, ControlValueAccessor, Validators {
@Input()
device: EntityId;
@coerceBoolean()
@Input()
dialogMode = false;
gatewayConfigGroup: FormGroup; @Output()
initialCredentialsUpdated = new EventEmitter<DeviceCredentials>();
StorageTypes = StorageTypes; StorageTypes = StorageTypes;
storageTypes = Object.values(StorageTypes) as StorageTypes[]; storageTypes = Object.values(StorageTypes) as StorageTypes[];
storageTypesTranslationMap = StorageTypesTranslationMap; storageTypesTranslationMap = StorageTypesTranslationMap;
logSavingPeriods = LogSavingPeriodTranslations; logSavingPeriods = LogSavingPeriodTranslations;
localLogsConfigs = Object.keys(LocalLogsConfigs) as LocalLogsConfigs[]; localLogsConfigs = Object.keys(LocalLogsConfigs) as LocalLogsConfigs[];
localLogsConfigTranslateMap = LocalLogsConfigTranslateMap; localLogsConfigTranslateMap = LocalLogsConfigTranslateMap;
securityTypes = GecurityTypesTranslationsMap; securityTypes = GecurityTypesTranslationsMap;
gatewayLogLevel = Object.values(GatewayLogLevel); gatewayLogLevel = Object.values(GatewayLogLevel);
@Input()
device: EntityId;
@Input()
dialogRef: MatDialogRef<any>;
logSelector: FormControl; logSelector: FormControl;
basicFormGroup: FormGroup;
private onChange: (value: GatewayConfigValue) => void;
private onTouched: () => void;
private initialCredentials: DeviceCredentials; private destroy$ = new Subject<void>();
constructor(private fb: FormBuilder, constructor(private fb: FormBuilder,
private attributeService: AttributeService,
private deviceService: DeviceService, private deviceService: DeviceService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private dialog: MatDialog) { private dialog: MatDialog) {
this.initBasicFormGroup();
this.basicFormGroup.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
this.onChange(value);
this.onTouched();
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: (value: GatewayConfigValue) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
writeValue(basicConfig: GatewayConfigValue): void {
this.basicFormGroup.patchValue(basicConfig, {emitEvent: false});
if (basicConfig?.thingsboard?.security) {
this.checkAndFetchCredentials(basicConfig.thingsboard.security);
}
if (basicConfig?.grpc) {
this.toggleRpcFields(basicConfig.grpc.enabled);
}
const commands = basicConfig?.thingsboard?.statistics?.commands || [];
commands.forEach(command => this.addCommand(command, false));
}
validate(): ValidationErrors | null {
return this.basicFormGroup.valid ? null : {
basicFormGroup: {valid: false}
};
}
private atLeastOneRequired(validator: ValidatorFn, controls: string[] = null) {
return (group: FormGroup): ValidationErrors | null => {
if (!controls) {
controls = Object.keys(group.controls);
}
const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {atLeastOne: true};
};
} }
ngOnInit() { private toggleRpcFields(enable: boolean): void {
this.gatewayConfigGroup = this.fb.group({ const grpcGroup = this.basicFormGroup.get('grpc') as FormGroup;
if (enable) {
grpcGroup.get('serverPort').enable({emitEvent: false});
grpcGroup.get('keepAliveTimeMs').enable({emitEvent: false});
grpcGroup.get('keepAliveTimeoutMs').enable({emitEvent: false});
grpcGroup.get('keepalivePermitWithoutCalls').enable({emitEvent: false});
grpcGroup.get('maxPingsWithoutData').enable({emitEvent: false});
grpcGroup.get('minTimeBetweenPingsMs').enable({emitEvent: false});
grpcGroup.get('minPingIntervalWithoutDataMs').enable({emitEvent: false});
} else {
grpcGroup.get('serverPort').disable({emitEvent: false});
grpcGroup.get('keepAliveTimeMs').disable({emitEvent: false});
grpcGroup.get('keepAliveTimeoutMs').disable({emitEvent: false});
grpcGroup.get('keepalivePermitWithoutCalls').disable({emitEvent: false});
grpcGroup.get('maxPingsWithoutData').disable({emitEvent: false});
grpcGroup.get('minTimeBetweenPingsMs').disable({emitEvent: false});
grpcGroup.get('minPingIntervalWithoutDataMs').disable({emitEvent: false});
}
}
private addLocalLogConfig(name: string, config: LogConfig): void {
const localLogsFormGroup = this.basicFormGroup.get('logs.local') as FormGroup;
const configGroup = this.fb.group({
logLevel: [config.logLevel || GatewayLogLevel.INFO, [Validators.required]],
filePath: [config.filePath || './logs', [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.basicFormGroup.get(`logs.local.${value}`) as FormGroup;
}
commandFormArray(): FormArray {
return this.basicFormGroup.get('thingsboard.statistics.commands') as FormArray;
}
removeCommandControl(index: number, event: PointerEvent): void {
if (event.pointerType === '') {
return;
}
this.commandFormArray().removeAt(index);
this.basicFormGroup.markAsDirty();
}
private removeAllSecurityValidators(): void {
const securityGroup = this.basicFormGroup.get('thingsboard.security') as FormGroup;
securityGroup.clearValidators();
for (const controlsKey in securityGroup.controls) {
if (controlsKey !== 'type') {
securityGroup.controls[controlsKey].clearValidators();
securityGroup.controls[controlsKey].setErrors(null);
securityGroup.controls[controlsKey].updateValueAndValidity();
}
}
}
private removeAllStorageValidators(): void {
const storageGroup = this.basicFormGroup.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();
}
}
}
private openConfigurationConfirmDialog(): void {
this.deviceService.getDevice(this.device.id).pipe(takeUntil(this.destroy$)).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.basicFormGroup.get('thingsboard.remoteConfiguration').setValue(true, {emitEvent: false});
}
}
);
});
}
addCommand(command?: GatewayConfigCommand, emitEvent: boolean = true): void {
const { attributeOnGateway = null, command: cmd = null, timeout = null } = command || {};
const commandFormGroup = this.fb.group({
attributeOnGateway: [attributeOnGateway, [Validators.required, Validators.pattern(/^[^.\s]+$/)]],
command: [cmd, [Validators.required, Validators.pattern(/^(?=\S).*\S$/)]],
timeout: [timeout, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.pattern(/^[^.\s]+$/)]]
});
this.commandFormArray().push(commandFormGroup, { emitEvent });
}
private initBasicFormGroup(): void {
this.basicFormGroup = this.fb.group({
thingsboard: this.fb.group({ thingsboard: this.fb.group({
host: [window.location.hostname, [Validators.required, Validators.pattern(/^[^\s]+$/)]], host: [window.location.hostname, [Validators.required, Validators.pattern(/^[^\s]+$/)]],
port: [1883, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], port: [1883, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]],
@ -125,8 +310,14 @@ export class GatewayConfigurationComponent implements OnInit {
}), }),
storage: this.fb.group({ storage: this.fb.group({
type: [StorageTypes.MEMORY, [Validators.required]], type: [StorageTypes.MEMORY, [Validators.required]],
read_records_count: [100, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]], read_records_count: [
max_records_count: [100000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]], 100, [Validators.min(1),
Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]
],
max_records_count: [
100000,
[Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]
],
data_folder_path: ['./data/', [Validators.required]], data_folder_path: ['./data/', [Validators.required]],
max_file_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], max_file_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]],
max_read_records_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], max_read_records_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]],
@ -160,18 +351,18 @@ export class GatewayConfigurationComponent implements OnInit {
}) })
}); });
this.gatewayConfigGroup.get('thingsboard.security.password').valueChanges.subscribe(password => { this.basicFormGroup.get('thingsboard.security.password').valueChanges.subscribe(password => {
if (password && password !== '') { if (password && password !== '') {
this.gatewayConfigGroup.get('thingsboard.security.username').setValidators([Validators.required]); this.basicFormGroup.get('thingsboard.security.username').setValidators([Validators.required]);
} else { } else {
this.gatewayConfigGroup.get('thingsboard.security.username').clearValidators(); this.basicFormGroup.get('thingsboard.security.username').clearValidators();
} }
this.gatewayConfigGroup.get('thingsboard.security.username').updateValueAndValidity({emitEvent: false}); this.basicFormGroup.get('thingsboard.security.username').updateValueAndValidity({emitEvent: false});
}); });
this.toggleRpcFields(false); this.toggleRpcFields(false);
this.gatewayConfigGroup.get('thingsboard.remoteConfiguration').valueChanges.subscribe(enabled => { this.basicFormGroup.get('thingsboard.remoteConfiguration').valueChanges.subscribe(enabled => {
if (!enabled) { if (!enabled) {
this.openConfigurationConfirmDialog(); this.openConfigurationConfirmDialog();
} }
@ -180,15 +371,17 @@ export class GatewayConfigurationComponent implements OnInit {
this.logSelector = this.fb.control(LocalLogsConfigs.service); this.logSelector = this.fb.control(LocalLogsConfigs.service);
for (const localLogsConfigsKey of Object.keys(LocalLogsConfigs)) { for (const localLogsConfigsKey of Object.keys(LocalLogsConfigs)) {
this.addLocalLogConfig(localLogsConfigsKey, {}); this.addLocalLogConfig(localLogsConfigsKey, {} as LogConfig);
} }
const checkingDeviceActivityGroup = this.gatewayConfigGroup.get('thingsboard.checkingDeviceActivity') as FormGroup; const checkingDeviceActivityGroup = this.basicFormGroup.get('thingsboard.checkingDeviceActivity') as FormGroup;
checkingDeviceActivityGroup.get('checkDeviceInactivity').valueChanges.subscribe(enabled => { checkingDeviceActivityGroup.get('checkDeviceInactivity').valueChanges.subscribe(enabled => {
checkingDeviceActivityGroup.updateValueAndValidity(); checkingDeviceActivityGroup.updateValueAndValidity();
if (enabled) { if (enabled) {
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]); checkingDeviceActivityGroup.get('inactivityTimeoutSeconds')
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]); .setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]);
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds')
.setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]);
} else { } else {
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').clearValidators(); checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').clearValidators();
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').clearValidators(); checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').clearValidators();
@ -197,11 +390,11 @@ export class GatewayConfigurationComponent implements OnInit {
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').updateValueAndValidity({emitEvent: false}); checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').updateValueAndValidity({emitEvent: false});
}); });
this.gatewayConfigGroup.get('grpc.enabled').valueChanges.subscribe(value => { this.basicFormGroup.get('grpc.enabled').valueChanges.subscribe(value => {
this.toggleRpcFields(value); this.toggleRpcFields(value);
}); });
const securityGroup = this.gatewayConfigGroup.get('thingsboard.security') as FormGroup; const securityGroup = this.basicFormGroup.get('thingsboard.security') as FormGroup;
securityGroup.get('type').valueChanges.subscribe(type => { securityGroup.get('type').valueChanges.subscribe(type => {
this.removeAllSecurityValidators(); this.removeAllSecurityValidators();
if (type === SecurityTypes.ACCESS_TOKEN) { if (type === SecurityTypes.ACCESS_TOKEN) {
@ -229,7 +422,7 @@ export class GatewayConfigurationComponent implements OnInit {
securityGroup.get('privateKey').valueChanges.subscribe(() => this.cd.detectChanges()); securityGroup.get('privateKey').valueChanges.subscribe(() => this.cd.detectChanges());
securityGroup.get('cert').valueChanges.subscribe(() => this.cd.detectChanges()); securityGroup.get('cert').valueChanges.subscribe(() => this.cd.detectChanges());
const storageGroup = this.gatewayConfigGroup.get('storage') as FormGroup; const storageGroup = this.basicFormGroup.get('storage') as FormGroup;
storageGroup.get('type').valueChanges.subscribe(type => { storageGroup.get('type').valueChanges.subscribe(type => {
this.removeAllStorageValidators(); this.removeAllStorageValidators();
if (type === StorageTypes.MEMORY) { if (type === StorageTypes.MEMORY) {
@ -262,403 +455,32 @@ export class GatewayConfigurationComponent implements OnInit {
storageGroup.get('messages_ttl_in_days').updateValueAndValidity({emitEvent: false}); storageGroup.get('messages_ttl_in_days').updateValueAndValidity({emitEvent: false});
} }
}); });
this.fetchConfigAttribute(this.device);
}
private atLeastOneRequired(validator: ValidatorFn, controls: string[] = null) {
return (group: FormGroup): ValidationErrors | null => {
if (!controls) {
controls = Object.keys(group.controls);
}
const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {atLeastOne: true};
};
}
private fetchConfigAttribute(entityId: EntityId) {
if (entityId.id === NULL_UUID) {
return;
}
this.attributeService.getEntityAttributes(entityId, AttributeScope.CLIENT_SCOPE,
['general_configuration', 'grpc_configuration', 'logs_configuration', 'storage_configuration', 'RemoteLoggingLevel']).pipe(
mergeMap(attributes => attributes.length ? of(attributes) : this.attributeService.getEntityAttributes(
entityId, AttributeScope.SHARED_SCOPE, ['general_configuration', 'grpc_configuration',
'logs_configuration', 'storage_configuration', 'RemoteLoggingLevel']))
).subscribe(attributes => {
if (attributes.length) {
const general_configuration = attributes.find(attribute => attribute.key === 'general_configuration')?.value;
const grpc_configuration = attributes.find(attribute => attribute.key === 'grpc_configuration')?.value;
const logs_configuration = attributes.find(attribute => attribute.key === 'logs_configuration')?.value;
const storage_configuration = attributes.find(attribute => attribute.key === 'storage_configuration')?.value;
const remoteLoggingLevel = attributes.find(attribute => attribute.key === 'RemoteLoggingLevel')?.value;
if (general_configuration) {
const configObj = {thingsboard: general_configuration};
if (configObj.thingsboard.statistics && configObj.thingsboard.statistics.commands) {
for (const command of configObj.thingsboard.statistics.commands) {
this.addCommand(command);
}
delete configObj.thingsboard.statistics.commands;
}
this.gatewayConfigGroup.patchValue(configObj, {emitEvent: false});
this.gatewayConfigGroup.markAsPristine();
if (!configObj.thingsboard.remoteConfiguration) {
this.gatewayConfigGroup.disable({emitEvent: false});
}
this.checkAndFetchCredentials(configObj.thingsboard.security);
}
if (grpc_configuration) {
const configObj = {grpc: grpc_configuration};
this.gatewayConfigGroup.patchValue(configObj, {emitEvent: false});
this.toggleRpcFields(grpc_configuration.enabled);
}
if (logs_configuration) {
const configObj = {logs: this.logsToObj(logs_configuration)};
this.gatewayConfigGroup.patchValue(configObj, {emitEvent: false});
this.cd.detectChanges();
}
if (storage_configuration) {
const configObj = {storage: storage_configuration};
this.gatewayConfigGroup.patchValue(configObj, {emitEvent: false});
}
if (remoteLoggingLevel) {
const remoteLogsFormGroup = this.gatewayConfigGroup.get('logs.remote');
remoteLogsFormGroup.patchValue({
enabled: remoteLoggingLevel !== GatewayLogLevel.NONE,
logLevel: remoteLoggingLevel
}, {emitEvent: false});
remoteLogsFormGroup.markAsPristine();
}
this.cd.detectChanges();
} else {
this.checkAndFetchCredentials();
}
});
} }
private checkAndFetchCredentials(security: any = {}): void { private checkAndFetchCredentials(security: any = {}): void {
if (security.type !== SecurityTypes.TLS_PRIVATE_KEY) { if (security.type !== SecurityTypes.TLS_PRIVATE_KEY) {
this.deviceService.getDeviceCredentials(this.device.id).subscribe(credentials => { this.deviceService.getDeviceCredentials(this.device.id).subscribe(credentials => {
this.initialCredentials = credentials; this.initialCredentialsUpdated.emit(credentials);
if (credentials.credentialsType === DeviceCredentialsType.ACCESS_TOKEN || security.type === SecurityTypes.TLS_ACCESS_TOKEN) { if (credentials.credentialsType === DeviceCredentialsType.ACCESS_TOKEN || security.type === SecurityTypes.TLS_ACCESS_TOKEN) {
this.gatewayConfigGroup.get('thingsboard.security.type').setValue(security.type === SecurityTypes.TLS_ACCESS_TOKEN this.basicFormGroup.get('thingsboard.security.type')
? SecurityTypes.TLS_ACCESS_TOKEN .setValue(security.type === SecurityTypes.TLS_ACCESS_TOKEN
: SecurityTypes.ACCESS_TOKEN); ? SecurityTypes.TLS_ACCESS_TOKEN
this.gatewayConfigGroup.get('thingsboard.security.accessToken').setValue(credentials.credentialsId); : SecurityTypes.ACCESS_TOKEN, {emitEvent: false});
this.basicFormGroup.get('thingsboard.security.accessToken').setValue(credentials.credentialsId, {emitEvent: false});
if(security.type === SecurityTypes.TLS_ACCESS_TOKEN) { if(security.type === SecurityTypes.TLS_ACCESS_TOKEN) {
this.gatewayConfigGroup.get('thingsboard.security.caCert').setValue(security.caCert); this.basicFormGroup.get('thingsboard.security.caCert').setValue(security.caCert, {emitEvent: false});
} }
} else if (credentials.credentialsType === DeviceCredentialsType.MQTT_BASIC) { } else if (credentials.credentialsType === DeviceCredentialsType.MQTT_BASIC) {
const parsedValue = JSON.parse(credentials.credentialsValue); const parsedValue = JSON.parse(credentials.credentialsValue);
this.gatewayConfigGroup.get('thingsboard.security.type').setValue(SecurityTypes.USERNAME_PASSWORD); this.basicFormGroup.get('thingsboard.security.type').setValue(SecurityTypes.USERNAME_PASSWORD, {emitEvent: false});
this.gatewayConfigGroup.get('thingsboard.security.clientId').setValue(parsedValue.clientId); this.basicFormGroup.get('thingsboard.security.clientId').setValue(parsedValue.clientId, {emitEvent: false});
this.gatewayConfigGroup.get('thingsboard.security.username').setValue(parsedValue.userName); this.basicFormGroup.get('thingsboard.security.username').setValue(parsedValue.userName, {emitEvent: false});
this.gatewayConfigGroup.get('thingsboard.security.password').setValue(parsedValue.password, {emitEvent: false}); this.basicFormGroup.get('thingsboard.security.password')
.setValue(parsedValue.password, {emitEvent: false});
} else if (credentials.credentialsType === DeviceCredentialsType.X509_CERTIFICATE) { } else if (credentials.credentialsType === DeviceCredentialsType.X509_CERTIFICATE) {
//if sertificate is present set sertificate as present //if sertificate is present set sertificate as present
} }
}); });
} }
} }
private logsToObj(logsConfig: any) {
const logsObject = {
local: {}
};
const logFormat = logsConfig.formatters.LogFormatter.format;
const dateFormat = logsConfig.formatters.LogFormatter.datefmt;
for (const localLogsConfigsKey of Object.keys(LocalLogsConfigs)) {
const handlerKey = localLogsConfigsKey + 'Handler';
logsObject[localLogsConfigsKey] = {
logLevel: logsConfig.loggers[localLogsConfigsKey].level,
filePath: logsConfig.handlers[handlerKey].filename.split('/' + localLogsConfigsKey)[0],
backupCount: logsConfig.handlers[handlerKey].backupCount,
savingTime: logsConfig.handlers[handlerKey].interval,
savingPeriod: logsConfig.handlers[handlerKey].when,
};
}
return {local: logsObject, logFormat, dateFormat};
}
private toggleRpcFields(enable: boolean) {
const grpcGroup = this.gatewayConfigGroup.get('grpc') as FormGroup;
if (enable) {
grpcGroup.get('serverPort').enable({emitEvent: false});
grpcGroup.get('keepAliveTimeMs').enable({emitEvent: false});
grpcGroup.get('keepAliveTimeoutMs').enable({emitEvent: false});
grpcGroup.get('keepalivePermitWithoutCalls').enable({emitEvent: false});
grpcGroup.get('maxPingsWithoutData').enable({emitEvent: false});
grpcGroup.get('minTimeBetweenPingsMs').enable({emitEvent: false});
grpcGroup.get('minPingIntervalWithoutDataMs').enable({emitEvent: false});
} else {
grpcGroup.get('serverPort').disable({emitEvent: false});
grpcGroup.get('keepAliveTimeMs').disable({emitEvent: false});
grpcGroup.get('keepAliveTimeoutMs').disable({emitEvent: false});
grpcGroup.get('keepalivePermitWithoutCalls').disable({emitEvent: false});
grpcGroup.get('maxPingsWithoutData').disable({emitEvent: false});
grpcGroup.get('minTimeBetweenPingsMs').disable({emitEvent: false});
grpcGroup.get('minPingIntervalWithoutDataMs').disable({emitEvent: false});
}
}
addCommand(command: any = {}): void {
const commandsFormArray = this.commandFormArray();
const commandFormGroup = this.fb.group({
attributeOnGateway: [command.attributeOnGateway || null, [Validators.required, Validators.pattern(/^[^.\s]+$/)]],
command: [command.command || null, [Validators.required, Validators.pattern(/^(?=\S).*\S$/)]],
timeout: [command.timeout || null, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.pattern(/^[^.\s]+$/)]],
});
commandsFormArray.push(commandFormGroup);
}
private addLocalLogConfig(name: string, config: any): 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 || './logs', [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, event: any): void {
if (event.pointerType === '') {
return;
}
this.commandFormArray().removeAt(index);
this.gatewayConfigGroup.markAsDirty();
}
private removeAllSecurityValidators(): void {
const securityGroup = this.gatewayConfigGroup.get('thingsboard.security') as FormGroup;
securityGroup.clearValidators();
for (const controlsKey in securityGroup.controls) {
if (controlsKey !== 'type') {
securityGroup.controls[controlsKey].clearValidators();
securityGroup.controls[controlsKey].setErrors(null);
securityGroup.controls[controlsKey].updateValueAndValidity();
}
}
}
private 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();
}
}
}
private removeEmpty(obj: any) {
return Object.fromEntries(
Object.entries(obj)
.filter(([_, v]) => v != null)
.map(([k, v]) => [k, v === Object(v) ? this.removeEmpty(v) : v])
);
}
private generateLogsFile(logsObj: any) {
const logAttrObj = {
version: 1,
disable_existing_loggers: false,
formatters: {
LogFormatter: {
class: 'logging.Formatter',
format: logsObj.logFormat,
datefmt: logsObj.dateFormat,
}
},
handlers: {
consoleHandler: {
class: 'logging.StreamHandler',
formatter: 'LogFormatter',
level: 'DEBUG',
stream: 'ext://sys.stdout'
},
databaseHandler: {
class: 'thingsboard_gateway.tb_utility.tb_handler.TimedRotatingFileHandler',
formatter: 'LogFormatter',
filename: './logs/database.log',
backupCount: 1,
encoding: 'utf-8'
}
},
loggers: {
database: {
handlers: ['databaseHandler', 'consoleHandler'],
level: 'DEBUG',
propagate: false
}
},
root: {
level: 'ERROR',
handlers: [
'consoleHandler'
]
},
ts: new Date().getTime()
};
for (const key of Object.keys(logsObj.local)) {
logAttrObj.handlers[key + 'Handler'] = this.createHandlerObj(logsObj.local[key], key);
logAttrObj.loggers[key] = this.createLoggerObj(logsObj.local[key], key);
}
return logAttrObj;
}
private createHandlerObj(logObj: any, key: string) {
return {
class: 'thingsboard_gateway.tb_utility.tb_handler.TimedRotatingFileHandler',
formatter: 'LogFormatter',
filename: `${logObj.filePath}/${key}.log`,
backupCount: logObj.backupCount,
interval: logObj.savingTime,
when: logObj.savingPeriod,
encoding: 'utf-8'
};
}
private createLoggerObj(logObj: any, key: string) {
return {
handlers: [`${key}Handler`, 'consoleHandler'],
level: logObj.logLevel,
propagate: false
};
}
saveConfig(): void {
const value = deepTrim(this.removeEmpty(this.gatewayConfigGroup.value));
value.thingsboard.statistics.commands = Object.values(value.thingsboard.statistics.commands);
const attributes = [];
attributes.push({
key: 'RemoteLoggingLevel',
value: value.logs.remote.enabled ? value.logs.remote.logLevel : GatewayLogLevel.NONE
});
delete value.connectors;
attributes.push({
key: 'logs_configuration',
value: this.generateLogsFile(value.logs)
});
value.grpc.ts = new Date().getTime();
attributes.push({
key: 'grpc_configuration',
value: value.grpc
});
value.storage.ts = new Date().getTime();
attributes.push({
key: 'storage_configuration',
value: value.storage
});
value.thingsboard.ts = new Date().getTime();
attributes.push({
key: 'general_configuration',
value: value.thingsboard
});
this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, attributes).subscribe(_ => {
this.updateCredentials(value.thingsboard.security).subscribe(() => {
if (this.dialogRef) {
this.dialogRef.close();
} else {
this.gatewayConfigGroup.markAsPristine();
this.cd.detectChanges();
}
});
});
}
private updateCredentials(securityConfig: any): Observable<any> {
let updateCredentials = false;
let newCredentials = {};
if (securityConfig.type === SecurityTypes.USERNAME_PASSWORD) {
if (this.initialCredentials.credentialsType !== DeviceCredentialsType.MQTT_BASIC) {
updateCredentials = true;
} else {
const parsedCredentials = JSON.parse(this.initialCredentials.credentialsValue);
updateCredentials = !(
parsedCredentials.clientId === securityConfig.clientId &&
parsedCredentials.userName === securityConfig.username &&
parsedCredentials.password === securityConfig.password);
}
if (updateCredentials) {
const credentialsValue: { clientId?: string; userName?: string; password?: string } = {};
const credentialsType = DeviceCredentialsType.MQTT_BASIC;
if (securityConfig.clientId) {
credentialsValue.clientId = securityConfig.clientId;
}
if (securityConfig.username) {
credentialsValue.userName = securityConfig.username;
}
if (securityConfig.password) {
credentialsValue.password = securityConfig.password;
}
newCredentials = {
credentialsType,
credentialsValue: JSON.stringify(credentialsValue)
};
}
} else if (securityConfig.type === SecurityTypes.ACCESS_TOKEN || securityConfig.type === SecurityTypes.TLS_ACCESS_TOKEN) {
if (this.initialCredentials.credentialsType !== DeviceCredentialsType.ACCESS_TOKEN) {
updateCredentials = true;
} else {
updateCredentials = this.initialCredentials.credentialsId !== securityConfig.accessToken;
if (updateCredentials) {
this.initialCredentials.credentialsId = securityConfig.accessToken;
}
}
if (updateCredentials) {
newCredentials = {
credentialsType: DeviceCredentialsType.ACCESS_TOKEN,
credentialsId: securityConfig.accessToken
};
}
}
if (updateCredentials) {
return this.deviceService.saveDeviceCredentials({...this.initialCredentials,...newCredentials});
}
return of(null);
}
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});
}
}
);
});
}
} }

64
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.html

@ -0,0 +1,64 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div [formGroup]="gatewayConfigGroup" class="gateway-config-container">
<div class="content-wrapper">
<mat-toolbar color="primary" [class.page-header]="!dialogRef">
<div class="tb-flex space-between">
<h2 translate>gateway.gateway-configuration</h2>
<div class="toolbar-actions">
<tb-toggle-select [class.dialog-toggle]="!!dialogRef" formControlName="mode" appearance="{{dialogRef ? 'stroked' : 'fill'}}">
<tb-toggle-option [value]="ConfigurationModes.BASIC">
{{ 'gateway.basic' | translate }}
</tb-toggle-option>
<tb-toggle-option [value]="ConfigurationModes.ADVANCED">
{{ 'gateway.advanced' | translate }}
</tb-toggle-option>
</tb-toggle-select>
<button *ngIf="dialogRef" mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</div>
</div>
</mat-toolbar>
<tb-gateway-basic-configuration
*ngIf="gatewayConfigGroup.get('mode').value === ConfigurationModes.BASIC"
formControlName="basicConfig"
[device]="device"
[dialogMode]="!!dialogRef"
(initialCredentialsUpdated)="initialCredentials = $event"
/>
<tb-gateway-advanced-configuration
*ngIf="gatewayConfigGroup.get('mode').value === ConfigurationModes.ADVANCED"
formControlName="advancedConfig"
/>
</div>
<div class="actions">
<button mat-button color="primary"
type="button"
*ngIf="dialogRef"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="button"
[disabled]="gatewayConfigGroup.invalid || !gatewayConfigGroup.dirty"
(click)="saveConfig()">
{{ 'action.save' | translate }}
</button>
</div>
</div>

64
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.scss

@ -0,0 +1,64 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.page-header.mat-toolbar {
background: transparent;
color: rgba(0, 0, 0, .87) !important;
}
.actions {
grid-row: 3;
padding: 8px 16px 8px 8px;
display: flex;
gap: 8px;
justify-content: flex-end;
position: absolute;
bottom: 0;
right: 0;
z-index: 1;
background: white;
width: 100%;
}
.gateway-config-container {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.content-wrapper {
flex: 1;
}
.toolbar-actions {
display: flex;
align-items: center;
}
}
.dialog-toggle {
::ng-deep.mat-button-toggle-button {
color: rgba(255, 255, 255, .75);
}
}

377
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts

@ -0,0 +1,377 @@
import { ChangeDetectorRef, Component, Input, AfterViewInit, OnDestroy } from '@angular/core';
import {
FormBuilder,
FormGroup,
} 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 { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { DeviceService } from '@core/http/device.service';
import { Observable, of, Subject } from 'rxjs';
import { mergeMap, takeUntil } from 'rxjs/operators';
import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import {
GatewayLogLevel,
SecurityTypes,
ConfigurationModes,
LocalLogsConfigs,
LogSavingPeriod, Attribute
} from '../gateway-widget.models';
import { deepTrim, isEqual } from '@core/utils';
import {
GatewayConfigSecurity,
GatewayConfigValue,
GatewayGeneralConfig,
GatewayLogsConfig,
LocalLogs,
LogAttribute,
LogConfig,
} from './models/gateway-configuration.models';
import { DeviceId } from '@shared/models/id/device-id';
@Component({
selector: 'tb-gateway-configuration',
templateUrl: './gateway-configuration.component.html',
styleUrls: ['./gateway-configuration.component.scss']
})
export class GatewayConfigurationComponent implements AfterViewInit, OnDestroy {
@Input() device: EntityId;
@Input() dialogRef: MatDialogRef<GatewayConfigurationComponent>;
initialCredentials: DeviceCredentials;
gatewayConfigGroup: FormGroup;
ConfigurationModes = ConfigurationModes;
private destroy$ = new Subject<void>();
private readonly gatewayConfigAttributeKeys =
['general_configuration', 'grpc_configuration', 'logs_configuration', 'storage_configuration', 'RemoteLoggingLevel', 'mode'];
constructor(private fb: FormBuilder,
private attributeService: AttributeService,
private deviceService: DeviceService,
private cd: ChangeDetectorRef,
private dialog: MatDialog) {
this.gatewayConfigGroup = this.fb.group({
basicConfig: [],
advancedConfig: [],
mode: [ConfigurationModes.BASIC],
});
this.observeAlignConfigs();
}
ngAfterViewInit(): void {
this.fetchConfigAttribute(this.device);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
saveConfig(): void {
const { mode, advancedConfig } = deepTrim(this.removeEmpty(this.gatewayConfigGroup.value));
const value = { mode, ...advancedConfig };
value.thingsboard.statistics.commands = Object.values(value.thingsboard.statistics.commands ?? []);
const attributes = this.generateAttributes(value);
this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, attributes).subscribe(_ => {
this.updateCredentials(value.thingsboard.security).subscribe(() => {
if (this.dialogRef) {
this.dialogRef.close();
} else {
this.gatewayConfigGroup.markAsPristine();
this.cd.detectChanges();
}
});
});
}
private observeAlignConfigs(): void {
this.gatewayConfigGroup.get('basicConfig').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => {
const advancedControl = this.gatewayConfigGroup.get('advancedConfig');
if (!isEqual(advancedControl.value, value)) {
advancedControl.patchValue(value, {emitEvent: false});
}
});
this.gatewayConfigGroup.get('advancedConfig').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => {
const basicControl = this.gatewayConfigGroup.get('basicConfig');
if (!isEqual(basicControl.value, value)) {
basicControl.patchValue(value, {emitEvent: false});
}
});
}
private generateAttributes(value: GatewayConfigValue): Attribute[] {
const attributes = [];
const addAttribute = (key: string, val: unknown) => {
attributes.push({ key, value: val });
};
const addTimestampedAttribute = (key: string, val: unknown) => {
val = {...val as Record<string, unknown>, ts: new Date().getTime()};
addAttribute(key, val);
};
addAttribute('RemoteLoggingLevel', value.logs?.remote?.enabled ? value.logs.remote.logLevel : GatewayLogLevel.NONE);
delete value.connectors;
addAttribute('logs_configuration', this.generateLogsFile(value.logs));
addTimestampedAttribute('grpc_configuration', value.grpc);
addTimestampedAttribute('storage_configuration', value.storage);
addTimestampedAttribute('general_configuration', value.thingsboard);
addAttribute('mode', value.mode);
return attributes;
}
private updateCredentials(securityConfig: GatewayConfigSecurity): Observable<DeviceCredentials> {
let newCredentials: Partial<DeviceCredentials> = {};
switch (securityConfig.type) {
case SecurityTypes.USERNAME_PASSWORD:
if (this.shouldUpdateCredentials(securityConfig)) {
newCredentials = this.generateMqttCredentials(securityConfig);
}
break;
case SecurityTypes.ACCESS_TOKEN:
case SecurityTypes.TLS_ACCESS_TOKEN:
if (this.shouldUpdateAccessToken(securityConfig)) {
newCredentials = {
credentialsType: DeviceCredentialsType.ACCESS_TOKEN,
credentialsId: securityConfig.accessToken
};
}
break;
}
return Object.keys(newCredentials).length
? this.deviceService.saveDeviceCredentials({ ...this.initialCredentials, ...newCredentials })
: of(null);
}
private shouldUpdateCredentials(securityConfig: GatewayConfigSecurity): boolean {
if (this.initialCredentials.credentialsType !== DeviceCredentialsType.MQTT_BASIC) {
return true;
}
const parsedCredentials = JSON.parse(this.initialCredentials.credentialsValue);
return !(
parsedCredentials.clientId === securityConfig.clientId &&
parsedCredentials.userName === securityConfig.username &&
parsedCredentials.password === securityConfig.password
);
}
private generateMqttCredentials(securityConfig: GatewayConfigSecurity): Partial<DeviceCredentials> {
const { clientId, username, password } = securityConfig;
const credentialsValue = {
...(clientId && { clientId }),
...(username && { userName: username }),
...(password && { password }),
};
return {
credentialsType: DeviceCredentialsType.MQTT_BASIC,
credentialsValue: JSON.stringify(credentialsValue)
};
}
private shouldUpdateAccessToken(securityConfig: GatewayConfigSecurity): boolean {
return this.initialCredentials.credentialsType !== DeviceCredentialsType.ACCESS_TOKEN ||
this.initialCredentials.credentialsId !== securityConfig.accessToken;
}
cancel(): void {
if (this.dialogRef) {
this.dialogRef.close();
}
}
private removeEmpty(obj: unknown) {
return Object.fromEntries(
Object.entries(obj)
.filter(([_, v]) => v != null)
.map(([k, v]) => [k, v === Object(v) ? this.removeEmpty(v) : v])
);
}
private generateLogsFile(logsObj: GatewayLogsConfig): LogAttribute {
const logAttrObj = {
version: 1,
disable_existing_loggers: false,
formatters: {
LogFormatter: {
class: 'logging.Formatter',
format: logsObj.logFormat,
datefmt: logsObj.dateFormat,
}
},
handlers: {
consoleHandler: {
class: 'logging.StreamHandler',
formatter: 'LogFormatter',
level: 'DEBUG',
stream: 'ext://sys.stdout'
},
databaseHandler: {
class: 'thingsboard_gateway.tb_utility.tb_handler.TimedRotatingFileHandler',
formatter: 'LogFormatter',
filename: './logs/database.log',
backupCount: 1,
encoding: 'utf-8'
}
},
loggers: {
database: {
handlers: ['databaseHandler', 'consoleHandler'],
level: 'DEBUG',
propagate: false
}
},
root: {
level: 'ERROR',
handlers: [
'consoleHandler'
]
},
ts: new Date().getTime()
};
this.addLocalLoggers(logAttrObj, logsObj.local);
return logAttrObj;
}
private addLocalLoggers(logAttrObj: LogAttribute, localLogs: LocalLogs): void {
for (const key of Object.keys(localLogs)) {
logAttrObj.handlers[key + 'Handler'] = this.createHandlerObj(localLogs[key], key);
logAttrObj.loggers[key] = this.createLoggerObj(localLogs[key], key);
}
}
private createHandlerObj(logObj: LogConfig, key: string) {
return {
class: 'thingsboard_gateway.tb_utility.tb_handler.TimedRotatingFileHandler',
formatter: 'LogFormatter',
filename: `${logObj.filePath}/${key}.log`,
backupCount: logObj.backupCount,
interval: logObj.savingTime,
when: logObj.savingPeriod,
encoding: 'utf-8'
};
}
private createLoggerObj(logObj: LogConfig, key: string) {
return {
handlers: [`${key}Handler`, 'consoleHandler'],
level: logObj.logLevel,
propagate: false
};
}
private fetchConfigAttribute(entityId: EntityId): void {
if (entityId.id === NULL_UUID) {
return;
}
this.attributeService.getEntityAttributes(entityId, AttributeScope.CLIENT_SCOPE,
)
.pipe(
mergeMap(attributes => attributes.length ? of(attributes) : this.attributeService.getEntityAttributes(
entityId, AttributeScope.SHARED_SCOPE, this.gatewayConfigAttributeKeys)
),
takeUntil(this.destroy$)
)
.subscribe(attributes => {
this.updateConfigs(attributes);
this.cd.detectChanges();
});
}
private updateConfigs(attributes: AttributeData[]): void {
const formValue: GatewayConfigValue = {
thingsboard: null,
grpc: null,
logs: null,
storage: null,
mode: ConfigurationModes.BASIC
};
attributes.forEach(attr => {
switch (attr.key) {
case 'general_configuration':
formValue.thingsboard = attr.value;
this.updateFormControls(attr.value);
break;
case 'grpc_configuration':
formValue.grpc = attr.value;
break;
case 'logs_configuration':
formValue.logs = this.logsToObj(attr.value);
break;
case 'storage_configuration':
formValue.storage = attr.value;
break;
case 'mode':
formValue.mode = attr.value;
break;
case 'RemoteLoggingLevel':
formValue.logs = {
...formValue.logs,
remote: {
enabled: attr.value !== GatewayLogLevel.NONE,
logLevel: attr.value
}
};
}
});
this.gatewayConfigGroup.get('basicConfig').setValue(formValue, { emitEvent: false });
this.gatewayConfigGroup.get('advancedConfig').setValue(formValue, { emitEvent: false });
}
private updateFormControls(thingsboard: GatewayGeneralConfig): void {
const { type, accessToken, ...securityConfig } = thingsboard.security || {};
this.initialCredentials = {
deviceId: this.device as DeviceId,
credentialsType: type as unknown as DeviceCredentialsType,
credentialsId: accessToken,
credentialsValue: JSON.stringify(securityConfig)
};
}
private logsToObj(logsConfig: LogAttribute): GatewayLogsConfig {
const { format: logFormat, datefmt: dateFormat } = logsConfig.formatters.LogFormatter;
const localLogs = Object.keys(LocalLogsConfigs).reduce((acc, key) => {
const handler = logsConfig.handlers[`${key}Handler`] || {};
const logger = logsConfig.loggers[key] || {};
acc[key] = {
logLevel: logger.level || GatewayLogLevel.INFO,
filePath: handler.filename?.split(`/${key}`)[0] || './logs',
backupCount: handler.backupCount || 7,
savingTime: handler.interval || 3,
savingPeriod: handler.when || LogSavingPeriod.days
};
return acc;
}, {}) as LocalLogs;
return { local: localLogs, logFormat, dateFormat };
}
}

148
ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts

@ -0,0 +1,148 @@
import {
ConfigurationModes,
GatewayConnector, LocalLogsConfigs, LogSavingPeriod,
SecurityTypes,
StorageTypes
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { GatewayLogLevel } from '@home/components/widget/lib/gateway/gateway-form.models';
export interface GatewayConfigValue {
mode: ConfigurationModes;
thingsboard: GatewayGeneralConfig;
storage: {
type: StorageTypes;
read_records_count?: number;
max_records_count?: number;
data_folder_path?: string;
max_file_count?: number;
max_read_records_count?: number;
max_records_per_file?: number;
data_file_path?: string;
messages_ttl_check_in_hours?: number;
messages_ttl_in_days?: number;
};
grpc: {
enabled: boolean;
serverPort: number;
keepAliveTimeMs: number;
keepAliveTimeoutMs: number;
keepalivePermitWithoutCalls: boolean;
maxPingsWithoutData: number;
minTimeBetweenPingsMs: number;
minPingIntervalWithoutDataMs: number;
};
connectors?: GatewayConnector[];
logs: GatewayLogsConfig;
}
export interface GatewayGeneralConfig {
host: string;
port: number;
remoteShell: boolean;
remoteConfiguration: boolean;
checkConnectorsConfigurationInSeconds: number;
statistics: {
enable: boolean;
statsSendPeriodInSeconds: number;
commands: GatewayConfigCommand[];
};
maxPayloadSizeBytes: number;
minPackSendDelayMS: number;
minPackSizeToSend: number;
handleDeviceRenaming: boolean;
checkingDeviceActivity: {
checkDeviceInactivity: boolean;
inactivityTimeoutSeconds?: number;
inactivityCheckPeriodSeconds?: number;
};
security: GatewayConfigSecurity;
qos: number;
}
export interface GatewayLogsConfig {
dateFormat: string;
logFormat: string;
type?: string;
remote?: {
enabled: boolean;
logLevel: GatewayLogLevel;
};
local: LocalLogs;
}
export interface GatewayConfigSecurity {
type: SecurityTypes;
accessToken?: string;
clientId?: string;
username?: string;
password?: string;
caCert?: string;
cert?: string;
privateKey?: string;
}
export interface GatewayConfigCommand {
attributeOnGateway: string;
command: string;
timeout: number;
}
export interface LogConfig {
logLevel: GatewayLogLevel;
filePath: string;
backupCount: number;
savingTime: number;
savingPeriod: LogSavingPeriod;
}
export type LocalLogs = Record<LocalLogsConfigs, LogConfig>;
interface LogFormatterConfig {
class: string;
format: string;
datefmt: string;
}
interface StreamHandlerConfig {
class: string;
formatter: string;
level: string;
stream: string;
}
interface FileHandlerConfig {
class: string;
formatter: string;
filename: string;
backupCount: number;
encoding: string;
}
interface LoggerConfig {
handlers: string[];
level: string;
propagate: boolean;
}
interface RootConfig {
level: string;
handlers: string[];
}
export interface LogAttribute {
version: number;
disable_existing_loggers: boolean;
formatters: {
LogFormatter: LogFormatterConfig;
};
handlers: {
consoleHandler: StreamHandlerConfig;
databaseHandler: FileHandlerConfig;
};
loggers: {
database: LoggerConfig;
};
root: RootConfig;
ts: number;
}

16
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts

@ -52,7 +52,7 @@ import {
AddConnectorConfigData, AddConnectorConfigData,
ConnectorBaseConfig, ConnectorBaseConfig,
ConnectorBaseInfo, ConnectorBaseInfo,
ConnectorConfigurationModes, ConfigurationModes,
ConnectorType, ConnectorType,
GatewayAttributeData, GatewayAttributeData,
GatewayConnector, GatewayConnector,
@ -97,13 +97,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
readonly gatewayLogLevel = Object.values(GatewayLogLevel); readonly gatewayLogLevel = Object.values(GatewayLogLevel);
readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions']; readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions'];
readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap; readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap;
readonly ConnectorConfigurationModes = ConnectorConfigurationModes; readonly ConnectorConfigurationModes = ConfigurationModes;
pageLink: PageLink; pageLink: PageLink;
dataSource: MatTableDataSource<GatewayAttributeData>; dataSource: MatTableDataSource<GatewayAttributeData>;
connectorForm: FormGroup; connectorForm: FormGroup;
activeConnectors: Array<string>; activeConnectors: Array<string>;
mode: ConnectorConfigurationModes = this.ConnectorConfigurationModes.BASIC; mode: ConfigurationModes = this.ConnectorConfigurationModes.BASIC;
initialConnector: GatewayConnector; initialConnector: GatewayConnector;
private inactiveConnectors: Array<string>; private inactiveConnectors: Array<string>;
@ -332,7 +332,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private clearOutConnectorForm(): void { private clearOutConnectorForm(): void {
this.initialConnector = null; this.initialConnector = null;
this.connectorForm.setValue({ this.connectorForm.setValue({
mode: ConnectorConfigurationModes.BASIC, mode: ConfigurationModes.BASIC,
name: '', name: '',
type: ConnectorType.MQTT, type: ConnectorType.MQTT,
sendDataOnlyOnChange: false, sendDataOnlyOnChange: false,
@ -518,7 +518,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private initConnectorForm(): void { private initConnectorForm(): void {
this.connectorForm = this.fb.group({ this.connectorForm = this.fb.group({
mode: [ConnectorConfigurationModes.BASIC], mode: [ConfigurationModes.BASIC],
name: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(noLeadTrailSpacesRegex)]], name: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(noLeadTrailSpacesRegex)]],
type: ['', [Validators.required]], type: ['', [Validators.required]],
enableRemoteLogging: [false], enableRemoteLogging: [false],
@ -691,7 +691,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
const configJson = this.connectorForm.get('configurationJson'); const configJson = this.connectorForm.get('configurationJson');
const type = this.connectorForm.get('type').value; const type = this.connectorForm.get('type').value;
const mode = this.connectorForm.get('mode').value; const mode = this.connectorForm.get('mode').value;
if (!isEqual(config, configJson?.value) && this.allowBasicConfig.has(type) && mode === ConnectorConfigurationModes.BASIC) { if (!isEqual(config, configJson?.value) && this.allowBasicConfig.has(type) && mode === ConfigurationModes.BASIC) {
const newConfig = {...configJson.value, ...config}; const newConfig = {...configJson.value, ...config};
this.connectorForm.get('configurationJson').patchValue(newConfig, {emitEvent: false}); this.connectorForm.get('configurationJson').patchValue(newConfig, {emitEvent: false});
} }
@ -708,7 +708,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
const basicConfig = this.connectorForm.get('basicConfig'); const basicConfig = this.connectorForm.get('basicConfig');
const type = this.connectorForm.get('type').value; const type = this.connectorForm.get('type').value;
const mode = this.connectorForm.get('mode').value; const mode = this.connectorForm.get('mode').value;
if (!isEqual(config, basicConfig?.value) && this.allowBasicConfig.has(type) && mode === ConnectorConfigurationModes.ADVANCED) { if (!isEqual(config, basicConfig?.value) && this.allowBasicConfig.has(type) && mode === ConfigurationModes.ADVANCED) {
this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false}); this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false});
} }
}); });
@ -749,7 +749,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
case ConnectorType.MQTT: case ConnectorType.MQTT:
case ConnectorType.OPCUA: case ConnectorType.OPCUA:
case ConnectorType.MODBUS: case ConnectorType.MODBUS:
this.connectorForm.get('mode').setValue(connector.mode || ConnectorConfigurationModes.BASIC, {emitEvent: false}); this.connectorForm.get('mode').setValue(connector.mode || ConfigurationModes.BASIC, {emitEvent: false});
setTimeout(() => { setTimeout(() => {
this.connectorForm.patchValue(connector, {emitEvent: false}); this.connectorForm.patchValue(connector, {emitEvent: false});
this.connectorForm.markAsPristine(); this.connectorForm.markAsPristine();

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts

@ -123,7 +123,7 @@ export interface GatewayConnector {
logLevel: string; logLevel: string;
key?: string; key?: string;
class?: string; class?: string;
mode?: ConnectorConfigurationModes; mode?: ConfigurationModes;
} }
export interface DataMapping { export interface DataMapping {
@ -493,7 +493,7 @@ export interface ModbusSlaveInfo {
buttonTitle: string; buttonTitle: string;
} }
export enum ConnectorConfigurationModes { export enum ConfigurationModes {
BASIC = 'basic', BASIC = 'basic',
ADVANCED = 'advanced' ADVANCED = 'advanced'
} }

10
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -56,7 +56,7 @@ import {
GatewayServiceRPCConnectorTemplatesComponent GatewayServiceRPCConnectorTemplatesComponent
} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component'; } from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component';
import { DeviceGatewayCommandComponent } from '@home/components/widget/lib/gateway/device-gateway-command.component'; import { DeviceGatewayCommandComponent } from '@home/components/widget/lib/gateway/device-gateway-command.component';
import { GatewayConfigurationComponent } from '@home/components/widget/lib/gateway/gateway-configuration.component'; import { GatewayConfigurationComponent } from '@home/components/widget/lib/gateway/configuration/gateway-configuration.component';
import { import {
GatewayRemoteConfigurationDialogComponent GatewayRemoteConfigurationDialogComponent
} from '@home/components/widget/lib/gateway/gateway-remote-configuration-dialog'; } from '@home/components/widget/lib/gateway/gateway-remote-configuration-dialog';
@ -145,6 +145,12 @@ import {
ModbusRpcParametersComponent ModbusRpcParametersComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component'; } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component';
import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component';
import {
GatewayBasicConfigurationComponent
} from '@home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component';
import {
GatewayAdvancedConfigurationComponent
} from '@home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -233,6 +239,8 @@ import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/sc
ModbusBasicConfigComponent, ModbusBasicConfigComponent,
EllipsisChipListDirective, EllipsisChipListDirective,
ModbusRpcParametersComponent, ModbusRpcParametersComponent,
GatewayBasicConfigurationComponent,
GatewayAdvancedConfigurationComponent,
], ],
exports: [ exports: [
EntitiesTableWidgetComponent, EntitiesTableWidgetComponent,

Loading…
Cancel
Save