Commit af7e22ad8ee15950a834655e8dfbde8cfea69596

Authored by Igor Kulikov
Committed by GitHub
2 parents 7ae3eea2 fe3378ea

Merge pull request #4761 from vvlladd28/feature/snmp-transport/view

UI: Add SNMP tranport config
Showing 20 changed files with 1195 additions and 88 deletions
@@ -51,6 +51,13 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @@ -51,6 +51,13 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur
51 private String privacyPassphrase; 51 private String privacyPassphrase;
52 private String engineId; 52 private String engineId;
53 53
  54 + public SnmpDeviceTransportConfiguration() {
  55 + this.host = "localhost";
  56 + this.port = 161;
  57 + this.protocolVersion = SnmpProtocolVersion.V2C;
  58 + this.community = "public";
  59 + }
  60 +
54 @Override 61 @Override
55 public DeviceTransportType getType() { 62 public DeviceTransportType getType() {
56 return DeviceTransportType.SNMP; 63 return DeviceTransportType.SNMP;
@@ -76,7 +83,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @@ -76,7 +83,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur
76 isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName) 83 isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName)
77 && contextName != null && authenticationProtocol != null 84 && contextName != null && authenticationProtocol != null
78 && StringUtils.isNotBlank(authenticationPassphrase) 85 && StringUtils.isNotBlank(authenticationPassphrase)
79 - && privacyProtocol != null && privacyPassphrase != null && engineId != null; 86 + && privacyProtocol != null && StringUtils.isNotBlank(privacyPassphrase) && engineId != null;
80 break; 87 break;
81 } 88 }
82 } 89 }
@@ -99,7 +99,6 @@ import { DeviceProfileDialogComponent } from '@home/components/profile/device-pr @@ -99,7 +99,6 @@ import { DeviceProfileDialogComponent } from '@home/components/profile/device-pr
99 import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component'; 99 import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component';
100 import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component'; 100 import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component';
101 import { CoapDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/coap-device-profile-transport-configuration.component'; 101 import { CoapDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/coap-device-profile-transport-configuration.component';
102 -import { SnmpDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/snmp-device-profile-transport-configuration.component';  
103 import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component'; 102 import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component';
104 import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component'; 103 import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component';
105 import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component'; 104 import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component';
@@ -143,6 +142,7 @@ import { SecurityConfigLwm2mComponent } from '@home/components/device/security-c @@ -143,6 +142,7 @@ import { SecurityConfigLwm2mComponent } from '@home/components/device/security-c
143 import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; 142 import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
144 import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; 143 import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
145 import { WidgetContainerComponent } from '@home/components/widget/widget-container.component'; 144 import { WidgetContainerComponent } from '@home/components/widget/widget-container.component';
  145 +import { SnmpDeviceProfileTransportModule } from '@home/components/profile/device/snpm/snmp-device-profile-transport.module';
146 146
147 @NgModule({ 147 @NgModule({
148 declarations: 148 declarations:
@@ -228,7 +228,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain @@ -228,7 +228,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
228 DefaultDeviceProfileTransportConfigurationComponent, 228 DefaultDeviceProfileTransportConfigurationComponent,
229 MqttDeviceProfileTransportConfigurationComponent, 229 MqttDeviceProfileTransportConfigurationComponent,
230 CoapDeviceProfileTransportConfigurationComponent, 230 CoapDeviceProfileTransportConfigurationComponent,
231 - SnmpDeviceProfileTransportConfigurationComponent,  
232 DeviceProfileTransportConfigurationComponent, 231 DeviceProfileTransportConfigurationComponent,
233 CreateAlarmRulesComponent, 232 CreateAlarmRulesComponent,
234 AlarmRuleComponent, 233 AlarmRuleComponent,
@@ -272,6 +271,7 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain @@ -272,6 +271,7 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
272 SharedModule, 271 SharedModule,
273 SharedHomeComponentsModule, 272 SharedHomeComponentsModule,
274 Lwm2mProfileComponentsModule, 273 Lwm2mProfileComponentsModule,
  274 + SnmpDeviceProfileTransportModule,
275 StatesControllerModule 275 StatesControllerModule
276 ], 276 ],
277 exports: [ 277 exports: [
@@ -339,7 +339,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain @@ -339,7 +339,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
339 DefaultDeviceProfileTransportConfigurationComponent, 339 DefaultDeviceProfileTransportConfigurationComponent,
340 MqttDeviceProfileTransportConfigurationComponent, 340 MqttDeviceProfileTransportConfigurationComponent,
341 CoapDeviceProfileTransportConfigurationComponent, 341 CoapDeviceProfileTransportConfigurationComponent,
342 - SnmpDeviceProfileTransportConfigurationComponent,  
343 DeviceProfileTransportConfigurationComponent, 342 DeviceProfileTransportConfigurationComponent,
344 CreateAlarmRulesComponent, 343 CreateAlarmRulesComponent,
345 AlarmRuleComponent, 344 AlarmRuleComponent,
@@ -89,7 +89,9 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu @@ -89,7 +89,9 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu
89 if (configuration) { 89 if (configuration) {
90 delete configuration.type; 90 delete configuration.type;
91 } 91 }
92 - this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); 92 + setTimeout(() => {
  93 + this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
  94 + });
93 } 95 }
94 96
95 private updateModel() { 97 private updateModel() {
1 -<!--  
2 -  
3 - Copyright © 2016-2021 The Thingsboard Authors  
4 -  
5 - Licensed under the Apache License, Version 2.0 (the "License");  
6 - you may not use this file except in compliance with the License.  
7 - You may obtain a copy of the License at  
8 -  
9 - http://www.apache.org/licenses/LICENSE-2.0  
10 -  
11 - Unless required by applicable law or agreed to in writing, software  
12 - distributed under the License is distributed on an "AS IS" BASIS,  
13 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 - See the License for the specific language governing permissions and  
15 - limitations under the License.  
16 -  
17 --->  
18 -<form [formGroup]="snmpDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;">  
19 - <tb-json-object-edit  
20 - required  
21 - formControlName="configuration">  
22 - </tb-json-object-edit>  
23 -</form>  
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div fxLayout="column">
  19 + <div *ngFor="let deviceProfileCommunication of communicationConfigFormArray().controls; let $index = index;
  20 + last as isLast;" fxLayout="row" fxLayoutAlign="start center"
  21 + fxLayoutGap="8px" class="scope-row" [formGroup]="deviceProfileCommunication">
  22 + <div class="communication-config" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
  23 + <mat-form-field class="spec mat-block" floatLabel="always" hideRequiredMarker>
  24 + <mat-label translate>device-profile.snmp.scope</mat-label>
  25 + <mat-select formControlName="spec" required>
  26 + <mat-option *ngFor="let snmpSpecType of snmpSpecTypes" [value]="snmpSpecType"
  27 + [disabled]="isDisabledSeverity(snmpSpecType, $index)">
  28 + {{ snmpSpecTypeTranslationMap.get(snmpSpecType) }}
  29 + </mat-option>
  30 + </mat-select>
  31 + <mat-error *ngIf="deviceProfileCommunication.get('spec').hasError('required')">
  32 + {{ 'device-profile.snmp.scope-required' | translate }}
  33 + </mat-error>
  34 + </mat-form-field>
  35 + <mat-divider vertical></mat-divider>
  36 + <section fxFlex fxLayout="column">
  37 + <mat-form-field *ngIf="isShowFrequency(deviceProfileCommunication.get('spec').value)">
  38 + <mat-label translate>device-profile.snmp.querying-frequency</mat-label>
  39 + <input matInput formControlName="queryingFrequencyMs" type="number" min="0" required/>
  40 + <mat-error *ngIf="deviceProfileCommunication.get('queryingFrequencyMs').hasError('required')">
  41 + {{ 'device-profile.snmp.querying-frequency-required' | translate }}
  42 + </mat-error>
  43 + <mat-error *ngIf="deviceProfileCommunication.get('queryingFrequencyMs').hasError('pattern') ||
  44 + deviceProfileCommunication.get('queryingFrequencyMs').hasError('min')">
  45 + {{ 'device-profile.snmp.querying-frequency-invalid-format' | translate }}
  46 + </mat-error>
  47 + </mat-form-field>
  48 + <tb-snmp-device-profile-mapping formControlName="mappings">
  49 + </tb-snmp-device-profile-mapping>
  50 + </section>
  51 + </div>
  52 + <button *ngIf="!disabled"
  53 + mat-icon-button color="primary" style="min-width: 40px;"
  54 + type="button"
  55 + (click)="removeCommunicationConfig($index)"
  56 + matTooltip="{{ 'action.remove' | translate }}"
  57 + matTooltipPosition="above">
  58 + <mat-icon>remove_circle_outline</mat-icon>
  59 + </button>
  60 + </div>
  61 + <div *ngIf="!communicationConfigFormArray().controls.length && !disabled">
  62 + <span fxLayoutAlign="center center" class="tb-prompt required required-text" translate>device-profile.snmp.please-add-communication-config</span>
  63 + </div>
  64 + <div *ngIf="!disabled && isAddEnabled">
  65 + <button mat-stroked-button color="primary"
  66 + type="button"
  67 + (click)="addCommunicationConfig()">
  68 + <mat-icon class="button-icon">add_circle_outline</mat-icon>
  69 + {{ 'device-profile.snmp.add-communication-config' | translate }}
  70 + </button>
  71 + </div>
  72 +</div>
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .communication-config {
  18 + border: 2px groove rgba(0, 0, 0, 0.25);
  19 + border-radius: 4px;
  20 + padding: 8px;
  21 + min-width: 0;
  22 + }
  23 +
  24 + .scope-row {
  25 + padding-bottom: 8px;
  26 + }
  27 +
  28 + .required-text {
  29 + margin: 16px 0
  30 + }
  31 +}
  32 +
  33 +:host ::ng-deep {
  34 + .mat-form-field.spec {
  35 + .mat-form-field-infix {
  36 + width: 160px;
  37 + }
  38 + }
  39 + .button-icon{
  40 + font-size: 20px;
  41 + width: 20px;
  42 + height: 20px;
  43 + }
  44 +}
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
  18 +import {
  19 + AbstractControl,
  20 + ControlValueAccessor,
  21 + FormArray,
  22 + FormBuilder,
  23 + FormGroup,
  24 + NG_VALIDATORS,
  25 + NG_VALUE_ACCESSOR,
  26 + Validator,
  27 + Validators
  28 +} from '@angular/forms';
  29 +import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models';
  30 +import { Subject, Subscription } from 'rxjs';
  31 +import { isUndefinedOrNull } from '@core/utils';
  32 +import { takeUntil } from 'rxjs/operators';
  33 +
  34 +@Component({
  35 + selector: 'tb-snmp-device-profile-communication-config',
  36 + templateUrl: './snmp-device-profile-communication-config.component.html',
  37 + styleUrls: ['./snmp-device-profile-communication-config.component.scss'],
  38 + providers: [
  39 + {
  40 + provide: NG_VALUE_ACCESSOR,
  41 + useExisting: forwardRef(() => SnmpDeviceProfileCommunicationConfigComponent),
  42 + multi: true
  43 + },
  44 + {
  45 + provide: NG_VALIDATORS,
  46 + useExisting: forwardRef(() => SnmpDeviceProfileCommunicationConfigComponent),
  47 + multi: true
  48 + }]
  49 +})
  50 +export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
  51 +
  52 + snmpSpecTypes = Object.values(SnmpSpecType);
  53 + snmpSpecTypeTranslationMap = SnmpSpecTypeTranslationMap;
  54 +
  55 + deviceProfileCommunicationConfig: FormGroup;
  56 +
  57 + @Input()
  58 + disabled: boolean;
  59 +
  60 + private usedSpecType: SnmpSpecType[] = [];
  61 + private valueChange$: Subscription = null;
  62 + private destroy$ = new Subject();
  63 + private propagateChange = (v: any) => { };
  64 +
  65 + constructor(private fb: FormBuilder) { }
  66 +
  67 + ngOnInit(): void {
  68 + this.deviceProfileCommunicationConfig = this.fb.group({
  69 + communicationConfig: this.fb.array([])
  70 + });
  71 + }
  72 +
  73 + ngOnDestroy() {
  74 + if (this.valueChange$) {
  75 + this.valueChange$.unsubscribe();
  76 + }
  77 + this.destroy$.next();
  78 + this.destroy$.complete();
  79 + }
  80 +
  81 + communicationConfigFormArray(): FormArray {
  82 + return this.deviceProfileCommunicationConfig.get('communicationConfig') as FormArray;
  83 + }
  84 +
  85 + registerOnChange(fn: any): void {
  86 + this.propagateChange = fn;
  87 + }
  88 +
  89 + registerOnTouched(fn: any): void {
  90 + }
  91 +
  92 + setDisabledState(isDisabled: boolean) {
  93 + this.disabled = isDisabled;
  94 + if (this.disabled) {
  95 + this.deviceProfileCommunicationConfig.disable({emitEvent: false});
  96 + } else {
  97 + this.deviceProfileCommunicationConfig.enable({emitEvent: false});
  98 + }
  99 + }
  100 +
  101 + writeValue(communicationConfig: SnmpCommunicationConfig[]) {
  102 + if (this.valueChange$) {
  103 + this.valueChange$.unsubscribe();
  104 + }
  105 + const communicationConfigControl: Array<AbstractControl> = [];
  106 + if (communicationConfig) {
  107 + communicationConfig.forEach((config) => {
  108 + communicationConfigControl.push(this.createdFormGroup(config));
  109 + });
  110 + }
  111 + this.deviceProfileCommunicationConfig.setControl('communicationConfig', this.fb.array(communicationConfigControl));
  112 + if (!communicationConfig || !communicationConfig.length) {
  113 + this.addCommunicationConfig();
  114 + }
  115 + if (this.disabled) {
  116 + this.deviceProfileCommunicationConfig.disable({emitEvent: false});
  117 + } else {
  118 + this.deviceProfileCommunicationConfig.enable({emitEvent: false});
  119 + }
  120 + this.valueChange$ = this.deviceProfileCommunicationConfig.valueChanges.subscribe(() => {
  121 + this.updateModel();
  122 + });
  123 + this.updateUsedSpecType();
  124 + if (!this.disabled && !this.deviceProfileCommunicationConfig.valid) {
  125 + this.updateModel();
  126 + }
  127 + }
  128 +
  129 + public validate() {
  130 + return this.deviceProfileCommunicationConfig.valid && this.deviceProfileCommunicationConfig.value.communicationConfig.length ? null : {
  131 + communicationConfig: false
  132 + };
  133 + }
  134 +
  135 + public removeCommunicationConfig(index: number) {
  136 + this.communicationConfigFormArray().removeAt(index);
  137 + }
  138 +
  139 +
  140 + get isAddEnabled(): boolean {
  141 + return this.communicationConfigFormArray().length !== Object.keys(SnmpSpecType).length;
  142 + }
  143 +
  144 + public addCommunicationConfig() {
  145 + this.communicationConfigFormArray().push(this.createdFormGroup());
  146 + this.deviceProfileCommunicationConfig.updateValueAndValidity();
  147 + if (!this.deviceProfileCommunicationConfig.valid) {
  148 + this.updateModel();
  149 + }
  150 + }
  151 +
  152 + private getFirstUnusedSeverity(): SnmpSpecType {
  153 + for (const type of Object.values(SnmpSpecType)) {
  154 + if (this.usedSpecType.indexOf(type) === -1) {
  155 + return type;
  156 + }
  157 + }
  158 + return null;
  159 + }
  160 +
  161 + public isDisabledSeverity(type: SnmpSpecType, index: number): boolean {
  162 + const usedIndex = this.usedSpecType.indexOf(type);
  163 + return usedIndex > -1 && usedIndex !== index;
  164 + }
  165 +
  166 + public isShowFrequency(type: SnmpSpecType): boolean {
  167 + return type === SnmpSpecType.TELEMETRY_QUERYING || type === SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING;
  168 + }
  169 +
  170 + private updateUsedSpecType() {
  171 + this.usedSpecType = [];
  172 + const value: SnmpCommunicationConfig[] = this.deviceProfileCommunicationConfig.get('communicationConfig').value;
  173 + value.forEach((rule, index) => {
  174 + this.usedSpecType[index] = rule.spec;
  175 + });
  176 + }
  177 +
  178 + private createdFormGroup(value?: SnmpCommunicationConfig): FormGroup {
  179 + if (isUndefinedOrNull(value)) {
  180 + value = {
  181 + spec: this.getFirstUnusedSeverity(),
  182 + queryingFrequencyMs: 5000,
  183 + mappings: null
  184 + };
  185 + }
  186 + const form = this.fb.group({
  187 + spec: [value.spec, Validators.required],
  188 + mappings: [value.mappings]
  189 + });
  190 + if (this.isShowFrequency(value.spec)) {
  191 + form.addControl('queryingFrequencyMs',
  192 + this.fb.control(value.queryingFrequencyMs, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]));
  193 + }
  194 + form.get('spec').valueChanges.pipe(
  195 + takeUntil(this.destroy$)
  196 + ).subscribe(spec => {
  197 + if (this.isShowFrequency(spec)) {
  198 + form.addControl('queryingFrequencyMs',
  199 + this.fb.control(5000, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]));
  200 + } else {
  201 + form.removeControl('queryingFrequencyMs');
  202 + }
  203 + });
  204 + return form;
  205 + }
  206 +
  207 + private updateModel() {
  208 + const value: SnmpCommunicationConfig[] = this.deviceProfileCommunicationConfig.get('communicationConfig').value;
  209 + value.forEach(config => {
  210 + if (!this.isShowFrequency(config.spec)) {
  211 + delete config.queryingFrequencyMs;
  212 + }
  213 + });
  214 + this.updateUsedSpecType();
  215 + this.propagateChange(value);
  216 + }
  217 +
  218 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div fxFlex fxLayout="column" class="mapping-config">
  19 + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="100">
  20 + <div fxFlex fxLayout="row" fxLayoutGap="8px">
  21 + <label fxFlex="26" class="tb-title no-padding" translate>device-profile.snmp.data-type</label>
  22 + <label fxFlex="37" class="tb-title no-padding" translate>device-profile.snmp.data-key</label>
  23 + <label fxFlex="37" class="tb-title no-padding" translate>device-profile.snmp.oid</label>
  24 + <span style="min-width: 40px" [fxShow]="!disabled"></span>
  25 + </div>
  26 + </div>
  27 + <mat-divider></mat-divider>
  28 + <div *ngFor="let mappingConfig of mappingsConfigFormArray().controls; let $index = index;
  29 + last as isLast;" fxLayout="row" fxLayoutAlign="start center"
  30 + fxLayoutGap="8px" [formGroup]="mappingConfig" class="mapping-list">
  31 + <div fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
  32 + <mat-form-field fxFlex="26" floatLabel="always" hideRequiredMarker>
  33 + <mat-label></mat-label>
  34 + <mat-select formControlName="dataType" required>
  35 + <mat-option *ngFor="let dataType of dataTypes" [value]="dataType">
  36 + {{ dataTypesTranslationMap.get(dataType) | translate }}
  37 + </mat-option>
  38 + </mat-select>
  39 + <mat-error *ngIf="mappingConfig.get('dataType').hasError('required')">
  40 + {{ 'device-profile.snmp.data-type-required' | translate }}
  41 + </mat-error>
  42 + </mat-form-field>
  43 + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="37">
  44 + <mat-label></mat-label>
  45 + <input matInput formControlName="key" required/>
  46 + <mat-error *ngIf="mappingConfig.get('key').hasError('required')">
  47 + {{ 'device-profile.snmp.data-key-required' | translate }}
  48 + </mat-error>
  49 + </mat-form-field>
  50 + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="37">
  51 + <mat-label></mat-label>
  52 + <input matInput formControlName="oid" required/>
  53 + <mat-error *ngIf="mappingConfig.get('oid').hasError('required')">
  54 + {{ 'device-profile.snmp.oid-required' | translate }}
  55 + </mat-error>
  56 + <mat-error *ngIf="mappingConfig.get('oid').hasError('pattern')">
  57 + {{ 'device-profile.snmp.oid-pattern' | translate }}
  58 + </mat-error>
  59 + </mat-form-field>
  60 + <button *ngIf="!disabled"
  61 + mat-icon-button color="primary"
  62 + type="button"
  63 + (click)="removeMappingConfig($index)"
  64 + matTooltip="{{ 'action.remove' | translate }}"
  65 + matTooltipPosition="above">
  66 + <mat-icon>close</mat-icon>
  67 + </button>
  68 + </div>
  69 + </div>
  70 + <div *ngIf="!mappingsConfigFormArray().controls.length && !disabled">
  71 + <span fxLayoutAlign="center center" class="tb-prompt required required-text" translate>device-profile.snmp.please-add-mapping-config</span>
  72 + </div>
  73 + <div *ngIf="!disabled">
  74 + <button mat-stroked-button color="primary"
  75 + type="button"
  76 + (click)="addMappingConfig()">
  77 + <mat-icon class="button-icon">add_circle_outline</mat-icon>
  78 + {{ 'device-profile.snmp.add-mapping' | translate }}
  79 + </button>
  80 + </div>
  81 +</div>
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .mapping-config {
  18 + min-width: 518px;
  19 + }
  20 + .mapping-list {
  21 + padding-bottom: 8px;
  22 + height: 46px;
  23 + }
  24 +
  25 + .required-text {
  26 + margin: 14px 0;
  27 + }
  28 +}
  29 +
  30 +:host ::ng-deep {
  31 + .mapping-list {
  32 + mat-form-field {
  33 + .mat-form-field-wrapper {
  34 + padding-bottom: 0;
  35 + .mat-form-field-infix {
  36 + border-top-width: 0.2em;
  37 + width: auto;
  38 + min-width: auto;
  39 + }
  40 + .mat-form-field-underline {
  41 + bottom: 0;
  42 + }
  43 + .mat-form-field-subscript-wrapper{
  44 + margin-top: 1.8em;
  45 + }
  46 + }
  47 + }
  48 + }
  49 +}
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
  18 +import {
  19 + AbstractControl,
  20 + ControlValueAccessor,
  21 + FormArray,
  22 + FormBuilder,
  23 + FormGroup,
  24 + NG_VALIDATORS,
  25 + NG_VALUE_ACCESSOR,
  26 + ValidationErrors,
  27 + Validator,
  28 + Validators
  29 +} from '@angular/forms';
  30 +import { SnmpMapping } from '@shared/models/device.models';
  31 +import { Subscription } from 'rxjs';
  32 +import { DataType, DataTypeTranslationMap } from '@shared/models/constants';
  33 +import { isUndefinedOrNull } from '@core/utils';
  34 +
  35 +@Component({
  36 + selector: 'tb-snmp-device-profile-mapping',
  37 + templateUrl: './snmp-device-profile-mapping.component.html',
  38 + styleUrls: ['./snmp-device-profile-mapping.component.scss'],
  39 + providers: [
  40 + {
  41 + provide: NG_VALUE_ACCESSOR,
  42 + useExisting: forwardRef(() => SnmpDeviceProfileMappingComponent),
  43 + multi: true
  44 + },
  45 + {
  46 + provide: NG_VALIDATORS,
  47 + useExisting: forwardRef(() => SnmpDeviceProfileMappingComponent),
  48 + multi: true
  49 + }]
  50 +})
  51 +export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
  52 +
  53 + mappingsConfigForm: FormGroup;
  54 +
  55 + dataTypes = Object.values(DataType);
  56 + dataTypesTranslationMap = DataTypeTranslationMap;
  57 +
  58 + @Input()
  59 + disabled: boolean;
  60 +
  61 + private readonly oidPattern: RegExp = /^\.?([0-2])((\.0)|(\.[1-9][0-9]*))*$/;
  62 +
  63 + private valueChange$: Subscription = null;
  64 + private propagateChange = (v: any) => { };
  65 +
  66 + constructor(private fb: FormBuilder) { }
  67 +
  68 + ngOnInit() {
  69 + this.mappingsConfigForm = this.fb.group({
  70 + mappings: this.fb.array([])
  71 + });
  72 + }
  73 +
  74 + ngOnDestroy() {
  75 + if (this.valueChange$) {
  76 + this.valueChange$.unsubscribe();
  77 + }
  78 + }
  79 +
  80 + registerOnChange(fn: any) {
  81 + this.propagateChange = fn;
  82 + }
  83 +
  84 + registerOnTouched(fn: any) {
  85 + }
  86 +
  87 + setDisabledState(isDisabled: boolean) {
  88 + this.disabled = isDisabled;
  89 + if (this.disabled) {
  90 + this.mappingsConfigForm.disable({emitEvent: false});
  91 + } else {
  92 + this.mappingsConfigForm.enable({emitEvent: false});
  93 + }
  94 + }
  95 +
  96 + validate(): ValidationErrors | null {
  97 + return this.mappingsConfigForm.valid && this.mappingsConfigForm.value.mappings.length ? null : {
  98 + mapping: false
  99 + };
  100 + }
  101 +
  102 + writeValue(mappings: SnmpMapping[]) {
  103 + if (this.valueChange$) {
  104 + this.valueChange$.unsubscribe();
  105 + }
  106 + const mappingsControl: Array<AbstractControl> = [];
  107 + if (mappings) {
  108 + mappings.forEach((config) => {
  109 + mappingsControl.push(this.createdFormGroup(config));
  110 + });
  111 + }
  112 + this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl));
  113 + if (!mappings || !mappings.length) {
  114 + this.addMappingConfig();
  115 + }
  116 + if (this.disabled) {
  117 + this.mappingsConfigForm.disable({emitEvent: false});
  118 + } else {
  119 + this.mappingsConfigForm.enable({emitEvent: false});
  120 + }
  121 + this.valueChange$ = this.mappingsConfigForm.valueChanges.subscribe(() => {
  122 + this.updateModel();
  123 + });
  124 + if (!this.disabled && !this.mappingsConfigForm.valid) {
  125 + this.updateModel();
  126 + }
  127 + }
  128 +
  129 + mappingsConfigFormArray(): FormArray {
  130 + return this.mappingsConfigForm.get('mappings') as FormArray;
  131 + }
  132 +
  133 + public addMappingConfig() {
  134 + this.mappingsConfigFormArray().push(this.createdFormGroup());
  135 + this.mappingsConfigForm.updateValueAndValidity();
  136 + if (!this.mappingsConfigForm.valid) {
  137 + this.updateModel();
  138 + }
  139 + }
  140 +
  141 + public removeMappingConfig(index: number) {
  142 + this.mappingsConfigFormArray().removeAt(index);
  143 + }
  144 +
  145 + private createdFormGroup(value?: SnmpMapping): FormGroup {
  146 + if (isUndefinedOrNull(value)) {
  147 + value = {
  148 + dataType: DataType.STRING,
  149 + key: '',
  150 + oid: ''
  151 + };
  152 + }
  153 + return this.fb.group({
  154 + dataType: [value.dataType, Validators.required],
  155 + key: [value.key, Validators.required],
  156 + oid: [value.oid, [Validators.required, Validators.pattern(this.oidPattern)]]
  157 + });
  158 + }
  159 +
  160 + private updateModel() {
  161 + const value: SnmpMapping[] = this.mappingsConfigForm.get('mappings').value;
  162 + this.propagateChange(value);
  163 + }
  164 +
  165 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form [formGroup]="snmpDeviceProfileTransportConfigurationFormGroup" style="padding: 8px 0 16px;">
  19 + <section fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
  20 + <mat-form-field fxFlex>
  21 + <mat-label translate>device-profile.snmp.timeout-ms</mat-label>
  22 + <input matInput formControlName="timeoutMs" type="number" min="0" required/>
  23 + <mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('required')">
  24 + {{ 'device-profile.snmp.timeout-ms-required' | translate }}
  25 + </mat-error>
  26 + <mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('pattern') ||
  27 + snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('min')">
  28 + {{ 'device-profile.snmp.timeout-ms-invalid-format' | translate }}
  29 + </mat-error>
  30 + </mat-form-field>
  31 + <mat-form-field fxFlex>
  32 + <mat-label translate>device-profile.snmp.retries</mat-label>
  33 + <input matInput formControlName="retries" type="number" min="0" required/>
  34 + <mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('required')">
  35 + {{ 'device-profile.snmp.retries-required' | translate }}
  36 + </mat-error>
  37 + <mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('pattern') ||
  38 + snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('min')">
  39 + {{ 'device-profile.snmp.retries-invalid-format' | translate }}
  40 + </mat-error>
  41 + </mat-form-field>
  42 + </section>
  43 + <div class="tb-small" style="padding-bottom: 8px" translate>device-profile.snmp.communication-configs</div>
  44 + <tb-snmp-device-profile-communication-config formControlName="communicationConfigs">
  45 + </tb-snmp-device-profile-communication-config>
  46 +</form>
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts renamed from ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts
@@ -15,9 +15,16 @@ @@ -15,9 +15,16 @@
15 /// 15 ///
16 16
17 import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; 17 import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
18 -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';  
19 -import { Store } from '@ngrx/store';  
20 -import { AppState } from '@app/core/core.state'; 18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormGroup,
  22 + NG_VALIDATORS,
  23 + NG_VALUE_ACCESSOR,
  24 + ValidationErrors,
  25 + Validator,
  26 + Validators
  27 +} from '@angular/forms';
21 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 28 import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 import { 29 import {
23 DeviceProfileTransportConfiguration, 30 DeviceProfileTransportConfiguration,
@@ -40,19 +47,24 @@ export interface OidMappingConfiguration { @@ -40,19 +47,24 @@ export interface OidMappingConfiguration {
40 selector: 'tb-snmp-device-profile-transport-configuration', 47 selector: 'tb-snmp-device-profile-transport-configuration',
41 templateUrl: './snmp-device-profile-transport-configuration.component.html', 48 templateUrl: './snmp-device-profile-transport-configuration.component.html',
42 styleUrls: [], 49 styleUrls: [],
43 - providers: [{  
44 - provide: NG_VALUE_ACCESSOR,  
45 - useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent),  
46 - multi: true  
47 - }] 50 + providers: [
  51 + {
  52 + provide: NG_VALUE_ACCESSOR,
  53 + useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent),
  54 + multi: true
  55 + },
  56 + {
  57 + provide: NG_VALIDATORS,
  58 + useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent),
  59 + multi: true
  60 + }]
48 }) 61 })
49 -export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { 62 +export class SnmpDeviceProfileTransportConfigurationComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
50 63
51 snmpDeviceProfileTransportConfigurationFormGroup: FormGroup; 64 snmpDeviceProfileTransportConfigurationFormGroup: FormGroup;
52 65
53 private destroy$ = new Subject(); 66 private destroy$ = new Subject();
54 private requiredValue: boolean; 67 private requiredValue: boolean;
55 - private configuration = [];  
56 68
57 get required(): boolean { 69 get required(): boolean {
58 return this.requiredValue; 70 return this.requiredValue;
@@ -69,12 +81,14 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control @@ -69,12 +81,14 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
69 private propagateChange = (v: any) => { 81 private propagateChange = (v: any) => {
70 } 82 }
71 83
72 - constructor(private store: Store<AppState>, private fb: FormBuilder) { 84 + constructor(private fb: FormBuilder) {
73 } 85 }
74 86
75 ngOnInit(): void { 87 ngOnInit(): void {
76 this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ 88 this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({
77 - configuration: [null, Validators.required] 89 + timeoutMs: [500, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]],
  90 + retries: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]],
  91 + communicationConfigs: [null, Validators.required],
78 }); 92 });
79 this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( 93 this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe(
80 takeUntil(this.destroy$) 94 takeUntil(this.destroy$)
@@ -95,18 +109,33 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control @@ -95,18 +109,33 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
95 registerOnTouched(fn: any): void { 109 registerOnTouched(fn: any): void {
96 } 110 }
97 111
  112 + setDisabledState(isDisabled: boolean) {
  113 + this.disabled = isDisabled;
  114 + if (this.disabled) {
  115 + this.snmpDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false});
  116 + } else {
  117 + this.snmpDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false});
  118 + }
  119 + }
  120 +
98 writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void { 121 writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void {
99 if (isDefinedAndNotNull(value)) { 122 if (isDefinedAndNotNull(value)) {
100 - this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); 123 + this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: !value.communicationConfigs});
101 } 124 }
102 } 125 }
103 126
104 private updateModel() { 127 private updateModel() {
105 let configuration: DeviceProfileTransportConfiguration = null; 128 let configuration: DeviceProfileTransportConfiguration = null;
106 if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) { 129 if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) {
107 - configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; 130 + configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue();
108 configuration.type = DeviceTransportType.SNMP; 131 configuration.type = DeviceTransportType.SNMP;
109 } 132 }
110 this.propagateChange(configuration); 133 this.propagateChange(configuration);
111 } 134 }
  135 +
  136 + validate(): ValidationErrors | null {
  137 + return this.snmpDeviceProfileTransportConfigurationFormGroup.valid ? null : {
  138 + snmpDeviceProfileTransportConfiguration: false
  139 + };
  140 + }
112 } 141 }
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { NgModule } from '@angular/core';
  18 +import { SharedModule } from '@shared/shared.module';
  19 +import { CommonModule } from '@angular/common';
  20 +import { SnmpDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component';
  21 +import { SnmpDeviceProfileCommunicationConfigComponent } from './snmp-device-profile-communication-config.component';
  22 +import { SnmpDeviceProfileMappingComponent } from './snmp-device-profile-mapping.component';
  23 +
  24 +@NgModule({
  25 + declarations: [
  26 + SnmpDeviceProfileTransportConfigurationComponent,
  27 + SnmpDeviceProfileCommunicationConfigComponent,
  28 + SnmpDeviceProfileMappingComponent
  29 + ],
  30 + imports: [
  31 + CommonModule,
  32 + SharedModule
  33 + ],
  34 + exports: [
  35 + SnmpDeviceProfileTransportConfigurationComponent
  36 + ]
  37 +})
  38 +export class SnmpDeviceProfileTransportModule { }
@@ -15,7 +15,16 @@ @@ -15,7 +15,16 @@
15 /// 15 ///
16 16
17 import { Component, forwardRef, Input, OnInit } from '@angular/core'; 17 import { Component, forwardRef, Input, OnInit } from '@angular/core';
18 -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; 18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormGroup,
  22 + NG_VALIDATORS,
  23 + NG_VALUE_ACCESSOR,
  24 + ValidationErrors,
  25 + Validator,
  26 + Validators
  27 +} from '@angular/forms';
19 import { Store } from '@ngrx/store'; 28 import { Store } from '@ngrx/store';
20 import { AppState } from '@app/core/core.state'; 29 import { AppState } from '@app/core/core.state';
21 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
@@ -29,13 +38,20 @@ import { @@ -29,13 +38,20 @@ import {
29 selector: 'tb-device-data', 38 selector: 'tb-device-data',
30 templateUrl: './device-data.component.html', 39 templateUrl: './device-data.component.html',
31 styleUrls: [], 40 styleUrls: [],
32 - providers: [{  
33 - provide: NG_VALUE_ACCESSOR,  
34 - useExisting: forwardRef(() => DeviceDataComponent),  
35 - multi: true  
36 - }] 41 + providers: [
  42 + {
  43 + provide: NG_VALUE_ACCESSOR,
  44 + useExisting: forwardRef(() => DeviceDataComponent),
  45 + multi: true
  46 + },
  47 + {
  48 + provide: NG_VALIDATORS,
  49 + useExisting: forwardRef(() => DeviceDataComponent),
  50 + multi: true
  51 + },
  52 + ]
37 }) 53 })
38 -export class DeviceDataComponent implements ControlValueAccessor, OnInit { 54 +export class DeviceDataComponent implements ControlValueAccessor, OnInit, Validator {
39 55
40 deviceDataFormGroup: FormGroup; 56 deviceDataFormGroup: FormGroup;
41 57
@@ -97,6 +113,12 @@ export class DeviceDataComponent implements ControlValueAccessor, OnInit { @@ -97,6 +113,12 @@ export class DeviceDataComponent implements ControlValueAccessor, OnInit {
97 this.deviceDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); 113 this.deviceDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
98 } 114 }
99 115
  116 + validate(): ValidationErrors | null {
  117 + return this.deviceDataFormGroup.valid ? null : {
  118 + deviceDataForm: false
  119 + };
  120 + }
  121 +
100 private updateModel() { 122 private updateModel() {
101 let deviceData: DeviceData = null; 123 let deviceData: DeviceData = null;
102 if (this.deviceDataFormGroup.valid) { 124 if (this.deviceDataFormGroup.valid) {
@@ -15,27 +15,39 @@ @@ -15,27 +15,39 @@
15 /// 15 ///
16 16
17 import { Component, forwardRef, Input, OnInit } from '@angular/core'; 17 import { Component, forwardRef, Input, OnInit } from '@angular/core';
18 -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; 18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormGroup,
  22 + NG_VALIDATORS,
  23 + NG_VALUE_ACCESSOR,
  24 + ValidationErrors,
  25 + Validator,
  26 + Validators
  27 +} from '@angular/forms';
19 import { Store } from '@ngrx/store'; 28 import { Store } from '@ngrx/store';
20 import { AppState } from '@app/core/core.state'; 29 import { AppState } from '@app/core/core.state';
21 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 -import {  
23 - DeviceTransportConfiguration,  
24 - DeviceTransportType  
25 -} from '@shared/models/device.models'; 31 +import { DeviceTransportConfiguration, DeviceTransportType } from '@shared/models/device.models';
26 import { deepClone } from '@core/utils'; 32 import { deepClone } from '@core/utils';
27 33
28 @Component({ 34 @Component({
29 selector: 'tb-device-transport-configuration', 35 selector: 'tb-device-transport-configuration',
30 templateUrl: './device-transport-configuration.component.html', 36 templateUrl: './device-transport-configuration.component.html',
31 styleUrls: [], 37 styleUrls: [],
32 - providers: [{  
33 - provide: NG_VALUE_ACCESSOR,  
34 - useExisting: forwardRef(() => DeviceTransportConfigurationComponent),  
35 - multi: true  
36 - }] 38 + providers: [
  39 + {
  40 + provide: NG_VALUE_ACCESSOR,
  41 + useExisting: forwardRef(() => DeviceTransportConfigurationComponent),
  42 + multi: true
  43 + },
  44 + {
  45 + provide: NG_VALIDATORS,
  46 + useExisting: forwardRef(() => DeviceTransportConfigurationComponent),
  47 + multi: true
  48 + }]
37 }) 49 })
38 -export class DeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { 50 +export class DeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit, Validator {
39 51
40 deviceTransportType = DeviceTransportType; 52 deviceTransportType = DeviceTransportType;
41 53
@@ -92,7 +104,15 @@ export class DeviceTransportConfigurationComponent implements ControlValueAccess @@ -92,7 +104,15 @@ export class DeviceTransportConfigurationComponent implements ControlValueAccess
92 if (configuration) { 104 if (configuration) {
93 delete configuration.type; 105 delete configuration.type;
94 } 106 }
95 - this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); 107 + setTimeout(() => {
  108 + this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
  109 + }, 0);
  110 + }
  111 +
  112 + validate(): ValidationErrors | null {
  113 + return this.deviceTransportConfigurationFormGroup.valid ? null : {
  114 + deviceTransportConfiguration: false
  115 + };
96 } 116 }
97 117
98 private updateModel() { 118 private updateModel() {
@@ -15,10 +15,119 @@ @@ -15,10 +15,119 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<form [formGroup]="snmpDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;">  
19 - <tb-json-object-edit  
20 - [required]="required"  
21 - label="{{ 'device-profile.transport-type-snmp-hint' | translate }}"  
22 - formControlName="configuration">  
23 - </tb-json-object-edit> 18 +<form [formGroup]="snmpDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;" fxLayoutGap="8px" fxLayout="column">
  19 + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
  20 + <mat-form-field fxFlex>
  21 + <mat-label translate>device-profile.snmp.host</mat-label>
  22 + <input matInput formControlName="host" required>
  23 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('host').hasError('required') ||
  24 + snmpDeviceTransportConfigurationFormGroup.get('host').hasError('pattern')">
  25 + {{ 'device-profile.snmp.host-required' | translate }}
  26 + </mat-error>
  27 + </mat-form-field>
  28 + <mat-form-field fxFlex>
  29 + <mat-label translate>device-profile.snmp.port</mat-label>
  30 + <input matInput formControlName="port" type="number" min="0" required>
  31 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('port').hasError('required')">
  32 + {{ 'device-profile.snmp.port-required' | translate }}
  33 + </mat-error>
  34 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('port').hasError('pattern') ||
  35 + snmpDeviceTransportConfigurationFormGroup.get('port').hasError('min')">
  36 + {{ 'device-profile.snmp.port-format' | translate }}
  37 + </mat-error>
  38 + </mat-form-field>
  39 + </div>
  40 + <mat-form-field class="mat-block" floatLabel="always" hideRequiredMarker>
  41 + <mat-label translate>device-profile.snmp.protocol-version</mat-label>
  42 + <mat-select formControlName="protocolVersion" required>
  43 + <mat-option *ngFor="let snmpDeviceProtocolVersion of snmpDeviceProtocolVersions" [value]="snmpDeviceProtocolVersion">
  44 + {{ snmpDeviceProtocolVersion | lowercase }}
  45 + </mat-option>
  46 + </mat-select>
  47 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('protocolVersion').hasError('required')">
  48 + {{ 'device-profile.snmp.protocol-version-required' | translate }}
  49 + </mat-error>
  50 + </mat-form-field>
  51 + <section *ngIf="!isV3protocolVersion()">
  52 + <mat-form-field class="mat-block">
  53 + <mat-label translate>device-profile.snmp.community</mat-label>
  54 + <input matInput formControlName="community" required>
  55 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('community').hasError('required') ||
  56 + snmpDeviceTransportConfigurationFormGroup.get('community').hasError('pattern')">
  57 + {{ 'device-profile.snmp.community-required' | translate }}
  58 + </mat-error>
  59 + </mat-form-field>
  60 + </section>
  61 + <section *ngIf="isV3protocolVersion()">
  62 + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
  63 + <mat-form-field fxFlex>
  64 + <mat-label translate>device-profile.snmp.user-name</mat-label>
  65 + <input matInput formControlName="username" required>
  66 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('username').hasError('required') ||
  67 + snmpDeviceTransportConfigurationFormGroup.get('username').hasError('pattern')">
  68 + {{ 'device-profile.snmp.user-name-required' | translate }}
  69 + </mat-error>
  70 + </mat-form-field>
  71 + <mat-form-field fxFlex>
  72 + <mat-label translate>device-profile.snmp.security-name</mat-label>
  73 + <input matInput formControlName="securityName" required>
  74 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('securityName').hasError('required') ||
  75 + snmpDeviceTransportConfigurationFormGroup.get('securityName').hasError('pattern')">
  76 + {{ 'device-profile.snmp.security-name-required' | translate }}
  77 + </mat-error>
  78 + </mat-form-field>
  79 + </div>
  80 + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
  81 + <mat-form-field fxFlex floatLabel="always" hideRequiredMarker>
  82 + <mat-label translate>device-profile.snmp.authentication-protocol</mat-label>
  83 + <mat-select formControlName="authenticationProtocol" required>
  84 + <mat-option *ngFor="let snmpAuthenticationProtocol of snmpAuthenticationProtocols" [value]="snmpAuthenticationProtocol">
  85 + {{ snmpAuthenticationProtocolTranslation.get(snmpAuthenticationProtocol) }}
  86 + </mat-option>
  87 + </mat-select>
  88 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('authenticationProtocol').hasError('required')">
  89 + {{ 'device-profile.snmp.authentication-protocol-required' | translate }}
  90 + </mat-error>
  91 + </mat-form-field>
  92 + <mat-form-field fxFlex>
  93 + <mat-label translate>device-profile.snmp.authentication-passphrase</mat-label>
  94 + <input matInput formControlName="authenticationPassphrase" required>
  95 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').hasError('required') ||
  96 + snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').hasError('pattern')">
  97 + {{ 'device-profile.snmp.authentication-passphrase-required' | translate }}
  98 + </mat-error>
  99 + </mat-form-field>
  100 + </div>
  101 + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
  102 + <mat-form-field fxFlex floatLabel="always" hideRequiredMarker>
  103 + <mat-label translate>device-profile.snmp.privacy-protocol</mat-label>
  104 + <mat-select formControlName="privacyProtocol" required>
  105 + <mat-option *ngFor="let snmpPrivacyProtocol of snmpPrivacyProtocols" [value]="snmpPrivacyProtocol">
  106 + {{ snmpPrivacyProtocolTranslation.get(snmpPrivacyProtocol) }}
  107 + </mat-option>
  108 + </mat-select>
  109 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('privacyProtocol').hasError('required')">
  110 + {{ 'device-profile.snmp.privacy-protocol-required' | translate }}
  111 + </mat-error>
  112 + </mat-form-field>
  113 + <mat-form-field fxFlex>
  114 + <mat-label translate>device-profile.snmp.privacy-passphrase</mat-label>
  115 + <input matInput formControlName="privacyPassphrase" required>
  116 + <mat-error *ngIf="snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').hasError('required') ||
  117 + snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').hasError('pattern')">
  118 + {{ 'device-profile.snmp.privacy-passphrase-required' | translate }}
  119 + </mat-error>
  120 + </mat-form-field>
  121 + </div>
  122 + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
  123 + <mat-form-field fxFlex>
  124 + <mat-label translate>device-profile.snmp.context-name</mat-label>
  125 + <input matInput formControlName="contextName">
  126 + </mat-form-field>
  127 + <mat-form-field fxFlex>
  128 + <mat-label translate>device-profile.snmp.engine-id</mat-label>
  129 + <input matInput formControlName="engineId">
  130 + </mat-form-field>
  131 + </div>
  132 + </section>
24 </form> 133 </form>
@@ -14,31 +14,57 @@ @@ -14,31 +14,57 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import {Component, forwardRef, Input, OnInit} from '@angular/core';  
18 -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';  
19 -import {Store} from '@ngrx/store';  
20 -import {AppState} from '@app/core/core.state';  
21 -import {coerceBooleanProperty} from '@angular/cdk/coercion'; 17 +import { Component, forwardRef, Input, OnInit } from '@angular/core';
  18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormGroup,
  22 + NG_VALIDATORS,
  23 + NG_VALUE_ACCESSOR,
  24 + ValidationErrors,
  25 + Validator,
  26 + Validators
  27 +} from '@angular/forms';
  28 +import { Store } from '@ngrx/store';
  29 +import { AppState } from '@app/core/core.state';
  30 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 import { 31 import {
23 DeviceTransportConfiguration, 32 DeviceTransportConfiguration,
24 DeviceTransportType, 33 DeviceTransportType,
25 - SnmpDeviceTransportConfiguration 34 + SnmpAuthenticationProtocol,
  35 + SnmpAuthenticationProtocolTranslationMap,
  36 + SnmpDeviceProtocolVersion,
  37 + SnmpDeviceTransportConfiguration,
  38 + SnmpPrivacyProtocol,
  39 + SnmpPrivacyProtocolTranslationMap
26 } from '@shared/models/device.models'; 40 } from '@shared/models/device.models';
  41 +import { isDefinedAndNotNull } from '@core/utils';
27 42
28 @Component({ 43 @Component({
29 selector: 'tb-snmp-device-transport-configuration', 44 selector: 'tb-snmp-device-transport-configuration',
30 templateUrl: './snmp-device-transport-configuration.component.html', 45 templateUrl: './snmp-device-transport-configuration.component.html',
31 styleUrls: [], 46 styleUrls: [],
32 - providers: [{  
33 - provide: NG_VALUE_ACCESSOR,  
34 - useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent),  
35 - multi: true  
36 - }] 47 + providers: [
  48 + {
  49 + provide: NG_VALUE_ACCESSOR,
  50 + useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent),
  51 + multi: true
  52 + }, {
  53 + provide: NG_VALIDATORS,
  54 + useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent),
  55 + multi: true
  56 + }]
37 }) 57 })
38 -export class SnmpDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { 58 +export class SnmpDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit, Validator {
39 59
40 snmpDeviceTransportConfigurationFormGroup: FormGroup; 60 snmpDeviceTransportConfigurationFormGroup: FormGroup;
41 61
  62 + snmpDeviceProtocolVersions = Object.values(SnmpDeviceProtocolVersion);
  63 + snmpAuthenticationProtocols = Object.values(SnmpAuthenticationProtocol);
  64 + snmpAuthenticationProtocolTranslation = SnmpAuthenticationProtocolTranslationMap;
  65 + snmpPrivacyProtocols = Object.values(SnmpPrivacyProtocol);
  66 + snmpPrivacyProtocolTranslation = SnmpPrivacyProtocolTranslationMap;
  67 +
42 private requiredValue: boolean; 68 private requiredValue: boolean;
43 69
44 get required(): boolean { 70 get required(): boolean {
@@ -53,8 +79,7 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc @@ -53,8 +79,7 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
53 @Input() 79 @Input()
54 disabled: boolean; 80 disabled: boolean;
55 81
56 - private propagateChange = (v: any) => {  
57 - }; 82 + private propagateChange = (v: any) => { };
58 83
59 constructor(private store: Store<AppState>, 84 constructor(private store: Store<AppState>,
60 private fb: FormBuilder) { 85 private fb: FormBuilder) {
@@ -69,13 +94,33 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc @@ -69,13 +94,33 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
69 94
70 ngOnInit() { 95 ngOnInit() {
71 this.snmpDeviceTransportConfigurationFormGroup = this.fb.group({ 96 this.snmpDeviceTransportConfigurationFormGroup = this.fb.group({
72 - configuration: [null, Validators.required] 97 + host: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  98 + port: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]],
  99 + protocolVersion: [SnmpDeviceProtocolVersion.V2C, Validators.required],
  100 + community: ['public', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  101 + username: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  102 + securityName: ['public', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  103 + contextName: [null],
  104 + authenticationProtocol: [SnmpAuthenticationProtocol.SHA_512, Validators.required],
  105 + authenticationPassphrase: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  106 + privacyProtocol: [SnmpPrivacyProtocol.DES, Validators.required],
  107 + privacyPassphrase: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  108 + engineId: ['']
  109 + });
  110 + this.snmpDeviceTransportConfigurationFormGroup.get('protocolVersion').valueChanges.subscribe((protocol: SnmpDeviceProtocolVersion) => {
  111 + this.updateDisabledFormValue(protocol);
73 }); 112 });
74 this.snmpDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { 113 this.snmpDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => {
75 this.updateModel(); 114 this.updateModel();
76 }); 115 });
77 } 116 }
78 117
  118 + validate(): ValidationErrors | null {
  119 + return this.snmpDeviceTransportConfigurationFormGroup.valid ? null : {
  120 + snmpDeviceTransportConfiguration: false
  121 + };
  122 + }
  123 +
79 setDisabledState(isDisabled: boolean): void { 124 setDisabledState(isDisabled: boolean): void {
80 this.disabled = isDisabled; 125 this.disabled = isDisabled;
81 if (this.disabled) { 126 if (this.disabled) {
@@ -86,13 +131,46 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc @@ -86,13 +131,46 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
86 } 131 }
87 132
88 writeValue(value: SnmpDeviceTransportConfiguration | null): void { 133 writeValue(value: SnmpDeviceTransportConfiguration | null): void {
89 - this.snmpDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); 134 + if (isDefinedAndNotNull(value)) {
  135 + this.snmpDeviceTransportConfigurationFormGroup.patchValue(value, {emitEvent: false});
  136 + if (this.snmpDeviceTransportConfigurationFormGroup.enabled) {
  137 + this.updateDisabledFormValue(value.protocolVersion || SnmpDeviceProtocolVersion.V2C);
  138 + }
  139 + }
  140 + }
  141 +
  142 + isV3protocolVersion(): boolean {
  143 + return this.snmpDeviceTransportConfigurationFormGroup.get('protocolVersion').value === SnmpDeviceProtocolVersion.V3;
  144 + }
  145 +
  146 + private updateDisabledFormValue(protocol: SnmpDeviceProtocolVersion) {
  147 + if (protocol === SnmpDeviceProtocolVersion.V3) {
  148 + this.snmpDeviceTransportConfigurationFormGroup.get('community').disable({emitEvent: false});
  149 + this.snmpDeviceTransportConfigurationFormGroup.get('username').enable({emitEvent: false});
  150 + this.snmpDeviceTransportConfigurationFormGroup.get('securityName').enable({emitEvent: false});
  151 + this.snmpDeviceTransportConfigurationFormGroup.get('contextName').enable({emitEvent: false});
  152 + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationProtocol').enable({emitEvent: false});
  153 + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').enable({emitEvent: false});
  154 + this.snmpDeviceTransportConfigurationFormGroup.get('privacyProtocol').enable({emitEvent: false});
  155 + this.snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').enable({emitEvent: false});
  156 + this.snmpDeviceTransportConfigurationFormGroup.get('engineId').enable({emitEvent: false});
  157 + } else {
  158 + this.snmpDeviceTransportConfigurationFormGroup.get('community').enable({emitEvent: false});
  159 + this.snmpDeviceTransportConfigurationFormGroup.get('username').disable({emitEvent: false});
  160 + this.snmpDeviceTransportConfigurationFormGroup.get('securityName').disable({emitEvent: false});
  161 + this.snmpDeviceTransportConfigurationFormGroup.get('contextName').disable({emitEvent: false});
  162 + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationProtocol').disable({emitEvent: false});
  163 + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').disable({emitEvent: false});
  164 + this.snmpDeviceTransportConfigurationFormGroup.get('privacyProtocol').disable({emitEvent: false});
  165 + this.snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').disable({emitEvent: false});
  166 + this.snmpDeviceTransportConfigurationFormGroup.get('engineId').disable({emitEvent: false});
  167 + }
90 } 168 }
91 169
92 private updateModel() { 170 private updateModel() {
93 let configuration: DeviceTransportConfiguration = null; 171 let configuration: DeviceTransportConfiguration = null;
94 if (this.snmpDeviceTransportConfigurationFormGroup.valid) { 172 if (this.snmpDeviceTransportConfigurationFormGroup.valid) {
95 - configuration = this.snmpDeviceTransportConfigurationFormGroup.getRawValue().configuration; 173 + configuration = this.snmpDeviceTransportConfigurationFormGroup.value;
96 configuration.type = DeviceTransportType.SNMP; 174 configuration.type = DeviceTransportType.SNMP;
97 } 175 }
98 this.propagateChange(configuration); 176 this.propagateChange(configuration);
@@ -148,6 +148,22 @@ export enum ValueType { @@ -148,6 +148,22 @@ export enum ValueType {
148 JSON = 'JSON' 148 JSON = 'JSON'
149 } 149 }
150 150
  151 +export enum DataType {
  152 + STRING = 'STRING',
  153 + LONG = 'LONG',
  154 + BOOLEAN = 'BOOLEAN',
  155 + DOUBLE = 'DOUBLE',
  156 + JSON = 'JSON'
  157 +}
  158 +
  159 +export const DataTypeTranslationMap = new Map([
  160 + [DataType.STRING, 'value.string'],
  161 + [DataType.LONG, 'value.integer'],
  162 + [DataType.BOOLEAN, 'value.boolean'],
  163 + [DataType.DOUBLE, 'value.double'],
  164 + [DataType.JSON, 'value.json']
  165 +]);
  166 +
151 export const valueTypesMap = new Map<ValueType, ValueTypeData>( 167 export const valueTypesMap = new Map<ValueType, ValueTypeData>(
152 [ 168 [
153 [ 169 [
@@ -29,6 +29,7 @@ import * as _moment from 'moment'; @@ -29,6 +29,7 @@ import * as _moment from 'moment';
29 import { AbstractControl, ValidationErrors } from '@angular/forms'; 29 import { AbstractControl, ValidationErrors } from '@angular/forms';
30 import { OtaPackageId } from '@shared/models/id/ota-package-id'; 30 import { OtaPackageId } from '@shared/models/id/ota-package-id';
31 import { DashboardId } from '@shared/models/id/dashboard-id'; 31 import { DashboardId } from '@shared/models/id/dashboard-id';
  32 +import { DataType } from '@shared/models/constants';
32 33
33 export enum DeviceProfileType { 34 export enum DeviceProfileType {
34 DEFAULT = 'DEFAULT', 35 DEFAULT = 'DEFAULT',
@@ -257,7 +258,35 @@ export interface Lwm2mDeviceProfileTransportConfiguration { @@ -257,7 +258,35 @@ export interface Lwm2mDeviceProfileTransportConfiguration {
257 } 258 }
258 259
259 export interface SnmpDeviceProfileTransportConfiguration { 260 export interface SnmpDeviceProfileTransportConfiguration {
260 - [key: string]: any; 261 + timeoutMs?: number;
  262 + retries?: number;
  263 + communicationConfigs?: SnmpCommunicationConfig[];
  264 +}
  265 +
  266 +export enum SnmpSpecType {
  267 + TELEMETRY_QUERYING = 'TELEMETRY_QUERYING',
  268 + CLIENT_ATTRIBUTES_QUERYING = 'CLIENT_ATTRIBUTES_QUERYING',
  269 + SHARED_ATTRIBUTES_SETTING = 'SHARED_ATTRIBUTES_SETTING',
  270 + TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST'
  271 +}
  272 +
  273 +export const SnmpSpecTypeTranslationMap = new Map<SnmpSpecType, string>([
  274 + [SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry'],
  275 + [SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes'],
  276 + [SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes'],
  277 + [SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'RPC request']
  278 +]);
  279 +
  280 +export interface SnmpCommunicationConfig {
  281 + spec: SnmpSpecType;
  282 + mappings: SnmpMapping[];
  283 + queryingFrequencyMs?: number;
  284 +}
  285 +
  286 +export interface SnmpMapping {
  287 + oid: string;
  288 + key: string;
  289 + dataType: DataType;
261 } 290 }
262 291
263 export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration & 292 export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration &
@@ -332,7 +361,11 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT @@ -332,7 +361,11 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT
332 transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M}; 361 transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M};
333 break; 362 break;
334 case DeviceTransportType.SNMP: 363 case DeviceTransportType.SNMP:
335 - const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {}; 364 + const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {
  365 + timeoutMs: 500,
  366 + retries: 0,
  367 + communicationConfigs: null
  368 + };
336 transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP}; 369 transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP};
337 break; 370 break;
338 } 371 }
@@ -361,7 +394,12 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D @@ -361,7 +394,12 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D
361 transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M}; 394 transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M};
362 break; 395 break;
363 case DeviceTransportType.SNMP: 396 case DeviceTransportType.SNMP:
364 - const snmpTransportConfiguration: SnmpDeviceTransportConfiguration = {}; 397 + const snmpTransportConfiguration: SnmpDeviceTransportConfiguration = {
  398 + host: 'localhost',
  399 + port: 161,
  400 + protocolVersion: SnmpDeviceProtocolVersion.V2C,
  401 + community: 'public'
  402 + };
365 transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP}; 403 transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP};
366 break; 404 break;
367 } 405 }
@@ -539,8 +577,57 @@ export interface Lwm2mDeviceTransportConfiguration { @@ -539,8 +577,57 @@ export interface Lwm2mDeviceTransportConfiguration {
539 [key: string]: any; 577 [key: string]: any;
540 } 578 }
541 579
  580 +export enum SnmpDeviceProtocolVersion {
  581 + V1 = 'V1',
  582 + V2C = 'V2C',
  583 + V3 = 'V3'
  584 +}
  585 +
  586 +export enum SnmpAuthenticationProtocol {
  587 + SHA_1 = 'SHA_1',
  588 + SHA_224 = 'SHA_224',
  589 + SHA_256 = 'SHA_256',
  590 + SHA_384 = 'SHA_384',
  591 + SHA_512 = 'SHA_512',
  592 + MD5 = 'MD%'
  593 +}
  594 +
  595 +export const SnmpAuthenticationProtocolTranslationMap = new Map<SnmpAuthenticationProtocol, string>([
  596 + [SnmpAuthenticationProtocol.SHA_1, 'SHA-1'],
  597 + [SnmpAuthenticationProtocol.SHA_224, 'SHA-224'],
  598 + [SnmpAuthenticationProtocol.SHA_256, 'SHA-256'],
  599 + [SnmpAuthenticationProtocol.SHA_384, 'SHA-384'],
  600 + [SnmpAuthenticationProtocol.SHA_512, 'SHA-512'],
  601 + [SnmpAuthenticationProtocol.MD5, 'MD5']
  602 +]);
  603 +
  604 +export enum SnmpPrivacyProtocol {
  605 + DES = 'DES',
  606 + AES_128 = 'AES_128',
  607 + AES_192 = 'AES_192',
  608 + AES_256 = 'AES_256'
  609 +}
  610 +
  611 +export const SnmpPrivacyProtocolTranslationMap = new Map<SnmpPrivacyProtocol, string>([
  612 + [SnmpPrivacyProtocol.DES, 'DES'],
  613 + [SnmpPrivacyProtocol.AES_128, 'AES-128'],
  614 + [SnmpPrivacyProtocol.AES_192, 'AES-192'],
  615 + [SnmpPrivacyProtocol.AES_256, 'AES-256'],
  616 +]);
  617 +
542 export interface SnmpDeviceTransportConfiguration { 618 export interface SnmpDeviceTransportConfiguration {
543 - [key: string]: any; 619 + host?: string;
  620 + port?: number;
  621 + protocolVersion?: SnmpDeviceProtocolVersion;
  622 + community?: string;
  623 + username?: string;
  624 + securityName?: string;
  625 + contextName?: string;
  626 + authenticationProtocol?: SnmpAuthenticationProtocol;
  627 + authenticationPassphrase?: string;
  628 + privacyProtocol?: SnmpPrivacyProtocol;
  629 + privacyPassphrase?: string;
  630 + engineId?: string;
544 } 631 }
545 632
546 export type DeviceTransportConfigurations = DefaultDeviceTransportConfiguration & 633 export type DeviceTransportConfigurations = DefaultDeviceTransportConfiguration &
@@ -1297,6 +1297,54 @@ @@ -1297,6 +1297,54 @@
1297 "sw-update-recourse": "Software update CoAP recourse", 1297 "sw-update-recourse": "Software update CoAP recourse",
1298 "sw-update-recourse-required": "Software update CoAP recourse is required.", 1298 "sw-update-recourse-required": "Software update CoAP recourse is required.",
1299 "config-json-tab": "Json Config Profile Device" 1299 "config-json-tab": "Json Config Profile Device"
  1300 + },
  1301 + "snmp": {
  1302 + "add-communication-config": "Add communication config",
  1303 + "add-mapping": "Add mapping",
  1304 + "authentication-passphrase": "Authentication passphrase",
  1305 + "authentication-passphrase-required": "Authentication passphrase is required.",
  1306 + "authentication-protocol": "Authentication protocol",
  1307 + "authentication-protocol-required": "Authentication protocol is required.",
  1308 + "communication-configs": "Communication configs",
  1309 + "community": "Community string",
  1310 + "community-required": "Community string is required.",
  1311 + "context-name": "Context name",
  1312 + "data-key": "Data key",
  1313 + "data-key-required": "Data key is required.",
  1314 + "data-type": "Data type",
  1315 + "data-type-required": "Data type is required.",
  1316 + "engine-id": "Engine ID",
  1317 + "host": "Host",
  1318 + "host-required": "Host is required.",
  1319 + "oid": "OID",
  1320 + "oid-pattern": "Invalid OID format.",
  1321 + "oid-required": "OID is required.",
  1322 + "please-add-communication-config": "Please add communication config",
  1323 + "please-add-mapping-config": "Please add mapping config",
  1324 + "port": "Port",
  1325 + "port-format": "Invalid port format.",
  1326 + "port-required": "Port is required.",
  1327 + "privacy-passphrase": "Privacy passphrase",
  1328 + "privacy-passphrase-required": "Privacy passphrase is required.",
  1329 + "privacy-protocol": "Privacy protocol",
  1330 + "privacy-protocol-required": "Privacy protocol is required.",
  1331 + "protocol-version": "Protocol version",
  1332 + "protocol-version-required": "Protocol version is required.",
  1333 + "querying-frequency": "Querying frequency, ms",
  1334 + "querying-frequency-invalid-format": "Querying frequency must be a positive integer.",
  1335 + "querying-frequency-required": "Querying frequency is required.",
  1336 + "retries": "Retries",
  1337 + "retries-invalid-format": "Retries must be a positive integer.",
  1338 + "retries-required": "Retries is required.",
  1339 + "scope": "Scope",
  1340 + "scope-required": "Scope is required.",
  1341 + "security-name": "Security name",
  1342 + "security-name-required": "Security name is required.",
  1343 + "timeout-ms": "Timeout, ms",
  1344 + "timeout-ms-invalid-format": "Timeout must be a positive integer.",
  1345 + "timeout-ms-required": "Timeout is required.",
  1346 + "user-name": "User name",
  1347 + "user-name-required": "User name is required."
1300 } 1348 }
1301 }, 1349 },
1302 "dialog": { 1350 "dialog": {