Commit c9b83a4c88bfaafa3943232bd72266008c37767d

Authored by Vladyslav_Prykhodko
1 parent 74d2a3eb

UI: Refactoring LWM2M device profile transport configuration attributes settings

... ... @@ -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 {
... ...
... ... @@ -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>
... ...
  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",
... ...