Browse Source

UI: Add SNMP device profile configuration

pull/4761/head
Vladyslav_Prykhodko 5 years ago
parent
commit
75801045cf
  1. 5
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  2. 4
      ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts
  3. 23
      ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html
  4. 72
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.html
  5. 44
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.scss
  6. 218
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts
  7. 81
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.html
  8. 46
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss
  9. 165
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts
  10. 46
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.html
  11. 57
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts
  12. 38
      ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts
  13. 16
      ui-ngx/src/app/shared/models/constants.ts
  14. 37
      ui-ngx/src/app/shared/models/device.models.ts
  15. 25
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -99,7 +99,6 @@ import { DeviceProfileDialogComponent } from '@home/components/profile/device-pr
import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component';
import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component';
import { CoapDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/coap-device-profile-transport-configuration.component';
import { SnmpDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/snmp-device-profile-transport-configuration.component';
import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component';
import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component';
import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component';
@ -143,6 +142,7 @@ import { SecurityConfigLwm2mComponent } from '@home/components/device/security-c
import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
import { WidgetContainerComponent } from '@home/components/widget/widget-container.component';
import { SnmpDeviceProfileTransportModule } from '@home/components/profile/device/snpm/snmp-device-profile-transport.module';
@NgModule({
declarations:
@ -228,7 +228,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
DefaultDeviceProfileTransportConfigurationComponent,
MqttDeviceProfileTransportConfigurationComponent,
CoapDeviceProfileTransportConfigurationComponent,
SnmpDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
CreateAlarmRulesComponent,
AlarmRuleComponent,
@ -272,6 +271,7 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
SharedModule,
SharedHomeComponentsModule,
Lwm2mProfileComponentsModule,
SnmpDeviceProfileTransportModule,
StatesControllerModule
],
exports: [
@ -339,7 +339,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
DefaultDeviceProfileTransportConfigurationComponent,
MqttDeviceProfileTransportConfigurationComponent,
CoapDeviceProfileTransportConfigurationComponent,
SnmpDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
CreateAlarmRulesComponent,
AlarmRuleComponent,

4
ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts

@ -89,7 +89,9 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu
if (configuration) {
delete configuration.type;
}
this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
setTimeout(() => {
this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
});
}
private updateModel() {

23
ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html

@ -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>

72
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.html

@ -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>

44
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.scss

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

218
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts

@ -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: 0,
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(0, [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);
}
}

81
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.html

@ -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>

46
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss

@ -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.
*/
: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;
}
}
}
}
}

165
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts

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

46
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.html

@ -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>

57
ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts → ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts

@ -15,9 +15,16 @@
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceProfileTransportConfiguration,
@ -40,19 +47,24 @@ export interface OidMappingConfiguration {
selector: 'tb-snmp-device-profile-transport-configuration',
templateUrl: './snmp-device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent),
multi: true
}]
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent),
multi: true
}]
})
export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
export class SnmpDeviceProfileTransportConfigurationComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
snmpDeviceProfileTransportConfigurationFormGroup: FormGroup;
private destroy$ = new Subject();
private requiredValue: boolean;
private configuration = [];
get required(): boolean {
return this.requiredValue;
@ -69,12 +81,14 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
private propagateChange = (v: any) => {
}
constructor(private store: Store<AppState>, private fb: FormBuilder) {
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
timeoutMs: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]],
retries: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]],
communicationConfigs: [null, Validators.required],
});
this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
@ -95,18 +109,33 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
if (this.disabled) {
this.snmpDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.snmpDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void {
if (isDefinedAndNotNull(value)) {
this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false});
this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: !value.communicationConfigs});
}
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue();
configuration.type = DeviceTransportType.SNMP;
}
this.propagateChange(configuration);
}
validate(): ValidationErrors | null {
return this.snmpDeviceProfileTransportConfigurationFormGroup.valid ? null : {
snmpDeviceProfileTransportConfiguration: false
};
}
}

38
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts

@ -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 { }

16
ui-ngx/src/app/shared/models/constants.ts

@ -147,6 +147,22 @@ export enum ValueType {
JSON = 'JSON'
}
export enum DataType {
STRING = 'STRING',
LONG = 'LONG',
BOOLEAN = 'BOOLEAN',
DOUBLE = 'DOUBLE',
JSON = 'JSON'
}
export const DataTypeTranslationMap = new Map([
[DataType.STRING, 'value.string'],
[DataType.LONG, 'value.integer'],
[DataType.BOOLEAN, 'value.boolean'],
[DataType.DOUBLE, 'value.double'],
[DataType.JSON, 'value.json']
]);
export const valueTypesMap = new Map<ValueType, ValueTypeData>(
[
[

37
ui-ngx/src/app/shared/models/device.models.ts

@ -29,6 +29,7 @@ import * as _moment from 'moment';
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { OtaPackageId } from '@shared/models/id/ota-package-id';
import { DashboardId } from '@shared/models/id/dashboard-id';
import { DataType } from '@shared/models/constants';
export enum DeviceProfileType {
DEFAULT = 'DEFAULT',
@ -257,7 +258,35 @@ export interface Lwm2mDeviceProfileTransportConfiguration {
}
export interface SnmpDeviceProfileTransportConfiguration {
[key: string]: any;
timeoutMs?: number;
retries?: number;
communicationConfigs?: SnmpCommunicationConfig[];
}
export enum SnmpSpecType {
TELEMETRY_QUERYING = 'TELEMETRY_QUERYING',
CLIENT_ATTRIBUTES_QUERYING = 'CLIENT_ATTRIBUTES_QUERYING',
SHARED_ATTRIBUTES_SETTING = 'SHARED_ATTRIBUTES_SETTING',
TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST'
}
export const SnmpSpecTypeTranslationMap = new Map<SnmpSpecType, string>([
[SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry'],
[SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes'],
[SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes'],
[SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'RPC request']
]);
export interface SnmpCommunicationConfig {
spec: SnmpSpecType;
mappings: SnmpMapping[];
queryingFrequencyMs?: number;
}
export interface SnmpMapping {
oid: string;
key: string;
dataType: DataType;
}
export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration &
@ -332,7 +361,11 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT
transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M};
break;
case DeviceTransportType.SNMP:
const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {};
const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {
timeoutMs: 0,
retries: 0,
communicationConfigs: null
};
transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP};
break;
}

25
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1297,6 +1297,31 @@
"sw-update-recourse": "Software update CoAP recourse",
"sw-update-recourse-required": "Software update CoAP recourse is required.",
"config-json-tab": "Json Config Profile Device"
},
"snmp": {
"add-communication-config": "Add communication config",
"add-mapping": "Add mapping",
"communication-configs": "Communication configs",
"data-key": "Data key",
"data-key-required": "Data key is required.",
"data-type": "Data type",
"data-type-required": "Data type is required.",
"oid": "OID",
"oid-pattern": "Invalid OID format.",
"oid-required": "OID is required.",
"please-add-communication-config": "Please add communication config",
"please-add-mapping-config": "Please add mapping config",
"querying-frequency": "Querying frequency, ms",
"querying-frequency-invalid-format": "Querying frequency must be a positive integer.",
"querying-frequency-required": "Querying frequency is required.",
"retries": "Retries",
"retries-invalid-format": "Retries must be a positive integer.",
"retries-required": "Retries is required.",
"scope": "Scope",
"scope-required": "Scope is required.",
"timeout-ms": "Timeout, ms",
"timeout-ms-invalid-format": "Timeout must be a positive integer.",
"timeout-ms-required": "Timeout is required."
}
},
"dialog": {

Loading…
Cancel
Save