diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 0190c3e790..3ac679e0d7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -30,7 +30,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; @@ -42,10 +41,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import java.util.ArrayList; import java.util.Base64; import java.util.List; -import java.util.StringJoiner; @Slf4j @RestController @@ -111,25 +108,13 @@ public class TbResourceController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource", method = RequestMethod.POST) @ResponseBody - public List saveResources(@RequestBody List resources) throws ThingsboardException { + public TbResource saveResource(@RequestBody TbResource resource) throws ThingsboardException { try { - List addResources = new ArrayList<>(); - StringJoiner noSaveResources = new StringJoiner("; "); - resources.forEach(resource -> { - try { resource.setTenantId(getTenantId()); checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); - addResources.add(addResource(resource)); - } catch (Exception e) { - noSaveResources.add(resource.getFileName()); - log.warn("Fail save resource: [{}]", resource.getFileName(), e); + return addResource(resource); } - }); - if (noSaveResources.length() > 0) { - throw new ThingsboardException(String.format("Fail save resource: %s", noSaveResources.toString()), ThingsboardErrorCode.INVALID_ARGUMENTS); - } - return addResources; - } catch (Exception e) { + catch (Exception e) { throw handleException(e); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java index 8fae23881a..8850b70f86 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java @@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.security.Authority; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -110,7 +109,7 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes TbResource savedResource = save(resource); loginDifferentTenant(); - doPostWithTypedResponse("/api/resource", Collections.singletonList(savedResource), new TypeReference<>(){}, status().isBadRequest()); + doPostWithTypedResponse("/api/resource", savedResource, new TypeReference<>(){}, status().isForbidden()); deleteDifferentTenant(); } @@ -148,18 +147,15 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes @Test public void testFindTenantTbResources() throws Exception { - List resourcesToSave = new ArrayList<>(); + List resources = new ArrayList<>(); for (int i = 0; i < 173; i++) { TbResource resource = new TbResource(); resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); resource.setData("Test Data"); - resourcesToSave.add(resource); + resources.add(new TbResourceInfo(save(resource))); } - - List resources =save(resourcesToSave).stream().map(TbResourceInfo::new).collect(Collectors.toList()); - List loadedResources = new ArrayList<>(); PageLink pageLink = new PageLink(24); PageData pageData; @@ -287,10 +283,6 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes } private TbResource save(TbResource tbResource) throws Exception { - return save(Collections.singletonList(tbResource)).get(0); - } - - private List save(List tbResources) throws Exception { - return doPostWithTypedResponse("/api/resource", tbResources, new TypeReference<>(){}); + return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>(){}); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index 0f969a848f..653f6978f6 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -28,6 +28,7 @@ import java.util.Arrays; @ClasspathSuite.ClassnameFilters({ // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", +// "org.thingsboard.server.controller.sql.TbResourceControllerSqlTest", "org.thingsboard.server.controller.sql.*Test", }) public class ControllerSqlTestSuite { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index ad1e542ce1..a879e4bc78 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -122,7 +122,7 @@ public class LwM2mTransportRequest { DownlinkRequest request = null; ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null; LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - ResourceModel resource = lwM2MClient.getResourceModel(target); + ResourceModel resource = lwM2MClient.getResourceModel(targetIdVer); timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; switch (typeOper) { case GET_TYPE_OPER_READ: diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index f7e82f3a41..7e3183255a 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -22,7 +22,7 @@ import {Observable} from 'rxjs'; import {PageData} from '@shared/models/page/page-data'; import {DeviceProfile, DeviceProfileInfo, DeviceTransportType} from '@shared/models/device.models'; import {isDefinedAndNotNull, isEmptyStr} from '@core/utils'; -import {ObjectLwM2M, ServerSecurityConfig} from '@home/components/profile/device/lwm2m/profile-config.models'; +import {ObjectLwM2M, ServerSecurityConfig} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; import {SortOrder} from '@shared/models/page/sort-order'; @Injectable({ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html new file mode 100644 index 0000000000..5599e8d116 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html @@ -0,0 +1,53 @@ + +
+ +
+

{{ (readonly ? 'device-profile.lwm2m.attribute-lwm2m-toolbar-view' : + 'device-profile.lwm2m.attribute-lwm2m-toolbar-edit') | translate }}

+
+ + +
+ + +
+ + +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts new file mode 100644 index 0000000000..834be7535c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts @@ -0,0 +1,90 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, Inject, OnInit, SkipSelf} from "@angular/core"; +import {ErrorStateMatcher} from "@angular/material/core"; +import {DialogComponent} from "@shared/components/dialog.component"; +import {Store} from "@ngrx/store"; +import {AppState} from "@core/core.state"; +import {Router} from "@angular/router"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from "@angular/forms"; +import {TranslateService} from "@ngx-translate/core"; +import {JsonObject} from "@angular/compiler-cli/ngcc/src/packages/entry_point"; + +export interface Lwm2mAttributesDialogData { + readonly: boolean; + attributeLwm2m: JsonObject; + destName: string +} + +@Component({ + selector: 'tb-lwm2m-attributes-dialog', + templateUrl: './lwm2m-attributes-dialog.component.html', + styleUrls: ['./lwm2m-attributes.component.scss'], + providers: [{provide: ErrorStateMatcher, useExisting: Lwm2mAttributesDialogComponent}], +}) +export class Lwm2mAttributesDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { + + readonly = this.data.readonly; + + attributeLwm2m = this.data.attributeLwm2m; + + submitted = false; + + dirtyValue = false; + + attributeLwm2mDialogFormGroup: FormGroup; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: Lwm2mAttributesDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + public translate: TranslateService) { + super(store, router, dialogRef); + + this.attributeLwm2mDialogFormGroup = this.fb.group({ + keyFilters: [{}, []] + }); + this.attributeLwm2mDialogFormGroup.patchValue({keyFilters: this.attributeLwm2m}); + this.attributeLwm2mDialogFormGroup.get('keyFilters').valueChanges.subscribe((attributes) => { + this.attributeLwm2m = attributes; + }); + if (this.readonly) { + this.attributeLwm2mDialogFormGroup.disable({emitEvent: false}); + } + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + save(): void { + this.submitted = true; + this.dialogRef.close(this.attributeLwm2m); + } + + cancel(): void { + this.dialogRef.close(null); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.html new file mode 100644 index 0000000000..e0a53c49c4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.html @@ -0,0 +1,83 @@ + +
+
+ device-profile.lwm2m.attribute-lwm2m-destination + {{ titleText }} +
+
+ device-profile.lwm2m.attribute-lwm2m-name + device-profile.lwm2m.attribute-lwm2m-value +
+
+
+
+ + + + + {{ attributeLwm2mMap.get(attrKey[attributeLwm2m]) }} + + + + + + + + +
+ + {{ 'device-profile.lwm2m.key-name' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + {{ 'device-profile.lwm2m.valid-attribute-lwm2m-key' | translate: {attrEnums: attrKeys} }} + + + {{ 'device-profile.lwm2m.valid-attribute-lwm2m-value' | translate: {attrEnums: attrKeys} }} + +
+ {{noDataText ? noDataText : 'device-profile.lwm2m.no-data'}} +
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts new file mode 100644 index 0000000000..7d7af196f0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts @@ -0,0 +1,227 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, forwardRef, Input, OnInit} from "@angular/core"; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from "@angular/forms"; +import {Subscription} from "rxjs"; +import {PageComponent} from "@shared/components/page.component"; +import {Store} from "@ngrx/store"; +import {AppState} from "@core/core.state"; +import { + ATTRIBUTE_KEYS, + ATTRIBUTE_LWM2M_ENUM, + ATTRIBUTE_LWM2M_MAP +} from "@home/components/profile/device/lwm2m/lwm2m-profile-config.models"; +import {isDefinedAndNotNull, isEmpty, isEmptyStr, isUndefinedOrNull} from "@core/utils"; + + +@Component({ + selector: 'tb-lwm2m-attributes-key-list', + templateUrl: './lwm2m-attributes-key-list.component.html', + styleUrls: ['./lwm2m-attributes.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => Lwm2mAttributesKeyListComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => Lwm2mAttributesKeyListComponent), + multi: true, + } + ] +}) +export class Lwm2mAttributesKeyListComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { + + attrKeys = ATTRIBUTE_KEYS; + + attrKey = ATTRIBUTE_LWM2M_ENUM; + + attributeLwm2mMap = ATTRIBUTE_LWM2M_MAP; + + @Input() disabled: boolean; + + @Input() titleText: string; + + @Input() noDataText: string; + + kvListFormGroup: FormGroup; + + private propagateChange = null; + + private valueChangeSubscription: Subscription = null; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.kvListFormGroup = this.fb.group({}); + this.kvListFormGroup.addControl('keyVals', + this.fb.array([])); + } + + keyValsFormArray(): FormArray { + return this.kvListFormGroup.get('keyVals') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.kvListFormGroup.disable({emitEvent: false}); + } else { + this.kvListFormGroup.enable({emitEvent: false}); + } + } + + writeValue(keyValMap: { [key: string]: string }): void { + if (this.valueChangeSubscription) { + this.valueChangeSubscription.unsubscribe(); + } + const keyValsControls: Array = []; + if (keyValMap) { + for (const property of Object.keys(keyValMap)) { + if (Object.prototype.hasOwnProperty.call(keyValMap, property)) { + keyValsControls.push(this.fb.group({ + key: [property, [Validators.required, this.attributeLwm2mKeyValidator]], + value: [keyValMap[property], this.attributeLwm2mValueValidator(property)] + })); + } + } + } + this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls)); + this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => { + // this.updateValidate(); + this.updateModel(); + }); + if (this.disabled) { + this.kvListFormGroup.disable({emitEvent: false}); + } else { + this.kvListFormGroup.enable({emitEvent: false}); + } + } + + public removeKeyVal(index: number) { + (this.kvListFormGroup.get('keyVals') as FormArray).removeAt(index); + } + + public addKeyVal() { + const keyValsFormArray = this.kvListFormGroup.get('keyVals') as FormArray; + keyValsFormArray.push(this.fb.group({ + key: ['', [Validators.required, this.attributeLwm2mKeyValidator]], + value: ['', []] + })); + } + + public validate(c?: FormControl) { + const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; + let valid = true; + for (const entry of kvList) { + if (isUndefinedOrNull(entry.key) || isEmptyStr(entry.key) || !ATTRIBUTE_KEYS.includes(entry.key)) { + valid = false; + break; + } + if (entry.key !== 'ver' && isNaN(Number(entry.value))) { + valid = false; + break; + } + } + return (valid) ? null : { + keyVals: { + valid: false, + }, + }; + } + + private updateValidate (c?: FormControl) { + const kvList = this.kvListFormGroup.get('keyVals') as FormArray; + kvList.controls.forEach(fg => { + if (fg.get('key').value==='ver') { + fg.get('value').setValidators(null); + fg.get('value').setErrors(null); + } + else { + fg.get('value').setValidators(this.attributeLwm2mValueNumberValidator); + fg.get('value').setErrors(this.attributeLwm2mValueNumberValidator(fg.get('value'))); + } + }); + } + + private updateModel() { + this.updateValidate(); + if (this.validate() === null) { + const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; + const keyValMap: { [key: string]: string | number } = {}; + kvList.forEach((entry) => { + if (isUndefinedOrNull(entry.value) || entry.key === 'ver' || isEmptyStr(entry.value.toString())) { + keyValMap[entry.key] = entry.value.toString(); + } else { + keyValMap[entry.key] = Number(entry.value) + } + }); + this.propagateChange(keyValMap); + } + else { + this.propagateChange(null); + } + } + + + private attributeLwm2mKeyValidator = (control: AbstractControl) => { + const key = control.value as string; + if (isDefinedAndNotNull(key) && !isEmpty(key)) { + if (!ATTRIBUTE_KEYS.includes(key)) { + return { + validAttributeKey: true + }; + } + } + return null; + } + + private attributeLwm2mValueNumberValidator = (control: AbstractControl) => { + if (isNaN(Number(control.value)) || Number(control.value) < 0) { + return { + 'validAttributeValue': true + }; + } + return null; + } + + private attributeLwm2mValueValidator = (property: string): Object [] => { + return property === 'ver'? [] : [this.attributeLwm2mValueNumberValidator]; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html new file mode 100644 index 0000000000..2011cd65f5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html @@ -0,0 +1,32 @@ + +
+
+ {{attributeLwm2mToString()}} +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss new file mode 100644 index 0000000000..63dd0bdb3e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .tb-kv-map { + span.no-data-found { + position: relative; + display: flex; + height: 40px; + + &.disabled { + color: rgba(0, 0, 0, .38); + } + } + } +} + +:host ::ng-deep { + .mat-form-field-wrapper { + padding-bottom: 0; + } + .mat-form-field-infix { + border-top: 0; + } + .mat-form-field-underline { + bottom: 0; + } +} + +.vertical-padding { + padding: 0 0 10px 20px; +} + +.resource-name-lw-end{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align:end; + //width: 80px; + cursor: pointer; +} + +.resource-name-lw{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; +} + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts new file mode 100644 index 0000000000..ca4b66ae36 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts @@ -0,0 +1,159 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, EventEmitter, forwardRef, Inject, Input, Output} from "@angular/core"; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from "@angular/forms"; +import {coerceBooleanProperty} from "@angular/cdk/coercion"; +import {Store} from "@ngrx/store"; +import {AppState} from "@core/core.state"; +import {DeviceProfileService} from "@core/http/device-profile.service"; +import {WINDOW} from "@core/services/window.service"; +import {deepClone, isDefinedAndNotNull, isEmpty} from "@core/utils"; +import { + Lwm2mAttributesDialogComponent, + Lwm2mAttributesDialogData +} from "@home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component"; +import {MatDialog} from "@angular/material/dialog"; +import {TranslateService} from "@ngx-translate/core"; +import {ATTRIBUTE_LWM2M_LABEL} from "@home/components/profile/device/lwm2m/lwm2m-profile-config.models"; + + +@Component({ + selector: 'tb-profile-lwm2m-attributes', + templateUrl: './lwm2m-attributes.component.html', + styleUrls: ['./lwm2m-attributes.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => Lwm2mAttributesComponent), + multi: true + }] +}) +export class Lwm2mAttributesComponent implements ControlValueAccessor { + attributeLwm2mFormGroup: FormGroup; + attributeLwm2mLabel = ATTRIBUTE_LWM2M_LABEL; + + private requiredValue: boolean; + private dirty = false; + + @Input() + attributeLwm2m: {}; + + @Input() + isAttributeTelemetry: boolean; + + @Input() + destName: string; + + @Input() + disabled: boolean; + + @Output() + updateAttributeLwm2m = new EventEmitter(); + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + private propagateChange = (v: any) => { + } + + constructor(private store: Store, + private dialog: MatDialog, + private fb: FormBuilder, + private deviceProfileService: DeviceProfileService, + private translate: TranslateService, + @Inject(WINDOW) private window: Window) {} + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.attributeLwm2mFormGroup.disable({emitEvent: false}); + } else { + this.attributeLwm2mFormGroup.enable({emitEvent: false}); + } + } + + ngOnInit() { + this.attributeLwm2mFormGroup = this.fb.group({ + attributeLwm2m: [this.attributeLwm2m] + }); + } + + writeValue(value: {} | null): void {} + + attributeLwm2mToString = (): string => { + return this.isIconEditAdd () ? this.attributeLwm2mLabelToString() : this.translate.instant('device-profile.lwm2m.no-data'); + } + + private attributeLwm2mLabelToString = (): string => { + let label = JSON.stringify(this.attributeLwm2m); + label = deepClone(label.replace('{', '')); + label = deepClone(label.replace('}', '')); + this.attributeLwm2mLabel.forEach((value: string, key: string) => { + const dest = '\"' + key + '\"\:'; + label = deepClone(label.replace(dest, value)); + }); + return label; + } + + isDisableBtn (): boolean { + return this.disabled || this.isAttributeTelemetry ? !(isDefinedAndNotNull(this.attributeLwm2m) && + !isEmpty(this.attributeLwm2m) && this.disabled) : this.disabled; + } + + isIconView (): boolean { + return this.isAttributeTelemetry || this.disabled; + } + + isIconEditAdd (): boolean { + return isDefinedAndNotNull(this.attributeLwm2m) && !isEmpty(this.attributeLwm2m); + } + + isToolTipLabel (): string { + return this.disabled ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip') : + this.isAttributeTelemetry ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-disable-tip') : + this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip'); + } + + public editAttributesLwm2m = ($event: Event): void => { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(Lwm2mAttributesDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + readonly: this.disabled, + attributeLwm2m: this.disabled ? this.attributeLwm2m : deepClone(this.attributeLwm2m), + destName: this.destName + } + }).afterClosed().subscribe((result) => { + if (result) { + this.attributeLwm2m = result; + this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: this.attributeLwm2m}); + this.updateAttributeLwm2m.next(this.attributeLwm2m); + } + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 0adc882f0f..b82da4bc06 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { Component, forwardRef, Inject, Input } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import {Component, forwardRef, Inject, Input} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; import { DEFAULT_CLIENT_HOLD_OFF_TIME, DEFAULT_ID_SERVER, @@ -27,13 +27,13 @@ import { SECURITY_CONFIG_MODE, SECURITY_CONFIG_MODE_NAMES, ServerSecurityConfig -} from './profile-config.models'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { WINDOW } from '@core/services/window.service'; -import { pairwise, startWith } from 'rxjs/operators'; -import { DeviceProfileService } from '@core/http/device-profile.service'; +} from './lwm2m-profile-config.models'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {WINDOW} from '@core/services/window.service'; +import {pairwise, startWith} from 'rxjs/operators'; +import {DeviceProfileService} from '@core/http/device-profile.service'; @Component({ selector: 'tb-profile-lwm2m-device-config-server', diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index c70e5b59dd..4231ab35ff 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -22,19 +22,20 @@ import {AppState} from '@app/core/core.state'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import { ATTRIBUTE, + DEFAULT_BINDING, getDefaultProfileConfig, INSTANCES, KEY_NAME, + Lwm2mProfileConfigModels, ModelValue, ObjectLwM2M, OBSERVE, OBSERVE_ATTR_TELEMETRY, - ProfileConfigModels, RESOURCES, TELEMETRY -} from './profile-config.models'; +} from './lwm2m-profile-config.models'; import {DeviceProfileService} from '@core/http/device-profile.service'; -import {deepClone, isDefinedAndNotNull, isUndefined} from '@core/utils'; +import {deepClone, isDefinedAndNotNull, isEmpty, isUndefined} from '@core/utils'; import {WINDOW} from '@core/services/window.service'; import {JsonArray, JsonObject} from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import {Direction} from '@shared/models/page/sort-order'; @@ -50,7 +51,7 @@ import {Direction} from '@shared/models/page/sort-order'; }) export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators { - private configurationValue: ProfileConfigModels; + private configurationValue: Lwm2mProfileConfigModels; private requiredValue: boolean; private disabled = false; @@ -70,7 +71,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.requiredValue = coerceBooleanProperty(value); } - private propagateChange = (v: any) => { } + private propagateChange = (v: any) => { + } constructor(private store: Store, private fb: FormBuilder, @@ -84,7 +86,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro lifetime: [null, Validators.required], defaultMinPeriod: [null, Validators.required], notifIfDisabled: [true, []], - binding: ['U', Validators.required], + binding: [DEFAULT_BINDING, Validators.required], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], }); @@ -118,7 +120,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } } - writeValue(value: ProfileConfigModels | null): void { + writeValue(value: Lwm2mProfileConfigModels | null): void { this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value; this.lwm2mDeviceConfigFormGroup.patchValue({ configurationJson: this.configurationValue @@ -171,7 +173,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => { - this.lwm2mDeviceProfileFormGroup.patchValue({ + this.lwm2mDeviceProfileFormGroup.patchValue({ observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList)) }, {emitEvent: false}); @@ -198,15 +200,15 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private getObserveAttrTelemetryObjects = (objectList: ObjectLwM2M[]): object => { const objectLwM2MS = deepClone(objectList); if (this.configurationValue.observeAttr && objectLwM2MS.length > 0) { - const observeArray = this.configurationValue.observeAttr.observe; const attributeArray = this.configurationValue.observeAttr.attribute; const telemetryArray = this.configurationValue.observeAttr.telemetry; let keyNameJson = this.configurationValue.observeAttr.keyName; if (this.includesNotZeroInstance(attributeArray, telemetryArray)) { this.addInstances(attributeArray, telemetryArray, objectLwM2MS); } - if (isDefinedAndNotNull(observeArray) && observeArray.length > 0) { - this.updateObserveAttrTelemetryObjects(observeArray, objectLwM2MS, OBSERVE); + if (isDefinedAndNotNull(this.configurationValue.observeAttr.observe) && + this.configurationValue.observeAttr.observe.length > 0) { + this.updateObserveAttrTelemetryObjects(this.configurationValue.observeAttr.observe, objectLwM2MS, OBSERVE); } if (isDefinedAndNotNull(attributeArray) && attributeArray.length > 0) { this.updateObserveAttrTelemetryObjects(attributeArray, objectLwM2MS, ATTRIBUTE); @@ -214,6 +216,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro if (isDefinedAndNotNull(telemetryArray) && telemetryArray.length > 0) { this.updateObserveAttrTelemetryObjects(telemetryArray, objectLwM2MS, TELEMETRY); } + if (isDefinedAndNotNull(this.configurationValue.observeAttr.attributeLwm2m)) { + this.updateAttributeLwm2m(objectLwM2MS); + } if (isDefinedAndNotNull(keyNameJson)) { this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray); this.upDateJsonAllConfig(); @@ -256,45 +261,72 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro [nameParameter] = true; } }); + } + private updateAttributeLwm2m = (objectLwM2MS: ObjectLwM2M[]): void => { + Object.keys(this.configurationValue.observeAttr.attributeLwm2m).forEach(key => { + const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String); + let objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId); + if (objectLwM2M && instanceId) { + let instance = objectLwM2M.instances.find(instance => instance.id === +instanceId) + if (instance && resourceId) { + instance.resources.find(resource => resource.id === +resourceId) + .attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key]; + } else if (instance) { + instance.attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key]; + } + } else if (objectLwM2M) { + objectLwM2M.attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key]; + } + }); } - private updateKeyNameObjects = (clientObserveAttrTelemetry: ObjectLwM2M[]): void => { + private updateKeyNameObjects = (objectLwM2MS: ObjectLwM2M[]): void => { Object.keys(this.configurationValue.observeAttr.keyName).forEach(key => { const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String); - const objectLwM2M = clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.keyId === objectKeyId) + const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId) if (objectLwM2M) { objectLwM2M.instances.find(instance => instance.id === +instanceId) - .resources.find(resource => resource.id === +resourceId) - .keyName = this.configurationValue.observeAttr.keyName[key]; - }}); + .resources.find(resource => resource.id === +resourceId) + .keyName = this.configurationValue.observeAttr.keyName[key]; + } + }); } private validateKeyNameObjects = (nameJson: JsonObject, attributeArray: JsonArray, telemetryArray: JsonArray): {} => { const keyName = JSON.parse(JSON.stringify(nameJson)); let keyNameValidate = {}; - const keyAttrTelemetry = attributeArray.concat(telemetryArray) ; + const keyAttrTelemetry = attributeArray.concat(telemetryArray); Object.keys(keyName).forEach(key => { if (keyAttrTelemetry.includes(key)) { keyNameValidate[key] = keyName[key]; } }); - return keyNameValidate; + return keyNameValidate; } private updateObserveAttrTelemetryFromGroupToJson = (val: ObjectLwM2M[]): void => { const observeArray: Array = []; const attributeArray: Array = []; const telemetryArray: Array = []; + const attributeLwm2m: any = {}; const keyNameNew = {}; const observeJson: ObjectLwM2M[] = JSON.parse(JSON.stringify(val)); observeJson.forEach(obj => { + if (isDefinedAndNotNull(obj.attributeLwm2m) && !isEmpty(obj.attributeLwm2m)) { + const pathObject = `/${obj.keyId}`; + attributeLwm2m[pathObject] = obj.attributeLwm2m; + } if (obj.hasOwnProperty(INSTANCES) && Array.isArray(obj.instances)) { obj.instances.forEach(instance => { + if (isDefinedAndNotNull(instance.attributeLwm2m) && !isEmpty(instance.attributeLwm2m)) { + const pathInstance = `/${obj.keyId}/${instance.id}`; + attributeLwm2m[pathInstance] = instance.attributeLwm2m; + } if (instance.hasOwnProperty(RESOURCES) && Array.isArray(instance.resources)) { instance.resources.forEach(resource => { if (resource.attribute || resource.telemetry) { - let pathRes = `/${obj.keyId}/${instance.id}/${resource.id}`; + const pathRes = `/${obj.keyId}/${instance.id}/${resource.id}`; if (resource.observe) { observeArray.push(pathRes); } @@ -305,6 +337,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro telemetryArray.push(pathRes); } keyNameNew[pathRes] = resource.keyName; + if (isDefinedAndNotNull(resource.attributeLwm2m) && !isEmpty(resource.attributeLwm2m)) { + attributeLwm2m[pathRes] = resource.attributeLwm2m; + } } }) } @@ -316,13 +351,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro observe: observeArray, attribute: attributeArray, telemetry: telemetryArray, - keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew) + keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew), + attributeLwm2m: attributeLwm2m }; } else { this.configurationValue.observeAttr.observe = observeArray; this.configurationValue.observeAttr.attribute = attributeArray; this.configurationValue.observeAttr.telemetry = telemetryArray; - this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew); + this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m; } } @@ -392,6 +428,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.removeObserveAttrTelemetryFromJson(TELEMETRY, value.keyId); this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.keyId); this.removeKeyNameFromJson(value.keyId); + this.removeAttributeLwm2mFromJson(value.keyId); this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); this.upDateJsonAllConfig(); } @@ -413,4 +450,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } }); } + + private removeAttributeLwm2mFromJson = (keyId: string): void => { + debugger + const keyNameJson = this.configurationValue.observeAttr.attributeLwm2m; + Object.keys(keyNameJson).forEach(key => { + if (key.startsWith(`/${keyId}`)) { + delete keyNameJson[key]; + } + }); + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.html similarity index 95% rename from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html rename to ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.html index b37abd2c79..50f49a8ceb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.html @@ -17,7 +17,7 @@ -->
- {{data.objectName}}    (object [{{data.objectKeyId}}]) + {{data.objectName}} (object <{{data.objectKeyId}}>) @@ -45,22 +55,10 @@
-
- Instance [{{instances.get('id').value}}] +
+ {{'device-profile.lwm2m.instance-label' | translate}} <{{instances.get('id').value}}>
-
- - -
-
+
-
+
+ + +
+
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.css b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss similarity index 65% rename from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.css rename to ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss index 7dc73ee405..30ce849c83 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.css +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss @@ -13,14 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -.vertical-padding { - padding: 0 0 10px 20px; -} - -.left-padding { - padding-left: 5px; -} - .tb-panel-title-height { user-select: none; min-height: 32px; @@ -28,21 +20,5 @@ .checkbox-padding { padding-left: 22px; -} - -.label-resource { - /*padding: 4px;*/ - color: #002699; - /*text-transform: uppercase;*/ - background: rgba(220, 220, 220, .35); - border-radius: 20px; - width: inherit; - /*margin: 10px 0;*/ - overflow: hidden; - font-size: 15px; - text-overflow: ellipsis; - white-space: nowrap; - opacity: .8; text-align:center; } - diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts index 814876dab8..c38c413e98 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts @@ -14,7 +14,6 @@ /// limitations under the License. /// - import {Component, forwardRef, Input} from '@angular/core'; import { AbstractControl, @@ -28,19 +27,29 @@ import { import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {CLIENT_LWM2M, Instance, INSTANCES, ObjectLwM2M, ResourceLwM2M, RESOURCES} from './profile-config.models'; +import { + ATTRIBUTE, + ATTRIBUTE_LWM2M, + CLIENT_LWM2M, + Instance, + INSTANCES, + ObjectLwM2M, + ResourceLwM2M, + RESOURCES, + TELEMETRY +} from './lwm2m-profile-config.models'; import {deepClone, isDefinedAndNotNull, isEqual, isUndefined} from '@core/utils'; import {MatDialog} from '@angular/material/dialog'; import {TranslateService} from '@ngx-translate/core'; import { - Lwm2mObjectAddInstancesComponent, - Lwm2mObjectAddInstancesData -} from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances.component'; + Lwm2mObjectAddInstancesData, + Lwm2mObjectAddInstancesDialogComponent +} from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component'; @Component({ selector: 'tb-profile-lwm2m-observe-attr-telemetry', templateUrl: './lwm2m-observe-attr-telemetry.component.html', - styleUrls: ['./lwm2m-observe-attr-telemetry.component.css'], + styleUrls: [ './lwm2m-observe-attr-telemetry.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -56,6 +65,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor valuePrev = null as any; observeAttrTelemetryFormGroup: FormGroup; + resources = RESOURCES; get required(): boolean { return this.requiredValue; @@ -143,6 +153,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor name: objectLwM2M.name, multiple: objectLwM2M.multiple, mandatory: objectLwM2M.mandatory, + attributeLwm2m: objectLwM2M.attributeLwm2m, instances: this.createInstanceLwM2M(objectLwM2M.instances) }); })); @@ -152,6 +163,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor return this.fb.array(instancesLwM2M.map((instanceLwM2M) => { return this.fb.group({ id: instanceLwM2M.id, + attributeLwm2m: {value: instanceLwM2M.attributeLwm2m, disabled: this.disabled}, resources: {value: instanceLwM2M.resources, disabled: this.disabled} }); })); @@ -222,7 +234,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor $event.stopPropagation(); $event.preventDefault(); } - this.dialog.open(Lwm2mObjectAddInstancesComponent, { + this.dialog.open(Lwm2mObjectAddInstancesDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { @@ -240,6 +252,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor } private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => { + debugger const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls .find(e => e.value.keyId === data.objectKeyId) as FormGroup; const instancesArray = objectLwM2MFormGroup.value.instances as Instance []; @@ -249,6 +262,8 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor r.attribute = false; r.telemetry = false; r.observe = false; + r.keyName = {}; + r.attributeLwm2m = {}; }); const valueOld = this.instancesToSetId(instancesArray); if (!isEqual(valueOld, data.instancesIds)) { @@ -286,6 +301,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor private pushInstance = (instancesFormArray: FormArray, x: number, instanceNew: Instance): void => { instancesFormArray.push(this.fb.group({ id: x, + attributeLwm2m: instanceNew.attributeLwm2m, resources: {value: instanceNew.resources, disabled: this.disabled} })); } @@ -297,4 +313,54 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor private instancesToSetId = (instances: Instance[]): Set => { return new Set(instances.map(x => x.id)); } + + getNameObjectLwm2m = (objectName: string, idVerObj: string): string => { + return objectName + ' <' + idVerObj + '>'; + } + getNameInstanceLwm2m = (instance: Instance, idVerObj: string): string => { + return ' instance <' + idVerObj + '/' + instance.id +'>'; + } + + updateAttributeLwm2mObject = (event: Event, objectKeyId: number): void => { + const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls + .find(e => e.value.keyId === objectKeyId) as FormGroup; + objectLwM2MFormGroup.patchValue({attributeLwm2m: event}); + } + + updateAttributeLwm2mInstance = (event: Event, indexInstance: number, objectKeyId: number): void => { + + const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls + .find(e => e.value.keyId === objectKeyId) as FormGroup; + const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray; + instancesFormArray.at(indexInstance).patchValue({attributeLwm2m: event}); + } + + disableObserveInstance = (instance: AbstractControl): boolean => { + const checkedAttrTelemetry = this.observeInstance(instance); + if (checkedAttrTelemetry) { + instance.get(ATTRIBUTE_LWM2M).patchValue(null); + } + return checkedAttrTelemetry; + } + + disableObserveObject = (index: number): boolean => { + const object = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).at(index) as FormGroup; + const instances = object.controls.instances as FormArray; + const checkedAttrTelemetry = instances.controls.filter(instance => !this.disableObserveInstance(instance)); + if (checkedAttrTelemetry.length === 0) { + object.controls.attributeLwm2m.patchValue(null); + } + return checkedAttrTelemetry.length === 0; + } + + + observeInstance = (instance: AbstractControl): boolean => { + const resources = instance.get(RESOURCES).value as ResourceLwM2M[]; + if (isDefinedAndNotNull(resources)) { + const checkedAttribute = resources.filter(resource => resource[ATTRIBUTE]); + const checkedTelemetry = resources.filter(resource => resource[TELEMETRY]); + return checkedAttribute.length === 0 && checkedTelemetry.length === 0; + } + return false; + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts index 70334c9908..43c297df56 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts @@ -14,16 +14,19 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './lwm2m-device-profile-transport-configuration.component'; -import { Lwm2mObjectListComponent } from './lwm2m-object-list.component'; -import { Lwm2mObserveAttrTelemetryComponent } from './lwm2m-observe-attr-telemetry.component'; -import { Lwm2mObserveAttrTelemetryResourceComponent } from './lwm2m-observe-attr-telemetry-resource.component'; -import { Lwm2mDeviceConfigServerComponent } from './lwm2m-device-config-server.component'; -import { Lwm2mObjectAddInstancesComponent } from './lwm2m-object-add-instances.component'; -import { Lwm2mObjectAddInstancesListComponent } from './lwm2m-object-add-instances-list.component'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@app/shared/shared.module'; +import {NgModule} from '@angular/core'; +import {Lwm2mDeviceProfileTransportConfigurationComponent} from './lwm2m-device-profile-transport-configuration.component'; +import {Lwm2mObjectListComponent} from './lwm2m-object-list.component'; +import {Lwm2mObserveAttrTelemetryComponent} from './lwm2m-observe-attr-telemetry.component'; +import {Lwm2mObserveAttrTelemetryResourceComponent} from './lwm2m-observe-attr-telemetry-resource.component'; +import {Lwm2mAttributesDialogComponent} from './lwm2m-attributes-dialog.component'; +import {Lwm2mAttributesComponent} from './lwm2m-attributes.component'; +import {Lwm2mAttributesKeyListComponent} from './lwm2m-attributes-key-list.component'; +import {Lwm2mDeviceConfigServerComponent} from './lwm2m-device-config-server.component'; +import {Lwm2mObjectAddInstancesDialogComponent} from './lwm2m-object-add-instances-dialog.component'; +import {Lwm2mObjectAddInstancesListComponent} from './lwm2m-object-add-instances-list.component'; +import {CommonModule} from '@angular/common'; +import {SharedModule} from '@app/shared/shared.module'; @NgModule({ declarations: @@ -32,8 +35,11 @@ import { SharedModule } from '@app/shared/shared.module'; Lwm2mObjectListComponent, Lwm2mObserveAttrTelemetryComponent, Lwm2mObserveAttrTelemetryResourceComponent, + Lwm2mAttributesDialogComponent, + Lwm2mAttributesComponent, + Lwm2mAttributesKeyListComponent, Lwm2mDeviceConfigServerComponent, - Lwm2mObjectAddInstancesComponent, + Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesListComponent ], imports: [ @@ -45,8 +51,11 @@ import { SharedModule } from '@app/shared/shared.module'; Lwm2mObjectListComponent, Lwm2mObserveAttrTelemetryComponent, Lwm2mObserveAttrTelemetryResourceComponent, + Lwm2mAttributesDialogComponent, + Lwm2mAttributesComponent, + Lwm2mAttributesKeyListComponent, Lwm2mDeviceConfigServerComponent, - Lwm2mObjectAddInstancesComponent, + Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesListComponent ], providers: [ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts similarity index 79% rename from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts rename to ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 7241abbf76..678f7e260a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -16,7 +16,9 @@ export const PAGE_SIZE_LIMIT = 50; export const INSTANCES = 'instances'; +export const INSTANCE = 'instance'; export const RESOURCES = 'resources'; +export const ATTRIBUTE_LWM2M = 'attributeLwm2m'; export const CLIENT_LWM2M = 'clientLwM2M'; export const CLIENT_LWM2M_SETTINGS = 'clientLwM2mSettings'; export const OBSERVE_ATTR_TELEMETRY = 'observeAttrTelemetry'; @@ -33,7 +35,7 @@ export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1; export const DEFAULT_LIFE_TIME = 300; export const DEFAULT_MIN_PERIOD = 1; export const DEFAULT_NOTIF_IF_DESIBLED = true; -export const DEFAULT_BINDING = 'U'; +export const DEFAULT_BINDING = 'UQ'; export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0; export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_X509 = 3000; @@ -42,6 +44,44 @@ export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/; export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MAX = 65535; +export enum ATTRIBUTE_LWM2M_ENUM { + dim = 'dim', + ver = 'ver', + pmin = 'pmin', + pmax = 'pmax', + gt = 'gt', + lt = 'lt', + st = 'st' +} + +export const ATTRIBUTE_LWM2M_LABEL = new Map( + [ + [ATTRIBUTE_LWM2M_ENUM.dim, 'dim='], + [ATTRIBUTE_LWM2M_ENUM.ver, 'ver='], + [ATTRIBUTE_LWM2M_ENUM.pmin, 'pmin='], + [ATTRIBUTE_LWM2M_ENUM.pmax, 'pmax='], + [ATTRIBUTE_LWM2M_ENUM.gt, '>'], + [ATTRIBUTE_LWM2M_ENUM.lt, '<'], + [ATTRIBUTE_LWM2M_ENUM.st, 'st='], + + ] +); + +export const ATTRIBUTE_LWM2M_MAP = new Map( + [ + [ATTRIBUTE_LWM2M_ENUM.dim, 'Dimension'], + [ATTRIBUTE_LWM2M_ENUM.ver, 'Object version'], + [ATTRIBUTE_LWM2M_ENUM.pmin, 'Minimum period'], + [ATTRIBUTE_LWM2M_ENUM.pmax, 'Maximum period'], + [ATTRIBUTE_LWM2M_ENUM.gt, 'Greater than'], + [ATTRIBUTE_LWM2M_ENUM.lt, 'Lesser than'], + [ATTRIBUTE_LWM2M_ENUM.st, 'Step'], + + ] +); + +export const ATTRIBUTE_KEYS = Object.keys(ATTRIBUTE_LWM2M_ENUM) as string[]; + export enum SECURITY_CONFIG_MODE { PSK = 'PSK', RPK = 'RPK', @@ -54,7 +94,7 @@ export const SECURITY_CONFIG_MODE_NAMES = new Map( [SECURITY_CONFIG_MODE.PSK, 'Pre-Shared Key'], [SECURITY_CONFIG_MODE.RPK, 'Raw Public Key'], [SECURITY_CONFIG_MODE.X509, 'X.509 Certificate'], - [SECURITY_CONFIG_MODE.NO_SEC, 'No Security'], + [SECURITY_CONFIG_MODE.NO_SEC, 'No Security'] ] ); @@ -90,7 +130,7 @@ interface BootstrapSecurityConfig { lwm2mServer: ServerSecurityConfig; } -export interface ProfileConfigModels { +export interface Lwm2mProfileConfigModels { clientLwM2mSettings: ClientLwM2mSettings; observeAttr: ObservableAttributes; bootstrap: BootstrapSecurityConfig; @@ -100,11 +140,13 @@ export interface ProfileConfigModels { export interface ClientLwM2mSettings { clientOnlyObserveAfterConnect: number; } + export interface ObservableAttributes { observe: string[]; attribute: string[]; telemetry: string[]; keyName: {}; + attributeLwm2m: {}; } export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { @@ -151,21 +193,22 @@ function getDefaultProfileObserveAttrConfig(): ObservableAttributes { observe: [], attribute: [], telemetry: [], - keyName: {} + keyName: {}, + attributeLwm2m: {} }; } -function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { +export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModels { return { - clientOnlyObserveAfterConnect: 1 + clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(), + observeAttr: getDefaultProfileObserveAttrConfig(), + bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME) }; } -export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels { +function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { return { - clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(), - observeAttr: getDefaultProfileObserveAttrConfig(), - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME) + clientOnlyObserveAfterConnect: 1 }; } @@ -175,11 +218,13 @@ export interface ResourceLwM2M { observe: boolean; attribute: boolean; telemetry: boolean; - keyName: string; + keyName: {}; + attributeLwm2m?: {}; } export interface Instance { id: number; + attributeLwm2m?: {}; resources: ResourceLwM2M[]; } @@ -190,11 +235,12 @@ export interface Instance { * mandatory == false => Optional */ export interface ObjectLwM2M { + id: number; keyId: string; name: string; multiple?: boolean; mandatory?: boolean; + attributeLwm2m?: {}; instances?: Instance []; } - diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 7a5bcda9c8..c16f0c62ef 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1171,17 +1171,32 @@ "instances-list": "Instances list", "instances-input": "Input Instance Id value", "instances-input-holder": "Input Instance number...", - "resource-label": "Resource", + "instance-label": "Instance", + "resource-label": " Resource name", "observe-label": "Observe", "attribute-label": "Attribute", "telemetry-label": "Telemetry", "key-name-label": "Key Name", + "attribute-lwm2m-label": "AttrLwm2m", + "resource-tip": "ID & Original Name of the Resource", "is-observe-tip": "Is Observe", + "not-observe-tip": "To observe select telemetry or attributes first", "is-attr-tip": "Is Attribute", "is-telemetry-tip": "Is Telemetry", - "key-name-tip": "Key Name", + "key-name-tip": "Key Name in Camel format", + "attribute-lwm2m-tip": "Attributes Lwm2m", + "attribute-lwm2m-disable-tip": "To edit Attributes Lwm2m select telemetry or attributes first", + "valid-attribute-lwm2m-key": "Name have be only '{{attrEnums}}'", + "valid-attribute-lwm2m-value": "Value have be Long, Double and greater than zero or null/empty", + "no-data": "No attributes", "key-name": "Key Name", - "key-name_label": "Key Name in Camel format", + "attribute-lwm2m-name": "Name attribute", + "attribute-lwm2m-value": "Value", + "attribute-lwm2m-toolbar-edit": "Edit attributes Lwm2m", + "attribute-lwm2m-toolbar-view": "View Attributes Lwm2m", + "attribute-lwm2m-destination": "Destination:", + "attribute-lwm2m-add-tip": "Add attribute lwm2m", + "attribute-lwm2m-remove-tip": "Remove attribute lwm2m", "required": " value is required.", "mode": "Security config mode", "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }",