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 51 private String privacyPassphrase;
52 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 61 @Override
55 62 public DeviceTransportType getType() {
56 63 return DeviceTransportType.SNMP;
... ... @@ -76,7 +83,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur
76 83 isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName)
77 84 && contextName != null && authenticationProtocol != null
78 85 && StringUtils.isNotBlank(authenticationPassphrase)
79   - && privacyProtocol != null && privacyPassphrase != null && engineId != null;
  86 + && privacyProtocol != null && StringUtils.isNotBlank(privacyPassphrase) && engineId != null;
80 87 break;
81 88 }
82 89 }
... ...
... ... @@ -99,7 +99,6 @@ import { DeviceProfileDialogComponent } from '@home/components/profile/device-pr
99 99 import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component';
100 100 import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component';
101 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 102 import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component';
104 103 import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component';
105 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 142 import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
144 143 import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
145 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 147 @NgModule({
148 148 declarations:
... ... @@ -228,7 +228,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
228 228 DefaultDeviceProfileTransportConfigurationComponent,
229 229 MqttDeviceProfileTransportConfigurationComponent,
230 230 CoapDeviceProfileTransportConfigurationComponent,
231   - SnmpDeviceProfileTransportConfigurationComponent,
232 231 DeviceProfileTransportConfigurationComponent,
233 232 CreateAlarmRulesComponent,
234 233 AlarmRuleComponent,
... ... @@ -272,6 +271,7 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
272 271 SharedModule,
273 272 SharedHomeComponentsModule,
274 273 Lwm2mProfileComponentsModule,
  274 + SnmpDeviceProfileTransportModule,
275 275 StatesControllerModule
276 276 ],
277 277 exports: [
... ... @@ -339,7 +339,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain
339 339 DefaultDeviceProfileTransportConfigurationComponent,
340 340 MqttDeviceProfileTransportConfigurationComponent,
341 341 CoapDeviceProfileTransportConfigurationComponent,
342   - SnmpDeviceProfileTransportConfigurationComponent,
343 342 DeviceProfileTransportConfigurationComponent,
344 343 CreateAlarmRulesComponent,
345 344 AlarmRuleComponent,
... ...
... ... @@ -89,7 +89,9 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu
89 89 if (configuration) {
90 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 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 15 ///
16 16
17 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 28 import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 29 import {
23 30 DeviceProfileTransportConfiguration,
... ... @@ -40,19 +47,24 @@ export interface OidMappingConfiguration {
40 47 selector: 'tb-snmp-device-profile-transport-configuration',
41 48 templateUrl: './snmp-device-profile-transport-configuration.component.html',
42 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 64 snmpDeviceProfileTransportConfigurationFormGroup: FormGroup;
52 65
53 66 private destroy$ = new Subject();
54 67 private requiredValue: boolean;
55   - private configuration = [];
56 68
57 69 get required(): boolean {
58 70 return this.requiredValue;
... ... @@ -69,12 +81,14 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
69 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 87 ngOnInit(): void {
76 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 93 this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe(
80 94 takeUntil(this.destroy$)
... ... @@ -95,18 +109,33 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
95 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 121 writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void {
99 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 127 private updateModel() {
105 128 let configuration: DeviceProfileTransportConfiguration = null;
106 129 if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) {
107   - configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
  130 + configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue();
108 131 configuration.type = DeviceTransportType.SNMP;
109 132 }
110 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 15 ///
16 16
17 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 28 import { Store } from '@ngrx/store';
20 29 import { AppState } from '@app/core/core.state';
21 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
... ... @@ -29,13 +38,20 @@ import {
29 38 selector: 'tb-device-data',
30 39 templateUrl: './device-data.component.html',
31 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 56 deviceDataFormGroup: FormGroup;
41 57
... ... @@ -97,6 +113,12 @@ export class DeviceDataComponent implements ControlValueAccessor, OnInit {
97 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 122 private updateModel() {
101 123 let deviceData: DeviceData = null;
102 124 if (this.deviceDataFormGroup.valid) {
... ...
... ... @@ -15,27 +15,39 @@
15 15 ///
16 16
17 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 28 import { Store } from '@ngrx/store';
20 29 import { AppState } from '@app/core/core.state';
21 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 32 import { deepClone } from '@core/utils';
27 33
28 34 @Component({
29 35 selector: 'tb-device-transport-configuration',
30 36 templateUrl: './device-transport-configuration.component.html',
31 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 52 deviceTransportType = DeviceTransportType;
41 53
... ... @@ -92,7 +104,15 @@ export class DeviceTransportConfigurationComponent implements ControlValueAccess
92 104 if (configuration) {
93 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 118 private updateModel() {
... ...
... ... @@ -15,10 +15,119 @@
15 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 133 </form>
... ...
... ... @@ -14,31 +14,57 @@
14 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 31 import {
23 32 DeviceTransportConfiguration,
24 33 DeviceTransportType,
25   - SnmpDeviceTransportConfiguration
  34 + SnmpAuthenticationProtocol,
  35 + SnmpAuthenticationProtocolTranslationMap,
  36 + SnmpDeviceProtocolVersion,
  37 + SnmpDeviceTransportConfiguration,
  38 + SnmpPrivacyProtocol,
  39 + SnmpPrivacyProtocolTranslationMap
26 40 } from '@shared/models/device.models';
  41 +import { isDefinedAndNotNull } from '@core/utils';
27 42
28 43 @Component({
29 44 selector: 'tb-snmp-device-transport-configuration',
30 45 templateUrl: './snmp-device-transport-configuration.component.html',
31 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 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 68 private requiredValue: boolean;
43 69
44 70 get required(): boolean {
... ... @@ -53,8 +79,7 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
53 79 @Input()
54 80 disabled: boolean;
55 81
56   - private propagateChange = (v: any) => {
57   - };
  82 + private propagateChange = (v: any) => { };
58 83
59 84 constructor(private store: Store<AppState>,
60 85 private fb: FormBuilder) {
... ... @@ -69,13 +94,33 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
69 94
70 95 ngOnInit() {
71 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 113 this.snmpDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => {
75 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 124 setDisabledState(isDisabled: boolean): void {
80 125 this.disabled = isDisabled;
81 126 if (this.disabled) {
... ... @@ -86,13 +131,46 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
86 131 }
87 132
88 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 170 private updateModel() {
93 171 let configuration: DeviceTransportConfiguration = null;
94 172 if (this.snmpDeviceTransportConfigurationFormGroup.valid) {
95   - configuration = this.snmpDeviceTransportConfigurationFormGroup.getRawValue().configuration;
  173 + configuration = this.snmpDeviceTransportConfigurationFormGroup.value;
96 174 configuration.type = DeviceTransportType.SNMP;
97 175 }
98 176 this.propagateChange(configuration);
... ...
... ... @@ -148,6 +148,22 @@ export enum ValueType {
148 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 167 export const valueTypesMap = new Map<ValueType, ValueTypeData>(
152 168 [
153 169 [
... ...
... ... @@ -29,6 +29,7 @@ import * as _moment from 'moment';
29 29 import { AbstractControl, ValidationErrors } from '@angular/forms';
30 30 import { OtaPackageId } from '@shared/models/id/ota-package-id';
31 31 import { DashboardId } from '@shared/models/id/dashboard-id';
  32 +import { DataType } from '@shared/models/constants';
32 33
33 34 export enum DeviceProfileType {
34 35 DEFAULT = 'DEFAULT',
... ... @@ -257,7 +258,35 @@ export interface Lwm2mDeviceProfileTransportConfiguration {
257 258 }
258 259
259 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 292 export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration &
... ... @@ -332,7 +361,11 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT
332 361 transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M};
333 362 break;
334 363 case DeviceTransportType.SNMP:
335   - const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {};
  364 + const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {
  365 + timeoutMs: 500,
  366 + retries: 0,
  367 + communicationConfigs: null
  368 + };
336 369 transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP};
337 370 break;
338 371 }
... ... @@ -361,7 +394,12 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D
361 394 transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M};
362 395 break;
363 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 403 transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP};
366 404 break;
367 405 }
... ... @@ -539,8 +577,57 @@ export interface Lwm2mDeviceTransportConfiguration {
539 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 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 633 export type DeviceTransportConfigurations = DefaultDeviceTransportConfiguration &
... ...
... ... @@ -1297,6 +1297,54 @@
1297 1297 "sw-update-recourse": "Software update CoAP recourse",
1298 1298 "sw-update-recourse-required": "Software update CoAP recourse is required.",
1299 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 1350 "dialog": {
... ...