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,
"showWidgetActionFunction": "return true;",
"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}",
"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": [],

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.
-->
<mat-toolbar color="primary" *ngIf="!dialogRef">
<h2 translate>gateway.gateway-configuration</h2>
</mat-toolbar>
<mat-tab-group class="tab-group-block" [formGroup]="gatewayConfigGroup">
<mat-tab-group class="tab-group-block" [formGroup]="basicFormGroup" [class.dialog-mode]="dialogMode">
<mat-tab label="{{ 'gateway.general' | translate }}">
<ng-template matTabContent>
<div formGroupName="thingsboard" class="mat-content mat-padding configuration-block">
@ -42,23 +39,23 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.host' | translate }}">info_outlined
</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 }}
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.thingsboard-port</mat-label>
<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 }}
</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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -76,17 +73,17 @@
</tb-toggle-option>
</tb-toggle-select>
<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>
<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 }}
</mat-error>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.accessToken').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.accessToken').value"
*ngIf="basicFormGroup.get('thingsboard.security.accessToken').value"
[copyText]="basicFormGroup.get('thingsboard.security.accessToken').value"
tooltipText="{{ 'device.copy-access-token' | translate }}"
tooltipPosition="above"
icon="content_copy">
@ -97,18 +94,18 @@
</mat-form-field>
<section>
<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-label translate>security.clientId</mat-label>
<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 }}
</mat-error>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.clientId').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.clientId').value"
*ngIf="basicFormGroup.get('thingsboard.security.clientId').value"
[copyText]="basicFormGroup.get('thingsboard.security.clientId').value"
tooltipText="{{ 'gateway.copy-client-id' | translate }}"
tooltipPosition="above"
icon="content_copy">
@ -120,14 +117,14 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>security.username</mat-label>
<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 }}
</mat-error>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.username').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.username').value"
*ngIf="basicFormGroup.get('thingsboard.security.username').value"
[copyText]="basicFormGroup.get('thingsboard.security.username').value"
tooltipText="{{ 'gateway.copy-username' | translate }}"
tooltipPosition="above"
icon="content_copy">
@ -138,14 +135,14 @@
</mat-form-field>
</div>
<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>
<input matInput formControlName="password"/>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="gatewayConfigGroup.get('thingsboard.security.password').value"
[copyText]="gatewayConfigGroup.get('thingsboard.security.password').value"
*ngIf="basicFormGroup.get('thingsboard.security.password').value"
[copyText]="basicFormGroup.get('thingsboard.security.password').value"
tooltipText="{{ 'gateway.copy-password' | translate }}"
tooltipPosition="above"
icon="content_copy">
@ -156,13 +153,13 @@
</mat-form-field>
</section>
<tb-error style="margin-top: -12px; display: block;" fxFlex="100"
*ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'"
[error]="gatewayConfigGroup.get('thingsboard.security').hasError('atLeastOne') ?
*ngIf="basicFormGroup.get('thingsboard.security.type').value === 'usernamePassword'"
[error]="basicFormGroup.get('thingsboard.security').hasError('atLeastOne') ?
('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error>
<tb-file-input
fxFlex="100"
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"
label="{{ 'security.ca-cert' | translate }}"
[allowedExtensions]="'pem, cert, key'"
@ -182,7 +179,7 @@
<mat-form-field appearance="outline">
<mat-label translate>gateway.logs.date-format</mat-label>
<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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -192,7 +189,7 @@
<mat-form-field appearance="outline">
<mat-label translate>gateway.logs.log-format</mat-label>
<textarea matInput formControlName="logFormat" rows="2"></textarea>
<mat-error *ngIf="gatewayConfigGroup.get('logs.logFormat').hasError('required')">
<mat-error *ngIf="basicFormGroup.get('logs.logFormat').hasError('required')">
{{ 'gateway.logs.log-format-required' | translate }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -233,7 +230,7 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.logs.file-path</mat-label>
<input matInput formControlName="filePath"/>
<mat-error *ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.filePath').hasError('required')">
<mat-error *ngIf="basicFormGroup.get('logs.local.' + logSelector.value + '.filePath').hasError('required')">
{{ 'gateway.logs.file-path-required' | translate }}
</mat-error>
</mat-form-field>
@ -244,11 +241,11 @@
<mat-label translate>gateway.logs.saving-period</mat-label>
<input matInput formControlName="savingTime" type="number" min="0"/>
<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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -264,11 +261,11 @@
<mat-label translate>gateway.logs.backup-count</mat-label>
<input matInput formControlName="backupCount" type="number" min="0"/>
<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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -292,19 +289,19 @@
{{ storageTypesTranslationMap.get(storageType) | translate }}
</tb-toggle-option>
</tb-toggle-select>
<div class="tb-form-panel-hint">{{ 'gateway.hints.' + gatewayConfigGroup.get('storage.type').value | translate }}</div>
<ng-container [ngSwitch]="gatewayConfigGroup.get('storage.type').value">
<div class="tb-form-panel-hint">{{ 'gateway.hints.' + basicFormGroup.get('storage.type').value | translate }}</div>
<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">
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-read-record-count</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -314,13 +311,13 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-records</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -333,7 +330,7 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-data-folder-path</mat-label>
<input matInput formControlName="data_folder_path"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.data_folder_path').hasError('required')">
<mat-error *ngIf="basicFormGroup.get('storage.data_folder_path').hasError('required')">
{{ 'gateway.storage-data-folder-path-required' | translate }}
</mat-error>
<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-label translate>gateway.storage-max-files</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -363,13 +360,13 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-read-record-count</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -379,13 +376,13 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-max-file-records</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -399,7 +396,7 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.storage-path</mat-label>
<input matInput formControlName="data_file_path"/>
<mat-error *ngIf="gatewayConfigGroup.get('storage.data_file_path').hasError('required')">
<mat-error *ngIf="basicFormGroup.get('storage.data_file_path').hasError('required')">
{{ 'gateway.storage-path-required' | translate }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -409,13 +406,13 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.messages-ttl-check-in-hours</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -426,13 +423,13 @@
<mat-form-field appearance="outline" class="mat-block">
<mat-label translate>gateway.messages-ttl-in-days</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -466,16 +463,16 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.server-port' | translate }}">info_outlined
</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 }}
</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 }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -485,13 +482,13 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-keep-alive-timeout' | translate }}">info_outlined
</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 }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -503,13 +500,13 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-keep-alive' | translate }}">info_outlined
</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 }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -519,13 +516,13 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-min-time-between-pings' | translate }}">info_outlined
</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 }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -537,13 +534,13 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-max-pings-without-data' | translate }}">info_outlined
</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 }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -553,13 +550,13 @@
<mat-icon matIconSuffix style="cursor:pointer;"
matTooltip="{{ 'gateway.hints.grpc-min-ping-interval-without-data' | translate }}">info_outlined
</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 }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -580,15 +577,15 @@
<mat-label translate>gateway.statistics.send-period</mat-label>
<input matInput formControlName="statsSendPeriodInSeconds" type="number" min="60"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('required')">
*ngIf="basicFormGroup.get('thingsboard.statistics.statsSendPeriodInSeconds').hasError('required')">
{{ 'gateway.statistics.send-period-required' | translate }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -643,7 +640,7 @@
</mat-form-field>
</section>
<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 }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
@ -652,7 +649,7 @@
<button mat-stroked-button color="primary"
style="width: fit-content;"
type="button"
[disabled]="!gatewayConfigGroup.get('thingsboard.remoteConfiguration').value"
[disabled]="!basicFormGroup.get('thingsboard.remoteConfiguration').value"
(click)="addCommand()">
{{ 'gateway.statistics.add' | translate }}
</button>
@ -665,7 +662,7 @@
<ng-template matTabContent>
<div formGroupName="thingsboard" class="mat-content mat-padding configuration-block">
<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 }}"
class="tb-form-row no-border no-padding">
<mat-slide-toggle class="mat-slide" color="primary" formControlName="checkDeviceInactivity">
@ -673,20 +670,20 @@
</mat-slide-toggle>
</div>
<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-label translate>gateway.inactivity-timeout-seconds</mat-label>
<input matInput formControlName="inactivityTimeoutSeconds" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('required')">
*ngIf="basicFormGroup.get('thingsboard.checkingDeviceActivity.inactivityTimeoutSeconds').hasError('required')">
{{ 'gateway.inactivity-timeout-seconds-required' | translate }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -697,15 +694,15 @@
<mat-label translate>gateway.inactivity-check-period-seconds</mat-label>
<input matInput formControlName="inactivityCheckPeriodSeconds"/>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -720,14 +717,14 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.min-pack-send-delay</mat-label>
<input matInput formControlName="minPackSendDelayMS" type="number" min="0"/>
<mat-error *ngIf="gatewayConfigGroup.get('thingsboard.minPackSendDelayMS').hasError('required')">
<mat-error *ngIf="basicFormGroup.get('thingsboard.minPackSendDelayMS').hasError('required')">
{{ 'gateway.min-pack-send-delay-required' | translate }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -737,13 +734,13 @@
<mat-form-field appearance="outline" class="flex">
<mat-label translate>gateway.mqtt-qos</mat-label>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -756,15 +753,15 @@
<mat-label translate>gateway.statistics.check-connectors-configuration</mat-label>
<input matInput formControlName="checkConnectorsConfigurationInSeconds" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')">
*ngIf="basicFormGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')">
{{ 'gateway.statistics.check-connectors-configuration-required' | translate }}
</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 }}
</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 }}
</mat-error>
</mat-form-field>
@ -772,15 +769,15 @@
<mat-label translate>gateway.statistics.max-payload-size-bytes</mat-label>
<input matInput formControlName="maxPayloadSizeBytes" type="number" min="0"/>
<mat-error
*ngIf="gatewayConfigGroup.get('thingsboard.maxPayloadSizeBytes').hasError('required')">
*ngIf="basicFormGroup.get('thingsboard.maxPayloadSizeBytes').hasError('required')">
{{ 'gateway.statistics.max-payload-size-bytes-required' | translate }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -793,15 +790,15 @@
<mat-label translate>gateway.statistics.min-pack-size-to-send</mat-label>
<input matInput formControlName="minPackSizeToSend" type="number" min="0"/>
<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 }}
</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 }}
</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 }}
</mat-error>
<mat-icon matIconSuffix style="cursor:pointer;"
@ -814,17 +811,3 @@
</ng-template>
</mat-tab>
</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;
flex-direction: column;
gap: 16px;
max-height: 70vh;
}
.dialog-mode {
.configuration-block {
max-height: 60vh;
}
}
.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-error {
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.
///
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import {
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
Input,
OnDestroy,
Output
} from '@angular/core';
import {
ControlValueAccessor,
FormArray,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
ValidatorFn,
Validators
} from '@angular/forms';
import { EntityId } from '@shared/models/id/entity-id';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { AttributeService } from '@core/http/attribute.service';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { MatDialog } from '@angular/material/dialog';
import {
GatewayRemoteConfigurationDialogComponent,
GatewayRemoteConfigurationDialogData
} from '@home/components/widget/lib/gateway/gateway-remote-configuration-dialog';
import { DeviceService } from '@core/http/device.service';
import { Observable, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import {
GatewayLogLevel,
GecurityTypesTranslationsMap,
@ -46,51 +54,228 @@ import {
LogSavingPeriodTranslations,
SecurityTypes,
StorageTypes,
StorageTypesTranslationMap
} from './gateway-widget.models';
import { deepTrim } from '@core/utils';
StorageTypesTranslationMap,
} from '../../gateway-widget.models';
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({
selector: 'tb-gateway-configuration',
templateUrl: './gateway-configuration.component.html',
styleUrls: ['./gateway-configuration.component.scss']
selector: 'tb-gateway-basic-configuration',
templateUrl: './gateway-basic-configuration.component.html',
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 = Object.values(StorageTypes) as StorageTypes[];
storageTypesTranslationMap = StorageTypesTranslationMap;
logSavingPeriods = LogSavingPeriodTranslations;
localLogsConfigs = Object.keys(LocalLogsConfigs) as LocalLogsConfigs[];
localLogsConfigTranslateMap = LocalLogsConfigTranslateMap;
securityTypes = GecurityTypesTranslationsMap;
gatewayLogLevel = Object.values(GatewayLogLevel);
@Input()
device: EntityId;
@Input()
dialogRef: MatDialogRef<any>;
logSelector: FormControl;
basicFormGroup: FormGroup;
private onChange: (value: GatewayConfigValue) => void;
private onTouched: () => void;
private initialCredentials: DeviceCredentials;
private destroy$ = new Subject<void>();
constructor(private fb: FormBuilder,
private attributeService: AttributeService,
private deviceService: DeviceService,
private cd: ChangeDetectorRef,
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() {
this.gatewayConfigGroup = this.fb.group({
private toggleRpcFields(enable: boolean): void {
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({
host: [window.location.hostname, [Validators.required, Validators.pattern(/^[^\s]+$/)]],
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({
type: [StorageTypes.MEMORY, [Validators.required]],
read_records_count: [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]+$/)]],
read_records_count: [
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]],
max_file_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 !== '') {
this.gatewayConfigGroup.get('thingsboard.security.username').setValidators([Validators.required]);
this.basicFormGroup.get('thingsboard.security.username').setValidators([Validators.required]);
} 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.gatewayConfigGroup.get('thingsboard.remoteConfiguration').valueChanges.subscribe(enabled => {
this.basicFormGroup.get('thingsboard.remoteConfiguration').valueChanges.subscribe(enabled => {
if (!enabled) {
this.openConfigurationConfirmDialog();
}
@ -180,15 +371,17 @@ export class GatewayConfigurationComponent implements OnInit {
this.logSelector = this.fb.control(LocalLogsConfigs.service);
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.updateValueAndValidity();
if (enabled) {
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]);
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]);
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds')
.setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]);
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds')
.setValidators([Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]);
} else {
checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').clearValidators();
checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').clearValidators();
@ -197,11 +390,11 @@ export class GatewayConfigurationComponent implements OnInit {
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);
});
const securityGroup = this.gatewayConfigGroup.get('thingsboard.security') as FormGroup;
const securityGroup = this.basicFormGroup.get('thingsboard.security') as FormGroup;
securityGroup.get('type').valueChanges.subscribe(type => {
this.removeAllSecurityValidators();
if (type === SecurityTypes.ACCESS_TOKEN) {
@ -229,7 +422,7 @@ export class GatewayConfigurationComponent implements OnInit {
securityGroup.get('privateKey').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 => {
this.removeAllStorageValidators();
if (type === StorageTypes.MEMORY) {
@ -262,403 +455,32 @@ export class GatewayConfigurationComponent implements OnInit {
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 {
if (security.type !== SecurityTypes.TLS_PRIVATE_KEY) {
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) {
this.gatewayConfigGroup.get('thingsboard.security.type').setValue(security.type === SecurityTypes.TLS_ACCESS_TOKEN
? SecurityTypes.TLS_ACCESS_TOKEN
: SecurityTypes.ACCESS_TOKEN);
this.gatewayConfigGroup.get('thingsboard.security.accessToken').setValue(credentials.credentialsId);
this.basicFormGroup.get('thingsboard.security.type')
.setValue(security.type === SecurityTypes.TLS_ACCESS_TOKEN
? SecurityTypes.TLS_ACCESS_TOKEN
: SecurityTypes.ACCESS_TOKEN, {emitEvent: false});
this.basicFormGroup.get('thingsboard.security.accessToken').setValue(credentials.credentialsId, {emitEvent: false});
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) {
const parsedValue = JSON.parse(credentials.credentialsValue);
this.gatewayConfigGroup.get('thingsboard.security.type').setValue(SecurityTypes.USERNAME_PASSWORD);
this.gatewayConfigGroup.get('thingsboard.security.clientId').setValue(parsedValue.clientId);
this.gatewayConfigGroup.get('thingsboard.security.username').setValue(parsedValue.userName);
this.gatewayConfigGroup.get('thingsboard.security.password').setValue(parsedValue.password, {emitEvent: false});
this.basicFormGroup.get('thingsboard.security.type').setValue(SecurityTypes.USERNAME_PASSWORD, {emitEvent: false});
this.basicFormGroup.get('thingsboard.security.clientId').setValue(parsedValue.clientId, {emitEvent: false});
this.basicFormGroup.get('thingsboard.security.username').setValue(parsedValue.userName, {emitEvent: false});
this.basicFormGroup.get('thingsboard.security.password')
.setValue(parsedValue.password, {emitEvent: false});
} else if (credentials.credentialsType === DeviceCredentialsType.X509_CERTIFICATE) {
//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,
ConnectorBaseConfig,
ConnectorBaseInfo,
ConnectorConfigurationModes,
ConfigurationModes,
ConnectorType,
GatewayAttributeData,
GatewayConnector,
@ -97,13 +97,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
readonly gatewayLogLevel = Object.values(GatewayLogLevel);
readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions'];
readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap;
readonly ConnectorConfigurationModes = ConnectorConfigurationModes;
readonly ConnectorConfigurationModes = ConfigurationModes;
pageLink: PageLink;
dataSource: MatTableDataSource<GatewayAttributeData>;
connectorForm: FormGroup;
activeConnectors: Array<string>;
mode: ConnectorConfigurationModes = this.ConnectorConfigurationModes.BASIC;
mode: ConfigurationModes = this.ConnectorConfigurationModes.BASIC;
initialConnector: GatewayConnector;
private inactiveConnectors: Array<string>;
@ -332,7 +332,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private clearOutConnectorForm(): void {
this.initialConnector = null;
this.connectorForm.setValue({
mode: ConnectorConfigurationModes.BASIC,
mode: ConfigurationModes.BASIC,
name: '',
type: ConnectorType.MQTT,
sendDataOnlyOnChange: false,
@ -518,7 +518,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private initConnectorForm(): void {
this.connectorForm = this.fb.group({
mode: [ConnectorConfigurationModes.BASIC],
mode: [ConfigurationModes.BASIC],
name: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(noLeadTrailSpacesRegex)]],
type: ['', [Validators.required]],
enableRemoteLogging: [false],
@ -691,7 +691,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
const configJson = this.connectorForm.get('configurationJson');
const type = this.connectorForm.get('type').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};
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 type = this.connectorForm.get('type').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});
}
});
@ -749,7 +749,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
case ConnectorType.MQTT:
case ConnectorType.OPCUA:
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(() => {
this.connectorForm.patchValue(connector, {emitEvent: false});
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;
key?: string;
class?: string;
mode?: ConnectorConfigurationModes;
mode?: ConfigurationModes;
}
export interface DataMapping {
@ -493,7 +493,7 @@ export interface ModbusSlaveInfo {
buttonTitle: string;
}
export enum ConnectorConfigurationModes {
export enum ConfigurationModes {
BASIC = 'basic',
ADVANCED = 'advanced'
}

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

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

Loading…
Cancel
Save