Commit c634859e906d866b09b571cab27e69abc89a1e57

Authored by nickAS21
Committed by GitHub
1 parent 1b531e28

lwm2m: front start add attributes Lwm2m (#4388)

* lwm2m: front start add attributes Lwm2m

* lwm2m: front finish add attributes Lwm2m for resources

* lwm2m: front add attributes Lwm2m for resources if isAttribte or isTelemetry

* lwm2m: front add attributes Lwm2m for objects if isAttribte or isTelemetry

* lwm2m: back add resource one

* lwm2m: back fix bug resource controller test
Showing 27 changed files with 1065 additions and 179 deletions
... ... @@ -30,7 +30,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
30 30 import org.springframework.web.bind.annotation.RestController;
31 31 import org.thingsboard.server.common.data.TbResource;
32 32 import org.thingsboard.server.common.data.TbResourceInfo;
33   -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
34 33 import org.thingsboard.server.common.data.exception.ThingsboardException;
35 34 import org.thingsboard.server.common.data.id.TbResourceId;
36 35 import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
... ... @@ -42,10 +41,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
42 41 import org.thingsboard.server.service.security.permission.Operation;
43 42 import org.thingsboard.server.service.security.permission.Resource;
44 43
45   -import java.util.ArrayList;
46 44 import java.util.Base64;
47 45 import java.util.List;
48   -import java.util.StringJoiner;
49 46
50 47 @Slf4j
51 48 @RestController
... ... @@ -111,25 +108,13 @@ public class TbResourceController extends BaseController {
111 108 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
112 109 @RequestMapping(value = "/resource", method = RequestMethod.POST)
113 110 @ResponseBody
114   - public List<TbResource> saveResources(@RequestBody List<TbResource> resources) throws ThingsboardException {
  111 + public TbResource saveResource(@RequestBody TbResource resource) throws ThingsboardException {
115 112 try {
116   - List<TbResource> addResources = new ArrayList<>();
117   - StringJoiner noSaveResources = new StringJoiner("; ");
118   - resources.forEach(resource -> {
119   - try {
120 113 resource.setTenantId(getTenantId());
121 114 checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
122   - addResources.add(addResource(resource));
123   - } catch (Exception e) {
124   - noSaveResources.add(resource.getFileName());
125   - log.warn("Fail save resource: [{}]", resource.getFileName(), e);
  115 + return addResource(resource);
126 116 }
127   - });
128   - if (noSaveResources.length() > 0) {
129   - throw new ThingsboardException(String.format("Fail save resource: %s", noSaveResources.toString()), ThingsboardErrorCode.INVALID_ARGUMENTS);
130   - }
131   - return addResources;
132   - } catch (Exception e) {
  117 + catch (Exception e) {
133 118 throw handleException(e);
134 119 }
135 120 }
... ...
... ... @@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.security.Authority;
32 32 import java.util.ArrayList;
33 33 import java.util.Collections;
34 34 import java.util.List;
35   -import java.util.stream.Collectors;
36 35
37 36 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
38 37
... ... @@ -110,7 +109,7 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes
110 109 TbResource savedResource = save(resource);
111 110
112 111 loginDifferentTenant();
113   - doPostWithTypedResponse("/api/resource", Collections.singletonList(savedResource), new TypeReference<>(){}, status().isBadRequest());
  112 + doPostWithTypedResponse("/api/resource", savedResource, new TypeReference<>(){}, status().isForbidden());
114 113 deleteDifferentTenant();
115 114 }
116 115
... ... @@ -148,18 +147,15 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes
148 147
149 148 @Test
150 149 public void testFindTenantTbResources() throws Exception {
151   - List<TbResource> resourcesToSave = new ArrayList<>();
  150 + List<TbResourceInfo> resources = new ArrayList<>();
152 151 for (int i = 0; i < 173; i++) {
153 152 TbResource resource = new TbResource();
154 153 resource.setTitle("Resource" + i);
155 154 resource.setResourceType(ResourceType.JKS);
156 155 resource.setFileName(i + DEFAULT_FILE_NAME);
157 156 resource.setData("Test Data");
158   - resourcesToSave.add(resource);
  157 + resources.add(new TbResourceInfo(save(resource)));
159 158 }
160   -
161   - List<TbResourceInfo> resources =save(resourcesToSave).stream().map(TbResourceInfo::new).collect(Collectors.toList());
162   -
163 159 List<TbResourceInfo> loadedResources = new ArrayList<>();
164 160 PageLink pageLink = new PageLink(24);
165 161 PageData<TbResourceInfo> pageData;
... ... @@ -287,10 +283,6 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes
287 283 }
288 284
289 285 private TbResource save(TbResource tbResource) throws Exception {
290   - return save(Collections.singletonList(tbResource)).get(0);
291   - }
292   -
293   - private List<TbResource> save(List<TbResource> tbResources) throws Exception {
294   - return doPostWithTypedResponse("/api/resource", tbResources, new TypeReference<>(){});
  286 + return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>(){});
295 287 }
296 288 }
... ...
... ... @@ -28,6 +28,7 @@ import java.util.Arrays;
28 28 @ClasspathSuite.ClassnameFilters({
29 29 // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
30 30 // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
  31 +// "org.thingsboard.server.controller.sql.TbResourceControllerSqlTest",
31 32 "org.thingsboard.server.controller.sql.*Test",
32 33 })
33 34 public class ControllerSqlTestSuite {
... ...
... ... @@ -122,7 +122,7 @@ public class LwM2mTransportRequest {
122 122 DownlinkRequest request = null;
123 123 ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null;
124 124 LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
125   - ResourceModel resource = lwM2MClient.getResourceModel(target);
  125 + ResourceModel resource = lwM2MClient.getResourceModel(targetIdVer);
126 126 timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
127 127 switch (typeOper) {
128 128 case GET_TYPE_OPER_READ:
... ...
... ... @@ -22,7 +22,7 @@ import {Observable} from 'rxjs';
22 22 import {PageData} from '@shared/models/page/page-data';
23 23 import {DeviceProfile, DeviceProfileInfo, DeviceTransportType} from '@shared/models/device.models';
24 24 import {isDefinedAndNotNull, isEmptyStr} from '@core/utils';
25   -import {ObjectLwM2M, ServerSecurityConfig} from '@home/components/profile/device/lwm2m/profile-config.models';
  25 +import {ObjectLwM2M, ServerSecurityConfig} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
26 26 import {SortOrder} from '@shared/models/page/sort-order';
27 27
28 28 @Injectable({
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form [formGroup]="attributeLwm2mDialogFormGroup" (ngSubmit)="save()" style="width: 500px;">
  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>
  24 + <span fxFlex></span>
  25 + <button mat-icon-button
  26 + (click)="cancel()"
  27 + type="button">
  28 + <mat-icon class="material-icons">close</mat-icon>
  29 + </button>
  30 + </mat-toolbar>
  31 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  32 + </mat-progress-bar>
  33 + <div mat-dialog-content>
  34 + <tb-lwm2m-attributes-key-list
  35 + formControlName="keyFilters"
  36 + titleText="{{data.destName}}">
  37 + </tb-lwm2m-attributes-key-list>
  38 + </div>
  39 + <div mat-dialog-actions fxLayoutAlign="end center">
  40 + <button mat-button color="primary"
  41 + type="button"
  42 + [disabled]="(isLoading$ | async)"
  43 + (click)="cancel()" cdkFocusInitial>
  44 + {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
  45 + </button>
  46 + <button mat-raised-button color="primary"
  47 + *ngIf="!readonly"
  48 + type="submit"
  49 + [disabled]="(isLoading$ | async) || attributeLwm2mDialogFormGroup.invalid || !attributeLwm2mDialogFormGroup.dirty">
  50 + {{ 'action.save' | translate }}
  51 + </button>
  52 + </div>
  53 +</form>
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import {Component, Inject, OnInit, SkipSelf} from "@angular/core";
  18 +import {ErrorStateMatcher} from "@angular/material/core";
  19 +import {DialogComponent} from "@shared/components/dialog.component";
  20 +import {Store} from "@ngrx/store";
  21 +import {AppState} from "@core/core.state";
  22 +import {Router} from "@angular/router";
  23 +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
  24 +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from "@angular/forms";
  25 +import {TranslateService} from "@ngx-translate/core";
  26 +import {JsonObject} from "@angular/compiler-cli/ngcc/src/packages/entry_point";
  27 +
  28 +export interface Lwm2mAttributesDialogData {
  29 + readonly: boolean;
  30 + attributeLwm2m: JsonObject;
  31 + destName: string
  32 +}
  33 +
  34 +@Component({
  35 + selector: 'tb-lwm2m-attributes-dialog',
  36 + templateUrl: './lwm2m-attributes-dialog.component.html',
  37 + styleUrls: ['./lwm2m-attributes.component.scss'],
  38 + providers: [{provide: ErrorStateMatcher, useExisting: Lwm2mAttributesDialogComponent}],
  39 +})
  40 +export class Lwm2mAttributesDialogComponent extends DialogComponent<Lwm2mAttributesDialogComponent, Object> implements OnInit, ErrorStateMatcher {
  41 +
  42 + readonly = this.data.readonly;
  43 +
  44 + attributeLwm2m = this.data.attributeLwm2m;
  45 +
  46 + submitted = false;
  47 +
  48 + dirtyValue = false;
  49 +
  50 + attributeLwm2mDialogFormGroup: FormGroup;
  51 +
  52 + constructor(protected store: Store<AppState>,
  53 + protected router: Router,
  54 + @Inject(MAT_DIALOG_DATA) public data: Lwm2mAttributesDialogData,
  55 + @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
  56 + public dialogRef: MatDialogRef<Lwm2mAttributesDialogComponent, object>,
  57 + private fb: FormBuilder,
  58 + public translate: TranslateService) {
  59 + super(store, router, dialogRef);
  60 +
  61 + this.attributeLwm2mDialogFormGroup = this.fb.group({
  62 + keyFilters: [{}, []]
  63 + });
  64 + this.attributeLwm2mDialogFormGroup.patchValue({keyFilters: this.attributeLwm2m});
  65 + this.attributeLwm2mDialogFormGroup.get('keyFilters').valueChanges.subscribe((attributes) => {
  66 + this.attributeLwm2m = attributes;
  67 + });
  68 + if (this.readonly) {
  69 + this.attributeLwm2mDialogFormGroup.disable({emitEvent: false});
  70 + }
  71 + }
  72 +
  73 + ngOnInit(): void {
  74 + }
  75 +
  76 + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
  77 + const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
  78 + const customErrorState = !!(control && control.invalid && this.submitted);
  79 + return originalErrorState || customErrorState;
  80 + }
  81 +
  82 + save(): void {
  83 + this.submitted = true;
  84 + this.dialogRef.close(this.attributeLwm2m);
  85 + }
  86 +
  87 + cancel(): void {
  88 + this.dialogRef.close(null);
  89 + }
  90 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<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>
  23 + <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>
  27 + </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">
  32 + <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]) }}
  37 + </mat-option>
  38 + </mat-select>
  39 + </mat-form-field>
  40 + <mat-form-field fxFlex floatLabel="always" hideRequiredMarker class="mat-block"
  41 + style="max-height: 40px;">
  42 + <mat-label></mat-label>
  43 + <input [formControl]="keyValControl.get('value')" matInput
  44 + placeholder="{{ ('key-val.value') | translate }}"/>
  45 + </mat-form-field>
  46 + <button mat-button mat-icon-button color="primary"
  47 + [fxShow]="!disabled"
  48 + type="button"
  49 + (click)="removeKeyVal($index)"
  50 + [disabled]="isLoading$ | async"
  51 + matTooltip="{{ 'device-profile.lwm2m.attribute-lwm2m-remove-tip' | translate }}"
  52 + matTooltipPosition="above">
  53 + <mat-icon>close</mat-icon>
  54 + </button>
  55 + </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 + </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"
  75 + [disabled]="isLoading$ | async"
  76 + (click)="addKeyVal()"
  77 + type="button"
  78 + matTooltip="{{ 'device-profile.lwm2m.attribute-lwm2m-add-tip' | translate }}"
  79 + matTooltipPosition="above">
  80 + {{ 'action.add' | translate }}
  81 + </button>
  82 + </div>
  83 +</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 +
  17 +import {Component, forwardRef, Input, OnInit} from "@angular/core";
  18 +import {
  19 + AbstractControl,
  20 + ControlValueAccessor,
  21 + FormArray,
  22 + FormBuilder,
  23 + FormControl,
  24 + FormGroup,
  25 + NG_VALIDATORS,
  26 + NG_VALUE_ACCESSOR,
  27 + Validator,
  28 + Validators
  29 +} from "@angular/forms";
  30 +import {Subscription} from "rxjs";
  31 +import {PageComponent} from "@shared/components/page.component";
  32 +import {Store} from "@ngrx/store";
  33 +import {AppState} from "@core/core.state";
  34 +import {
  35 + ATTRIBUTE_KEYS,
  36 + ATTRIBUTE_LWM2M_ENUM,
  37 + ATTRIBUTE_LWM2M_MAP
  38 +} from "@home/components/profile/device/lwm2m/lwm2m-profile-config.models";
  39 +import {isDefinedAndNotNull, isEmpty, isEmptyStr, isUndefinedOrNull} from "@core/utils";
  40 +
  41 +
  42 +@Component({
  43 + selector: 'tb-lwm2m-attributes-key-list',
  44 + templateUrl: './lwm2m-attributes-key-list.component.html',
  45 + styleUrls: ['./lwm2m-attributes.component.scss'],
  46 + providers: [
  47 + {
  48 + provide: NG_VALUE_ACCESSOR,
  49 + useExisting: forwardRef(() => Lwm2mAttributesKeyListComponent),
  50 + multi: true
  51 + },
  52 + {
  53 + provide: NG_VALIDATORS,
  54 + useExisting: forwardRef(() => Lwm2mAttributesKeyListComponent),
  55 + multi: true,
  56 + }
  57 + ]
  58 +})
  59 +export class Lwm2mAttributesKeyListComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator {
  60 +
  61 + attrKeys = ATTRIBUTE_KEYS;
  62 +
  63 + attrKey = ATTRIBUTE_LWM2M_ENUM;
  64 +
  65 + attributeLwm2mMap = ATTRIBUTE_LWM2M_MAP;
  66 +
  67 + @Input() disabled: boolean;
  68 +
  69 + @Input() titleText: string;
  70 +
  71 + @Input() noDataText: string;
  72 +
  73 + kvListFormGroup: FormGroup;
  74 +
  75 + private propagateChange = null;
  76 +
  77 + private valueChangeSubscription: Subscription = null;
  78 +
  79 + constructor(protected store: Store<AppState>,
  80 + private fb: FormBuilder) {
  81 + super(store);
  82 + }
  83 +
  84 + ngOnInit(): void {
  85 + this.kvListFormGroup = this.fb.group({});
  86 + this.kvListFormGroup.addControl('keyVals',
  87 + this.fb.array([]));
  88 + }
  89 +
  90 + keyValsFormArray(): FormArray {
  91 + return this.kvListFormGroup.get('keyVals') as FormArray;
  92 + }
  93 +
  94 + registerOnChange(fn: any): void {
  95 + this.propagateChange = fn;
  96 + }
  97 +
  98 + registerOnTouched(fn: any): void {
  99 + }
  100 +
  101 + setDisabledState?(isDisabled: boolean): void {
  102 + this.disabled = isDisabled;
  103 + if (this.disabled) {
  104 + this.kvListFormGroup.disable({emitEvent: false});
  105 + } else {
  106 + this.kvListFormGroup.enable({emitEvent: false});
  107 + }
  108 + }
  109 +
  110 + writeValue(keyValMap: { [key: string]: string }): void {
  111 + if (this.valueChangeSubscription) {
  112 + this.valueChangeSubscription.unsubscribe();
  113 + }
  114 + const keyValsControls: Array<AbstractControl> = [];
  115 + 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 + }
  125 + this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls));
  126 + this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => {
  127 + // this.updateValidate();
  128 + this.updateModel();
  129 + });
  130 + if (this.disabled) {
  131 + this.kvListFormGroup.disable({emitEvent: false});
  132 + } else {
  133 + this.kvListFormGroup.enable({emitEvent: false});
  134 + }
  135 + }
  136 +
  137 + public removeKeyVal(index: number) {
  138 + (this.kvListFormGroup.get('keyVals') as FormArray).removeAt(index);
  139 + }
  140 +
  141 + 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 + }
  161 + }
  162 + return (valid) ? null : {
  163 + keyVals: {
  164 + valid: false,
  165 + },
  166 + };
  167 + }
  168 +
  169 + private updateValidate (c?: FormControl) {
  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 + }
  180 + });
  181 + }
  182 +
  183 + 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 + }
  200 + }
  201 +
  202 +
  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 + };
  210 + }
  211 + }
  212 + return null;
  213 + }
  214 +
  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;
  222 + }
  223 +
  224 + private attributeLwm2mValueValidator = (property: string): Object [] => {
  225 + return property === 'ver'? [] : [this.attributeLwm2mValueNumberValidator];
  226 + }
  227 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div fxLayout="row" [formGroup]="attributeLwm2mFormGroup">
  19 + <div fxFlex fxLayout="column" class="resource-name-lw-end" fxLayoutAlign="center"
  20 + [matTooltip]="isToolTipLabel()" matTooltipPosition="above">
  21 + {{attributeLwm2mToString()}}
  22 + </div>
  23 + <button type="button"
  24 + [disabled]="isDisableBtn()"
  25 + mat-button mat-icon-button
  26 + (click)="editAttributesLwm2m($event)"
  27 + [matTooltip]="(isIconView() ? 'action.view' : isIconEditAdd() ? 'action.edit' : 'action.add' ) | translate"
  28 + matTooltipPosition="above">
  29 + <mat-icon
  30 + class="material-icons">{{isIconView() ? 'visibility' : isIconEditAdd() ? 'edit' : 'add' }}</mat-icon>
  31 + </button>
  32 +</div>
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .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 + }
  27 + }
  28 +}
  29 +
  30 +:host ::ng-deep {
  31 + .mat-form-field-wrapper {
  32 + padding-bottom: 0;
  33 + }
  34 + .mat-form-field-infix {
  35 + border-top: 0;
  36 + }
  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 +}
  61 +
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import {Component, EventEmitter, forwardRef, Inject, Input, Output} from "@angular/core";
  18 +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from "@angular/forms";
  19 +import {coerceBooleanProperty} from "@angular/cdk/coercion";
  20 +import {Store} from "@ngrx/store";
  21 +import {AppState} from "@core/core.state";
  22 +import {DeviceProfileService} from "@core/http/device-profile.service";
  23 +import {WINDOW} from "@core/services/window.service";
  24 +import {deepClone, isDefinedAndNotNull, isEmpty} from "@core/utils";
  25 +import {
  26 + Lwm2mAttributesDialogComponent,
  27 + Lwm2mAttributesDialogData
  28 +} from "@home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component";
  29 +import {MatDialog} from "@angular/material/dialog";
  30 +import {TranslateService} from "@ngx-translate/core";
  31 +import {ATTRIBUTE_LWM2M_LABEL} from "@home/components/profile/device/lwm2m/lwm2m-profile-config.models";
  32 +
  33 +
  34 +@Component({
  35 + selector: 'tb-profile-lwm2m-attributes',
  36 + templateUrl: './lwm2m-attributes.component.html',
  37 + styleUrls: ['./lwm2m-attributes.component.scss'],
  38 + providers: [{
  39 + provide: NG_VALUE_ACCESSOR,
  40 + useExisting: forwardRef(() => Lwm2mAttributesComponent),
  41 + multi: true
  42 + }]
  43 +})
  44 +export class Lwm2mAttributesComponent implements ControlValueAccessor {
  45 + attributeLwm2mFormGroup: FormGroup;
  46 + attributeLwm2mLabel = ATTRIBUTE_LWM2M_LABEL;
  47 +
  48 + private requiredValue: boolean;
  49 + private dirty = false;
  50 +
  51 + @Input()
  52 + attributeLwm2m: {};
  53 +
  54 + @Input()
  55 + isAttributeTelemetry: boolean;
  56 +
  57 + @Input()
  58 + destName: string;
  59 +
  60 + @Input()
  61 + disabled: boolean;
  62 +
  63 + @Output()
  64 + updateAttributeLwm2m = new EventEmitter<any>();
  65 +
  66 + @Input()
  67 + set required(value: boolean) {
  68 + this.requiredValue = coerceBooleanProperty(value);
  69 + }
  70 +
  71 + private propagateChange = (v: any) => {
  72 + }
  73 +
  74 + constructor(private store: Store<AppState>,
  75 + private dialog: MatDialog,
  76 + private fb: FormBuilder,
  77 + private deviceProfileService: DeviceProfileService,
  78 + private translate: TranslateService,
  79 + @Inject(WINDOW) private window: Window) {}
  80 +
  81 + registerOnChange(fn: any): void {
  82 + this.propagateChange = fn;
  83 + }
  84 +
  85 + registerOnTouched(fn: any): void {
  86 + }
  87 +
  88 + setDisabledState(isDisabled: boolean): void {
  89 + this.disabled = isDisabled;
  90 + if (isDisabled) {
  91 + this.attributeLwm2mFormGroup.disable({emitEvent: false});
  92 + } else {
  93 + this.attributeLwm2mFormGroup.enable({emitEvent: false});
  94 + }
  95 + }
  96 +
  97 + ngOnInit() {
  98 + this.attributeLwm2mFormGroup = this.fb.group({
  99 + attributeLwm2m: [this.attributeLwm2m]
  100 + });
  101 + }
  102 +
  103 + writeValue(value: {} | null): void {}
  104 +
  105 + attributeLwm2mToString = (): string => {
  106 + return this.isIconEditAdd () ? this.attributeLwm2mLabelToString() : this.translate.instant('device-profile.lwm2m.no-data');
  107 + }
  108 +
  109 + private attributeLwm2mLabelToString = (): string => {
  110 + let label = JSON.stringify(this.attributeLwm2m);
  111 + label = deepClone(label.replace('{', ''));
  112 + label = deepClone(label.replace('}', ''));
  113 + this.attributeLwm2mLabel.forEach((value: string, key: string) => {
  114 + const dest = '\"' + key + '\"\:';
  115 + label = deepClone(label.replace(dest, value));
  116 + });
  117 + return label;
  118 + }
  119 +
  120 + isDisableBtn (): boolean {
  121 + return this.disabled || this.isAttributeTelemetry ? !(isDefinedAndNotNull(this.attributeLwm2m) &&
  122 + !isEmpty(this.attributeLwm2m) && this.disabled) : this.disabled;
  123 + }
  124 +
  125 + isIconView (): boolean {
  126 + return this.isAttributeTelemetry || this.disabled;
  127 + }
  128 +
  129 + isIconEditAdd (): boolean {
  130 + return isDefinedAndNotNull(this.attributeLwm2m) && !isEmpty(this.attributeLwm2m);
  131 + }
  132 +
  133 + isToolTipLabel (): string {
  134 + return this.disabled ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip') :
  135 + this.isAttributeTelemetry ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-disable-tip') :
  136 + this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip');
  137 + }
  138 +
  139 + public editAttributesLwm2m = ($event: Event): void => {
  140 + if ($event) {
  141 + $event.stopPropagation();
  142 + }
  143 + this.dialog.open<Lwm2mAttributesDialogComponent, Lwm2mAttributesDialogData, Object>(Lwm2mAttributesDialogComponent, {
  144 + disableClose: true,
  145 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  146 + data: {
  147 + readonly: this.disabled,
  148 + attributeLwm2m: this.disabled ? this.attributeLwm2m : deepClone(this.attributeLwm2m),
  149 + destName: this.destName
  150 + }
  151 + }).afterClosed().subscribe((result) => {
  152 + if (result) {
  153 + this.attributeLwm2m = result;
  154 + this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: this.attributeLwm2m});
  155 + this.updateAttributeLwm2m.next(this.attributeLwm2m);
  156 + }
  157 + });
  158 + }
  159 +}
... ...
... ... @@ -14,8 +14,8 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, forwardRef, Inject, Input } from '@angular/core';
18   -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  17 +import {Component, forwardRef, Inject, Input} from '@angular/core';
  18 +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';
19 19 import {
20 20 DEFAULT_CLIENT_HOLD_OFF_TIME,
21 21 DEFAULT_ID_SERVER,
... ... @@ -27,13 +27,13 @@ import {
27 27 SECURITY_CONFIG_MODE,
28 28 SECURITY_CONFIG_MODE_NAMES,
29 29 ServerSecurityConfig
30   -} from './profile-config.models';
31   -import { Store } from '@ngrx/store';
32   -import { AppState } from '@core/core.state';
33   -import { coerceBooleanProperty } from '@angular/cdk/coercion';
34   -import { WINDOW } from '@core/services/window.service';
35   -import { pairwise, startWith } from 'rxjs/operators';
36   -import { DeviceProfileService } from '@core/http/device-profile.service';
  30 +} from './lwm2m-profile-config.models';
  31 +import {Store} from '@ngrx/store';
  32 +import {AppState} from '@core/core.state';
  33 +import {coerceBooleanProperty} from '@angular/cdk/coercion';
  34 +import {WINDOW} from '@core/services/window.service';
  35 +import {pairwise, startWith} from 'rxjs/operators';
  36 +import {DeviceProfileService} from '@core/http/device-profile.service';
37 37
38 38 @Component({
39 39 selector: 'tb-profile-lwm2m-device-config-server',
... ...
... ... @@ -22,19 +22,20 @@ import {AppState} from '@app/core/core.state';
22 22 import {coerceBooleanProperty} from '@angular/cdk/coercion';
23 23 import {
24 24 ATTRIBUTE,
  25 + DEFAULT_BINDING,
25 26 getDefaultProfileConfig,
26 27 INSTANCES,
27 28 KEY_NAME,
  29 + Lwm2mProfileConfigModels,
28 30 ModelValue,
29 31 ObjectLwM2M,
30 32 OBSERVE,
31 33 OBSERVE_ATTR_TELEMETRY,
32   - ProfileConfigModels,
33 34 RESOURCES,
34 35 TELEMETRY
35   -} from './profile-config.models';
  36 +} from './lwm2m-profile-config.models';
36 37 import {DeviceProfileService} from '@core/http/device-profile.service';
37   -import {deepClone, isDefinedAndNotNull, isUndefined} from '@core/utils';
  38 +import {deepClone, isDefinedAndNotNull, isEmpty, isUndefined} from '@core/utils';
38 39 import {WINDOW} from '@core/services/window.service';
39 40 import {JsonArray, JsonObject} from '@angular/compiler-cli/ngcc/src/packages/entry_point';
40 41 import {Direction} from '@shared/models/page/sort-order';
... ... @@ -50,7 +51,7 @@ import {Direction} from '@shared/models/page/sort-order';
50 51 })
51 52 export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators {
52 53
53   - private configurationValue: ProfileConfigModels;
  54 + private configurationValue: Lwm2mProfileConfigModels;
54 55 private requiredValue: boolean;
55 56 private disabled = false;
56 57
... ... @@ -70,7 +71,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
70 71 this.requiredValue = coerceBooleanProperty(value);
71 72 }
72 73
73   - private propagateChange = (v: any) => { }
  74 + private propagateChange = (v: any) => {
  75 + }
74 76
75 77 constructor(private store: Store<AppState>,
76 78 private fb: FormBuilder,
... ... @@ -84,7 +86,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
84 86 lifetime: [null, Validators.required],
85 87 defaultMinPeriod: [null, Validators.required],
86 88 notifIfDisabled: [true, []],
87   - binding: ['U', Validators.required],
  89 + binding: [DEFAULT_BINDING, Validators.required],
88 90 bootstrapServer: [null, Validators.required],
89 91 lwm2mServer: [null, Validators.required],
90 92 });
... ... @@ -118,7 +120,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
118 120 }
119 121 }
120 122
121   - writeValue(value: ProfileConfigModels | null): void {
  123 + writeValue(value: Lwm2mProfileConfigModels | null): void {
122 124 this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value;
123 125 this.lwm2mDeviceConfigFormGroup.patchValue({
124 126 configurationJson: this.configurationValue
... ... @@ -171,7 +173,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
171 173 }
172 174
173 175 private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => {
174   - this.lwm2mDeviceProfileFormGroup.patchValue({
  176 + this.lwm2mDeviceProfileFormGroup.patchValue({
175 177 observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList))
176 178 },
177 179 {emitEvent: false});
... ... @@ -198,15 +200,15 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
198 200 private getObserveAttrTelemetryObjects = (objectList: ObjectLwM2M[]): object => {
199 201 const objectLwM2MS = deepClone(objectList);
200 202 if (this.configurationValue.observeAttr && objectLwM2MS.length > 0) {
201   - const observeArray = this.configurationValue.observeAttr.observe;
202 203 const attributeArray = this.configurationValue.observeAttr.attribute;
203 204 const telemetryArray = this.configurationValue.observeAttr.telemetry;
204 205 let keyNameJson = this.configurationValue.observeAttr.keyName;
205 206 if (this.includesNotZeroInstance(attributeArray, telemetryArray)) {
206 207 this.addInstances(attributeArray, telemetryArray, objectLwM2MS);
207 208 }
208   - if (isDefinedAndNotNull(observeArray) && observeArray.length > 0) {
209   - this.updateObserveAttrTelemetryObjects(observeArray, objectLwM2MS, OBSERVE);
  209 + if (isDefinedAndNotNull(this.configurationValue.observeAttr.observe) &&
  210 + this.configurationValue.observeAttr.observe.length > 0) {
  211 + this.updateObserveAttrTelemetryObjects(this.configurationValue.observeAttr.observe, objectLwM2MS, OBSERVE);
210 212 }
211 213 if (isDefinedAndNotNull(attributeArray) && attributeArray.length > 0) {
212 214 this.updateObserveAttrTelemetryObjects(attributeArray, objectLwM2MS, ATTRIBUTE);
... ... @@ -214,6 +216,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
214 216 if (isDefinedAndNotNull(telemetryArray) && telemetryArray.length > 0) {
215 217 this.updateObserveAttrTelemetryObjects(telemetryArray, objectLwM2MS, TELEMETRY);
216 218 }
  219 + if (isDefinedAndNotNull(this.configurationValue.observeAttr.attributeLwm2m)) {
  220 + this.updateAttributeLwm2m(objectLwM2MS);
  221 + }
217 222 if (isDefinedAndNotNull(keyNameJson)) {
218 223 this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray);
219 224 this.upDateJsonAllConfig();
... ... @@ -256,45 +261,72 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
256 261 [nameParameter] = true;
257 262 }
258 263 });
  264 + }
259 265
  266 + private updateAttributeLwm2m = (objectLwM2MS: ObjectLwM2M[]): void => {
  267 + Object.keys(this.configurationValue.observeAttr.attributeLwm2m).forEach(key => {
  268 + const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String);
  269 + let objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId);
  270 + if (objectLwM2M && instanceId) {
  271 + let instance = objectLwM2M.instances.find(instance => instance.id === +instanceId)
  272 + if (instance && resourceId) {
  273 + instance.resources.find(resource => resource.id === +resourceId)
  274 + .attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key];
  275 + } else if (instance) {
  276 + instance.attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key];
  277 + }
  278 + } else if (objectLwM2M) {
  279 + objectLwM2M.attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key];
  280 + }
  281 + });
260 282 }
261 283
262   - private updateKeyNameObjects = (clientObserveAttrTelemetry: ObjectLwM2M[]): void => {
  284 + private updateKeyNameObjects = (objectLwM2MS: ObjectLwM2M[]): void => {
263 285 Object.keys(this.configurationValue.observeAttr.keyName).forEach(key => {
264 286 const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String);
265   - const objectLwM2M = clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.keyId === objectKeyId)
  287 + const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId)
266 288 if (objectLwM2M) {
267 289 objectLwM2M.instances.find(instance => instance.id === +instanceId)
268   - .resources.find(resource => resource.id === +resourceId)
269   - .keyName = this.configurationValue.observeAttr.keyName[key];
270   - }});
  290 + .resources.find(resource => resource.id === +resourceId)
  291 + .keyName = this.configurationValue.observeAttr.keyName[key];
  292 + }
  293 + });
271 294 }
272 295
273 296 private validateKeyNameObjects = (nameJson: JsonObject, attributeArray: JsonArray, telemetryArray: JsonArray): {} => {
274 297 const keyName = JSON.parse(JSON.stringify(nameJson));
275 298 let keyNameValidate = {};
276   - const keyAttrTelemetry = attributeArray.concat(telemetryArray) ;
  299 + const keyAttrTelemetry = attributeArray.concat(telemetryArray);
277 300 Object.keys(keyName).forEach(key => {
278 301 if (keyAttrTelemetry.includes(key)) {
279 302 keyNameValidate[key] = keyName[key];
280 303 }
281 304 });
282   - return keyNameValidate;
  305 + return keyNameValidate;
283 306 }
284 307
285 308 private updateObserveAttrTelemetryFromGroupToJson = (val: ObjectLwM2M[]): void => {
286 309 const observeArray: Array<string> = [];
287 310 const attributeArray: Array<string> = [];
288 311 const telemetryArray: Array<string> = [];
  312 + const attributeLwm2m: any = {};
289 313 const keyNameNew = {};
290 314 const observeJson: ObjectLwM2M[] = JSON.parse(JSON.stringify(val));
291 315 observeJson.forEach(obj => {
  316 + if (isDefinedAndNotNull(obj.attributeLwm2m) && !isEmpty(obj.attributeLwm2m)) {
  317 + const pathObject = `/${obj.keyId}`;
  318 + attributeLwm2m[pathObject] = obj.attributeLwm2m;
  319 + }
292 320 if (obj.hasOwnProperty(INSTANCES) && Array.isArray(obj.instances)) {
293 321 obj.instances.forEach(instance => {
  322 + if (isDefinedAndNotNull(instance.attributeLwm2m) && !isEmpty(instance.attributeLwm2m)) {
  323 + const pathInstance = `/${obj.keyId}/${instance.id}`;
  324 + attributeLwm2m[pathInstance] = instance.attributeLwm2m;
  325 + }
294 326 if (instance.hasOwnProperty(RESOURCES) && Array.isArray(instance.resources)) {
295 327 instance.resources.forEach(resource => {
296 328 if (resource.attribute || resource.telemetry) {
297   - let pathRes = `/${obj.keyId}/${instance.id}/${resource.id}`;
  329 + const pathRes = `/${obj.keyId}/${instance.id}/${resource.id}`;
298 330 if (resource.observe) {
299 331 observeArray.push(pathRes);
300 332 }
... ... @@ -305,6 +337,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
305 337 telemetryArray.push(pathRes);
306 338 }
307 339 keyNameNew[pathRes] = resource.keyName;
  340 + if (isDefinedAndNotNull(resource.attributeLwm2m) && !isEmpty(resource.attributeLwm2m)) {
  341 + attributeLwm2m[pathRes] = resource.attributeLwm2m;
  342 + }
308 343 }
309 344 })
310 345 }
... ... @@ -316,13 +351,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
316 351 observe: observeArray,
317 352 attribute: attributeArray,
318 353 telemetry: telemetryArray,
319   - keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew)
  354 + keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew),
  355 + attributeLwm2m: attributeLwm2m
320 356 };
321 357 } else {
322 358 this.configurationValue.observeAttr.observe = observeArray;
323 359 this.configurationValue.observeAttr.attribute = attributeArray;
324 360 this.configurationValue.observeAttr.telemetry = telemetryArray;
325   - this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew);
  361 + this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m;
326 362 }
327 363 }
328 364
... ... @@ -392,6 +428,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
392 428 this.removeObserveAttrTelemetryFromJson(TELEMETRY, value.keyId);
393 429 this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.keyId);
394 430 this.removeKeyNameFromJson(value.keyId);
  431 + this.removeAttributeLwm2mFromJson(value.keyId);
395 432 this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
396 433 this.upDateJsonAllConfig();
397 434 }
... ... @@ -413,4 +450,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
413 450 }
414 451 });
415 452 }
  453 +
  454 + private removeAttributeLwm2mFromJson = (keyId: string): void => {
  455 + debugger
  456 + const keyNameJson = this.configurationValue.observeAttr.attributeLwm2m;
  457 + Object.keys(keyNameJson).forEach(key => {
  458 + if (key.startsWith(`/${keyId}`)) {
  459 + delete keyNameJson[key];
  460 + }
  461 + });
  462 + }
416 463 }
... ...
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.html renamed from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html
... ... @@ -17,7 +17,7 @@
17 17 -->
18 18 <form [formGroup]="instancesFormGroup" (ngSubmit)="add()" style="min-width: 400px;">
19 19 <mat-toolbar fxLayout="row" color="primary">
20   - <b><i>{{data.objectName}}</i></b> &nbsp;&nbsp; (object&nbsp;[<b>{{data.objectKeyId}}</b>])
  20 + <b><i>{{data.objectName}}</i></b> (object <<b>{{data.objectKeyId}}</b>>)
21 21 <span fxFlex></span>
22 22 <button mat-button mat-icon-button
23 23 (click)="cancel()"
... ...
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.ts renamed from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts
... ... @@ -30,9 +30,10 @@ export interface Lwm2mObjectAddInstancesData {
30 30
31 31 @Component({
32 32 selector: 'tb-lwm2m-object-add-instances',
33   - templateUrl: './lwm2m-object-add-instances.component.html'
  33 + templateUrl: './lwm2m-object-add-instances-dialog.component.html'
34 34 })
35   -export class Lwm2mObjectAddInstancesComponent extends DialogComponent<Lwm2mObjectAddInstancesComponent, object> implements OnInit {
  35 +export class Lwm2mObjectAddInstancesDialogComponent extends DialogComponent<Lwm2mObjectAddInstancesDialogComponent, object>
  36 + implements OnInit {
36 37
37 38 instancesFormGroup: FormGroup;
38 39 submitted = false;
... ... @@ -40,7 +41,7 @@ export class Lwm2mObjectAddInstancesComponent extends DialogComponent<Lwm2mObjec
40 41 constructor(protected store: Store<AppState>,
41 42 protected router: Router,
42 43 @Inject(MAT_DIALOG_DATA) public data: Lwm2mObjectAddInstancesData,
43   - public dialogRef: MatDialogRef<Lwm2mObjectAddInstancesComponent, object>,
  44 + public dialogRef: MatDialogRef<Lwm2mObjectAddInstancesDialogComponent, object>,
44 45 public fb: FormBuilder) {
45 46 super(store, router, dialogRef);
46 47 }
... ...
... ... @@ -14,11 +14,11 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import {Component, forwardRef,} from '@angular/core';
  17 +import {Component, forwardRef} from '@angular/core';
18 18 import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';
19 19 import {Store} from '@ngrx/store';
20 20 import {AppState} from '@core/core.state';
21   -import {INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER} from './profile-config.models';
  21 +import {INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER} from './lwm2m-profile-config.models';
22 22
23 23 @Component({
24 24 selector: 'tb-profile-lwm2m-object-add-instances-list',
... ...
... ... @@ -22,7 +22,7 @@
22 22 [selectable]="!disabled"
23 23 [removable]="!disabled"
24 24 (removed)="remove(objectLwm2m)">
25   - {{objectLwm2m.name}}
  25 + {{objectLwm2m.name}} <{{objectLwm2m.keyId}}>
26 26 <mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
27 27 </mat-chip>
28 28 <input matInput type="text" placeholder="{{ !disabled ? ('device-profile.lwm2m.object-list' | translate) : '' }}"
... ...
... ... @@ -21,7 +21,7 @@ import {Store} from '@ngrx/store';
21 21 import {AppState} from '@core/core.state';
22 22 import {Observable} from 'rxjs';
23 23 import {filter, map, mergeMap, publishReplay, refCount, tap} from 'rxjs/operators';
24   -import {ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT} from './profile-config.models';
  24 +import {ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT} from './lwm2m-profile-config.models';
25 25 import {DeviceProfileService} from '@core/http/device-profile.service';
26 26 import {Direction} from '@shared/models/page/sort-order';
27 27 import {isDefined, isDefinedAndNotNull, isString} from '@core/utils';
... ...
... ... @@ -19,58 +19,56 @@
19 19 <div fxLayout="row" fxFill formArrayName="resources"
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   - <div fxLayout="row" fxFill [fxShow]="!i">
23   - <div fxFlex="55"></div>
24   - <div fxFlex="10" fxLayoutAlign="start center">
25   - <b>
26   - <mat-label translate>device-profile.lwm2m.observe-label</mat-label>
27   - </b>
  22 + <div fxLayout="row" fxFill fxLayoutAlign="start center" [fxShow]="!i">
  23 + <div fxFlex="20">
  24 + <mat-label translate>device-profile.lwm2m.resource-label</mat-label>
28 25 </div>
29 26 <div fxFlex="10">
30   - <b>
31 27 <mat-label translate>device-profile.lwm2m.attribute-label</mat-label>
32   - </b>
33 28 </div>
34 29 <div fxFlex="10">
35   - <b>
36 30 <mat-label translate>device-profile.lwm2m.telemetry-label</mat-label>
37   - </b>
38 31 </div>
39   - <div fxFlex="25">
40   - <b>
  32 + <div fxFlex="10">
  33 + <mat-label translate>device-profile.lwm2m.observe-label</mat-label>
  34 + </div>
  35 + <div fxFlex>
41 36 <mat-label translate>device-profile.lwm2m.key-name-label</mat-label>
42   - </b>
43 37 </div>
44   - </div>
45   - <div fxLayout="row" fxFill fxLayoutAlign="center center">
46   - <div fxFlex="55">
47   - Resource[<b>{{resourceLwM2M.get('id').value}}</b>]
48   - name:<b><i>{{resourceLwM2M.get('name').value}}</i></b>
  38 + <div fxFlex="17" fxFlexOffset="0">
  39 + <mat-label translate>device-profile.lwm2m.attribute-lwm2m-label</mat-label>
49 40 </div>
50   - <div fxFlex="10" fxLayoutAlign="start center">
51   - <mat-checkbox formControlName="observe" color="primary"
52   - matTooltip="{{'device-profile.lwm2m.is-observe-tip' | translate}}"
53   - matTooltipPosition="above">
54   - </mat-checkbox>
  41 + </div>
  42 + <div fxLayout="row" fxFill fxLayoutAlign="start center">
  43 + <div class="resource-name-lw" fxFlex="25"
  44 + matTooltip="{{'device-profile.lwm2m.resource-tip' | translate}}" matTooltipPosition="above">
  45 + <<b>{{resourceLwM2M.get('id').value}}</b>> <b><i>{{resourceLwM2M.get('name').value}}</i></b>
55 46 </div>
56   - <div fxFlex="10">
  47 + <div fxFlex="10" fxLayoutAlign="center center">
57 48 <mat-checkbox formControlName="attribute" color="warn"
58 49 [checked]="updateObserve(i)"
59 50 matTooltip="{{'device-profile.lwm2m.is-attr-tip' | translate}}"
60 51 matTooltipPosition="above">
61 52 </mat-checkbox>
62 53 </div>
63   - <div fxFlex="10">
  54 + <div fxFlex="10" fxLayoutAlign="center center">
64 55 <mat-checkbox formControlName="telemetry" color="primary"
65 56 [checked]="updateObserve(i)"
66 57 matTooltip="{{'device-profile.lwm2m.is-telemetry-tip' | translate}}"
67 58 matTooltipPosition="above">
68 59 </mat-checkbox>
69 60 </div>
  61 + <div fxFlex="10" fxLayoutAlign="center center">
  62 + <mat-checkbox formControlName="observe" color="primary"
  63 + [disabled]="disableObserve(i)"
  64 + matTooltip="{{(disableObserve(i) ? 'device-profile.lwm2m.not-observe-tip' : 'device-profile.lwm2m.is-observe-tip') | translate}}"
  65 + matTooltipPosition="above">
  66 + </mat-checkbox>
  67 + </div>
70 68 <mat-form-field fxFlex="25">
71 69 <mat-label *ngIf="resourceLwM2M.get('keyName').hasError('required')">
72   - {{ 'device-profile.lwm2m.key-name_label' | translate }}</mat-label>
73   - <input matInput type="text" formControlName="keyName" required
  70 + {{ 'device-profile.lwm2m.key-name-label' | translate }}</mat-label>
  71 + <input class="resource-name-lw" matInput type="text" formControlName="keyName" required
74 72 matTooltip="{{'device-profile.lwm2m.key-name-tip' | translate}}"
75 73 (input)="updateValueKeyName($event, i)"
76 74 matTooltipPosition="above">
... ... @@ -79,6 +77,16 @@
79 77 <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
80 78 </mat-error>
81 79 </mat-form-field>
  80 + <div fxFlex="20" class="resource-name-lw-end" fxFlexOffset="5">
  81 + <tb-profile-lwm2m-attributes
  82 + formControlName="attributeLwm2m"
  83 + [attributeLwm2m]="resourceLwM2M.get('attributeLwm2m').value"
  84 + [isAttributeTelemetry]="disableObserve(i)"
  85 + [destName]="getNameResourceLwm2m(resourceLwM2M.value)"
  86 + [disabled]="this.disabled"
  87 + (updateAttributeLwm2m)="updateAttributeLwm2m($event, i)">
  88 + </tb-profile-lwm2m-attributes>
  89 + </div>
82 90 </div>
83 91 </div>
84 92 </div>
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 import {Component, forwardRef, Input} from '@angular/core';
18 18 import {ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';
19   -import {ResourceLwM2M} from '@home/components/profile/device/lwm2m/profile-config.models';
  19 +import {ResourceLwM2M, RESOURCES} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
20 20 import {Store} from '@ngrx/store';
21 21 import {AppState} from '@core/core.state';
22 22 import _ from 'lodash';
... ... @@ -25,6 +25,7 @@ import {coerceBooleanProperty} from '@angular/cdk/coercion';
25 25 @Component({
26 26 selector: 'tb-profile-lwm2m-observe-attr-telemetry-resource',
27 27 templateUrl: './lwm2m-observe-attr-telemetry-resource.component.html',
  28 + styleUrls: ['./lwm2m-attributes.component.scss'],
28 29 providers: [
29 30 {
30 31 provide: NG_VALUE_ACCESSOR,
... ... @@ -73,7 +74,7 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
73 74 }
74 75
75 76 get resourceFormArray(): FormArray{
76   - return this.resourceFormGroup.get('resources') as FormArray;
  77 + return this.resourceFormGroup.get(RESOURCES) as FormArray;
77 78 }
78 79
79 80 setDisabledState(isDisabled: boolean): void {
... ... @@ -89,6 +90,14 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
89 90 this.resourceFormArray.at(index).patchValue({keyName: _.camelCase((event.target as HTMLInputElement).value)});
90 91 }
91 92
  93 + updateAttributeLwm2m = (event: Event, index: number): void => {
  94 + this.resourceFormArray.at(index).patchValue({attributeLwm2m: event});
  95 + }
  96 +
  97 + getNameResourceLwm2m = (resourceLwM2M: ResourceLwM2M): string => {
  98 + return '<' + resourceLwM2M.id +'> ' + resourceLwM2M.name;
  99 + }
  100 +
92 101 createResourceLwM2M(resourcesLwM2M: ResourceLwM2M[]): void {
93 102 if (resourcesLwM2M.length === this.resourceFormArray.length) {
94 103 this.resourceFormArray.patchValue(resourcesLwM2M, {emitEvent: false});
... ... @@ -101,7 +110,8 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
101 110 observe: resourceLwM2M.observe,
102 111 attribute: resourceLwM2M.attribute,
103 112 telemetry: resourceLwM2M.telemetry,
104   - keyName: [resourceLwM2M.keyName, Validators.required]
  113 + keyName: [resourceLwM2M.keyName, Validators.required],
  114 + attributeLwm2m: [resourceLwM2M.attributeLwm2m]
105 115 }));
106 116 });
107 117 }
... ... @@ -128,6 +138,11 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
128 138 updateObserve = (index: number): void =>{
129 139 if (this.resourceFormArray.at(index).value.attribute === false && this.resourceFormArray.at(index).value.telemetry === false) {
130 140 this.resourceFormArray.at(index).patchValue({observe: false});
  141 + this.resourceFormArray.at(index).patchValue({attributeLwm2m: {}});
131 142 }
132 143 }
  144 +
  145 + disableObserve = (index: number): boolean =>{
  146 + return !this.resourceFormArray.at(index).value.telemetry && !this.resourceFormArray.at(index).value.attribute;
  147 + }
133 148 }
... ...
... ... @@ -20,17 +20,27 @@
20 20 <mat-expansion-panel
21 21 *ngFor="let objectLwM2M of clientLwM2MFormArray.controls; let i = index;"
22 22 [formGroupName]="i">
23   - <mat-expansion-panel-header>
24   - <mat-panel-title fxLayoutAlign="start center">
25   - <b><i>{{ objectLwM2M.get('name').value}}</i></b>&nbsp;&lt;id: {{ objectLwM2M.get('keyId').value}}>
  23 + <mat-expansion-panel-header >
  24 + <mat-panel-title fxLayoutAlign="start center" >
  25 + <b><i>{{ objectLwM2M.get('name').value}}</i></b>&nbsp;&lt;{{ objectLwM2M.get('keyId').value}}>
  26 + <div fxFlex class="resource-name-lw-end">
  27 + <tb-profile-lwm2m-attributes
  28 + formControlName="attributeLwm2m"
  29 + [attributeLwm2m]="objectLwM2M.get('attributeLwm2m').value"
  30 + [isAttributeTelemetry]="disableObserveObject(i)"
  31 + [destName]="getNameObjectLwm2m( objectLwM2M.get('name').value, objectLwM2M.get('keyId').value)"
  32 + [disabled]="this.disabled"
  33 + (updateAttributeLwm2m)="updateAttributeLwm2mObject($event, objectLwM2M.get('keyId').value)">
  34 + </tb-profile-lwm2m-attributes>
  35 + </div>
26 36 </mat-panel-title>
27   - <mat-panel-description fxLayoutAlign="end center" *ngIf="!disabled">
  37 + <mat-panel-description fxFlex="5" fxLayoutAlign="end center" *ngIf="!disabled">
28 38 <button type="button"
29 39 [fxShow]="objectLwM2M.get('multiple').value"
30 40 mat-button mat-icon-button (click)="addInstances($event, objectLwM2M.value)"
31 41 matTooltip="{{'device-profile.lwm2m.add-instances-tip' | translate}}"
32 42 matTooltipPosition="above">
33   - <mat-icon class="material-icons">{{'add'}}</mat-icon>
  43 + <mat-icon class="material-icons">{{'note_add'}}</mat-icon>
34 44 </button>
35 45 </mat-panel-description>
36 46 </mat-expansion-panel-header>
... ... @@ -45,22 +55,10 @@
45 55 <mat-panel-title>
46 56 <div class="tb-panel-title-height" fxFlex="100">
47 57 <div fxLayout="row" fxFill>
48   - <div fxFlex="44">
49   - Instance [<b>{{instances.get('id').value}}</b>]
  58 + <div fxFlex="22">
  59 + {{'device-profile.lwm2m.instance-label' | translate}} <<b>{{instances.get('id').value}}</b>>
50 60 </div>
51   - <div class="checkbox-padding" fxFlex="10">
52   - <mat-checkbox color="primary"
53   - [disabled]="this.disabled"
54   - [checked]="getChecked(instances, 'observe')"
55   - (click)="$event.stopPropagation()"
56   - (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
57   - (keydown)="$event.stopPropagation()"
58   - [indeterminate]="getIndeterminate(instances, 'observe')"
59   - matTooltip="{{'device-profile.lwm2m.is-observe-tip' | translate}}"
60   - matTooltipPosition="above">
61   - </mat-checkbox>
62   - </div>
63   - <div class="checkbox-padding" fxFlex="10">
  61 + <div class="checkbox-padding" fxFlex="10">
64 62 <mat-checkbox color="warn"
65 63 [disabled]="this.disabled"
66 64 [checked]="getChecked(instances, 'attribute')"
... ... @@ -84,7 +82,29 @@
84 82 matTooltipPosition="above">
85 83 </mat-checkbox>
86 84 </div>
87   - <div class="checkbox-padding" fxFlex="25">
  85 + <div class="checkbox-padding" fxFlex="10">
  86 + <mat-checkbox color="primary"
  87 + [disabled]="this.disabled"
  88 + [checked]="getChecked(instances, 'observe')"
  89 + (click)="$event.stopPropagation()"
  90 + (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
  91 + (keydown)="$event.stopPropagation()"
  92 + [indeterminate]="getIndeterminate(instances, 'observe')"
  93 + matTooltip="{{'device-profile.lwm2m.is-observe-tip' | translate}}"
  94 + matTooltipPosition="above">
  95 + </mat-checkbox>
  96 + </div>
  97 + <div fxFlex="10">
  98 + </div>
  99 + <div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5" disabled="false">
  100 + <tb-profile-lwm2m-attributes
  101 + formControlName="attributeLwm2m"
  102 + [attributeLwm2m]="instances.get('attributeLwm2m').value"
  103 + [isAttributeTelemetry]="disableObserveInstance(instances)"
  104 + [destName]="getNameInstanceLwm2m(instances.value, objectLwM2M.get('keyId').value)"
  105 + [disabled]="this.disabled"
  106 + (updateAttributeLwm2m)="updateAttributeLwm2mInstance($event, y, objectLwM2M.get('keyId').value)">
  107 + </tb-profile-lwm2m-attributes>
88 108 </div>
89 109 </div>
90 110 </div>
... ...
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss renamed from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.css
... ... @@ -13,14 +13,6 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -.vertical-padding {
17   - padding: 0 0 10px 20px;
18   -}
19   -
20   -.left-padding {
21   - padding-left: 5px;
22   -}
23   -
24 16 .tb-panel-title-height {
25 17 user-select: none;
26 18 min-height: 32px;
... ... @@ -28,21 +20,5 @@
28 20
29 21 .checkbox-padding {
30 22 padding-left: 22px;
31   -}
32   -
33   -.label-resource {
34   - /*padding: 4px;*/
35   - color: #002699;
36   - /*text-transform: uppercase;*/
37   - background: rgba(220, 220, 220, .35);
38   - border-radius: 20px;
39   - width: inherit;
40   - /*margin: 10px 0;*/
41   - overflow: hidden;
42   - font-size: 15px;
43   - text-overflow: ellipsis;
44   - white-space: nowrap;
45   - opacity: .8;
46 23 text-align:center;
47 24 }
48   -
... ...
... ... @@ -14,7 +14,6 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -
18 17 import {Component, forwardRef, Input} from '@angular/core';
19 18 import {
20 19 AbstractControl,
... ... @@ -28,19 +27,29 @@ import {
28 27 import {Store} from '@ngrx/store';
29 28 import {AppState} from '@core/core.state';
30 29 import {coerceBooleanProperty} from '@angular/cdk/coercion';
31   -import {CLIENT_LWM2M, Instance, INSTANCES, ObjectLwM2M, ResourceLwM2M, RESOURCES} from './profile-config.models';
  30 +import {
  31 + ATTRIBUTE,
  32 + ATTRIBUTE_LWM2M,
  33 + CLIENT_LWM2M,
  34 + Instance,
  35 + INSTANCES,
  36 + ObjectLwM2M,
  37 + ResourceLwM2M,
  38 + RESOURCES,
  39 + TELEMETRY
  40 +} from './lwm2m-profile-config.models';
32 41 import {deepClone, isDefinedAndNotNull, isEqual, isUndefined} from '@core/utils';
33 42 import {MatDialog} from '@angular/material/dialog';
34 43 import {TranslateService} from '@ngx-translate/core';
35 44 import {
36   - Lwm2mObjectAddInstancesComponent,
37   - Lwm2mObjectAddInstancesData
38   -} from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances.component';
  45 + Lwm2mObjectAddInstancesData,
  46 + Lwm2mObjectAddInstancesDialogComponent
  47 +} from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component';
39 48
40 49 @Component({
41 50 selector: 'tb-profile-lwm2m-observe-attr-telemetry',
42 51 templateUrl: './lwm2m-observe-attr-telemetry.component.html',
43   - styleUrls: ['./lwm2m-observe-attr-telemetry.component.css'],
  52 + styleUrls: [ './lwm2m-observe-attr-telemetry.component.scss'],
44 53 providers: [
45 54 {
46 55 provide: NG_VALUE_ACCESSOR,
... ... @@ -56,6 +65,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
56 65
57 66 valuePrev = null as any;
58 67 observeAttrTelemetryFormGroup: FormGroup;
  68 + resources = RESOURCES;
59 69
60 70 get required(): boolean {
61 71 return this.requiredValue;
... ... @@ -143,6 +153,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
143 153 name: objectLwM2M.name,
144 154 multiple: objectLwM2M.multiple,
145 155 mandatory: objectLwM2M.mandatory,
  156 + attributeLwm2m: objectLwM2M.attributeLwm2m,
146 157 instances: this.createInstanceLwM2M(objectLwM2M.instances)
147 158 });
148 159 }));
... ... @@ -152,6 +163,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
152 163 return this.fb.array(instancesLwM2M.map((instanceLwM2M) => {
153 164 return this.fb.group({
154 165 id: instanceLwM2M.id,
  166 + attributeLwm2m: {value: instanceLwM2M.attributeLwm2m, disabled: this.disabled},
155 167 resources: {value: instanceLwM2M.resources, disabled: this.disabled}
156 168 });
157 169 }));
... ... @@ -222,7 +234,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
222 234 $event.stopPropagation();
223 235 $event.preventDefault();
224 236 }
225   - this.dialog.open<Lwm2mObjectAddInstancesComponent, Lwm2mObjectAddInstancesData, object>(Lwm2mObjectAddInstancesComponent, {
  237 + this.dialog.open<Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesData, object>(Lwm2mObjectAddInstancesDialogComponent, {
226 238 disableClose: true,
227 239 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
228 240 data: {
... ... @@ -240,6 +252,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
240 252 }
241 253
242 254 private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => {
  255 + debugger
243 256 const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls
244 257 .find(e => e.value.keyId === data.objectKeyId) as FormGroup;
245 258 const instancesArray = objectLwM2MFormGroup.value.instances as Instance [];
... ... @@ -249,6 +262,8 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
249 262 r.attribute = false;
250 263 r.telemetry = false;
251 264 r.observe = false;
  265 + r.keyName = {};
  266 + r.attributeLwm2m = {};
252 267 });
253 268 const valueOld = this.instancesToSetId(instancesArray);
254 269 if (!isEqual(valueOld, data.instancesIds)) {
... ... @@ -286,6 +301,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
286 301 private pushInstance = (instancesFormArray: FormArray, x: number, instanceNew: Instance): void => {
287 302 instancesFormArray.push(this.fb.group({
288 303 id: x,
  304 + attributeLwm2m: instanceNew.attributeLwm2m,
289 305 resources: {value: instanceNew.resources, disabled: this.disabled}
290 306 }));
291 307 }
... ... @@ -297,4 +313,54 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
297 313 private instancesToSetId = (instances: Instance[]): Set<number> => {
298 314 return new Set(instances.map(x => x.id));
299 315 }
  316 +
  317 + getNameObjectLwm2m = (objectName: string, idVerObj: string): string => {
  318 + return objectName + ' <' + idVerObj + '>';
  319 + }
  320 + getNameInstanceLwm2m = (instance: Instance, idVerObj: string): string => {
  321 + return ' instance <' + idVerObj + '/' + instance.id +'>';
  322 + }
  323 +
  324 + updateAttributeLwm2mObject = (event: Event, objectKeyId: number): void => {
  325 + const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls
  326 + .find(e => e.value.keyId === objectKeyId) as FormGroup;
  327 + objectLwM2MFormGroup.patchValue({attributeLwm2m: event});
  328 + }
  329 +
  330 + updateAttributeLwm2mInstance = (event: Event, indexInstance: number, objectKeyId: number): void => {
  331 +
  332 + const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls
  333 + .find(e => e.value.keyId === objectKeyId) as FormGroup;
  334 + const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray;
  335 + instancesFormArray.at(indexInstance).patchValue({attributeLwm2m: event});
  336 + }
  337 +
  338 + disableObserveInstance = (instance: AbstractControl): boolean => {
  339 + const checkedAttrTelemetry = this.observeInstance(instance);
  340 + if (checkedAttrTelemetry) {
  341 + instance.get(ATTRIBUTE_LWM2M).patchValue(null);
  342 + }
  343 + return checkedAttrTelemetry;
  344 + }
  345 +
  346 + disableObserveObject = (index: number): boolean => {
  347 + const object = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).at(index) as FormGroup;
  348 + const instances = object.controls.instances as FormArray;
  349 + const checkedAttrTelemetry = instances.controls.filter(instance => !this.disableObserveInstance(instance));
  350 + if (checkedAttrTelemetry.length === 0) {
  351 + object.controls.attributeLwm2m.patchValue(null);
  352 + }
  353 + return checkedAttrTelemetry.length === 0;
  354 + }
  355 +
  356 +
  357 + observeInstance = (instance: AbstractControl): boolean => {
  358 + const resources = instance.get(RESOURCES).value as ResourceLwM2M[];
  359 + if (isDefinedAndNotNull(resources)) {
  360 + const checkedAttribute = resources.filter(resource => resource[ATTRIBUTE]);
  361 + const checkedTelemetry = resources.filter(resource => resource[TELEMETRY]);
  362 + return checkedAttribute.length === 0 && checkedTelemetry.length === 0;
  363 + }
  364 + return false;
  365 + }
300 366 }
... ...
... ... @@ -14,16 +14,19 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { NgModule } from '@angular/core';
18   -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './lwm2m-device-profile-transport-configuration.component';
19   -import { Lwm2mObjectListComponent } from './lwm2m-object-list.component';
20   -import { Lwm2mObserveAttrTelemetryComponent } from './lwm2m-observe-attr-telemetry.component';
21   -import { Lwm2mObserveAttrTelemetryResourceComponent } from './lwm2m-observe-attr-telemetry-resource.component';
22   -import { Lwm2mDeviceConfigServerComponent } from './lwm2m-device-config-server.component';
23   -import { Lwm2mObjectAddInstancesComponent } from './lwm2m-object-add-instances.component';
24   -import { Lwm2mObjectAddInstancesListComponent } from './lwm2m-object-add-instances-list.component';
25   -import { CommonModule } from '@angular/common';
26   -import { SharedModule } from '@app/shared/shared.module';
  17 +import {NgModule} from '@angular/core';
  18 +import {Lwm2mDeviceProfileTransportConfigurationComponent} from './lwm2m-device-profile-transport-configuration.component';
  19 +import {Lwm2mObjectListComponent} from './lwm2m-object-list.component';
  20 +import {Lwm2mObserveAttrTelemetryComponent} from './lwm2m-observe-attr-telemetry.component';
  21 +import {Lwm2mObserveAttrTelemetryResourceComponent} from './lwm2m-observe-attr-telemetry-resource.component';
  22 +import {Lwm2mAttributesDialogComponent} from './lwm2m-attributes-dialog.component';
  23 +import {Lwm2mAttributesComponent} from './lwm2m-attributes.component';
  24 +import {Lwm2mAttributesKeyListComponent} from './lwm2m-attributes-key-list.component';
  25 +import {Lwm2mDeviceConfigServerComponent} from './lwm2m-device-config-server.component';
  26 +import {Lwm2mObjectAddInstancesDialogComponent} from './lwm2m-object-add-instances-dialog.component';
  27 +import {Lwm2mObjectAddInstancesListComponent} from './lwm2m-object-add-instances-list.component';
  28 +import {CommonModule} from '@angular/common';
  29 +import {SharedModule} from '@app/shared/shared.module';
27 30
28 31 @NgModule({
29 32 declarations:
... ... @@ -32,8 +35,11 @@ import { SharedModule } from '@app/shared/shared.module';
32 35 Lwm2mObjectListComponent,
33 36 Lwm2mObserveAttrTelemetryComponent,
34 37 Lwm2mObserveAttrTelemetryResourceComponent,
  38 + Lwm2mAttributesDialogComponent,
  39 + Lwm2mAttributesComponent,
  40 + Lwm2mAttributesKeyListComponent,
35 41 Lwm2mDeviceConfigServerComponent,
36   - Lwm2mObjectAddInstancesComponent,
  42 + Lwm2mObjectAddInstancesDialogComponent,
37 43 Lwm2mObjectAddInstancesListComponent
38 44 ],
39 45 imports: [
... ... @@ -45,8 +51,11 @@ import { SharedModule } from '@app/shared/shared.module';
45 51 Lwm2mObjectListComponent,
46 52 Lwm2mObserveAttrTelemetryComponent,
47 53 Lwm2mObserveAttrTelemetryResourceComponent,
  54 + Lwm2mAttributesDialogComponent,
  55 + Lwm2mAttributesComponent,
  56 + Lwm2mAttributesKeyListComponent,
48 57 Lwm2mDeviceConfigServerComponent,
49   - Lwm2mObjectAddInstancesComponent,
  58 + Lwm2mObjectAddInstancesDialogComponent,
50 59 Lwm2mObjectAddInstancesListComponent
51 60 ],
52 61 providers: [
... ...
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts renamed from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts
... ... @@ -16,7 +16,9 @@
16 16
17 17 export const PAGE_SIZE_LIMIT = 50;
18 18 export const INSTANCES = 'instances';
  19 +export const INSTANCE = 'instance';
19 20 export const RESOURCES = 'resources';
  21 +export const ATTRIBUTE_LWM2M = 'attributeLwm2m';
20 22 export const CLIENT_LWM2M = 'clientLwM2M';
21 23 export const CLIENT_LWM2M_SETTINGS = 'clientLwM2mSettings';
22 24 export const OBSERVE_ATTR_TELEMETRY = 'observeAttrTelemetry';
... ... @@ -33,7 +35,7 @@ export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1;
33 35 export const DEFAULT_LIFE_TIME = 300;
34 36 export const DEFAULT_MIN_PERIOD = 1;
35 37 export const DEFAULT_NOTIF_IF_DESIBLED = true;
36   -export const DEFAULT_BINDING = 'U';
  38 +export const DEFAULT_BINDING = 'UQ';
37 39 export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0;
38 40 export const LEN_MAX_PUBLIC_KEY_RPK = 182;
39 41 export const LEN_MAX_PUBLIC_KEY_X509 = 3000;
... ... @@ -42,6 +44,44 @@ export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/;
42 44 export const INSTANCES_ID_VALUE_MIN = 0;
43 45 export const INSTANCES_ID_VALUE_MAX = 65535;
44 46
  47 +export enum ATTRIBUTE_LWM2M_ENUM {
  48 + dim = 'dim',
  49 + ver = 'ver',
  50 + pmin = 'pmin',
  51 + pmax = 'pmax',
  52 + gt = 'gt',
  53 + lt = 'lt',
  54 + st = 'st'
  55 +}
  56 +
  57 +export const ATTRIBUTE_LWM2M_LABEL = new Map<ATTRIBUTE_LWM2M_ENUM, string>(
  58 + [
  59 + [ATTRIBUTE_LWM2M_ENUM.dim, 'dim='],
  60 + [ATTRIBUTE_LWM2M_ENUM.ver, 'ver='],
  61 + [ATTRIBUTE_LWM2M_ENUM.pmin, 'pmin='],
  62 + [ATTRIBUTE_LWM2M_ENUM.pmax, 'pmax='],
  63 + [ATTRIBUTE_LWM2M_ENUM.gt, '>'],
  64 + [ATTRIBUTE_LWM2M_ENUM.lt, '<'],
  65 + [ATTRIBUTE_LWM2M_ENUM.st, 'st='],
  66 +
  67 + ]
  68 +);
  69 +
  70 +export const ATTRIBUTE_LWM2M_MAP = new Map<ATTRIBUTE_LWM2M_ENUM, string>(
  71 + [
  72 + [ATTRIBUTE_LWM2M_ENUM.dim, 'Dimension'],
  73 + [ATTRIBUTE_LWM2M_ENUM.ver, 'Object version'],
  74 + [ATTRIBUTE_LWM2M_ENUM.pmin, 'Minimum period'],
  75 + [ATTRIBUTE_LWM2M_ENUM.pmax, 'Maximum period'],
  76 + [ATTRIBUTE_LWM2M_ENUM.gt, 'Greater than'],
  77 + [ATTRIBUTE_LWM2M_ENUM.lt, 'Lesser than'],
  78 + [ATTRIBUTE_LWM2M_ENUM.st, 'Step'],
  79 +
  80 + ]
  81 +);
  82 +
  83 +export const ATTRIBUTE_KEYS = Object.keys(ATTRIBUTE_LWM2M_ENUM) as string[];
  84 +
45 85 export enum SECURITY_CONFIG_MODE {
46 86 PSK = 'PSK',
47 87 RPK = 'RPK',
... ... @@ -54,7 +94,7 @@ export const SECURITY_CONFIG_MODE_NAMES = new Map<SECURITY_CONFIG_MODE, string>(
54 94 [SECURITY_CONFIG_MODE.PSK, 'Pre-Shared Key'],
55 95 [SECURITY_CONFIG_MODE.RPK, 'Raw Public Key'],
56 96 [SECURITY_CONFIG_MODE.X509, 'X.509 Certificate'],
57   - [SECURITY_CONFIG_MODE.NO_SEC, 'No Security'],
  97 + [SECURITY_CONFIG_MODE.NO_SEC, 'No Security']
58 98 ]
59 99 );
60 100
... ... @@ -90,7 +130,7 @@ interface BootstrapSecurityConfig {
90 130 lwm2mServer: ServerSecurityConfig;
91 131 }
92 132
93   -export interface ProfileConfigModels {
  133 +export interface Lwm2mProfileConfigModels {
94 134 clientLwM2mSettings: ClientLwM2mSettings;
95 135 observeAttr: ObservableAttributes;
96 136 bootstrap: BootstrapSecurityConfig;
... ... @@ -100,11 +140,13 @@ export interface ProfileConfigModels {
100 140 export interface ClientLwM2mSettings {
101 141 clientOnlyObserveAfterConnect: number;
102 142 }
  143 +
103 144 export interface ObservableAttributes {
104 145 observe: string[];
105 146 attribute: string[];
106 147 telemetry: string[];
107 148 keyName: {};
  149 + attributeLwm2m: {};
108 150 }
109 151
110 152 export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig {
... ... @@ -151,21 +193,22 @@ function getDefaultProfileObserveAttrConfig(): ObservableAttributes {
151 193 observe: [],
152 194 attribute: [],
153 195 telemetry: [],
154   - keyName: {}
  196 + keyName: {},
  197 + attributeLwm2m: {}
155 198 };
156 199 }
157 200
158   -function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings {
  201 +export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModels {
159 202 return {
160   - clientOnlyObserveAfterConnect: 1
  203 + clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(),
  204 + observeAttr: getDefaultProfileObserveAttrConfig(),
  205 + bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME)
161 206 };
162 207 }
163 208
164   -export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels {
  209 +function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings {
165 210 return {
166   - clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(),
167   - observeAttr: getDefaultProfileObserveAttrConfig(),
168   - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME)
  211 + clientOnlyObserveAfterConnect: 1
169 212 };
170 213 }
171 214
... ... @@ -175,11 +218,13 @@ export interface ResourceLwM2M {
175 218 observe: boolean;
176 219 attribute: boolean;
177 220 telemetry: boolean;
178   - keyName: string;
  221 + keyName: {};
  222 + attributeLwm2m?: {};
179 223 }
180 224
181 225 export interface Instance {
182 226 id: number;
  227 + attributeLwm2m?: {};
183 228 resources: ResourceLwM2M[];
184 229 }
185 230
... ... @@ -190,11 +235,12 @@ export interface Instance {
190 235 * mandatory == false => Optional
191 236 */
192 237 export interface ObjectLwM2M {
  238 +
193 239 id: number;
194 240 keyId: string;
195 241 name: string;
196 242 multiple?: boolean;
197 243 mandatory?: boolean;
  244 + attributeLwm2m?: {};
198 245 instances?: Instance [];
199 246 }
200   -
... ...
... ... @@ -1171,17 +1171,32 @@
1171 1171 "instances-list": "Instances list",
1172 1172 "instances-input": "Input Instance Id value",
1173 1173 "instances-input-holder": "Input Instance number...",
1174   - "resource-label": "Resource",
  1174 + "instance-label": "Instance",
  1175 + "resource-label": "<id> Resource name",
1175 1176 "observe-label": "Observe",
1176 1177 "attribute-label": "Attribute",
1177 1178 "telemetry-label": "Telemetry",
1178 1179 "key-name-label": "Key Name",
  1180 + "attribute-lwm2m-label": "AttrLwm2m",
  1181 + "resource-tip": "ID & Original Name of the Resource",
1179 1182 "is-observe-tip": "Is Observe",
  1183 + "not-observe-tip": "To observe select telemetry or attributes first",
1180 1184 "is-attr-tip": "Is Attribute",
1181 1185 "is-telemetry-tip": "Is Telemetry",
1182   - "key-name-tip": "Key Name",
  1186 + "key-name-tip": "Key Name in Camel format",
  1187 + "attribute-lwm2m-tip": "Attributes Lwm2m",
  1188 + "attribute-lwm2m-disable-tip": "To edit Attributes Lwm2m select telemetry or attributes first",
  1189 + "valid-attribute-lwm2m-key": "Name have be only '{{attrEnums}}'",
  1190 + "valid-attribute-lwm2m-value": "Value have be Long, Double and greater than zero or null/empty",
  1191 + "no-data": "No attributes",
1183 1192 "key-name": "Key Name",
1184   - "key-name_label": "Key Name in Camel format",
  1193 + "attribute-lwm2m-name": "Name attribute",
  1194 + "attribute-lwm2m-value": "Value",
  1195 + "attribute-lwm2m-toolbar-edit": "Edit attributes Lwm2m",
  1196 + "attribute-lwm2m-toolbar-view": "View Attributes Lwm2m",
  1197 + "attribute-lwm2m-destination": "Destination:",
  1198 + "attribute-lwm2m-add-tip": "Add attribute lwm2m",
  1199 + "attribute-lwm2m-remove-tip": "Remove attribute lwm2m",
1185 1200 "required": " value is required.",
1186 1201 "mode": "Security config mode",
1187 1202 "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }",
... ...