Commit 688a272035747cf0a411157eef82d67b906a59ee
Committed by
GitHub
Merge pull request #4780 from vvlladd28/refactoring/lwm2m/device-profile
UI: Refactoring LwM2M device profile
Showing
18 changed files
with
646 additions
and
539 deletions
... | ... | @@ -21,18 +21,23 @@ import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; |
21 | 21 | import { Observable, of, throwError } from 'rxjs'; |
22 | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | 23 | import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; |
24 | -import { isDefinedAndNotNull, isEmptyStr } from '@core/utils'; | |
25 | -import { ObjectLwM2M, ServerSecurityConfig } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; | |
24 | +import { deepClone, isDefinedAndNotNull, isEmptyStr } from '@core/utils'; | |
25 | +import { | |
26 | + ObjectLwM2M, | |
27 | + securityConfigMode, | |
28 | + ServerSecurityConfig, | |
29 | + ServerSecurityConfigInfo | |
30 | +} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; | |
26 | 31 | import { SortOrder } from '@shared/models/page/sort-order'; |
27 | 32 | import { OtaPackageService } from '@core/http/ota-package.service'; |
28 | -import { mergeMap, tap } from 'rxjs/operators'; | |
33 | +import { map, mergeMap, tap } from 'rxjs/operators'; | |
29 | 34 | |
30 | 35 | @Injectable({ |
31 | 36 | providedIn: 'root' |
32 | 37 | }) |
33 | 38 | export class DeviceProfileService { |
34 | 39 | |
35 | - private lwm2mBootstrapSecurityInfoInMemoryCache = new Map<boolean, ServerSecurityConfig>(); | |
40 | + private lwm2mBootstrapSecurityInfoInMemoryCache = new Map<boolean, ServerSecurityConfigInfo>(); | |
36 | 41 | |
37 | 42 | constructor( |
38 | 43 | private http: HttpClient, |
... | ... | @@ -60,12 +65,12 @@ export class DeviceProfileService { |
60 | 65 | return this.http.get<Array<ObjectLwM2M>>(url, defaultHttpOptionsFromConfig(config)); |
61 | 66 | } |
62 | 67 | |
63 | - public getLwm2mBootstrapSecurityInfo(isBootstrapServer: boolean, config?: RequestConfig): Observable<ServerSecurityConfig> { | |
68 | + public getLwm2mBootstrapSecurityInfo(isBootstrapServer: boolean, config?: RequestConfig): Observable<ServerSecurityConfigInfo> { | |
64 | 69 | const securityConfig = this.lwm2mBootstrapSecurityInfoInMemoryCache.get(isBootstrapServer); |
65 | 70 | if (securityConfig) { |
66 | 71 | return of(securityConfig); |
67 | 72 | } else { |
68 | - return this.http.get<ServerSecurityConfig>( | |
73 | + return this.http.get<ServerSecurityConfigInfo>( | |
69 | 74 | `/api/lwm2m/deviceProfile/bootstrap/${isBootstrapServer}`, |
70 | 75 | defaultHttpOptionsFromConfig(config) |
71 | 76 | ).pipe( |
... | ... | @@ -74,6 +79,31 @@ export class DeviceProfileService { |
74 | 79 | } |
75 | 80 | } |
76 | 81 | |
82 | + public getLwm2mBootstrapSecurityInfoBySecurityType(isBootstrapServer: boolean, securityMode = securityConfigMode.NO_SEC, | |
83 | + config?: RequestConfig): Observable<ServerSecurityConfig> { | |
84 | + return this.getLwm2mBootstrapSecurityInfo(isBootstrapServer, config).pipe( | |
85 | + map(securityConfig => { | |
86 | + const serverSecurityConfigInfo = deepClone(securityConfig); | |
87 | + switch (securityMode) { | |
88 | + case securityConfigMode.PSK: | |
89 | + serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; | |
90 | + serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; | |
91 | + serverSecurityConfigInfo.serverPublicKey = ''; | |
92 | + break; | |
93 | + case securityConfigMode.RPK: | |
94 | + case securityConfigMode.X509: | |
95 | + serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; | |
96 | + serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; | |
97 | + break; | |
98 | + case securityConfigMode.NO_SEC: | |
99 | + serverSecurityConfigInfo.serverPublicKey = ''; | |
100 | + break; | |
101 | + } | |
102 | + return serverSecurityConfigInfo; | |
103 | + }) | |
104 | + ); | |
105 | + } | |
106 | + | |
77 | 107 | public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable<Array<ObjectLwM2M>> { |
78 | 108 | return this.http.get<Array<ObjectLwM2M>>( |
79 | 109 | `/api/resource/lwm2m/page${pageLink.toQuery()}`, | ... | ... |
... | ... | @@ -89,9 +89,10 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu |
89 | 89 | if (configuration) { |
90 | 90 | delete configuration.type; |
91 | 91 | } |
92 | + this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); | |
92 | 93 | setTimeout(() => { |
93 | - this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); | |
94 | - }); | |
94 | + this.deviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); | |
95 | + }, 0); | |
95 | 96 | } |
96 | 97 | |
97 | 98 | private updateModel() { | ... | ... |
... | ... | @@ -15,12 +15,11 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<form [formGroup]="attributeLwm2mDialogFormGroup" (ngSubmit)="save()" style="width: 500px;"> | |
18 | +<form [formGroup]="attributeFormGroup" (ngSubmit)="save()" style="min-width: 500px;"> | |
19 | 19 | <mat-toolbar color="primary"> |
20 | - <div fxFlex fxLayout="column" fxLayoutAlign="start"> | |
21 | - <h2>{{ (readonly ? 'device-profile.lwm2m.attribute-lwm2m-toolbar-view' : | |
22 | - 'device-profile.lwm2m.attribute-lwm2m-toolbar-edit') | translate }}</h2> | |
23 | - </div> | |
20 | + <h2> | |
21 | + {{ (readonly ? 'device-profile.lwm2m.view-attributes' : 'device-profile.lwm2m.edit-attributes') | translate : {name: name} }} | |
22 | + </h2> | |
24 | 23 | <span fxFlex></span> |
25 | 24 | <button mat-icon-button |
26 | 25 | (click)="cancel()" |
... | ... | @@ -32,8 +31,8 @@ |
32 | 31 | </mat-progress-bar> |
33 | 32 | <div mat-dialog-content> |
34 | 33 | <tb-lwm2m-attributes-key-list |
35 | - formControlName="keyFilters" | |
36 | - titleText="{{data.destName}}"> | |
34 | + [isResource]="isResource" | |
35 | + formControlName="attributes"> | |
37 | 36 | </tb-lwm2m-attributes-key-list> |
38 | 37 | </div> |
39 | 38 | <div mat-dialog-actions fxLayoutAlign="end center"> |
... | ... | @@ -46,7 +45,7 @@ |
46 | 45 | <button mat-raised-button color="primary" |
47 | 46 | *ngIf="!readonly" |
48 | 47 | type="submit" |
49 | - [disabled]="(isLoading$ | async) || attributeLwm2mDialogFormGroup.invalid || !attributeLwm2mDialogFormGroup.dirty"> | |
48 | + [disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty"> | |
50 | 49 | {{ 'action.save' | translate }} |
51 | 50 | </button> |
52 | 51 | </div> | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; | |
17 | +import { Component, Inject, SkipSelf } from '@angular/core'; | |
18 | 18 | import { ErrorStateMatcher } from '@angular/material/core'; |
19 | 19 | import { DialogComponent } from '@shared/components/dialog.component'; |
20 | 20 | import { Store } from '@ngrx/store'; |
... | ... | @@ -22,12 +22,13 @@ import { AppState } from '@core/core.state'; |
22 | 22 | import { Router } from '@angular/router'; |
23 | 23 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
24 | 24 | import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; |
25 | -import { JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; | |
25 | +import { AttributesNameValueMap } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; | |
26 | 26 | |
27 | 27 | export interface Lwm2mAttributesDialogData { |
28 | 28 | readonly: boolean; |
29 | - attributeLwm2m: JsonObject; | |
30 | - destName: string; | |
29 | + attributes: AttributesNameValueMap; | |
30 | + modelName: string; | |
31 | + isResource: boolean; | |
31 | 32 | } |
32 | 33 | |
33 | 34 | @Component({ |
... | ... | @@ -36,42 +37,37 @@ export interface Lwm2mAttributesDialogData { |
36 | 37 | styleUrls: ['./lwm2m-attributes.component.scss'], |
37 | 38 | providers: [{provide: ErrorStateMatcher, useExisting: Lwm2mAttributesDialogComponent}], |
38 | 39 | }) |
39 | -export class Lwm2mAttributesDialogComponent extends DialogComponent<Lwm2mAttributesDialogComponent, object> | |
40 | - implements OnInit, ErrorStateMatcher { | |
40 | +export class Lwm2mAttributesDialogComponent | |
41 | + extends DialogComponent<Lwm2mAttributesDialogComponent, AttributesNameValueMap> implements ErrorStateMatcher { | |
41 | 42 | |
42 | - readonly = this.data.readonly; | |
43 | - | |
44 | - attributeLwm2m = this.data.attributeLwm2m; | |
45 | - | |
46 | - submitted = false; | |
43 | + readonly: boolean; | |
44 | + name: string; | |
45 | + isResource: boolean; | |
47 | 46 | |
48 | - dirtyValue = false; | |
47 | + private submitted = false; | |
49 | 48 | |
50 | - attributeLwm2mDialogFormGroup: FormGroup; | |
49 | + attributeFormGroup: FormGroup; | |
51 | 50 | |
52 | 51 | constructor(protected store: Store<AppState>, |
53 | 52 | protected router: Router, |
54 | - @Inject(MAT_DIALOG_DATA) public data: Lwm2mAttributesDialogData, | |
53 | + @Inject(MAT_DIALOG_DATA) private data: Lwm2mAttributesDialogData, | |
55 | 54 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
56 | - public dialogRef: MatDialogRef<Lwm2mAttributesDialogComponent, object>, | |
55 | + public dialogRef: MatDialogRef<Lwm2mAttributesDialogComponent, AttributesNameValueMap>, | |
57 | 56 | private fb: FormBuilder) { |
58 | 57 | super(store, router, dialogRef); |
59 | 58 | |
60 | - this.attributeLwm2mDialogFormGroup = this.fb.group({ | |
61 | - keyFilters: [{}, []] | |
62 | - }); | |
63 | - this.attributeLwm2mDialogFormGroup.patchValue({keyFilters: this.attributeLwm2m}); | |
64 | - this.attributeLwm2mDialogFormGroup.get('keyFilters').valueChanges.subscribe((attributes) => { | |
65 | - this.attributeLwm2m = attributes; | |
59 | + this.readonly = data.readonly; | |
60 | + this.name = data.modelName; | |
61 | + this.isResource = data.isResource; | |
62 | + | |
63 | + this.attributeFormGroup = this.fb.group({ | |
64 | + attributes: [data.attributes] | |
66 | 65 | }); |
67 | 66 | if (this.readonly) { |
68 | - this.attributeLwm2mDialogFormGroup.disable({emitEvent: false}); | |
67 | + this.attributeFormGroup.disable({emitEvent: false}); | |
69 | 68 | } |
70 | 69 | } |
71 | 70 | |
72 | - ngOnInit(): void { | |
73 | - } | |
74 | - | |
75 | 71 | isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
76 | 72 | const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
77 | 73 | const customErrorState = !!(control && control.invalid && this.submitted); |
... | ... | @@ -80,7 +76,7 @@ export class Lwm2mAttributesDialogComponent extends DialogComponent<Lwm2mAttribu |
80 | 76 | |
81 | 77 | save(): void { |
82 | 78 | this.submitted = true; |
83 | - this.dialogRef.close(this.attributeLwm2m); | |
79 | + this.dialogRef.close(this.attributeFormGroup.get('attributes').value); | |
84 | 80 | } |
85 | 81 | |
86 | 82 | cancel(): void { | ... | ... |
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.html
... | ... | @@ -15,69 +15,60 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<section fxLayout="column" class="tb-kv-map" [formGroup]="kvListFormGroup"> | |
19 | - <div> | |
20 | - <mat-label translate class="tb-title no-padding">device-profile.lwm2m.attribute-lwm2m-destination</mat-label> | |
21 | - <mat-label class="tb-editor-area-title-panel">{{ titleText }}</mat-label> | |
22 | - </div> | |
18 | +<section fxLayout="column" class="name-value-map" [formGroup]="attributesValueFormGroup"> | |
23 | 19 | <div fxLayout="row" fxLayoutGap="8px" style="max-height: 40px; margin-top: 8px;"> |
24 | - <mat-label fxFlex class="tb-title no-padding" translate>device-profile.lwm2m.attribute-lwm2m-name</mat-label> | |
25 | - <mat-label fxFlex class="tb-title no-padding" translate>device-profile.lwm2m.attribute-lwm2m-value</mat-label> | |
26 | - <div [fxShow]="!disabled" style="width: 40px;"></div> | |
20 | + <label fxFlex="40" class="tb-title no-padding" style="min-width: 230px;" translate>device-profile.lwm2m.attribute-name</label> | |
21 | + <label fxFlex="60" class="tb-title no-padding" translate>device-profile.lwm2m.attribute-value</label> | |
22 | + <span [fxShow]="!disabled" style="width: 40px;"></span> | |
27 | 23 | </div> |
28 | - <div fxLayout="column" formArrayName="keyVals" | |
29 | - *ngFor="let keyValControl of keyValsFormArray().controls; let $index = index"> | |
30 | - <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | |
31 | - <mat-form-field class="mat-block" style="max-heights: 400px"> | |
24 | + <div fxLayout="column" class="map-list" | |
25 | + *ngFor="let nameValueControl of attributesValueFormArray().controls; let $index = index" | |
26 | + [formGroup]="nameValueControl"> | |
27 | + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | |
28 | + <mat-form-field fxFlex="40" floatLabel="always" hideRequiredMarker> | |
32 | 29 | <mat-label></mat-label> |
33 | - <mat-select [formControl]="keyValControl.get('key')"> | |
34 | - <mat-option *ngFor="let attributeLwm2m of attrKeys" | |
35 | - [value]="attributeLwm2m"> | |
36 | - {{ attributeLwm2mMap.get(attrKey[attributeLwm2m]) }} | |
30 | + <mat-select formControlName="name" required> | |
31 | + <mat-option *ngFor="let attributeName of attributeNames" [value]="attributeName" | |
32 | + [disabled]="isDisabledAttributeName(attributeName, $index)"> | |
33 | + {{ attributeNameTranslationMap.get(attributeName) | translate }} | |
37 | 34 | </mat-option> |
38 | 35 | </mat-select> |
36 | + <mat-error *ngIf="nameValueControl.get('name').hasError('required')"> | |
37 | + {{ 'device-profile.lwm2m.attribute-name-required' | translate }} | |
38 | + </mat-error> | |
39 | 39 | </mat-form-field> |
40 | - <mat-form-field fxFlex floatLabel="always" hideRequiredMarker class="mat-block" | |
41 | - style="max-height: 40px;"> | |
40 | + <mat-form-field fxFlex="60" floatLabel="always" hideRequiredMarker> | |
42 | 41 | <mat-label></mat-label> |
43 | - <input [formControl]="keyValControl.get('value')" matInput | |
44 | - placeholder="{{ ('key-val.value') | translate }}"/> | |
42 | + <input formControlName="value" matInput required type="number" | |
43 | + placeholder="{{ 'key-val.value' | translate }}"> | |
44 | + <mat-error fxLayout="row" *ngIf="nameValueControl.get('value').hasError('required')"> | |
45 | + {{ 'device-profile.lwm2m.attribute-value-required' | translate }} | |
46 | + </mat-error> | |
47 | + <mat-error fxLayout="row" *ngIf="nameValueControl.get('value').hasError('min') || | |
48 | + nameValueControl.get('value').hasError('pattern')"> | |
49 | + {{ 'device-profile.lwm2m.attribute-value-pattern' | translate }} | |
50 | + </mat-error> | |
45 | 51 | </mat-form-field> |
46 | - <button mat-button mat-icon-button color="primary" | |
47 | - [fxShow]="!disabled" | |
52 | + <button *ngIf="!disabled" | |
53 | + mat-icon-button color="primary" style="min-width: 40px;" | |
48 | 54 | type="button" |
49 | 55 | (click)="removeKeyVal($index)" |
50 | - [disabled]="isLoading$ | async" | |
51 | - matTooltip="{{ 'device-profile.lwm2m.attribute-lwm2m-remove-tip' | translate }}" | |
56 | + matTooltip="{{ 'device-profile.lwm2m.remove-attribute' | translate }}" | |
52 | 57 | matTooltipPosition="above"> |
53 | 58 | <mat-icon>close</mat-icon> |
54 | 59 | </button> |
55 | 60 | </div> |
56 | - <mat-error *ngIf="keyValControl.get('key').hasError('required')" style="font-size: smaller"> | |
57 | - {{ 'device-profile.lwm2m.key-name' | translate }} | |
58 | - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> | |
59 | - </mat-error> | |
60 | - <mat-error fxLayout="row" *ngIf="keyValControl.get('key').hasError('validAttributeKey')" | |
61 | - style="font-size: smaller"> | |
62 | - {{ 'device-profile.lwm2m.valid-attribute-lwm2m-key' | translate: {attrEnums: attrKeys} }} | |
63 | - </mat-error> | |
64 | - <mat-error fxLayout="row" *ngIf="keyValControl.get('value').hasError('validAttributeValue')" | |
65 | - style="font-size: smaller"> | |
66 | - {{ 'device-profile.lwm2m.valid-attribute-lwm2m-value' | translate: {attrEnums: attrKeys} }} | |
67 | - </mat-error> | |
68 | 61 | </div> |
69 | - <span [fxShow]="!keyValsFormArray().length" | |
70 | - fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" | |
71 | - class="no-data-found" translate>{{noDataText ? noDataText : 'device-profile.lwm2m.no-data'}}</span> | |
72 | - <div style="margin-top: 8px;"> | |
73 | - <button mat-button mat-raised-button color="primary" | |
74 | - [fxShow]="!disabled" | |
62 | + <div [fxShow]="!attributesValueFormArray().length" | |
63 | + fxLayoutAlign="center center" | |
64 | + class="map-list" translate>device-profile.lwm2m.no-attributes-set</div> | |
65 | + <div style="margin-top: 9px;" *ngIf="!disabled && isAddEnabled"> | |
66 | + <button mat-stroked-button color="primary" | |
75 | 67 | [disabled]="isLoading$ | async" |
76 | - (click)="addKeyVal()" | |
77 | 68 | type="button" |
78 | - matTooltip="{{ 'device-profile.lwm2m.attribute-lwm2m-add-tip' | translate }}" | |
79 | - matTooltipPosition="above"> | |
80 | - {{ 'action.add' | translate }} | |
69 | + (click)="addKeyVal()"> | |
70 | + <mat-icon class="button-icon">add_circle_outline</mat-icon> | |
71 | + {{ 'device-profile.lwm2m.add-attribute' | translate }} | |
81 | 72 | </button> |
82 | 73 | </div> |
83 | 74 | </section> | ... | ... |
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.scss
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 | +:host { | |
17 | + .name-value-map { | |
18 | + span.no-data-found { | |
19 | + position: relative; | |
20 | + display: flex; | |
21 | + height: 40px; | |
22 | + | |
23 | + &.disabled { | |
24 | + color: rgba(0, 0, 0, .38); | |
25 | + } | |
26 | + } | |
27 | + | |
28 | + .map-list{ | |
29 | + height: 45px; | |
30 | + } | |
31 | + } | |
32 | +} | |
33 | + | |
34 | +:host ::ng-deep { | |
35 | + .mat-form-field-wrapper { | |
36 | + padding-bottom: 0; | |
37 | + } | |
38 | + .mat-form-field-infix { | |
39 | + border-top: 0; | |
40 | + } | |
41 | + .mat-form-field-underline { | |
42 | + bottom: 0; | |
43 | + } | |
44 | + | |
45 | + .button-icon{ | |
46 | + font-size: 20px; | |
47 | + width: 20px; | |
48 | + height: 20px; | |
49 | + } | |
50 | + | |
51 | + .map-list { | |
52 | + mat-form-field { | |
53 | + .mat-form-field-wrapper { | |
54 | + padding-bottom: 0; | |
55 | + .mat-form-field-infix { | |
56 | + border-top-width: 0.2em; | |
57 | + width: auto; | |
58 | + min-width: auto; | |
59 | + } | |
60 | + .mat-form-field-underline { | |
61 | + bottom: 0; | |
62 | + } | |
63 | + .mat-form-field-subscript-wrapper{ | |
64 | + margin-top: 1.8em; | |
65 | + } | |
66 | + } | |
67 | + } | |
68 | + } | |
69 | +} | ... | ... |
... | ... | @@ -14,35 +14,36 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, forwardRef, Input, OnInit } from '@angular/core'; | |
17 | +import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; | |
18 | 18 | import { |
19 | 19 | AbstractControl, |
20 | 20 | ControlValueAccessor, |
21 | 21 | FormArray, |
22 | 22 | FormBuilder, |
23 | - FormControl, | |
24 | 23 | FormGroup, |
25 | 24 | NG_VALIDATORS, |
26 | 25 | NG_VALUE_ACCESSOR, |
27 | 26 | Validator, |
28 | 27 | Validators |
29 | 28 | } from '@angular/forms'; |
30 | -import { Subscription } from 'rxjs'; | |
29 | +import { Subject, Subscription } from 'rxjs'; | |
31 | 30 | import { |
32 | - ATTRIBUTE_KEYS, | |
33 | - ATTRIBUTE_LWM2M_ENUM, | |
34 | - ATTRIBUTE_LWM2M_MAP | |
31 | + AttributeName, | |
32 | + AttributeNameTranslationMap, | |
33 | + AttributesNameValue, | |
34 | + AttributesNameValueMap, | |
35 | + valueValidatorByAttributeName | |
35 | 36 | } from './lwm2m-profile-config.models'; |
36 | -import { isDefinedAndNotNull, isEmpty, isEmptyStr, isUndefinedOrNull } from '@core/utils'; | |
37 | +import { isUndefinedOrNull } from '@core/utils'; | |
37 | 38 | import { Store } from '@ngrx/store'; |
38 | 39 | import { AppState } from '@core/core.state'; |
39 | 40 | import { PageComponent } from '@shared/components/page.component'; |
40 | - | |
41 | +import { takeUntil } from 'rxjs/operators'; | |
41 | 42 | |
42 | 43 | @Component({ |
43 | 44 | selector: 'tb-lwm2m-attributes-key-list', |
44 | 45 | templateUrl: './lwm2m-attributes-key-list.component.html', |
45 | - styleUrls: ['./lwm2m-attributes.component.scss'], | |
46 | + styleUrls: ['./lwm2m-attributes-key-list.component.scss'], | |
46 | 47 | providers: [ |
47 | 48 | { |
48 | 49 | provide: NG_VALUE_ACCESSOR, |
... | ... | @@ -56,39 +57,46 @@ import { PageComponent } from '@shared/components/page.component'; |
56 | 57 | } |
57 | 58 | ] |
58 | 59 | }) |
59 | -export class Lwm2mAttributesKeyListComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { | |
60 | - | |
61 | - attrKeys = ATTRIBUTE_KEYS; | |
62 | - | |
63 | - attrKey = ATTRIBUTE_LWM2M_ENUM; | |
60 | +export class Lwm2mAttributesKeyListComponent extends PageComponent implements ControlValueAccessor, OnDestroy, OnDestroy, Validator { | |
64 | 61 | |
65 | - attributeLwm2mMap = ATTRIBUTE_LWM2M_MAP; | |
62 | + attributeNames; | |
63 | + attributeNameTranslationMap = AttributeNameTranslationMap; | |
66 | 64 | |
67 | 65 | @Input() disabled: boolean; |
68 | 66 | |
69 | - @Input() titleText: string; | |
67 | + @Input() | |
68 | + isResource = false; | |
70 | 69 | |
71 | - @Input() noDataText: string; | |
72 | - | |
73 | - kvListFormGroup: FormGroup; | |
70 | + attributesValueFormGroup: FormGroup; | |
74 | 71 | |
75 | 72 | private propagateChange = null; |
76 | - | |
77 | - private valueChangeSubscription: Subscription = null; | |
73 | + private valueChange$: Subscription = null; | |
74 | + private destroy$ = new Subject(); | |
75 | + private usedAttributesName: AttributeName[] = []; | |
78 | 76 | |
79 | 77 | constructor(protected store: Store<AppState>, |
80 | 78 | private fb: FormBuilder) { |
81 | 79 | super(store); |
80 | + this.attributesValueFormGroup = this.fb.group({ | |
81 | + attributesValue: this.fb.array([]) | |
82 | + }); | |
82 | 83 | } |
83 | 84 | |
84 | - ngOnInit(): void { | |
85 | - this.kvListFormGroup = this.fb.group({}); | |
86 | - this.kvListFormGroup.addControl('keyVals', | |
87 | - this.fb.array([])); | |
85 | + ngOnInit() { | |
86 | + if (this.isResource) { | |
87 | + this.attributeNames = Object.values(AttributeName); | |
88 | + } else { | |
89 | + this.attributeNames = Object.values(AttributeName) | |
90 | + .filter(item => ![AttributeName.lt, AttributeName.gt, AttributeName.st].includes(item)); | |
91 | + } | |
88 | 92 | } |
89 | 93 | |
90 | - keyValsFormArray(): FormArray { | |
91 | - return this.kvListFormGroup.get('keyVals') as FormArray; | |
94 | + ngOnDestroy() { | |
95 | + if (this.valueChange$) { | |
96 | + this.valueChange$.unsubscribe(); | |
97 | + } | |
98 | + this.destroy$.next(); | |
99 | + this.destroy$.complete(); | |
92 | 100 | } |
93 | 101 | |
94 | 102 | registerOnChange(fn: any): void { |
... | ... | @@ -101,127 +109,111 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co |
101 | 109 | setDisabledState(isDisabled: boolean): void { |
102 | 110 | this.disabled = isDisabled; |
103 | 111 | if (this.disabled) { |
104 | - this.kvListFormGroup.disable({emitEvent: false}); | |
112 | + this.attributesValueFormGroup.disable({emitEvent: false}); | |
105 | 113 | } else { |
106 | - this.kvListFormGroup.enable({emitEvent: false}); | |
114 | + this.attributesValueFormGroup.enable({emitEvent: false}); | |
107 | 115 | } |
108 | 116 | } |
109 | 117 | |
110 | - writeValue(keyValMap: { [key: string]: string }): void { | |
111 | - if (this.valueChangeSubscription) { | |
112 | - this.valueChangeSubscription.unsubscribe(); | |
118 | + writeValue(keyValMap: AttributesNameValueMap): void { | |
119 | + if (this.valueChange$) { | |
120 | + this.valueChange$.unsubscribe(); | |
113 | 121 | } |
114 | - const keyValsControls: Array<AbstractControl> = []; | |
122 | + const attributesValueControls: Array<AbstractControl> = []; | |
115 | 123 | if (keyValMap) { |
116 | - for (const property of Object.keys(keyValMap)) { | |
117 | - if (Object.prototype.hasOwnProperty.call(keyValMap, property)) { | |
118 | - keyValsControls.push(this.fb.group({ | |
119 | - key: [property, [Validators.required, this.attributeLwm2mKeyValidator]], | |
120 | - value: [keyValMap[property], this.attributeLwm2mValueValidator(property)] | |
121 | - })); | |
122 | - } | |
123 | - } | |
124 | + (Object.keys(keyValMap) as AttributeName[]).forEach(name => { | |
125 | + attributesValueControls.push(this.createdFormGroup({name, value: keyValMap[name]})); | |
126 | + }); | |
124 | 127 | } |
125 | - this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls)); | |
126 | - this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => { | |
127 | - // this.updateValidate(); | |
128 | - this.updateModel(); | |
129 | - }); | |
128 | + this.attributesValueFormGroup.setControl('attributesValue', this.fb.array(attributesValueControls)); | |
130 | 129 | if (this.disabled) { |
131 | - this.kvListFormGroup.disable({emitEvent: false}); | |
130 | + this.attributesValueFormGroup.disable({emitEvent: false}); | |
132 | 131 | } else { |
133 | - this.kvListFormGroup.enable({emitEvent: false}); | |
132 | + this.attributesValueFormGroup.enable({emitEvent: false}); | |
134 | 133 | } |
134 | + this.valueChange$ = this.attributesValueFormGroup.valueChanges.subscribe(() => { | |
135 | + this.updateModel(); | |
136 | + }); | |
137 | + this.updateUsedAttributesName(); | |
138 | + } | |
139 | + | |
140 | + attributesValueFormArray(): FormArray { | |
141 | + return this.attributesValueFormGroup.get('attributesValue') as FormArray; | |
135 | 142 | } |
136 | 143 | |
137 | 144 | public removeKeyVal(index: number) { |
138 | - (this.kvListFormGroup.get('keyVals') as FormArray).removeAt(index); | |
145 | + this.attributesValueFormArray().removeAt(index); | |
139 | 146 | } |
140 | 147 | |
141 | 148 | public addKeyVal() { |
142 | - const keyValsFormArray = this.kvListFormGroup.get('keyVals') as FormArray; | |
143 | - keyValsFormArray.push(this.fb.group({ | |
144 | - key: ['', [Validators.required, this.attributeLwm2mKeyValidator]], | |
145 | - value: ['', []] | |
146 | - })); | |
147 | - } | |
148 | - | |
149 | - public validate(c?: FormControl) { | |
150 | - const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; | |
151 | - let valid = true; | |
152 | - for (const entry of kvList) { | |
153 | - if (isUndefinedOrNull(entry.key) || isEmptyStr(entry.key) || !ATTRIBUTE_KEYS.includes(entry.key)) { | |
154 | - valid = false; | |
155 | - break; | |
156 | - } | |
157 | - if (entry.key !== 'ver' && isNaN(Number(entry.value))) { | |
158 | - valid = false; | |
159 | - break; | |
160 | - } | |
149 | + this.attributesValueFormArray().push(this.createdFormGroup()); | |
150 | + this.attributesValueFormGroup.updateValueAndValidity({emitEvent: false}); | |
151 | + if (this.attributesValueFormGroup.invalid) { | |
152 | + this.updateModel(); | |
161 | 153 | } |
162 | - return (valid) ? null : { | |
163 | - keyVals: { | |
164 | - valid: false, | |
165 | - }, | |
166 | - }; | |
167 | 154 | } |
168 | 155 | |
169 | - private updateValidate() { | |
170 | - const kvList = this.kvListFormGroup.get('keyVals') as FormArray; | |
171 | - kvList.controls.forEach(fg => { | |
172 | - if (fg.get('key').value === 'ver') { | |
173 | - fg.get('value').setValidators(null); | |
174 | - fg.get('value').setErrors(null); | |
175 | - } | |
176 | - else { | |
177 | - fg.get('value').setValidators(this.attributeLwm2mValueNumberValidator); | |
178 | - fg.get('value').setErrors(this.attributeLwm2mValueNumberValidator(fg.get('value'))); | |
179 | - } | |
156 | + private createdFormGroup(value?: AttributesNameValue): FormGroup { | |
157 | + if (isUndefinedOrNull(value)) { | |
158 | + value = { | |
159 | + name: this.getFirstUnusedAttributesName(), | |
160 | + value: null | |
161 | + }; | |
162 | + } | |
163 | + const form = this.fb.group({ | |
164 | + name: [value.name, Validators.required], | |
165 | + value: [value.value, valueValidatorByAttributeName(value.name)] | |
180 | 166 | }); |
167 | + form.get('name').valueChanges.pipe( | |
168 | + takeUntil(this.destroy$) | |
169 | + ).subscribe(name => { | |
170 | + form.get('value').setValidators(valueValidatorByAttributeName(name)); | |
171 | + form.get('value').updateValueAndValidity(); | |
172 | + }); | |
173 | + return form; | |
174 | + } | |
175 | + | |
176 | + public validate() { | |
177 | + return this.attributesValueFormGroup.valid ? null : { | |
178 | + attributesValue: { | |
179 | + valid: false | |
180 | + } | |
181 | + }; | |
181 | 182 | } |
182 | 183 | |
183 | 184 | private updateModel() { |
184 | - this.updateValidate(); | |
185 | - if (this.validate() === null) { | |
186 | - const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; | |
187 | - const keyValMap: { [key: string]: string | number } = {}; | |
188 | - kvList.forEach((entry) => { | |
189 | - if (isUndefinedOrNull(entry.value) || entry.key === 'ver' || isEmptyStr(entry.value.toString())) { | |
190 | - keyValMap[entry.key] = entry.value.toString(); | |
191 | - } else { | |
192 | - keyValMap[entry.key] = Number(entry.value); | |
193 | - } | |
194 | - }); | |
195 | - this.propagateChange(keyValMap); | |
196 | - } | |
197 | - else { | |
198 | - this.propagateChange(null); | |
199 | - } | |
185 | + const value: AttributesNameValue[] = this.attributesValueFormGroup.get('attributesValue').value; | |
186 | + const attributesNameValueMap: AttributesNameValueMap = {}; | |
187 | + value.forEach(attribute => { | |
188 | + attributesNameValueMap[attribute.name] = attribute.value; | |
189 | + }); | |
190 | + this.updateUsedAttributesName(); | |
191 | + this.propagateChange(attributesNameValueMap); | |
200 | 192 | } |
201 | 193 | |
194 | + public isDisabledAttributeName(type: AttributeName, index: number): boolean { | |
195 | + const usedIndex = this.usedAttributesName.indexOf(type); | |
196 | + return usedIndex > -1 && usedIndex !== index; | |
197 | + } | |
202 | 198 | |
203 | - private attributeLwm2mKeyValidator = (control: AbstractControl) => { | |
204 | - const key = control.value as string; | |
205 | - if (isDefinedAndNotNull(key) && !isEmpty(key)) { | |
206 | - if (!ATTRIBUTE_KEYS.includes(key)) { | |
207 | - return { | |
208 | - validAttributeKey: true | |
209 | - }; | |
199 | + private getFirstUnusedAttributesName(): AttributeName { | |
200 | + for (const attributeName of this.attributeNames) { | |
201 | + if (this.usedAttributesName.indexOf(attributeName) === -1) { | |
202 | + return attributeName; | |
210 | 203 | } |
211 | 204 | } |
212 | 205 | return null; |
213 | 206 | } |
214 | 207 | |
215 | - private attributeLwm2mValueNumberValidator = (control: AbstractControl) => { | |
216 | - if (isNaN(Number(control.value)) || Number(control.value) < 0) { | |
217 | - return { | |
218 | - validAttributeValue: true | |
219 | - }; | |
220 | - } | |
221 | - return null; | |
208 | + private updateUsedAttributesName() { | |
209 | + this.usedAttributesName = []; | |
210 | + const value: AttributesNameValue[] = this.attributesValueFormGroup.get('attributesValue').value; | |
211 | + value.forEach((attributesValue, index) => { | |
212 | + this.usedAttributesName[index] = attributesValue.name; | |
213 | + }); | |
222 | 214 | } |
223 | 215 | |
224 | - private attributeLwm2mValueValidator = (property: string): object[] => { | |
225 | - return property === 'ver' ? [] : [this.attributeLwm2mValueNumberValidator]; | |
216 | + get isAddEnabled(): boolean { | |
217 | + return this.attributesValueFormArray().length !== this.attributeNames.length; | |
226 | 218 | } |
227 | 219 | } | ... | ... |
... | ... | @@ -15,18 +15,14 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div fxLayout="row" [formGroup]="attributeLwm2mFormGroup"> | |
19 | - <div fxFlex fxLayout="column" class="resource-name-lw-end" fxLayoutAlign="center" | |
20 | - [matTooltip]="isToolTipLabel()" matTooltipPosition="above"> | |
21 | - {{attributeLwm2mToString()}} | |
22 | - </div> | |
18 | +<div fxLayout="row" [fxHide]="disabled && isEmpty()" fxLayoutAlign="end center" matTooltip="{{ tooltipSetAttributesTelemetry | translate }}" matTooltipPosition="above"> | |
23 | 19 | <button type="button" |
24 | 20 | [disabled]="isDisableBtn()" |
25 | 21 | mat-button mat-icon-button |
26 | 22 | (click)="editAttributesLwm2m($event)" |
27 | - [matTooltip]="(isIconView() ? 'action.view' : isIconEditAdd() ? 'action.edit' : 'action.add' ) | translate" | |
23 | + matTooltip="{{ tooltipButton | translate }}" | |
28 | 24 | matTooltipPosition="above"> |
29 | 25 | <mat-icon |
30 | - class="material-icons">{{isIconView() ? 'visibility' : isIconEditAdd() ? 'edit' : 'add' }}</mat-icon> | |
26 | + class="material-icons">{{ iconButton }}</mat-icon> | |
31 | 27 | </button> |
32 | 28 | </div> | ... | ... |
... | ... | @@ -14,48 +14,20 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | :host { |
17 | - .tb-kv-map { | |
18 | - span.no-data-found { | |
19 | - position: relative; | |
20 | - display: flex; | |
21 | - height: 40px; | |
22 | - | |
23 | - &.disabled { | |
24 | - color: rgba(0, 0, 0, .38); | |
25 | - } | |
26 | - } | |
17 | + .resource-name-lw-end{ | |
18 | + white-space: nowrap; | |
19 | + overflow: hidden; | |
20 | + text-overflow: ellipsis; | |
21 | + text-align:end; | |
22 | + //width: 80px; | |
23 | + cursor: pointer; | |
27 | 24 | } |
28 | -} | |
29 | 25 | |
30 | -:host ::ng-deep { | |
31 | - .mat-form-field-wrapper { | |
32 | - padding-bottom: 0; | |
33 | - } | |
34 | - .mat-form-field-infix { | |
35 | - border-top: 0; | |
26 | + .resource-name-lw{ | |
27 | + white-space: nowrap; | |
28 | + overflow: hidden; | |
29 | + text-overflow: ellipsis; | |
30 | + cursor: pointer; | |
36 | 31 | } |
37 | - .mat-form-field-underline { | |
38 | - bottom: 0; | |
39 | - } | |
40 | -} | |
41 | - | |
42 | -.vertical-padding { | |
43 | - padding: 0 0 10px 20px; | |
44 | -} | |
45 | - | |
46 | -.resource-name-lw-end{ | |
47 | - white-space: nowrap; | |
48 | - overflow: hidden; | |
49 | - text-overflow: ellipsis; | |
50 | - text-align:end; | |
51 | - //width: 80px; | |
52 | - cursor: pointer; | |
53 | -} | |
54 | - | |
55 | -.resource-name-lw{ | |
56 | - white-space: nowrap; | |
57 | - overflow: hidden; | |
58 | - text-overflow: ellipsis; | |
59 | - cursor: pointer; | |
60 | 32 | } |
61 | 33 | ... | ... |
... | ... | @@ -17,11 +17,10 @@ |
17 | 17 | import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; |
18 | 18 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | 19 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
20 | -import { deepClone, isDefinedAndNotNull, isEmpty } from '@core/utils'; | |
20 | +import { isEmpty, isUndefinedOrNull } from '@core/utils'; | |
21 | 21 | import { Lwm2mAttributesDialogComponent, Lwm2mAttributesDialogData } from './lwm2m-attributes-dialog.component'; |
22 | 22 | import { MatDialog } from '@angular/material/dialog'; |
23 | -import { TranslateService } from '@ngx-translate/core'; | |
24 | -import { ATTRIBUTE_LWM2M_LABEL } from './lwm2m-profile-config.models'; | |
23 | +import { AttributesNameValueMap } from './lwm2m-profile-config.models'; | |
25 | 24 | |
26 | 25 | |
27 | 26 | @Component({ |
... | ... | @@ -36,22 +35,21 @@ import { ATTRIBUTE_LWM2M_LABEL } from './lwm2m-profile-config.models'; |
36 | 35 | }) |
37 | 36 | export class Lwm2mAttributesComponent implements ControlValueAccessor { |
38 | 37 | attributeLwm2mFormGroup: FormGroup; |
39 | - attributeLwm2mLabel = ATTRIBUTE_LWM2M_LABEL; | |
40 | 38 | |
41 | 39 | private requiredValue: boolean; |
42 | 40 | |
43 | 41 | @Input() |
44 | - attributeLwm2m: {}; | |
45 | - | |
46 | - @Input() | |
47 | 42 | isAttributeTelemetry: boolean; |
48 | 43 | |
49 | 44 | @Input() |
50 | - destName: string; | |
45 | + modelName: string; | |
51 | 46 | |
52 | 47 | @Input() |
53 | 48 | disabled: boolean; |
54 | 49 | |
50 | + @Input() | |
51 | + isResource = false; | |
52 | + | |
55 | 53 | @Output() |
56 | 54 | updateAttributeLwm2m = new EventEmitter<any>(); |
57 | 55 | |
... | ... | @@ -64,8 +62,7 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { |
64 | 62 | } |
65 | 63 | |
66 | 64 | constructor(private dialog: MatDialog, |
67 | - private fb: FormBuilder, | |
68 | - private translate: TranslateService) {} | |
65 | + private fb: FormBuilder) {} | |
69 | 66 | |
70 | 67 | registerOnChange(fn: any): void { |
71 | 68 | this.propagateChange = fn; |
... | ... | @@ -85,63 +82,66 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { |
85 | 82 | |
86 | 83 | ngOnInit() { |
87 | 84 | this.attributeLwm2mFormGroup = this.fb.group({ |
88 | - attributeLwm2m: [this.attributeLwm2m] | |
85 | + attributes: [{}] | |
89 | 86 | }); |
90 | 87 | } |
91 | 88 | |
92 | - writeValue(value: {} | null): void {} | |
93 | - | |
94 | - attributeLwm2mToString = (): string => { | |
95 | - return this.isIconEditAdd () ? this.attributeLwm2mLabelToString() : this.translate.instant('device-profile.lwm2m.no-data'); | |
89 | + writeValue(value: AttributesNameValueMap | null) { | |
90 | + this.attributeLwm2mFormGroup.patchValue({attributes: value}, {emitEvent: false}); | |
96 | 91 | } |
97 | 92 | |
98 | - private attributeLwm2mLabelToString = (): string => { | |
99 | - let label = JSON.stringify(this.attributeLwm2m); | |
100 | - label = deepClone(label.replace('{', '')); | |
101 | - label = deepClone(label.replace('}', '')); | |
102 | - this.attributeLwm2mLabel.forEach((value: string, key: string) => { | |
103 | - const dest = '\"' + key + '\"\:'; | |
104 | - label = deepClone(label.replace(dest, value)); | |
105 | - }); | |
106 | - return label; | |
93 | + get attributesValueMap(): AttributesNameValueMap { | |
94 | + return this.attributeLwm2mFormGroup.get('attributes').value; | |
107 | 95 | } |
108 | 96 | |
109 | 97 | isDisableBtn(): boolean { |
110 | - return this.disabled || this.isAttributeTelemetry ? !(isDefinedAndNotNull(this.attributeLwm2m) && | |
111 | - !isEmpty(this.attributeLwm2m) && this.disabled) : this.disabled; | |
98 | + return !this.disabled && this.isAttributeTelemetry; | |
112 | 99 | } |
113 | 100 | |
114 | - isIconView(): boolean { | |
115 | - return this.isAttributeTelemetry || this.disabled; | |
101 | + isEmpty(): boolean { | |
102 | + const value = this.attributesValueMap; | |
103 | + return isUndefinedOrNull(value) || isEmpty(value); | |
116 | 104 | } |
117 | 105 | |
118 | - isIconEditAdd(): boolean { | |
119 | - return isDefinedAndNotNull(this.attributeLwm2m) && !isEmpty(this.attributeLwm2m); | |
106 | + get tooltipSetAttributesTelemetry(): string { | |
107 | + return this.isDisableBtn() ? 'device-profile.lwm2m.edit-attributes-select' : ''; | |
120 | 108 | } |
121 | 109 | |
122 | - isToolTipLabel(): string { | |
123 | - return this.disabled ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip') : | |
124 | - this.isAttributeTelemetry ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-disable-tip') : | |
125 | - this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip'); | |
110 | + get tooltipButton(): string { | |
111 | + if (this.disabled) { | |
112 | + return 'device-profile.lwm2m.view-attribute'; | |
113 | + } else if (this.isEmpty()) { | |
114 | + return 'device-profile.lwm2m.add-attribute'; | |
115 | + } | |
116 | + return 'device-profile.lwm2m.edit-attribute'; | |
117 | + } | |
118 | + | |
119 | + get iconButton(): string { | |
120 | + if (this.disabled) { | |
121 | + return 'visibility'; | |
122 | + } else if (this.isEmpty()) { | |
123 | + return 'add'; | |
124 | + } | |
125 | + return 'edit'; | |
126 | 126 | } |
127 | 127 | |
128 | 128 | public editAttributesLwm2m = ($event: Event): void => { |
129 | 129 | if ($event) { |
130 | 130 | $event.stopPropagation(); |
131 | 131 | } |
132 | - this.dialog.open<Lwm2mAttributesDialogComponent, Lwm2mAttributesDialogData, object>(Lwm2mAttributesDialogComponent, { | |
132 | + this.dialog.open<Lwm2mAttributesDialogComponent, Lwm2mAttributesDialogData, AttributesNameValueMap>(Lwm2mAttributesDialogComponent, { | |
133 | 133 | disableClose: true, |
134 | 134 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
135 | 135 | data: { |
136 | 136 | readonly: this.disabled, |
137 | - attributeLwm2m: this.disabled ? this.attributeLwm2m : deepClone(this.attributeLwm2m), | |
138 | - destName: this.destName | |
137 | + attributes: this.attributesValueMap, | |
138 | + modelName: this.modelName, | |
139 | + isResource: this.isResource | |
139 | 140 | } |
140 | 141 | }).afterClosed().subscribe((result) => { |
141 | 142 | if (result) { |
142 | - this.attributeLwm2m = result; | |
143 | - this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: this.attributeLwm2m}); | |
144 | - this.updateAttributeLwm2m.next(this.attributeLwm2m); | |
143 | + this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: result}); | |
144 | + this.updateAttributeLwm2m.next(result); | |
145 | 145 | } |
146 | 146 | }); |
147 | 147 | } | ... | ... |
... | ... | @@ -35,19 +35,33 @@ |
35 | 35 | </mat-form-field> |
36 | 36 | <mat-form-field fxFlex> |
37 | 37 | <mat-label>{{ 'device-profile.lwm2m.server-port' | translate }}</mat-label> |
38 | - <input matInput type="number" formControlName="port" required min="0"> | |
38 | + <input matInput type="number" formControlName="port" required min="0" max="65535"> | |
39 | 39 | <mat-error *ngIf="serverFormGroup.get('port').hasError('required')"> |
40 | 40 | {{ 'device-profile.lwm2m.server-port-required' | translate }} |
41 | 41 | </mat-error> |
42 | + <mat-error *ngIf="serverFormGroup.get('port').hasError('pattern')"> | |
43 | + {{ 'device-profile.lwm2m.server-port-pattern' | translate }} | |
44 | + </mat-error> | |
45 | + <mat-error *ngIf="serverFormGroup.get('port').hasError('min') || | |
46 | + serverFormGroup.get('port').hasError('max')"> | |
47 | + {{ 'device-profile.lwm2m.server-port-range' | translate }} | |
48 | + </mat-error> | |
42 | 49 | </mat-form-field> |
43 | 50 | </div> |
44 | 51 | <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxLayoutGap.xs="0px"> |
45 | 52 | <mat-form-field fxFlex> |
46 | 53 | <mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label> |
47 | - <input matInput type="number" formControlName="serverId" required min="0"> | |
54 | + <input matInput type="number" min="1" max="65534" formControlName="serverId" required> | |
48 | 55 | <mat-error *ngIf="serverFormGroup.get('serverId').hasError('required')"> |
49 | 56 | {{ 'device-profile.lwm2m.short-id-required' | translate }} |
50 | 57 | </mat-error> |
58 | + <mat-error *ngIf="serverFormGroup.get('serverId').hasError('pattern')"> | |
59 | + {{ 'device-profile.lwm2m.short-id-pattern' | translate }} | |
60 | + </mat-error> | |
61 | + <mat-error *ngIf="serverFormGroup.get('serverId').hasError('min') || | |
62 | + serverFormGroup.get('serverId').hasError('max')"> | |
63 | + {{ 'device-profile.lwm2m.short-id-range' | translate }} | |
64 | + </mat-error> | |
51 | 65 | </mat-form-field> |
52 | 66 | <mat-form-field fxFlex> |
53 | 67 | <mat-label>{{ 'device-profile.lwm2m.client-hold-off-time' | translate }}</mat-label> |
... | ... | @@ -57,6 +71,10 @@ |
57 | 71 | <mat-error *ngIf="serverFormGroup.get('clientHoldOffTime').hasError('required')"> |
58 | 72 | {{ 'device-profile.lwm2m.client-hold-off-time-required' | translate }} |
59 | 73 | </mat-error> |
74 | + <mat-error *ngIf="serverFormGroup.get('clientHoldOffTime').hasError('min') || | |
75 | + serverFormGroup.get('clientHoldOffTime').hasError('pattern')"> | |
76 | + {{ 'device-profile.lwm2m.client-hold-off-time-pattern' | translate }} | |
77 | + </mat-error> | |
60 | 78 | </mat-form-field> |
61 | 79 | <mat-form-field fxFlex> |
62 | 80 | <mat-label>{{ 'device-profile.lwm2m.account-after-timeout' | translate }}</mat-label> |
... | ... | @@ -66,6 +84,10 @@ |
66 | 84 | <mat-error *ngIf="serverFormGroup.get('bootstrapServerAccountTimeout').hasError('required')"> |
67 | 85 | {{ 'device-profile.lwm2m.account-after-timeout-required' | translate }} |
68 | 86 | </mat-error> |
87 | + <mat-error *ngIf="serverFormGroup.get('bootstrapServerAccountTimeout').hasError('min') || | |
88 | + serverFormGroup.get('bootstrapServerAccountTimeout').hasError('pattern')"> | |
89 | + {{ 'device-profile.lwm2m.account-after-timeout-pattern' | translate }} | |
90 | + </mat-error> | |
69 | 91 | </mat-form-field> |
70 | 92 | </div> |
71 | 93 | <div *ngIf="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK || | ... | ... |
... | ... | @@ -15,7 +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'; | |
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 { |
20 | 29 | DEFAULT_PORT_BOOTSTRAP_NO_SEC, |
21 | 30 | DEFAULT_PORT_SERVER_NO_SEC, |
... | ... | @@ -27,10 +36,9 @@ import { |
27 | 36 | ServerSecurityConfig |
28 | 37 | } from './lwm2m-profile-config.models'; |
29 | 38 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
30 | -import { of, Subject } from 'rxjs'; | |
31 | -import { map, mergeMap, takeUntil, tap } from 'rxjs/operators'; | |
39 | +import { Subject } from 'rxjs'; | |
40 | +import { mergeMap, takeUntil, tap } from 'rxjs/operators'; | |
32 | 41 | import { Observable } from 'rxjs/internal/Observable'; |
33 | -import { deepClone } from '@core/utils'; | |
34 | 42 | |
35 | 43 | @Component({ |
36 | 44 | selector: 'tb-profile-lwm2m-device-config-server', |
... | ... | @@ -40,16 +48,21 @@ import { deepClone } from '@core/utils'; |
40 | 48 | provide: NG_VALUE_ACCESSOR, |
41 | 49 | useExisting: forwardRef(() => Lwm2mDeviceConfigServerComponent), |
42 | 50 | multi: true |
43 | - } | |
51 | + }, | |
52 | + { | |
53 | + provide: NG_VALIDATORS, | |
54 | + useExisting: forwardRef(() => Lwm2mDeviceConfigServerComponent), | |
55 | + multi: true | |
56 | + }, | |
44 | 57 | ] |
45 | 58 | }) |
46 | 59 | |
47 | -export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAccessor, OnDestroy { | |
60 | +export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAccessor, Validator, OnDestroy { | |
48 | 61 | |
49 | 62 | private disabled = false; |
50 | 63 | private destroy$ = new Subject(); |
51 | 64 | |
52 | - private securityDefaultConfig: ServerSecurityConfig; | |
65 | + private isDataLoadedIntoCache = false; | |
53 | 66 | |
54 | 67 | serverFormGroup: FormGroup; |
55 | 68 | securityConfigLwM2MType = securityConfigMode; |
... | ... | @@ -70,12 +83,13 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc |
70 | 83 | ngOnInit(): void { |
71 | 84 | this.serverFormGroup = this.fb.group({ |
72 | 85 | host: ['', Validators.required], |
73 | - port: [this.isBootstrapServer ? DEFAULT_PORT_BOOTSTRAP_NO_SEC : DEFAULT_PORT_SERVER_NO_SEC, [Validators.required, Validators.min(0)]], | |
86 | + port: [this.isBootstrapServer ? DEFAULT_PORT_BOOTSTRAP_NO_SEC : DEFAULT_PORT_SERVER_NO_SEC, | |
87 | + [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]], | |
74 | 88 | securityMode: [securityConfigMode.NO_SEC], |
75 | - serverPublicKey: ['', Validators.required], | |
76 | - clientHoldOffTime: ['', [Validators.required, Validators.min(0)]], | |
77 | - serverId: ['', [Validators.required, Validators.min(0)]], | |
78 | - bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0)]], | |
89 | + serverPublicKey: [''], | |
90 | + clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], | |
91 | + serverId: ['', [Validators.required, Validators.min(1), Validators.max(65534), Validators.pattern('[0-9]*')]], | |
92 | + bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], | |
79 | 93 | }); |
80 | 94 | this.serverFormGroup.get('securityMode').valueChanges.pipe( |
81 | 95 | tap(securityMode => this.updateValidate(securityMode)), |
... | ... | @@ -101,7 +115,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc |
101 | 115 | this.serverFormGroup.patchValue(serverData, {emitEvent: false}); |
102 | 116 | this.updateValidate(serverData.securityMode); |
103 | 117 | } |
104 | - if (!this.securityDefaultConfig){ | |
118 | + if (!this.isDataLoadedIntoCache){ | |
105 | 119 | this.getLwm2mBootstrapSecurityInfo().subscribe(value => { |
106 | 120 | if (!serverData) { |
107 | 121 | this.serverFormGroup.patchValue(value); |
... | ... | @@ -159,43 +173,19 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc |
159 | 173 | |
160 | 174 | private propagateChangeState = (value: ServerSecurityConfig): void => { |
161 | 175 | if (value !== undefined) { |
162 | - if (this.serverFormGroup.valid) { | |
163 | - this.propagateChange(value); | |
164 | - } else { | |
165 | - this.propagateChange(null); | |
166 | - } | |
176 | + this.propagateChange(value); | |
167 | 177 | } |
168 | 178 | } |
169 | 179 | |
170 | 180 | private getLwm2mBootstrapSecurityInfo(securityMode = securityConfigMode.NO_SEC): Observable<ServerSecurityConfig> { |
171 | - if (this.securityDefaultConfig) { | |
172 | - return of(this.processingBootstrapSecurityInfo(this.securityDefaultConfig, securityMode)); | |
173 | - } | |
174 | - return this.deviceProfileService.getLwm2mBootstrapSecurityInfo(this.isBootstrapServer).pipe( | |
175 | - map(securityInfo => { | |
176 | - this.securityDefaultConfig = securityInfo; | |
177 | - return this.processingBootstrapSecurityInfo(securityInfo, securityMode); | |
178 | - }) | |
181 | + return this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(this.isBootstrapServer, securityMode).pipe( | |
182 | + tap(() => this.isDataLoadedIntoCache = true) | |
179 | 183 | ); |
180 | 184 | } |
181 | 185 | |
182 | - private processingBootstrapSecurityInfo(securityConfig: ServerSecurityConfig, securityMode: securityConfigMode): ServerSecurityConfig { | |
183 | - const config = deepClone(securityConfig); | |
184 | - switch (securityMode) { | |
185 | - case securityConfigMode.PSK: | |
186 | - config.port = config.securityPort; | |
187 | - config.host = config.securityHost; | |
188 | - config.serverPublicKey = ''; | |
189 | - break; | |
190 | - case securityConfigMode.RPK: | |
191 | - case securityConfigMode.X509: | |
192 | - config.port = config.securityPort; | |
193 | - config.host = config.securityHost; | |
194 | - break; | |
195 | - case securityConfigMode.NO_SEC: | |
196 | - config.serverPublicKey = ''; | |
197 | - break; | |
198 | - } | |
199 | - return config; | |
186 | + validate(): ValidationErrors | null { | |
187 | + return this.serverFormGroup.valid ? null : { | |
188 | + serverFormGroup: true | |
189 | + }; | |
200 | 190 | } |
201 | 191 | } | ... | ... |
... | ... | @@ -34,53 +34,62 @@ |
34 | 34 | </ng-template> |
35 | 35 | </mat-tab> |
36 | 36 | <mat-tab label="{{ 'device-profile.lwm2m.servers' | translate }}"> |
37 | - <ng-template matTabContent> | |
38 | - <section [formGroup]="lwm2mDeviceProfileFormGroup" style="padding: 4px 2px"> | |
37 | + <section [formGroup]="lwm2mDeviceProfileFormGroup"> | |
38 | + <section formGroupName="bootstrap" style="padding: 4px 2px"> | |
39 | 39 | <mat-accordion multi="true"> |
40 | 40 | <mat-expansion-panel> |
41 | 41 | <mat-expansion-panel-header> |
42 | 42 | <mat-panel-title>{{ 'device-profile.lwm2m.servers' | translate }}</mat-panel-title> |
43 | 43 | </mat-expansion-panel-header> |
44 | - <ng-template matExpansionPanelContent> | |
44 | + <ng-template matExpansionPanelContent formGroupName="servers"> | |
45 | 45 | <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxLayoutGap.xs="0px"> |
46 | 46 | <mat-form-field fxFlex> |
47 | 47 | <mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label> |
48 | - <input matInput type="number" formControlName="shortId" required> | |
49 | - <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('shortId').hasError('required')"> | |
50 | - {{ 'device-profile.lwm2m.short-id' | translate }} | |
51 | - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> | |
48 | + <input matInput type="number" min="1" max="65534" formControlName="shortId" required> | |
49 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.shortId').hasError('required')"> | |
50 | + {{ 'device-profile.lwm2m.short-id-required' | translate }} | |
51 | + </mat-error> | |
52 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.shortId').hasError('min') || | |
53 | + lwm2mDeviceProfileFormGroup.get('bootstrap.servers.shortId').hasError('max')"> | |
54 | + {{ 'device-profile.lwm2m.short-id-range' | translate }} | |
55 | + </mat-error> | |
56 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.shortId').hasError('pattern')"> | |
57 | + {{ 'device-profile.lwm2m.short-id-pattern' | translate }} | |
52 | 58 | </mat-error> |
53 | 59 | </mat-form-field> |
54 | 60 | <mat-form-field fxFlex> |
55 | 61 | <mat-label>{{ 'device-profile.lwm2m.lifetime' | translate }}</mat-label> |
56 | - <input matInput type="number" formControlName="lifetime" required> | |
57 | - <mat-error | |
58 | - *ngIf="lwm2mDeviceProfileFormGroup.get('lifetime').hasError('required')"> | |
59 | - {{ 'device-profile.lwm2m.lifetime' | translate }} | |
60 | - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> | |
62 | + <input matInput type="number" min="0" formControlName="lifetime" required> | |
63 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.lifetime').hasError('required')"> | |
64 | + {{ 'device-profile.lwm2m.lifetime-required' | translate }} | |
65 | + </mat-error> | |
66 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.lifetime').hasError('pattern') || | |
67 | + lwm2mDeviceProfileFormGroup.get('bootstrap.servers.lifetime').hasError('min')"> | |
68 | + {{ 'device-profile.lwm2m.lifetime-pattern' | translate }} | |
61 | 69 | </mat-error> |
62 | 70 | </mat-form-field> |
63 | 71 | <mat-form-field fxFlex> |
64 | 72 | <mat-label>{{ 'device-profile.lwm2m.default-min-period' | translate }}</mat-label> |
65 | - <input matInput type="number" formControlName="defaultMinPeriod" required> | |
66 | - <mat-error | |
67 | - *ngIf="lwm2mDeviceProfileFormGroup.get('defaultMinPeriod').hasError('required')"> | |
68 | - {{ 'device-profile.lwm2m.default-min-period' | translate }} | |
69 | - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> | |
73 | + <input matInput type="number" min="0" formControlName="defaultMinPeriod" required> | |
74 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.defaultMinPeriod').hasError('required')"> | |
75 | + {{ 'device-profile.lwm2m.default-min-period-required' | translate }} | |
76 | + </mat-error> | |
77 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('bootstrap.servers.defaultMinPeriod').hasError('pattern') || | |
78 | + lwm2mDeviceProfileFormGroup.get('bootstrap.servers.defaultMinPeriod').hasError('min')"> | |
79 | + {{ 'device-profile.lwm2m.default-min-period-pattern' | translate }} | |
70 | 80 | </mat-error> |
71 | 81 | </mat-form-field> |
72 | 82 | </div> |
73 | 83 | <mat-form-field class="mat-block"> |
74 | 84 | <mat-label>{{ 'device-profile.lwm2m.binding' | translate }}</mat-label> |
75 | 85 | <mat-select formControlName="binding"> |
76 | - <mat-option *ngFor="let bindingMode of bindingModeTypes" | |
77 | - [value]="bindingMode"> | |
78 | - {{ bindingModeTypeNamesMap.get(bindingModeType[bindingMode]) }} | |
86 | + <mat-option *ngFor="let bindingMode of bindingModeTypes" [value]="bindingMode"> | |
87 | + {{ bindingModeTypeNamesMap.get(bindingMode) | translate }} | |
79 | 88 | </mat-option> |
80 | 89 | </mat-select> |
81 | 90 | </mat-form-field> |
82 | 91 | <mat-checkbox formControlName="notifIfDisabled" color="primary"> |
83 | - {{ 'device-profile.lwm2m.notif-if-disabled' | translate }} | |
92 | + {{ 'device-profile.lwm2m.notification-storing' | translate }} | |
84 | 93 | </mat-checkbox> |
85 | 94 | </ng-template> |
86 | 95 | </mat-expansion-panel> |
... | ... | @@ -90,7 +99,6 @@ |
90 | 99 | </mat-expansion-panel-header> |
91 | 100 | <ng-template matExpansionPanelContent> |
92 | 101 | <tb-profile-lwm2m-device-config-server |
93 | - [required]="required" | |
94 | 102 | formControlName="bootstrapServer" |
95 | 103 | [isBootstrapServer]="true"> |
96 | 104 | </tb-profile-lwm2m-device-config-server> |
... | ... | @@ -102,7 +110,6 @@ |
102 | 110 | </mat-expansion-panel-header> |
103 | 111 | <ng-template matExpansionPanelContent> |
104 | 112 | <tb-profile-lwm2m-device-config-server |
105 | - [required]="required" | |
106 | 113 | formControlName="lwm2mServer" |
107 | 114 | [isBootstrapServer]="false"> |
108 | 115 | </tb-profile-lwm2m-device-config-server> |
... | ... | @@ -110,11 +117,11 @@ |
110 | 117 | </mat-expansion-panel> |
111 | 118 | </mat-accordion> |
112 | 119 | </section> |
113 | - </ng-template> | |
120 | + </section> | |
114 | 121 | </mat-tab> |
115 | 122 | <mat-tab label="{{ 'device-profile.lwm2m.others-tab' | translate }}"> |
116 | - <ng-template matTabContent> | |
117 | - <section [formGroup]="lwm2mDeviceProfileFormGroup"> | |
123 | + <ng-template matTabContent [formGroup]="lwm2mDeviceProfileFormGroup"> | |
124 | + <section formGroupName="clientLwM2mSettings"> | |
118 | 125 | <fieldset class="fields-group"> |
119 | 126 | <legend class="group-title" translate>device-profile.lwm2m.fw-update</legend> |
120 | 127 | <mat-form-field class="mat-block" fxFlex> |
... | ... | @@ -128,7 +135,7 @@ |
128 | 135 | <mat-form-field class="mat-block" fxFlex *ngIf="isFwUpdateStrategy"> |
129 | 136 | <mat-label>{{ 'device-profile.lwm2m.fw-update-recourse' | translate }}</mat-label> |
130 | 137 | <input matInput formControlName="fwUpdateRecourse" required> |
131 | - <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').hasError('required')"> | |
138 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').hasError('required')"> | |
132 | 139 | {{ 'device-profile.lwm2m.fw-update-recourse-required' | translate }} |
133 | 140 | </mat-error> |
134 | 141 | </mat-form-field> |
... | ... | @@ -145,7 +152,7 @@ |
145 | 152 | <mat-form-field class="mat-block" fxFlex *ngIf="isSwUpdateStrategy"> |
146 | 153 | <mat-label>{{ 'device-profile.lwm2m.sw-update-recourse' | translate }}</mat-label> |
147 | 154 | <input matInput formControlName="swUpdateRecourse" required> |
148 | - <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').hasError('required')"> | |
155 | + <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').hasError('required')"> | |
149 | 156 | {{ 'device-profile.lwm2m.sw-update-recourse-required' | translate }} |
150 | 157 | </mat-error> |
151 | 158 | </mat-form-field> |
... | ... | @@ -182,6 +189,7 @@ |
182 | 189 | <ng-template matTabContent> |
183 | 190 | <section [formGroup]="lwm2mDeviceConfigFormGroup" style="padding: 8px 0"> |
184 | 191 | <tb-json-object-edit |
192 | + readonly | |
185 | 193 | [required]="required" |
186 | 194 | [sort]="sortFunction" |
187 | 195 | label="{{ 'device-profile.transport-type-lwm2m' | translate }}" | ... | ... |
... | ... | @@ -20,11 +20,19 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Valida |
20 | 20 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
21 | 21 | import { |
22 | 22 | ATTRIBUTE, |
23 | - BINDING_MODE, | |
24 | - BINDING_MODE_NAMES, | |
23 | + BingingMode, | |
24 | + BingingModeTranslationsMap, | |
25 | + DEFAULT_BINDING, | |
25 | 26 | DEFAULT_FW_UPDATE_RESOURCE, |
27 | + DEFAULT_ID_SERVER, | |
28 | + DEFAULT_LIFE_TIME, | |
29 | + DEFAULT_MIN_PERIOD, | |
30 | + DEFAULT_NOTIF_IF_DESIBLED, | |
26 | 31 | DEFAULT_SW_UPDATE_RESOURCE, |
27 | - getDefaultProfileConfig, | |
32 | + getDefaultBootstrapServerSecurityConfig, | |
33 | + getDefaultBootstrapServersSecurityConfig, getDefaultLwM2MServerSecurityConfig, | |
34 | + getDefaultProfileClientLwM2mSettingsConfig, | |
35 | + getDefaultProfileObserveAttrConfig, | |
28 | 36 | Instance, |
29 | 37 | INSTANCES, |
30 | 38 | KEY_NAME, |
... | ... | @@ -33,7 +41,7 @@ import { |
33 | 41 | ObjectLwM2M, |
34 | 42 | OBSERVE, |
35 | 43 | OBSERVE_ATTR_TELEMETRY, |
36 | - RESOURCES, | |
44 | + RESOURCES, ServerSecurityConfig, | |
37 | 45 | TELEMETRY |
38 | 46 | } from './lwm2m-profile-config.models'; |
39 | 47 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
... | ... | @@ -61,14 +69,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
61 | 69 | private disabled = false; |
62 | 70 | private destroy$ = new Subject(); |
63 | 71 | |
64 | - bindingModeType = BINDING_MODE; | |
65 | - bindingModeTypes = Object.keys(BINDING_MODE); | |
66 | - bindingModeTypeNamesMap = BINDING_MODE_NAMES; | |
72 | + bindingModeTypes = Object.values(BingingMode); | |
73 | + bindingModeTypeNamesMap = BingingModeTranslationsMap; | |
67 | 74 | lwm2mDeviceProfileFormGroup: FormGroup; |
68 | 75 | lwm2mDeviceConfigFormGroup: FormGroup; |
69 | - bootstrapServers: string; | |
70 | - bootstrapServer: string; | |
71 | - lwm2mServer: string; | |
72 | 76 | sortFunction: (key: string, value: object) => object; |
73 | 77 | isFwUpdateStrategy: boolean; |
74 | 78 | isSwUpdateStrategy: boolean; |
... | ... | @@ -90,45 +94,53 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
90 | 94 | this.lwm2mDeviceProfileFormGroup = this.fb.group({ |
91 | 95 | objectIds: [null, Validators.required], |
92 | 96 | observeAttrTelemetry: [null, Validators.required], |
93 | - shortId: [null, Validators.required], | |
94 | - lifetime: [null, Validators.required], | |
95 | - defaultMinPeriod: [null, Validators.required], | |
96 | - notifIfDisabled: [true, []], | |
97 | - binding: [], | |
98 | - bootstrapServer: [null, Validators.required], | |
99 | - lwm2mServer: [null, Validators.required], | |
100 | - clientOnlyObserveAfterConnect: [1, []], | |
101 | - fwUpdateStrategy: [1, []], | |
102 | - swUpdateStrategy: [1, []], | |
103 | - fwUpdateRecourse: [{value: '', disabled: true}, []], | |
104 | - swUpdateRecourse: [{value: '', disabled: true}, []] | |
97 | + bootstrap: this.fb.group({ | |
98 | + servers: this.fb.group({ | |
99 | + binding: [DEFAULT_BINDING], | |
100 | + shortId: [DEFAULT_ID_SERVER, [Validators.required, Validators.min(1), Validators.max(65534), Validators.pattern('[0-9]*')]], | |
101 | + lifetime: [DEFAULT_LIFE_TIME, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], | |
102 | + notifIfDisabled: [DEFAULT_NOTIF_IF_DESIBLED, []], | |
103 | + defaultMinPeriod: [DEFAULT_MIN_PERIOD, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], | |
104 | + }), | |
105 | + bootstrapServer: [null, Validators.required], | |
106 | + lwm2mServer: [null, Validators.required] | |
107 | + }), | |
108 | + clientLwM2mSettings: this.fb.group({ | |
109 | + clientOnlyObserveAfterConnect: [1, []], | |
110 | + fwUpdateStrategy: [1, []], | |
111 | + swUpdateStrategy: [1, []], | |
112 | + fwUpdateRecourse: [{value: '', disabled: true}, []], | |
113 | + swUpdateRecourse: [{value: '', disabled: true}, []] | |
114 | + }) | |
105 | 115 | }); |
106 | 116 | this.lwm2mDeviceConfigFormGroup = this.fb.group({ |
107 | 117 | configurationJson: [null, Validators.required] |
108 | 118 | }); |
109 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateStrategy').valueChanges.pipe( | |
119 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe( | |
110 | 120 | takeUntil(this.destroy$) |
111 | 121 | ).subscribe((fwStrategy) => { |
112 | 122 | if (fwStrategy === 2) { |
113 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').enable({emitEvent: false}); | |
114 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').patchValue(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); | |
123 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').enable({emitEvent: false}); | |
124 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse') | |
125 | + .patchValue(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); | |
115 | 126 | this.isFwUpdateStrategy = true; |
116 | 127 | } else { |
117 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').disable({emitEvent: false}); | |
128 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').disable({emitEvent: false}); | |
118 | 129 | this.isFwUpdateStrategy = false; |
119 | 130 | } |
120 | 131 | this.otaUpdateFwStrategyValidate(true); |
121 | 132 | }); |
122 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateStrategy').valueChanges.pipe( | |
133 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').valueChanges.pipe( | |
123 | 134 | takeUntil(this.destroy$) |
124 | 135 | ).subscribe((swStrategy) => { |
125 | 136 | if (swStrategy === 2) { |
126 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').enable({emitEvent: false}); | |
127 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').patchValue(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); | |
137 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').enable({emitEvent: false}); | |
138 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse') | |
139 | + .patchValue(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); | |
128 | 140 | this.isSwUpdateStrategy = true; |
129 | 141 | } else { |
130 | 142 | this.isSwUpdateStrategy = false; |
131 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').disable({emitEvent: false}); | |
143 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').disable({emitEvent: false}); | |
132 | 144 | } |
133 | 145 | this.otaUpdateSwStrategyValidate(true); |
134 | 146 | }); |
... | ... | @@ -168,12 +180,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
168 | 180 | } |
169 | 181 | } |
170 | 182 | |
171 | - writeValue(value: Lwm2mProfileConfigModels | null): void { | |
183 | + async writeValue(value: Lwm2mProfileConfigModels | null) { | |
172 | 184 | if (isDefinedAndNotNull(value)) { |
173 | - if (Object.keys(value).length !== 0 && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { | |
185 | + if (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap) { | |
174 | 186 | this.configurationValue = value; |
175 | 187 | } else { |
176 | - this.configurationValue = getDefaultProfileConfig(); | |
188 | + this.configurationValue = await this.defaultProfileConfig(); | |
177 | 189 | } |
178 | 190 | this.lwm2mDeviceConfigFormGroup.patchValue({ |
179 | 191 | configurationJson: this.configurationValue |
... | ... | @@ -182,6 +194,29 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
182 | 194 | } |
183 | 195 | } |
184 | 196 | |
197 | + private async defaultProfileConfig(): Promise<Lwm2mProfileConfigModels> { | |
198 | + let bootstrap: ServerSecurityConfig; | |
199 | + let lwm2m: ServerSecurityConfig; | |
200 | + try { | |
201 | + [bootstrap, lwm2m] = await Promise.all([ | |
202 | + this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(true).toPromise(), | |
203 | + this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(false).toPromise() | |
204 | + ]); | |
205 | + } catch (e) { | |
206 | + bootstrap = getDefaultBootstrapServerSecurityConfig(); | |
207 | + lwm2m = getDefaultLwM2MServerSecurityConfig(); | |
208 | + } | |
209 | + return { | |
210 | + observeAttr: getDefaultProfileObserveAttrConfig(), | |
211 | + bootstrap: { | |
212 | + servers: getDefaultBootstrapServersSecurityConfig(), | |
213 | + bootstrapServer: bootstrap, | |
214 | + lwm2mServer: lwm2m | |
215 | + }, | |
216 | + clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig() | |
217 | + }; | |
218 | + } | |
219 | + | |
185 | 220 | private initWriteValue = (): void => { |
186 | 221 | const modelValue = {objectIds: [], objectsList: []} as ModelValue; |
187 | 222 | modelValue.objectIds = this.getObjectsFromJsonAllConfig(); |
... | ... | @@ -209,18 +244,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
209 | 244 | this.lwm2mDeviceProfileFormGroup.patchValue({ |
210 | 245 | objectIds: value, |
211 | 246 | observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), |
212 | - shortId: this.configurationValue.bootstrap.servers.shortId, | |
213 | - lifetime: this.configurationValue.bootstrap.servers.lifetime, | |
214 | - defaultMinPeriod: this.configurationValue.bootstrap.servers.defaultMinPeriod, | |
215 | - notifIfDisabled: this.configurationValue.bootstrap.servers.notifIfDisabled, | |
216 | - binding: this.configurationValue.bootstrap.servers.binding, | |
217 | - bootstrapServer: this.configurationValue.bootstrap.bootstrapServer, | |
218 | - lwm2mServer: this.configurationValue.bootstrap.lwm2mServer, | |
219 | - clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, | |
220 | - fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, | |
221 | - swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, | |
222 | - fwUpdateRecourse: fwResource, | |
223 | - swUpdateRecourse: swResource | |
247 | + bootstrap: this.configurationValue.bootstrap, | |
248 | + clientLwM2mSettings: { | |
249 | + clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, | |
250 | + fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, | |
251 | + swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, | |
252 | + fwUpdateRecourse: fwResource, | |
253 | + swUpdateRecourse: swResource | |
254 | + } | |
224 | 255 | }, |
225 | 256 | {emitEvent: false}); |
226 | 257 | this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = fwResource; |
... | ... | @@ -249,21 +280,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
249 | 280 | private updateDeviceProfileValue(config): void { |
250 | 281 | if (this.lwm2mDeviceProfileFormGroup.valid) { |
251 | 282 | this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry.clientLwM2M); |
252 | - this.configurationValue.bootstrap.bootstrapServer = config.bootstrapServer; | |
253 | - this.configurationValue.bootstrap.lwm2mServer = config.lwm2mServer; | |
254 | - const bootstrapServers = this.configurationValue.bootstrap.servers; | |
255 | - bootstrapServers.shortId = config.shortId; | |
256 | - bootstrapServers.lifetime = config.lifetime; | |
257 | - bootstrapServers.defaultMinPeriod = config.defaultMinPeriod; | |
258 | - bootstrapServers.notifIfDisabled = config.notifIfDisabled; | |
259 | - bootstrapServers.binding = config.binding; | |
260 | - this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect = config.clientOnlyObserveAfterConnect; | |
261 | - this.configurationValue.clientLwM2mSettings.fwUpdateStrategy = config.fwUpdateStrategy; | |
262 | - this.configurationValue.clientLwM2mSettings.swUpdateStrategy = config.swUpdateStrategy; | |
263 | - this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = config.fwUpdateRecourse; | |
264 | - this.configurationValue.clientLwM2mSettings.swUpdateRecourse = config.swUpdateRecourse; | |
265 | - this.upDateJsonAllConfig(); | |
266 | 283 | } |
284 | + this.configurationValue.bootstrap.bootstrapServer = config.bootstrap.bootstrapServer; | |
285 | + this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer; | |
286 | + this.configurationValue.bootstrap.servers = config.bootstrap.servers; | |
287 | + this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings; | |
288 | + this.upDateJsonAllConfig(); | |
267 | 289 | this.updateModel(); |
268 | 290 | } |
269 | 291 | |
... | ... | @@ -539,20 +561,20 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
539 | 561 | |
540 | 562 | private otaUpdateFwStrategyValidate(updated = false): void { |
541 | 563 | if (this.isFwUpdateStrategy) { |
542 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').setValidators([Validators.required]); | |
564 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').setValidators([Validators.required]); | |
543 | 565 | } else { |
544 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').clearValidators(); | |
566 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').clearValidators(); | |
545 | 567 | } |
546 | - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').updateValueAndValidity({emitEvent: updated}); | |
568 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').updateValueAndValidity({emitEvent: updated}); | |
547 | 569 | } |
548 | 570 | |
549 | 571 | private otaUpdateSwStrategyValidate(updated = false): void { |
550 | 572 | if (this.isSwUpdateStrategy) { |
551 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').setValidators([Validators.required]); | |
573 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').setValidators([Validators.required]); | |
552 | 574 | } else { |
553 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').clearValidators(); | |
575 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').clearValidators(); | |
554 | 576 | } |
555 | - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').updateValueAndValidity({emitEvent: updated}); | |
577 | + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').updateValueAndValidity({emitEvent: updated}); | |
556 | 578 | } |
557 | 579 | |
558 | 580 | } | ... | ... |
... | ... | @@ -20,27 +20,24 @@ |
20 | 20 | *ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams"> |
21 | 21 | <div class="vertical-padding" fxLayout="column" fxFill [formGroupName]="i"> |
22 | 22 | <div fxLayout="row" fxFill fxLayoutAlign="start center" [fxShow]="!i"> |
23 | - <div fxFlex="20"> | |
23 | + <div fxFlex="30"> | |
24 | 24 | <mat-label translate>device-profile.lwm2m.resource-label</mat-label> |
25 | 25 | </div> |
26 | - <div fxFlex="10"> | |
26 | + <div fxFlex="10" style="text-align: center"> | |
27 | 27 | <mat-label translate>device-profile.lwm2m.attribute-label</mat-label> |
28 | 28 | </div> |
29 | - <div fxFlex="10"> | |
29 | + <div fxFlex="10" style="text-align: center"> | |
30 | 30 | <mat-label translate>device-profile.lwm2m.telemetry-label</mat-label> |
31 | 31 | </div> |
32 | - <div fxFlex="10"> | |
32 | + <div fxFlex="10" style="text-align: center"> | |
33 | 33 | <mat-label translate>device-profile.lwm2m.observe-label</mat-label> |
34 | 34 | </div> |
35 | 35 | <div fxFlex> |
36 | 36 | <mat-label translate>device-profile.lwm2m.key-name-label</mat-label> |
37 | 37 | </div> |
38 | - <div fxFlex="17" fxFlexOffset="0"> | |
39 | - <mat-label translate>device-profile.lwm2m.attribute-lwm2m-label</mat-label> | |
40 | - </div> | |
41 | 38 | </div> |
42 | 39 | <div fxLayout="row" fxFill fxLayoutAlign="start center"> |
43 | - <div class="resource-name-lw" fxFlex="25" | |
40 | + <div class="resource-name-lw" fxFlex="30" | |
44 | 41 | matTooltip="{{'device-profile.lwm2m.resource-tip' | translate}}" matTooltipPosition="above"> |
45 | 42 | <<b>{{resourceLwM2M.get('id').value}}</b>> <b><i>{{resourceLwM2M.get('name').value}}</i></b> |
46 | 43 | </div> |
... | ... | @@ -65,7 +62,7 @@ |
65 | 62 | matTooltipPosition="above"> |
66 | 63 | </mat-checkbox> |
67 | 64 | </div> |
68 | - <mat-form-field fxFlex="25"> | |
65 | + <mat-form-field fxFlex="33"> | |
69 | 66 | <mat-label *ngIf="resourceLwM2M.get('keyName').hasError('required')"> |
70 | 67 | {{ 'device-profile.lwm2m.key-name-label' | translate }}</mat-label> |
71 | 68 | <input class="resource-name-lw" matInput type="text" formControlName="keyName" required |
... | ... | @@ -77,12 +74,13 @@ |
77 | 74 | <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> |
78 | 75 | </mat-error> |
79 | 76 | </mat-form-field> |
80 | - <div fxFlex="20" class="resource-name-lw-end" fxFlexOffset="5"> | |
77 | + <span fxFlex></span> | |
78 | + <div class="resource-name-lw-end"> | |
81 | 79 | <tb-profile-lwm2m-attributes |
82 | 80 | formControlName="attributeLwm2m" |
83 | - [attributeLwm2m]="resourceLwM2M.get('attributeLwm2m').value" | |
84 | 81 | [isAttributeTelemetry]="disableObserve(i)" |
85 | - [destName]="getNameResourceLwm2m(resourceLwM2M.value)" | |
82 | + isResource="true" | |
83 | + [modelName]="getNameResourceLwm2m(resourceLwM2M.value)" | |
86 | 84 | [disabled]="this.disabled" |
87 | 85 | (updateAttributeLwm2m)="updateAttributeLwm2m($event, i)"> |
88 | 86 | </tb-profile-lwm2m-attributes> | ... | ... |
... | ... | @@ -26,17 +26,15 @@ |
26 | 26 | <div fxFlex class="resource-name-lw-end"> |
27 | 27 | <tb-profile-lwm2m-attributes |
28 | 28 | formControlName="attributeLwm2m" |
29 | - [attributeLwm2m]="objectLwM2M.get('attributeLwm2m').value" | |
30 | 29 | [isAttributeTelemetry]="disableObserveObject(i)" |
31 | - [destName]="getNameObjectLwm2m( objectLwM2M.get('name').value, objectLwM2M.get('keyId').value)" | |
30 | + [modelName]="getNameObjectLwm2m( objectLwM2M.get('name').value, objectLwM2M.get('keyId').value)" | |
32 | 31 | [disabled]="this.disabled" |
33 | 32 | (updateAttributeLwm2m)="updateAttributeLwm2mObject($event, objectLwM2M.get('keyId').value)"> |
34 | 33 | </tb-profile-lwm2m-attributes> |
35 | 34 | </div> |
36 | 35 | </mat-panel-title> |
37 | - <mat-panel-description fxFlex="5" fxLayoutAlign="end center" *ngIf="!disabled"> | |
36 | + <mat-panel-description fxFlex="5" fxLayoutAlign="end center" *ngIf="!disabled && objectLwM2M.get('multiple').value"> | |
38 | 37 | <button type="button" |
39 | - [fxShow]="objectLwM2M.get('multiple').value" | |
40 | 38 | mat-button mat-icon-button (click)="addInstances($event, objectLwM2M.value)" |
41 | 39 | matTooltip="{{'device-profile.lwm2m.add-instances-tip' | translate}}" |
42 | 40 | matTooltipPosition="above"> |
... | ... | @@ -56,7 +54,7 @@ |
56 | 54 | <mat-panel-title> |
57 | 55 | <div class="tb-panel-title-height" fxFlex="100"> |
58 | 56 | <div fxLayout="row" fxFill> |
59 | - <div fxFlex="22"> | |
57 | + <div fxFlex="30"> | |
60 | 58 | {{'device-profile.lwm2m.instance-label' | translate}} <<b>{{instances.get('id').value}}</b>> |
61 | 59 | </div> |
62 | 60 | <div class="checkbox-padding" fxFlex="10"> |
... | ... | @@ -95,14 +93,13 @@ |
95 | 93 | matTooltipPosition="above"> |
96 | 94 | </mat-checkbox> |
97 | 95 | </div> |
98 | - <div fxFlex="10"> | |
96 | + <div fxFlex="7"> | |
99 | 97 | </div> |
100 | 98 | <div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5"> |
101 | 99 | <tb-profile-lwm2m-attributes |
102 | 100 | formControlName="attributeLwm2m" |
103 | - [attributeLwm2m]="instances.get('attributeLwm2m').value" | |
104 | 101 | [isAttributeTelemetry]="disableObserveInstance(instances)" |
105 | - [destName]="getNameInstanceLwm2m(instances.value, objectLwM2M.get('keyId').value)" | |
102 | + [modelName]="getNameInstanceLwm2m(instances.value, objectLwM2M.get('keyId').value)" | |
106 | 103 | [disabled]="this.disabled" |
107 | 104 | (updateAttributeLwm2m)="updateAttributeLwm2mInstance($event, y, objectLwM2M.get('keyId').value)"> |
108 | 105 | </tb-profile-lwm2m-attributes> | ... | ... |
... | ... | @@ -14,6 +14,8 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | +import { ValidatorFn, Validators } from '@angular/forms'; | |
18 | + | |
17 | 19 | export const PAGE_SIZE_LIMIT = 50; |
18 | 20 | export const INSTANCES = 'instances'; |
19 | 21 | export const INSTANCE = 'instance'; |
... | ... | @@ -39,7 +41,7 @@ export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0; |
39 | 41 | export const LEN_MAX_PUBLIC_KEY_RPK = 182; |
40 | 42 | export const LEN_MAX_PUBLIC_KEY_X509 = 3000; |
41 | 43 | export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; |
42 | -export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/; | |
44 | +export const KEY_REGEXP_NUMBER = /^(-?|\+?)\d*$/; | |
43 | 45 | export const INSTANCES_ID_VALUE_MIN = 0; |
44 | 46 | export const INSTANCES_ID_VALUE_MAX = 65535; |
45 | 47 | export const DEFAULT_OTA_UPDATE_PROTOCOL = 'coap://'; |
... | ... | @@ -47,7 +49,7 @@ export const DEFAULT_FW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_ |
47 | 49 | export const DEFAULT_SW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC; |
48 | 50 | |
49 | 51 | |
50 | -export enum BINDING_MODE { | |
52 | +export enum BingingMode { | |
51 | 53 | U = 'U', |
52 | 54 | UQ = 'UQ', |
53 | 55 | T = 'T', |
... | ... | @@ -60,58 +62,43 @@ export enum BINDING_MODE { |
60 | 62 | TQS = 'TQS' |
61 | 63 | } |
62 | 64 | |
63 | -export const BINDING_MODE_NAMES = new Map<BINDING_MODE, string>( | |
65 | +export const BingingModeTranslationsMap = new Map<BingingMode, string>( | |
64 | 66 | [ |
65 | - [BINDING_MODE.U, 'U: UDP connection in standard mode'], | |
66 | - [BINDING_MODE.UQ, 'UQ: UDP connection in queue mode'], | |
67 | - [BINDING_MODE.US, 'US: both UDP and SMS connections active, both in standard mode'], | |
68 | - [BINDING_MODE.UQS, 'UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode'], | |
69 | - [BINDING_MODE.T, 'T: TCP connection in standard mode'], | |
70 | - [BINDING_MODE.TQ, 'TQ: TCP connection in queue mode'], | |
71 | - [BINDING_MODE.TS, 'TS: both TCP and SMS connections active, both in standard mode'], | |
72 | - [BINDING_MODE.TQS, 'TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode'], | |
73 | - [BINDING_MODE.S, 'S: SMS connection in standard mode'], | |
74 | - [BINDING_MODE.SQ, 'SQ: SMS connection in queue mode'] | |
67 | + [BingingMode.U, 'device-profile.lwm2m.binding-type.u'], | |
68 | + [BingingMode.UQ, 'device-profile.lwm2m.binding-type.uq'], | |
69 | + [BingingMode.US, 'device-profile.lwm2m.binding-type.us'], | |
70 | + [BingingMode.UQS, 'device-profile.lwm2m.binding-type.uqs'], | |
71 | + [BingingMode.T, 'device-profile.lwm2m.binding-type.t'], | |
72 | + [BingingMode.TQ, 'device-profile.lwm2m.binding-type.tq'], | |
73 | + [BingingMode.TS, 'device-profile.lwm2m.binding-type.ts'], | |
74 | + [BingingMode.TQS, 'device-profile.lwm2m.binding-type.tqs'], | |
75 | + [BingingMode.S, 'device-profile.lwm2m.binding-type.s'], | |
76 | + [BingingMode.SQ, 'device-profile.lwm2m.binding-type.sq'] | |
75 | 77 | ] |
76 | 78 | ); |
77 | - | |
78 | -export enum ATTRIBUTE_LWM2M_ENUM { | |
79 | - dim = 'dim', | |
80 | - ver = 'ver', | |
79 | +// TODO: wait release Leshan for issues: https://github.com/eclipse/leshan/issues/1026 | |
80 | +export enum AttributeName { | |
81 | 81 | pmin = 'pmin', |
82 | 82 | pmax = 'pmax', |
83 | 83 | gt = 'gt', |
84 | 84 | lt = 'lt', |
85 | 85 | st = 'st' |
86 | + // epmin = 'epmin', | |
87 | + // epmax = 'epmax' | |
86 | 88 | } |
87 | 89 | |
88 | -export const ATTRIBUTE_LWM2M_LABEL = new Map<ATTRIBUTE_LWM2M_ENUM, string>( | |
90 | +export const AttributeNameTranslationMap = new Map<AttributeName, string>( | |
89 | 91 | [ |
90 | - [ATTRIBUTE_LWM2M_ENUM.dim, 'dim='], | |
91 | - [ATTRIBUTE_LWM2M_ENUM.ver, 'ver='], | |
92 | - [ATTRIBUTE_LWM2M_ENUM.pmin, 'pmin='], | |
93 | - [ATTRIBUTE_LWM2M_ENUM.pmax, 'pmax='], | |
94 | - [ATTRIBUTE_LWM2M_ENUM.gt, '>'], | |
95 | - [ATTRIBUTE_LWM2M_ENUM.lt, '<'], | |
96 | - [ATTRIBUTE_LWM2M_ENUM.st, 'st='] | |
97 | - ] | |
98 | -); | |
99 | - | |
100 | -export const ATTRIBUTE_LWM2M_MAP = new Map<ATTRIBUTE_LWM2M_ENUM, string>( | |
101 | - [ | |
102 | - [ATTRIBUTE_LWM2M_ENUM.dim, 'Dimension'], | |
103 | - [ATTRIBUTE_LWM2M_ENUM.ver, 'Object version'], | |
104 | - [ATTRIBUTE_LWM2M_ENUM.pmin, 'Minimum period'], | |
105 | - [ATTRIBUTE_LWM2M_ENUM.pmax, 'Maximum period'], | |
106 | - [ATTRIBUTE_LWM2M_ENUM.gt, 'Greater than'], | |
107 | - [ATTRIBUTE_LWM2M_ENUM.lt, 'Lesser than'], | |
108 | - [ATTRIBUTE_LWM2M_ENUM.st, 'Step'], | |
109 | - | |
92 | + [AttributeName.pmin, 'device-profile.lwm2m.attributes-name.min-period'], | |
93 | + [AttributeName.pmax, 'device-profile.lwm2m.attributes-name.max-period'], | |
94 | + [AttributeName.gt, 'device-profile.lwm2m.attributes-name.greater-than'], | |
95 | + [AttributeName.lt, 'device-profile.lwm2m.attributes-name.less-than'], | |
96 | + [AttributeName.st, 'device-profile.lwm2m.attributes-name.step'], | |
97 | + // [AttributeName.epmin, 'device-profile.lwm2m.attributes-name.min-evaluation-period'], | |
98 | + // [AttributeName.epmax, 'device-profile.lwm2m.attributes-name.max-evaluation-period'] | |
110 | 99 | ] |
111 | 100 | ); |
112 | 101 | |
113 | -export const ATTRIBUTE_KEYS = Object.keys(ATTRIBUTE_LWM2M_ENUM) as string[]; | |
114 | - | |
115 | 102 | export enum securityConfigMode { |
116 | 103 | PSK = 'PSK', |
117 | 104 | RPK = 'RPK', |
... | ... | @@ -143,18 +130,20 @@ export interface BootstrapServersSecurityConfig { |
143 | 130 | |
144 | 131 | export interface ServerSecurityConfig { |
145 | 132 | host?: string; |
146 | - securityHost?: string; | |
147 | 133 | port?: number; |
148 | - securityPort?: number; | |
149 | 134 | securityMode: securityConfigMode; |
150 | - clientPublicKeyOrId?: string; | |
151 | - clientSecretKey?: string; | |
152 | 135 | serverPublicKey?: string; |
153 | 136 | clientHoldOffTime?: number; |
154 | 137 | serverId?: number; |
155 | 138 | bootstrapServerAccountTimeout: number; |
156 | 139 | } |
157 | 140 | |
141 | +export interface ServerSecurityConfigInfo extends ServerSecurityConfig { | |
142 | + securityHost?: string; | |
143 | + securityPort?: number; | |
144 | + bootstrapServerIs: boolean; | |
145 | +} | |
146 | + | |
158 | 147 | interface BootstrapSecurityConfig { |
159 | 148 | servers: BootstrapServersSecurityConfig; |
160 | 149 | bootstrapServer: ServerSecurityConfig; |
... | ... | @@ -180,7 +169,7 @@ export interface ObservableAttributes { |
180 | 169 | attribute: string[]; |
181 | 170 | telemetry: string[]; |
182 | 171 | keyName: {}; |
183 | - attributeLwm2m: {}; | |
172 | + attributeLwm2m?: AttributesNameValueMap; | |
184 | 173 | } |
185 | 174 | |
186 | 175 | export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { |
... | ... | @@ -193,9 +182,9 @@ export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecu |
193 | 182 | }; |
194 | 183 | } |
195 | 184 | |
196 | -export function getDefaultBootstrapServerSecurityConfig(hostname: string): ServerSecurityConfig { | |
185 | +export function getDefaultBootstrapServerSecurityConfig(): ServerSecurityConfig { | |
197 | 186 | return { |
198 | - host: hostname, | |
187 | + host: DEFAULT_LOCAL_HOST_NAME, | |
199 | 188 | port: DEFAULT_PORT_BOOTSTRAP_NO_SEC, |
200 | 189 | securityMode: securityConfigMode.NO_SEC, |
201 | 190 | serverPublicKey: '', |
... | ... | @@ -205,22 +194,14 @@ export function getDefaultBootstrapServerSecurityConfig(hostname: string): Serve |
205 | 194 | }; |
206 | 195 | } |
207 | 196 | |
208 | -export function getDefaultLwM2MServerSecurityConfig(hostname): ServerSecurityConfig { | |
209 | - const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(hostname); | |
197 | +export function getDefaultLwM2MServerSecurityConfig(): ServerSecurityConfig { | |
198 | + const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(); | |
210 | 199 | DefaultLwM2MServerSecurityConfig.port = DEFAULT_PORT_SERVER_NO_SEC; |
211 | 200 | DefaultLwM2MServerSecurityConfig.serverId = DEFAULT_ID_SERVER; |
212 | 201 | return DefaultLwM2MServerSecurityConfig; |
213 | 202 | } |
214 | 203 | |
215 | -function getDefaultProfileBootstrapSecurityConfig(hostname: any): BootstrapSecurityConfig { | |
216 | - return { | |
217 | - servers: getDefaultBootstrapServersSecurityConfig(), | |
218 | - bootstrapServer: getDefaultBootstrapServerSecurityConfig(hostname), | |
219 | - lwm2mServer: getDefaultLwM2MServerSecurityConfig(hostname) | |
220 | - }; | |
221 | -} | |
222 | - | |
223 | -function getDefaultProfileObserveAttrConfig(): ObservableAttributes { | |
204 | +export function getDefaultProfileObserveAttrConfig(): ObservableAttributes { | |
224 | 205 | return { |
225 | 206 | observe: [], |
226 | 207 | attribute: [], |
... | ... | @@ -230,15 +211,7 @@ function getDefaultProfileObserveAttrConfig(): ObservableAttributes { |
230 | 211 | }; |
231 | 212 | } |
232 | 213 | |
233 | -export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModels { | |
234 | - return { | |
235 | - clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(), | |
236 | - observeAttr: getDefaultProfileObserveAttrConfig(), | |
237 | - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_LOCAL_HOST_NAME) | |
238 | - }; | |
239 | -} | |
240 | - | |
241 | -function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { | |
214 | +export function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { | |
242 | 215 | return { |
243 | 216 | clientOnlyObserveAfterConnect: 1, |
244 | 217 | fwUpdateStrategy: 1, |
... | ... | @@ -255,12 +228,12 @@ export interface ResourceLwM2M { |
255 | 228 | attribute: boolean; |
256 | 229 | telemetry: boolean; |
257 | 230 | keyName: string; |
258 | - attributeLwm2m?: {}; | |
231 | + attributeLwm2m?: AttributesNameValueMap; | |
259 | 232 | } |
260 | 233 | |
261 | 234 | export interface Instance { |
262 | 235 | id: number; |
263 | - attributeLwm2m?: {}; | |
236 | + attributeLwm2m?: AttributesNameValueMap; | |
264 | 237 | resources: ResourceLwM2M[]; |
265 | 238 | } |
266 | 239 | |
... | ... | @@ -271,12 +244,33 @@ export interface Instance { |
271 | 244 | * mandatory == false => Optional |
272 | 245 | */ |
273 | 246 | export interface ObjectLwM2M { |
274 | - | |
275 | 247 | id: number; |
276 | 248 | keyId: string; |
277 | 249 | name: string; |
278 | 250 | multiple?: boolean; |
279 | 251 | mandatory?: boolean; |
280 | - attributeLwm2m?: {}; | |
252 | + attributeLwm2m?: AttributesNameValueMap; | |
281 | 253 | instances?: Instance []; |
282 | 254 | } |
255 | + | |
256 | +export type AttributesNameValueMap = { | |
257 | + [key in AttributeName]?: number; | |
258 | +}; | |
259 | + | |
260 | +export interface AttributesNameValue { | |
261 | + name: AttributeName; | |
262 | + value: number; | |
263 | +} | |
264 | + | |
265 | +export function valueValidatorByAttributeName(attributeName: AttributeName): ValidatorFn[] { | |
266 | + const validators = [Validators.required]; | |
267 | + switch (attributeName) { | |
268 | + case AttributeName.pmin: | |
269 | + case AttributeName.pmax: | |
270 | + // case AttributeName.epmin: | |
271 | + // case AttributeName.epmax: | |
272 | + validators.push(Validators.min(0), Validators.pattern('[0-9]*')); | |
273 | + break; | |
274 | + } | |
275 | + return validators; | |
276 | +} | ... | ... |
... | ... | @@ -1231,36 +1231,53 @@ |
1231 | 1231 | "attribute-label": "Attribute", |
1232 | 1232 | "telemetry-label": "Telemetry", |
1233 | 1233 | "key-name-label": "Key Name", |
1234 | - "attribute-lwm2m-label": "AttrLwm2m", | |
1235 | 1234 | "resource-tip": "ID & Original Name of the Resource (only Operations isReadable)", |
1236 | 1235 | "is-observe-tip": "Is Observe", |
1237 | 1236 | "not-observe-tip": "To observe select telemetry or attributes first", |
1238 | 1237 | "is-attr-tip": "Is Attribute", |
1239 | 1238 | "is-telemetry-tip": "Is Telemetry", |
1240 | 1239 | "key-name-tip": "Key Name in Camel format", |
1241 | - "attribute-lwm2m-tip": "Attributes Lwm2m", | |
1242 | - "attribute-lwm2m-disable-tip": "To edit Attributes Lwm2m select telemetry or attributes first", | |
1243 | - "valid-attribute-lwm2m-key": "Name have be only '{{attrEnums}}'", | |
1244 | - "valid-attribute-lwm2m-value": "Value have be Long, Double and greater than zero or null/empty", | |
1245 | - "no-data": "No attributes", | |
1240 | + "edit-attributes-select": "To edit attributes select telemetry or attributes", | |
1241 | + "no-attributes-set": "No attributes set", | |
1246 | 1242 | "key-name": "Key Name", |
1247 | - "attribute-lwm2m-name": "Name attribute", | |
1248 | - "attribute-lwm2m-value": "Value", | |
1249 | - "attribute-lwm2m-toolbar-edit": "Edit attributes Lwm2m", | |
1250 | - "attribute-lwm2m-toolbar-view": "View Attributes Lwm2m", | |
1251 | - "attribute-lwm2m-destination": "Destination:", | |
1252 | - "attribute-lwm2m-add-tip": "Add attribute lwm2m", | |
1253 | - "attribute-lwm2m-remove-tip": "Remove attribute lwm2m", | |
1254 | - "required": " value is required.", | |
1243 | + "attribute-name": "Name attribute", | |
1244 | + "attribute-name-required": "Name attribute is required.", | |
1245 | + "attribute-value": "Attribute value", | |
1246 | + "attribute-value-required": "Attribute value is required.", | |
1247 | + "attribute-value-pattern": "Attribute value must be a positive integer.", | |
1248 | + "edit-attributes": "Edit attributes: {{ name }}", | |
1249 | + "view-attributes": "View attributes: {{ name }}", | |
1250 | + "add-attribute": "Add attribute", | |
1251 | + "edit-attribute": "Edit attribute", | |
1252 | + "view-attribute": "View attribute", | |
1253 | + "remove-attribute": "Remove attribute", | |
1255 | 1254 | "mode": "Security config mode", |
1256 | 1255 | "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }", |
1257 | 1256 | "servers": "Servers", |
1258 | 1257 | "short-id": "Short ID", |
1259 | 1258 | "short-id-required": "Short ID is required.", |
1260 | - "lifetime": "Lifetime of the registration for this LwM2M client", | |
1261 | - "default-min-period": "Minimum Period between two notifications (sec)", | |
1262 | - "notif-if-disabled": "Notification Storing When Disabled or Offline", | |
1259 | + "short-id-range": "Short ID should be in a range from 1 to 65534.", | |
1260 | + "short-id-pattern": "Short ID must be a positive integer.", | |
1261 | + "lifetime": "Client registration lifetime", | |
1262 | + "lifetime-required": "Client registration lifetime is required.", | |
1263 | + "lifetime-pattern": "Client registration lifetime must be a positive integer.", | |
1264 | + "default-min-period": "Minimum period between two notifications (s)", | |
1265 | + "default-min-period-required": "Minimum period is required.", | |
1266 | + "default-min-period-pattern": "Minimum period must be a positive integer.", | |
1267 | + "notification-storing": "Notification storing when disabled or offline", | |
1263 | 1268 | "binding": "Binding", |
1269 | + "binding-type": { | |
1270 | + "u": "U: UDP connection in standard mode", | |
1271 | + "uq": "UQ: UDP connection in queue mode", | |
1272 | + "us": "US: both UDP and SMS connections active, both in standard mode", | |
1273 | + "uqs": "UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode", | |
1274 | + "t": "T: TCP connection in standard mode", | |
1275 | + "tq": "TQ: TCP connection in queue mode", | |
1276 | + "ts": "TS: both TCP and SMS connections active, both in standard mode", | |
1277 | + "tqs": "TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode", | |
1278 | + "s": "S: SMS connection in standard mode", | |
1279 | + "sq": "SQ: SMS connection in queue mode" | |
1280 | + }, | |
1264 | 1281 | "bootstrap-tab": "Bootstrap", |
1265 | 1282 | "bootstrap-server": "Bootstrap Server", |
1266 | 1283 | "lwm2m-server": "LwM2M Server", |
... | ... | @@ -1268,15 +1285,19 @@ |
1268 | 1285 | "server-host-required": "Host is required.", |
1269 | 1286 | "server-port": "Port", |
1270 | 1287 | "server-port-required": "Port is required.", |
1288 | + "server-port-pattern": "Port must be a positive integer.", | |
1289 | + "server-port-range": "Port should be in a range from 1 to 65535.", | |
1271 | 1290 | "server-public-key": "Server Public Key", |
1272 | 1291 | "server-public-key-required": "Server Public Key is required.", |
1273 | 1292 | "server-public-key-pattern": "Server Public Key must be hex decimal format.", |
1274 | 1293 | "server-public-key-length": "Server Public Key must be {{ count }} characters.", |
1275 | 1294 | "client-hold-off-time": "Hold Off Time", |
1276 | 1295 | "client-hold-off-time-required": "Hold Off Time is required.", |
1296 | + "client-hold-off-time-pattern": "Hold Off Time must be a positive integer.", | |
1277 | 1297 | "client-hold-off-time-tooltip": "Client Hold Off Time for use with a Bootstrap-Server only", |
1278 | 1298 | "account-after-timeout": "Account after the timeout", |
1279 | 1299 | "account-after-timeout-required": "Account after the timeout is required.", |
1300 | + "account-after-timeout-pattern": "Account after the timeout must be a positive integer.", | |
1280 | 1301 | "account-after-timeout-tooltip": "Bootstrap-Server Account after the timeout value given by this resource.", |
1281 | 1302 | "others-tab": "Other settings", |
1282 | 1303 | "client-strategy": "Client strategy when connecting", |
... | ... | @@ -1296,7 +1317,16 @@ |
1296 | 1317 | "fw-update-recourse-required": "Firmware update CoAP recourse is required.", |
1297 | 1318 | "sw-update-recourse": "Software update CoAP recourse", |
1298 | 1319 | "sw-update-recourse-required": "Software update CoAP recourse is required.", |
1299 | - "config-json-tab": "Json Config Profile Device" | |
1320 | + "config-json-tab": "Json Config Profile Device", | |
1321 | + "attributes-name": { | |
1322 | + "min-period": "Minimum period", | |
1323 | + "max-period": "Maximum period", | |
1324 | + "greater-than": "Greater than", | |
1325 | + "less-than": "Less than", | |
1326 | + "step": "Step", | |
1327 | + "min-evaluation-period": "Minimum evaluation period", | |
1328 | + "max-evaluation-period": "Maximum evaluation period" | |
1329 | + } | |
1300 | 1330 | }, |
1301 | 1331 | "snmp": { |
1302 | 1332 | "add-communication-config": "Add communication config", | ... | ... |