diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 5cf051367a..b428e1cc05 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -16,8 +16,6 @@ package org.thingsboard.server.service.resource; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.DDFFileParser; -import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ResourceType; @@ -53,11 +51,9 @@ import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService { private final ResourceService resourceService; - private final DDFFileParser ddfFileParser; public DefaultTbResourceService(ResourceService resourceService) { this.resourceService = resourceService; - this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java index bb2e49bd62..4a07444bbb 100644 --- a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; +import org.thingsboard.server.common.data.util.TbDDFFileParser; import org.thingsboard.server.dao.exception.DataValidationException; import java.io.ByteArrayInputStream; @@ -41,7 +42,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA @Slf4j public class LwM2mObjectModelUtils { - private static final DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); + private static final TbDDFFileParser ddfFileParser = new TbDDFFileParser(); public static void toLwm2mResource (TbResource resource) throws ThingsboardException { try { diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index e2c5985553..5c9d94cde2 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -45,6 +45,8 @@ import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest @@ -75,6 +77,32 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { "\n" + ""; + private static final String LWM2M_TEST_MODEL_WITH_XXE = " ]>" + + "\n" + + "\n" + + "My first resource\n" + + "\n" + + "0\n" + + "\n" + + "&ObjectVersion;\n" + + "Multiple\n" + + "Mandatory\n" + + "\n" + + "\n" + + "LWM2M\n" + + "RW\n" + + "Single\n" + + "Mandatory\n" + + "String\n" + + "0..255\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + private static final String DEFAULT_FILE_NAME = "test.jks"; private IdComparator idComparator = new IdComparator<>(); @@ -126,11 +154,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { loginTenantAdmin(); - Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); createResource("test", DEFAULT_FILE_NAME); - Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); try { assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME)) @@ -145,19 +173,19 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { @Test public void sumDataSizeByTenantId() throws Exception { - Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); createResource("test", DEFAULT_FILE_NAME); - Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); int maxSumDataSize = 8; for (int i = 2; i <= maxSumDataSize; i++) { createResource("test" + i, i + DEFAULT_FILE_NAME); - Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId)); } - Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId)); } private TbResource createResource(String title, String filename) throws Exception { @@ -184,16 +212,16 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource.getId()); Assert.assertTrue(savedResource.getCreatedTime() > 0); - Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId()); - Assert.assertEquals(resource.getTitle(), savedResource.getTitle()); - Assert.assertEquals(resource.getResourceKey(), savedResource.getResourceKey()); - Assert.assertEquals(resource.getData(), savedResource.getData()); + assertEquals(resource.getTenantId(), savedResource.getTenantId()); + assertEquals(resource.getTitle(), savedResource.getTitle()); + assertEquals(resource.getResourceKey(), savedResource.getResourceKey()); + assertEquals(resource.getData(), savedResource.getData()); savedResource.setTitle("My new resource"); resourceService.save(savedResource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); - Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); + assertEquals(foundResource.getTitle(), savedResource.getTitle()); resourceService.delete(savedResource, null); } @@ -211,10 +239,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource.getId()); Assert.assertTrue(savedResource.getCreatedTime() > 0); - Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId()); - Assert.assertEquals("My first resource id=0 v1.0", savedResource.getTitle()); - Assert.assertEquals("0_1.0", savedResource.getResourceKey()); - Assert.assertEquals(resource.getData(), savedResource.getData()); + assertEquals(resource.getTenantId(), savedResource.getTenantId()); + assertEquals("My first resource id=0 v1.0", savedResource.getTitle()); + assertEquals("0_1.0", savedResource.getResourceKey()); + assertEquals(resource.getData(), savedResource.getData()); resourceService.delete(savedResource, null); } @@ -228,7 +256,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setData("Test Data"); TbResource savedResource = resourceService.save(resource); - Assert.assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); + assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); resourceService.delete(savedResource, null); } @@ -285,6 +313,21 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { }); } + @Test + public void testSaveLwm2mTbResourceWithXXE() { + TbResource resource = new TbResource(); + resource.setTenantId(tenantId); + resource.setResourceType(ResourceType.LWM2M_MODEL); + resource.setFileName("xxe_test_model.xml"); + resource.setData(Base64.getEncoder().encodeToString(LWM2M_TEST_MODEL_WITH_XXE.getBytes())); + + DataValidationException thrown = assertThrows(DataValidationException.class, () -> { + resourceService.save(resource); + }); + assertEquals("Failed to parse file xxe_test_model.xml", thrown.getMessage()); + } + + @Test public void testFindResourceById() throws Exception { TbResource resource = new TbResource(); @@ -296,7 +339,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertNotNull(foundResource); - Assert.assertEquals(savedResource, foundResource); + assertEquals(savedResource, foundResource); resourceService.delete(savedResource, null); } @@ -312,7 +355,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey()); Assert.assertNotNull(foundResource); - Assert.assertEquals(savedResource, foundResource); + assertEquals(savedResource, foundResource); resourceService.delete(savedResource, null); } @@ -366,7 +409,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { Collections.sort(resources, idComparator); Collections.sort(loadedResources, idComparator); - Assert.assertEquals(resources, loadedResources); + assertEquals(resources, loadedResources); resourceService.deleteResourcesByTenantId(tenantId); @@ -427,14 +470,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { Collections.sort(resources, idComparator); Collections.sort(loadedResources, idComparator); - Assert.assertEquals(resources, loadedResources); + assertEquals(resources, loadedResources); resourceService.deleteResourcesByTenantId(tenantId); pageLink = new PageLink(100); pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); - Assert.assertEquals(pageData.getData().size(), 100); + assertEquals(pageData.getData().size(), 100); resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID); diff --git a/common/data/pom.xml b/common/data/pom.xml index f4e1fb2756..35e9d1dc55 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -116,6 +116,11 @@ com.google.protobuf protobuf-java-util + + org.eclipse.leshan + leshan-core + compile + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java new file mode 100644 index 0000000000..7c1b9307c5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java @@ -0,0 +1,272 @@ +/** + * Copyright © 2016-2023 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. + */ +package org.thingsboard.server.common.data.util; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.LwM2m; +import org.eclipse.leshan.core.model.DDFFileValidator; +import org.eclipse.leshan.core.model.DefaultDDFFileValidator; +import org.eclipse.leshan.core.model.InvalidDDFFileException; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.util.StringUtils; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class TbDDFFileParser { + private static final DDFFileValidator ddfFileValidator = new DefaultDDFFileValidator(); + + public List parse(InputStream inputStream, String streamName) + throws InvalidDDFFileException, IOException { + streamName = streamName == null ? "" : streamName; + + log.debug("Parsing DDF file {}", streamName); + + try { + // Parse XML file + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(inputStream); + + // Get DDF file validator + LwM2m.LwM2mVersion lwm2mVersion = null; + ddfFileValidator.validate(document); + + // Build list of ObjectModel + ArrayList objects = new ArrayList<>(); + NodeList nodeList = document.getDocumentElement().getElementsByTagName("Object"); + for (int i = 0; i < nodeList.getLength(); i++) { + objects.add(parseObject(nodeList.item(i), streamName, lwm2mVersion, true)); + } + return objects; + } catch (InvalidDDFFileException | SAXException e) { + throw new InvalidDDFFileException(e, "Invalid DDF file %s", streamName); + } + catch (ParserConfigurationException e) { + throw new IllegalStateException("Unable to create Document Builder", e); + } + } + + private ObjectModel parseObject(Node object, String streamName, LwM2m.LwM2mVersion schemaVersion, boolean validate) + throws InvalidDDFFileException { + + Node objectType = object.getAttributes().getNamedItem("ObjectType"); + if (validate && (objectType == null || !"MODefinition".equals(objectType.getTextContent()))) { + throw new InvalidDDFFileException( + "Object element in %s MUST have a ObjectType attribute equals to 'MODefinition'.", streamName); + } + + Integer id = null; + String name = null; + String description = null; + String version = ObjectModel.DEFAULT_VERSION; + Boolean multiple = null; + Boolean mandatory = null; + Map resources = new HashMap<>(); + String urn = null; + String description2 = null; + String lwm2mVersion = ObjectModel.DEFAULT_VERSION; + + for (int i = 0; i < object.getChildNodes().getLength(); i++) { + Node field = object.getChildNodes().item(i); + if (field.getNodeType() != Node.ELEMENT_NODE) + continue; + + switch (field.getNodeName()) { + case "ObjectID": + id = Integer.valueOf(field.getTextContent()); + break; + case "Name": + name = field.getTextContent(); + break; + case "Description1": + description = field.getTextContent(); + break; + case "ObjectVersion": + if (!StringUtils.isEmpty(field.getTextContent())) { + version = field.getTextContent(); + } + break; + case "MultipleInstances": + if ("Multiple".equals(field.getTextContent())) { + multiple = true; + } else if ("Single".equals(field.getTextContent())) { + multiple = false; + } + break; + case "Mandatory": + if ("Mandatory".equals(field.getTextContent())) { + mandatory = true; + } else if ("Optional".equals(field.getTextContent())) { + mandatory = false; + } + break; + case "Resources": + for (int j = 0; j < field.getChildNodes().getLength(); j++) { + Node item = field.getChildNodes().item(j); + if (item.getNodeType() != Node.ELEMENT_NODE) + continue; + + if (item.getNodeName().equals("Item")) { + ResourceModel resource = parseResource(item, streamName); + if (validate && resources.containsKey(resource.id)) { + throw new InvalidDDFFileException( + "Object %s in %s contains at least 2 resources with same id %s.", + id != null ? id : "", streamName, resource.id); + } else { + resources.put(resource.id, resource); + } + } + } + break; + case "ObjectURN": + urn = field.getTextContent(); + break; + case "LWM2MVersion": + if (!StringUtils.isEmpty(field.getTextContent())) { + lwm2mVersion = field.getTextContent(); + if (schemaVersion != null && !schemaVersion.toString().equals(lwm2mVersion)) { + throw new InvalidDDFFileException( + "LWM2MVersion is not consistent with xml shema(xsi:noNamespaceSchemaLocation) in %s : %s expected but was %s.", + streamName, schemaVersion, lwm2mVersion); + } + } + break; + case "Description2": + description2 = field.getTextContent(); + break; + default: + break; + } + } + + return new ObjectModel(id, name, description, version, multiple, mandatory, resources.values(), urn, + lwm2mVersion, description2); + + } + + private ResourceModel parseResource(Node item, String streamName) throws DOMException, InvalidDDFFileException { + + Integer id = Integer.valueOf(item.getAttributes().getNamedItem("ID").getTextContent()); + String name = null; + ResourceModel.Operations operations = null; + Boolean multiple = false; + Boolean mandatory = false; + ResourceModel.Type type = null; + String rangeEnumeration = null; + String units = null; + String description = null; + + for (int i = 0; i < item.getChildNodes().getLength(); i++) { + Node field = item.getChildNodes().item(i); + if (field.getNodeType() != Node.ELEMENT_NODE) + continue; + + switch (field.getNodeName()) { + case "Name": + name = field.getTextContent(); + break; + case "Operations": + String strOp = field.getTextContent(); + if (strOp != null && !strOp.isEmpty()) { + operations = ResourceModel.Operations.valueOf(strOp); + } else { + operations = ResourceModel.Operations.NONE; + } + break; + case "MultipleInstances": + if ("Multiple".equals(field.getTextContent())) { + multiple = true; + } else if ("Single".equals(field.getTextContent())) { + multiple = false; + } + break; + case "Mandatory": + if ("Mandatory".equals(field.getTextContent())) { + mandatory = true; + } else if ("Optional".equals(field.getTextContent())) { + mandatory = false; + } + break; + case "Type": + switch (field.getTextContent()) { + case "String": + type = ResourceModel.Type.STRING; + break; + case "Integer": + type = ResourceModel.Type.INTEGER; + break; + case "Float": + type = ResourceModel.Type.FLOAT; + break; + case "Boolean": + type = ResourceModel.Type.BOOLEAN; + break; + case "Opaque": + type = ResourceModel.Type.OPAQUE; + break; + case "Time": + type = ResourceModel.Type.TIME; + break; + case "Objlnk": + type = ResourceModel.Type.OBJLNK; + break; + case "Unsigned Integer": + type = ResourceModel.Type.UNSIGNED_INTEGER; + break; + case "Corelnk": + type = ResourceModel.Type.CORELINK; + break; + case "": + type = ResourceModel.Type.NONE; + break; + default: + break; + } + break; + case "RangeEnumeration": + rangeEnumeration = field.getTextContent(); + break; + case "Units": + units = field.getTextContent(); + break; + case "Description": + description = field.getTextContent(); + break; + default: + break; + } + } + return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description); + } +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index 8c65c04939..ff2464f6d2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.codec.CodecException; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.util.TbDDFFileParser; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; @@ -142,9 +143,9 @@ public class LwM2mTransportServerHelper { .build(); } - public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) { + public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName) { try { - DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator); + TbDDFFileParser ddfFileParser = new TbDDFFileParser(); return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0); } catch (IOException | InvalidDDFFileException e) { log.error("Could not parse the XML file [{}]", streamName, e); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java index d649c06b07..3dd13cd171 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java @@ -154,8 +154,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { Optional tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key); return tbResource.map(resource -> helper.parseFromXmlToObjectModel( Base64.getDecoder().decode(resource.getData()), - key + ".xml", - new DefaultDDFFileValidator())).orElse(null); + key + ".xml")).orElse(null); } } } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index cfd2251ea7..0ad9b72aad 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -35,6 +35,7 @@ import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { UtilsService } from '@core/services/utils.service'; export interface AlarmDetailsDialogData { alarmId?: string; @@ -79,6 +80,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent, public fb: UntypedFormBuilder) { super(store, router, dialogRef); @@ -136,7 +138,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent new EntityTableColumn('originatorName', 'alarm.originator', '25%', (entity) => entity.originatorName, entity => ({}), false)); this.columns.push( - new EntityTableColumn('type', 'alarm.type', '25%')); + new EntityTableColumn('type', 'alarm.type', '25%', + entity => this.utilsService.customTranslation(entity.type, entity.type))); this.columns.push( new EntityTableColumn('severity', 'alarm.severity', '25%', (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 61dc5bf013..32ebb28ae9 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -139,7 +139,7 @@ - + diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts index c3ff3ccc4c..0a5e5dc3ee 100644 --- a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -53,7 +53,7 @@ import { takeUntil } from 'rxjs/operators'; } ] }) -export class TenantProfileQueuesComponent implements ControlValueAccessor, Validator, OnDestroy { +export class TenantProfileQueuesComponent implements ControlValueAccessor, Validator, OnDestroy, OnInit { tenantProfileQueuesFormGroup: UntypedFormGroup; newQueue = false; @@ -107,6 +107,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + this.newQueue = false; if (this.disabled) { this.tenantProfileQueuesFormGroup.disable({emitEvent: false}); } else { diff --git a/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html b/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html index 8f8f509e94..9fee72fef4 100644 --- a/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html +++ b/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html @@ -40,7 +40,7 @@
- + {{ queueSubmitStrategyTypesMap.get(queueSubmitStrategyTypes[strategy]).label | translate }}
- + {{ queueProcessingStrategyTypesMap.get(queueProcessingStrategyTypes[strategy]).label | translate }} { this.submitStrategyTypeChanged(); }); - if (this.newQueue) { - this.queueFormGroup.get('name').enable({emitEvent: false}); - } else { - this.queueFormGroup.get('name').disable({emitEvent: false}); - } } ngOnDestroy() { @@ -149,7 +144,11 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr this.queueFormGroup.disable({emitEvent: false}); } else { this.queueFormGroup.enable({emitEvent: false}); - this.queueFormGroup.get('name').disable({emitEvent: false}); + if (this.newQueue) { + this.queueFormGroup.get('name').enable({emitEvent: false}); + } else { + this.queueFormGroup.get('name').disable({emitEvent: false}); + } } } @@ -203,7 +202,7 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr const type: QueueSubmitStrategyTypes = form.get('type').value; const batchSizeField = form.get('batchSize'); if (type === QueueSubmitStrategyTypes.BATCH) { - batchSizeField.patchValue(1000, {emitEvent: false}); + batchSizeField.patchValue(batchSizeField.value ?? 1000, {emitEvent: false}); batchSizeField.setValidators([Validators.min(1), Validators.required]); batchSizeField.updateValueAndValidity({emitEvent: false}); this.hideBatchSize = true; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts index 2c470ea363..5b44d1b7f2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts @@ -72,8 +72,8 @@ export class RoundSwitchWidgetSettingsComponent extends WidgetSettingsComponent protected prepareOutputSettings(settings: any): WidgetSettings { return { - title: settings.title, - ...settings.switchRpcSettings + ...settings.switchRpcSettings, + title: settings.title }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts index 8315d7eaf4..e89d6a9d6b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts @@ -78,10 +78,10 @@ export class SlideToggleWidgetSettingsComponent extends WidgetSettingsComponent protected prepareOutputSettings(settings: any): WidgetSettings { return { + ...settings.switchRpcSettings, title: settings.title, labelPosition: settings.labelPosition, - sliderColor: settings.sliderColor, - ...settings.switchRpcSettings + sliderColor: settings.sliderColor }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts index a95cea4628..fd6ebec6cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts @@ -75,9 +75,9 @@ export class SwitchControlWidgetSettingsComponent extends WidgetSettingsComponen protected prepareOutputSettings(settings: any): WidgetSettings { return { + ...settings.switchRpcSettings, title: settings.title, - showOnOffLabels: settings.showOnOffLabels, - ...settings.switchRpcSettings + showOnOffLabels: settings.showOnOffLabels }; } } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw index a4c9801720..769849cc9e 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw @@ -32,8 +32,8 @@ "units": null, "decimals": null, "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';" }, { "name": "jsExecutionLimit", @@ -72,7 +72,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : '';" + "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" }, { "name": "jsExecutionApiState", @@ -190,8 +190,8 @@ "units": null, "decimals": null, "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';" }, { "name": "storageDataPointsLimit", @@ -230,7 +230,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : '';" + "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" }, { "name": "dbApiState", @@ -348,8 +348,8 @@ "units": null, "decimals": null, "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';" }, { "name": "ruleEngineExecutionLimit", @@ -388,7 +388,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : '';" + "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" }, { "name": "ruleEngineApiState", @@ -516,8 +516,8 @@ "units": null, "decimals": null, "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';" }, { "name": "transportMsgLimit", @@ -556,7 +556,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : '';" + "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" }, { "name": "transportApiState", @@ -687,8 +687,8 @@ "units": null, "decimals": null, "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';" }, { "name": "createdAlarmsLimit", @@ -727,7 +727,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : '';" + "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" }, { "name": "alarmApiState", diff --git a/ui-ngx/src/app/shared/components/file-input.component.scss b/ui-ngx/src/app/shared/components/file-input.component.scss index 3426539be9..02924111b4 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.scss +++ b/ui-ngx/src/app/shared/components/file-input.component.scss @@ -87,7 +87,7 @@ $previewSize: 100px !default; button.browse-file { padding: 0; font-size: 16px; - span.mdc-button__label { + label { display: block; cursor: pointer; padding: 0 16px; diff --git a/ui-ngx/src/app/shared/components/image-input.component.scss b/ui-ngx/src/app/shared/components/image-input.component.scss index 0cdfce9e27..a776bc8cd7 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.scss +++ b/ui-ngx/src/app/shared/components/image-input.component.scss @@ -141,7 +141,7 @@ $previewSize: 96px !default; button.browse-file { padding: 0; font-size: 16px; - span.mdc-button__label { + label { display: block; cursor: pointer; padding: 0 16px; diff --git a/ui-ngx/src/app/shared/components/multiple-image-input.component.scss b/ui-ngx/src/app/shared/components/multiple-image-input.component.scss index c8c72a11ce..db097e06b1 100644 --- a/ui-ngx/src/app/shared/components/multiple-image-input.component.scss +++ b/ui-ngx/src/app/shared/components/multiple-image-input.component.scss @@ -157,7 +157,7 @@ $previewSize: 64px !default; button.browse-file { padding: 0; font-size: 16px; - span.mdc-button__label { + label { display: block; cursor: pointer; padding: 0 16px; diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.html b/ui-ngx/src/app/shared/components/notification/notification.component.html index 6801ef0c20..361d3589fb 100644 --- a/ui-ngx/src/app/shared/components/notification/notification.component.html +++ b/ui-ngx/src/app/shared/components/notification/notification.component.html @@ -28,8 +28,8 @@
-
-
+
+