|
|
|
@ -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}); |
|
|
|
} |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |