committed by
GitHub
20 changed files with 1195 additions and 88 deletions
@ -1,23 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2021 The Thingsboard Authors |
|
||||
|
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
you may not use this file except in compliance with the License. |
|
||||
You may obtain a copy of the License at |
|
||||
|
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
|
||||
|
|
||||
Unless required by applicable law or agreed to in writing, software |
|
||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
See the License for the specific language governing permissions and |
|
||||
limitations under the License. |
|
||||
|
|
||||
--> |
|
||||
<form [formGroup]="snmpDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;"> |
|
||||
<tb-json-object-edit |
|
||||
required |
|
||||
formControlName="configuration"> |
|
||||
</tb-json-object-edit> |
|
||||
</form> |
|
||||
@ -0,0 +1,72 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div fxLayout="column"> |
||||
|
<div *ngFor="let deviceProfileCommunication of communicationConfigFormArray().controls; let $index = index; |
||||
|
last as isLast;" fxLayout="row" fxLayoutAlign="start center" |
||||
|
fxLayoutGap="8px" class="scope-row" [formGroup]="deviceProfileCommunication"> |
||||
|
<div class="communication-config" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start"> |
||||
|
<mat-form-field class="spec mat-block" floatLabel="always" hideRequiredMarker> |
||||
|
<mat-label translate>device-profile.snmp.scope</mat-label> |
||||
|
<mat-select formControlName="spec" required> |
||||
|
<mat-option *ngFor="let snmpSpecType of snmpSpecTypes" [value]="snmpSpecType" |
||||
|
[disabled]="isDisabledSeverity(snmpSpecType, $index)"> |
||||
|
{{ snmpSpecTypeTranslationMap.get(snmpSpecType) }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="deviceProfileCommunication.get('spec').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.scope-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<section fxFlex fxLayout="column"> |
||||
|
<mat-form-field *ngIf="isShowFrequency(deviceProfileCommunication.get('spec').value)"> |
||||
|
<mat-label translate>device-profile.snmp.querying-frequency</mat-label> |
||||
|
<input matInput formControlName="queryingFrequencyMs" type="number" min="0" required/> |
||||
|
<mat-error *ngIf="deviceProfileCommunication.get('queryingFrequencyMs').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.querying-frequency-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="deviceProfileCommunication.get('queryingFrequencyMs').hasError('pattern') || |
||||
|
deviceProfileCommunication.get('queryingFrequencyMs').hasError('min')"> |
||||
|
{{ 'device-profile.snmp.querying-frequency-invalid-format' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-snmp-device-profile-mapping formControlName="mappings"> |
||||
|
</tb-snmp-device-profile-mapping> |
||||
|
</section> |
||||
|
</div> |
||||
|
<button *ngIf="!disabled" |
||||
|
mat-icon-button color="primary" style="min-width: 40px;" |
||||
|
type="button" |
||||
|
(click)="removeCommunicationConfig($index)" |
||||
|
matTooltip="{{ 'action.remove' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>remove_circle_outline</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div *ngIf="!communicationConfigFormArray().controls.length && !disabled"> |
||||
|
<span fxLayoutAlign="center center" class="tb-prompt required required-text" translate>device-profile.snmp.please-add-communication-config</span> |
||||
|
</div> |
||||
|
<div *ngIf="!disabled && isAddEnabled"> |
||||
|
<button mat-stroked-button color="primary" |
||||
|
type="button" |
||||
|
(click)="addCommunicationConfig()"> |
||||
|
<mat-icon class="button-icon">add_circle_outline</mat-icon> |
||||
|
{{ 'device-profile.snmp.add-communication-config' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,44 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 { |
||||
|
.communication-config { |
||||
|
border: 2px groove rgba(0, 0, 0, 0.25); |
||||
|
border-radius: 4px; |
||||
|
padding: 8px; |
||||
|
min-width: 0; |
||||
|
} |
||||
|
|
||||
|
.scope-row { |
||||
|
padding-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.required-text { |
||||
|
margin: 16px 0 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.mat-form-field.spec { |
||||
|
.mat-form-field-infix { |
||||
|
width: 160px; |
||||
|
} |
||||
|
} |
||||
|
.button-icon{ |
||||
|
font-size: 20px; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,218 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { |
||||
|
AbstractControl, |
||||
|
ControlValueAccessor, |
||||
|
FormArray, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models'; |
||||
|
import { Subject, Subscription } from 'rxjs'; |
||||
|
import { isUndefinedOrNull } from '@core/utils'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-snmp-device-profile-communication-config', |
||||
|
templateUrl: './snmp-device-profile-communication-config.component.html', |
||||
|
styleUrls: ['./snmp-device-profile-communication-config.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => SnmpDeviceProfileCommunicationConfigComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => SnmpDeviceProfileCommunicationConfigComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { |
||||
|
|
||||
|
snmpSpecTypes = Object.values(SnmpSpecType); |
||||
|
snmpSpecTypeTranslationMap = SnmpSpecTypeTranslationMap; |
||||
|
|
||||
|
deviceProfileCommunicationConfig: FormGroup; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private usedSpecType: SnmpSpecType[] = []; |
||||
|
private valueChange$: Subscription = null; |
||||
|
private destroy$ = new Subject(); |
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private fb: FormBuilder) { } |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.deviceProfileCommunicationConfig = this.fb.group({ |
||||
|
communicationConfig: this.fb.array([]) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
communicationConfigFormArray(): FormArray { |
||||
|
return this.deviceProfileCommunicationConfig.get('communicationConfig') as FormArray; |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean) { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.deviceProfileCommunicationConfig.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.deviceProfileCommunicationConfig.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(communicationConfig: SnmpCommunicationConfig[]) { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
const communicationConfigControl: Array<AbstractControl> = []; |
||||
|
if (communicationConfig) { |
||||
|
communicationConfig.forEach((config) => { |
||||
|
communicationConfigControl.push(this.createdFormGroup(config)); |
||||
|
}); |
||||
|
} |
||||
|
this.deviceProfileCommunicationConfig.setControl('communicationConfig', this.fb.array(communicationConfigControl)); |
||||
|
if (!communicationConfig || !communicationConfig.length) { |
||||
|
this.addCommunicationConfig(); |
||||
|
} |
||||
|
if (this.disabled) { |
||||
|
this.deviceProfileCommunicationConfig.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.deviceProfileCommunicationConfig.enable({emitEvent: false}); |
||||
|
} |
||||
|
this.valueChange$ = this.deviceProfileCommunicationConfig.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
this.updateUsedSpecType(); |
||||
|
if (!this.disabled && !this.deviceProfileCommunicationConfig.valid) { |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public validate() { |
||||
|
return this.deviceProfileCommunicationConfig.valid && this.deviceProfileCommunicationConfig.value.communicationConfig.length ? null : { |
||||
|
communicationConfig: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public removeCommunicationConfig(index: number) { |
||||
|
this.communicationConfigFormArray().removeAt(index); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
get isAddEnabled(): boolean { |
||||
|
return this.communicationConfigFormArray().length !== Object.keys(SnmpSpecType).length; |
||||
|
} |
||||
|
|
||||
|
public addCommunicationConfig() { |
||||
|
this.communicationConfigFormArray().push(this.createdFormGroup()); |
||||
|
this.deviceProfileCommunicationConfig.updateValueAndValidity(); |
||||
|
if (!this.deviceProfileCommunicationConfig.valid) { |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private getFirstUnusedSeverity(): SnmpSpecType { |
||||
|
for (const type of Object.values(SnmpSpecType)) { |
||||
|
if (this.usedSpecType.indexOf(type) === -1) { |
||||
|
return type; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public isDisabledSeverity(type: SnmpSpecType, index: number): boolean { |
||||
|
const usedIndex = this.usedSpecType.indexOf(type); |
||||
|
return usedIndex > -1 && usedIndex !== index; |
||||
|
} |
||||
|
|
||||
|
public isShowFrequency(type: SnmpSpecType): boolean { |
||||
|
return type === SnmpSpecType.TELEMETRY_QUERYING || type === SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING; |
||||
|
} |
||||
|
|
||||
|
private updateUsedSpecType() { |
||||
|
this.usedSpecType = []; |
||||
|
const value: SnmpCommunicationConfig[] = this.deviceProfileCommunicationConfig.get('communicationConfig').value; |
||||
|
value.forEach((rule, index) => { |
||||
|
this.usedSpecType[index] = rule.spec; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private createdFormGroup(value?: SnmpCommunicationConfig): FormGroup { |
||||
|
if (isUndefinedOrNull(value)) { |
||||
|
value = { |
||||
|
spec: this.getFirstUnusedSeverity(), |
||||
|
queryingFrequencyMs: 5000, |
||||
|
mappings: null |
||||
|
}; |
||||
|
} |
||||
|
const form = this.fb.group({ |
||||
|
spec: [value.spec, Validators.required], |
||||
|
mappings: [value.mappings] |
||||
|
}); |
||||
|
if (this.isShowFrequency(value.spec)) { |
||||
|
form.addControl('queryingFrequencyMs', |
||||
|
this.fb.control(value.queryingFrequencyMs, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')])); |
||||
|
} |
||||
|
form.get('spec').valueChanges.pipe( |
||||
|
takeUntil(this.destroy$) |
||||
|
).subscribe(spec => { |
||||
|
if (this.isShowFrequency(spec)) { |
||||
|
form.addControl('queryingFrequencyMs', |
||||
|
this.fb.control(5000, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')])); |
||||
|
} else { |
||||
|
form.removeControl('queryingFrequencyMs'); |
||||
|
} |
||||
|
}); |
||||
|
return form; |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
const value: SnmpCommunicationConfig[] = this.deviceProfileCommunicationConfig.get('communicationConfig').value; |
||||
|
value.forEach(config => { |
||||
|
if (!this.isShowFrequency(config.spec)) { |
||||
|
delete config.queryingFrequencyMs; |
||||
|
} |
||||
|
}); |
||||
|
this.updateUsedSpecType(); |
||||
|
this.propagateChange(value); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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 fxFlex fxLayout="column" class="mapping-config"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="100"> |
||||
|
<div fxFlex fxLayout="row" fxLayoutGap="8px"> |
||||
|
<label fxFlex="26" class="tb-title no-padding" translate>device-profile.snmp.data-type</label> |
||||
|
<label fxFlex="37" class="tb-title no-padding" translate>device-profile.snmp.data-key</label> |
||||
|
<label fxFlex="37" class="tb-title no-padding" translate>device-profile.snmp.oid</label> |
||||
|
<span style="min-width: 40px" [fxShow]="!disabled"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
<div *ngFor="let mappingConfig of mappingsConfigFormArray().controls; let $index = index; |
||||
|
last as isLast;" fxLayout="row" fxLayoutAlign="start center" |
||||
|
fxLayoutGap="8px" [formGroup]="mappingConfig" class="mapping-list"> |
||||
|
<div fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start"> |
||||
|
<mat-form-field fxFlex="26" floatLabel="always" hideRequiredMarker> |
||||
|
<mat-label></mat-label> |
||||
|
<mat-select formControlName="dataType" required> |
||||
|
<mat-option *ngFor="let dataType of dataTypes" [value]="dataType"> |
||||
|
{{ dataTypesTranslationMap.get(dataType) | translate }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="mappingConfig.get('dataType').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.data-type-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="37"> |
||||
|
<mat-label></mat-label> |
||||
|
<input matInput formControlName="key" required/> |
||||
|
<mat-error *ngIf="mappingConfig.get('key').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.data-key-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="37"> |
||||
|
<mat-label></mat-label> |
||||
|
<input matInput formControlName="oid" required/> |
||||
|
<mat-error *ngIf="mappingConfig.get('oid').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.oid-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="mappingConfig.get('oid').hasError('pattern')"> |
||||
|
{{ 'device-profile.snmp.oid-pattern' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<button *ngIf="!disabled" |
||||
|
mat-icon-button color="primary" |
||||
|
type="button" |
||||
|
(click)="removeMappingConfig($index)" |
||||
|
matTooltip="{{ 'action.remove' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="!mappingsConfigFormArray().controls.length && !disabled"> |
||||
|
<span fxLayoutAlign="center center" class="tb-prompt required required-text" translate>device-profile.snmp.please-add-mapping-config</span> |
||||
|
</div> |
||||
|
<div *ngIf="!disabled"> |
||||
|
<button mat-stroked-button color="primary" |
||||
|
type="button" |
||||
|
(click)="addMappingConfig()"> |
||||
|
<mat-icon class="button-icon">add_circle_outline</mat-icon> |
||||
|
{{ 'device-profile.snmp.add-mapping' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,49 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 { |
||||
|
.mapping-config { |
||||
|
min-width: 518px; |
||||
|
} |
||||
|
.mapping-list { |
||||
|
padding-bottom: 8px; |
||||
|
height: 46px; |
||||
|
} |
||||
|
|
||||
|
.required-text { |
||||
|
margin: 14px 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.mapping-list { |
||||
|
mat-form-field { |
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 0; |
||||
|
.mat-form-field-infix { |
||||
|
border-top-width: 0.2em; |
||||
|
width: auto; |
||||
|
min-width: auto; |
||||
|
} |
||||
|
.mat-form-field-underline { |
||||
|
bottom: 0; |
||||
|
} |
||||
|
.mat-form-field-subscript-wrapper{ |
||||
|
margin-top: 1.8em; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,165 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { |
||||
|
AbstractControl, |
||||
|
ControlValueAccessor, |
||||
|
FormArray, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { SnmpMapping } from '@shared/models/device.models'; |
||||
|
import { Subscription } from 'rxjs'; |
||||
|
import { DataType, DataTypeTranslationMap } from '@shared/models/constants'; |
||||
|
import { isUndefinedOrNull } from '@core/utils'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-snmp-device-profile-mapping', |
||||
|
templateUrl: './snmp-device-profile-mapping.component.html', |
||||
|
styleUrls: ['./snmp-device-profile-mapping.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => SnmpDeviceProfileMappingComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => SnmpDeviceProfileMappingComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { |
||||
|
|
||||
|
mappingsConfigForm: FormGroup; |
||||
|
|
||||
|
dataTypes = Object.values(DataType); |
||||
|
dataTypesTranslationMap = DataTypeTranslationMap; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private readonly oidPattern: RegExp = /^\.?([0-2])((\.0)|(\.[1-9][0-9]*))*$/; |
||||
|
|
||||
|
private valueChange$: Subscription = null; |
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private fb: FormBuilder) { } |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.mappingsConfigForm = this.fb.group({ |
||||
|
mappings: this.fb.array([]) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any) { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any) { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean) { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.mappingsConfigForm.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.mappingsConfigForm.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.mappingsConfigForm.valid && this.mappingsConfigForm.value.mappings.length ? null : { |
||||
|
mapping: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(mappings: SnmpMapping[]) { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
const mappingsControl: Array<AbstractControl> = []; |
||||
|
if (mappings) { |
||||
|
mappings.forEach((config) => { |
||||
|
mappingsControl.push(this.createdFormGroup(config)); |
||||
|
}); |
||||
|
} |
||||
|
this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl)); |
||||
|
if (!mappings || !mappings.length) { |
||||
|
this.addMappingConfig(); |
||||
|
} |
||||
|
if (this.disabled) { |
||||
|
this.mappingsConfigForm.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.mappingsConfigForm.enable({emitEvent: false}); |
||||
|
} |
||||
|
this.valueChange$ = this.mappingsConfigForm.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
if (!this.disabled && !this.mappingsConfigForm.valid) { |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mappingsConfigFormArray(): FormArray { |
||||
|
return this.mappingsConfigForm.get('mappings') as FormArray; |
||||
|
} |
||||
|
|
||||
|
public addMappingConfig() { |
||||
|
this.mappingsConfigFormArray().push(this.createdFormGroup()); |
||||
|
this.mappingsConfigForm.updateValueAndValidity(); |
||||
|
if (!this.mappingsConfigForm.valid) { |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public removeMappingConfig(index: number) { |
||||
|
this.mappingsConfigFormArray().removeAt(index); |
||||
|
} |
||||
|
|
||||
|
private createdFormGroup(value?: SnmpMapping): FormGroup { |
||||
|
if (isUndefinedOrNull(value)) { |
||||
|
value = { |
||||
|
dataType: DataType.STRING, |
||||
|
key: '', |
||||
|
oid: '' |
||||
|
}; |
||||
|
} |
||||
|
return this.fb.group({ |
||||
|
dataType: [value.dataType, Validators.required], |
||||
|
key: [value.key, Validators.required], |
||||
|
oid: [value.oid, [Validators.required, Validators.pattern(this.oidPattern)]] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
const value: SnmpMapping[] = this.mappingsConfigForm.get('mappings').value; |
||||
|
this.propagateChange(value); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="snmpDeviceProfileTransportConfigurationFormGroup" style="padding: 8px 0 16px;"> |
||||
|
<section fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column"> |
||||
|
<mat-form-field fxFlex> |
||||
|
<mat-label translate>device-profile.snmp.timeout-ms</mat-label> |
||||
|
<input matInput formControlName="timeoutMs" type="number" min="0" required/> |
||||
|
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.timeout-ms-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('pattern') || |
||||
|
snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('min')"> |
||||
|
{{ 'device-profile.snmp.timeout-ms-invalid-format' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex> |
||||
|
<mat-label translate>device-profile.snmp.retries</mat-label> |
||||
|
<input matInput formControlName="retries" type="number" min="0" required/> |
||||
|
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('required')"> |
||||
|
{{ 'device-profile.snmp.retries-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('pattern') || |
||||
|
snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('min')"> |
||||
|
{{ 'device-profile.snmp.retries-invalid-format' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</section> |
||||
|
<div class="tb-small" style="padding-bottom: 8px" translate>device-profile.snmp.communication-configs</div> |
||||
|
<tb-snmp-device-profile-communication-config formControlName="communicationConfigs"> |
||||
|
</tb-snmp-device-profile-communication-config> |
||||
|
</form> |
||||
@ -0,0 +1,38 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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 { NgModule } from '@angular/core'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SnmpDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component'; |
||||
|
import { SnmpDeviceProfileCommunicationConfigComponent } from './snmp-device-profile-communication-config.component'; |
||||
|
import { SnmpDeviceProfileMappingComponent } from './snmp-device-profile-mapping.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
SnmpDeviceProfileTransportConfigurationComponent, |
||||
|
SnmpDeviceProfileCommunicationConfigComponent, |
||||
|
SnmpDeviceProfileMappingComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule |
||||
|
], |
||||
|
exports: [ |
||||
|
SnmpDeviceProfileTransportConfigurationComponent |
||||
|
] |
||||
|
}) |
||||
|
export class SnmpDeviceProfileTransportModule { } |
||||
Loading…
Reference in new issue