Commit c9b83a4c88bfaafa3943232bd72266008c37767d
1 parent
74d2a3eb
UI: Refactoring LWM2M device profile transport configuration attributes settings
Showing
13 changed files
with
381 additions
and
355 deletions
common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java
... | ... | @@ -24,10 +24,10 @@ public class ObjectAttributes { |
24 | 24 | |
25 | 25 | private Long dim; |
26 | 26 | private String ver; |
27 | - private Long pmin; | |
28 | - private Long pmax; | |
29 | - private Double gt; | |
30 | - private Double lt; | |
31 | - private Double st; | |
27 | + private Integer pmin; | |
28 | + private Integer pmax; | |
29 | + private Float gt; | |
30 | + private Float lt; | |
31 | + private Float st; | |
32 | 32 | |
33 | 33 | } | ... | ... |
... | ... | @@ -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 | |
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 | } | ... | ... |
... | ... | @@ -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'; |
... | ... | @@ -74,44 +76,29 @@ export const BingingModeTranslationsMap = new Map<BingingMode, string>( |
74 | 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', |
... | ... | @@ -182,7 +169,7 @@ export interface ObservableAttributes { |
182 | 169 | attribute: string[]; |
183 | 170 | telemetry: string[]; |
184 | 171 | keyName: {}; |
185 | - attributeLwm2m: {}; | |
172 | + attributeLwm2m?: AttributesNameValueMap; | |
186 | 173 | } |
187 | 174 | |
188 | 175 | export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { |
... | ... | @@ -241,12 +228,12 @@ export interface ResourceLwM2M { |
241 | 228 | attribute: boolean; |
242 | 229 | telemetry: boolean; |
243 | 230 | keyName: string; |
244 | - attributeLwm2m?: {}; | |
231 | + attributeLwm2m?: AttributesNameValueMap; | |
245 | 232 | } |
246 | 233 | |
247 | 234 | export interface Instance { |
248 | 235 | id: number; |
249 | - attributeLwm2m?: {}; | |
236 | + attributeLwm2m?: AttributesNameValueMap; | |
250 | 237 | resources: ResourceLwM2M[]; |
251 | 238 | } |
252 | 239 | |
... | ... | @@ -257,12 +244,33 @@ export interface Instance { |
257 | 244 | * mandatory == false => Optional |
258 | 245 | */ |
259 | 246 | export interface ObjectLwM2M { |
260 | - | |
261 | 247 | id: number; |
262 | 248 | keyId: string; |
263 | 249 | name: string; |
264 | 250 | multiple?: boolean; |
265 | 251 | mandatory?: boolean; |
266 | - attributeLwm2m?: {}; | |
252 | + attributeLwm2m?: AttributesNameValueMap; | |
267 | 253 | instances?: Instance []; |
268 | 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,27 +1231,26 @@ |
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", |
... | ... | @@ -1318,7 +1317,16 @@ |
1318 | 1317 | "fw-update-recourse-required": "Firmware update CoAP recourse is required.", |
1319 | 1318 | "sw-update-recourse": "Software update CoAP recourse", |
1320 | 1319 | "sw-update-recourse-required": "Software update CoAP recourse is required.", |
1321 | - "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 | + } | |
1322 | 1330 | }, |
1323 | 1331 | "snmp": { |
1324 | 1332 | "add-communication-config": "Add communication config", | ... | ... |