Commit af7e22ad8ee15950a834655e8dfbde8cfea69596
Committed by
GitHub
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() { |
ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html
deleted
100644 → 0
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 | +} |
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts
0 → 100644
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 | } |
ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts
0 → 100644
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": { |