Browse Source

UI: Merge with master and resolve conflict

pull/8597/head
Artem Dzhereleiko 3 years ago
parent
commit
0f87014cb8
  1. 2
      application/pom.xml
  2. 23
      application/src/main/data/upgrade/3.5.0/schema_update.sql
  3. 16
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  4. 13
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  6. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  8. 25
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
  9. 3
      application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java
  10. 1
      application/src/main/resources/thingsboard.yml
  11. 85
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  12. 66
      application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepositoryTest.java
  13. 2
      common/actor/pom.xml
  14. 2
      common/cache/pom.xml
  15. 2
      common/cluster-api/pom.xml
  16. 2
      common/coap-server/pom.xml
  17. 2
      common/dao-api/pom.xml
  18. 7
      common/data/pom.xml
  19. 272
      common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java
  20. 2
      common/edge-api/pom.xml
  21. 2
      common/message/pom.xml
  22. 2
      common/pom.xml
  23. 2
      common/queue/pom.xml
  24. 2
      common/script/pom.xml
  25. 2
      common/script/remote-js-client/pom.xml
  26. 2
      common/script/script-api/pom.xml
  27. 2
      common/stats/pom.xml
  28. 2
      common/transport/coap/pom.xml
  29. 2
      common/transport/http/pom.xml
  30. 2
      common/transport/lwm2m/pom.xml
  31. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  32. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  33. 2
      common/transport/mqtt/pom.xml
  34. 2
      common/transport/pom.xml
  35. 2
      common/transport/snmp/pom.xml
  36. 2
      common/transport/transport-api/pom.xml
  37. 4
      common/util/pom.xml
  38. 14
      common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java
  39. 2
      common/version-control/pom.xml
  40. 2
      dao/pom.xml
  41. 16
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  42. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  43. 9
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java
  44. 8
      dao/src/main/resources/sql/schema-views-and-functions.sql
  45. 30
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
  46. 2
      monitoring/pom.xml
  47. 2
      msa/black-box-tests/pom.xml
  48. 2
      msa/js-executor/package.json
  49. 2
      msa/js-executor/pom.xml
  50. 2
      msa/monitoring/pom.xml
  51. 2
      msa/pom.xml
  52. 2
      msa/tb-node/pom.xml
  53. 2
      msa/tb/docker-cassandra/Dockerfile
  54. 4
      msa/tb/pom.xml
  55. 2
      msa/transport/coap/pom.xml
  56. 2
      msa/transport/http/pom.xml
  57. 2
      msa/transport/lwm2m/pom.xml
  58. 2
      msa/transport/mqtt/pom.xml
  59. 2
      msa/transport/pom.xml
  60. 2
      msa/transport/snmp/pom.xml
  61. 2
      msa/vc-executor-docker/pom.xml
  62. 2
      msa/vc-executor/pom.xml
  63. 2
      msa/web-ui/package.json
  64. 2
      msa/web-ui/pom.xml
  65. 4
      netty-mqtt/pom.xml
  66. 6
      pom.xml
  67. 2
      rest-client/pom.xml
  68. 2
      rule-engine/pom.xml
  69. 2
      rule-engine/rule-engine-api/pom.xml
  70. 2
      rule-engine/rule-engine-components/pom.xml
  71. 17
      security.md
  72. 2
      tools/pom.xml
  73. 2
      transport/coap/pom.xml
  74. 2
      transport/http/pom.xml
  75. 2
      transport/lwm2m/pom.xml
  76. 2
      transport/mqtt/pom.xml
  77. 2
      transport/pom.xml
  78. 2
      transport/snmp/pom.xml
  79. 32
      ui-ngx/package.json
  80. 20
      ui-ngx/patches/@angular+core+15.2.9.patch
  81. 2
      ui-ngx/pom.xml
  82. 2
      ui-ngx/src/app/core/http/widget.service.ts
  83. 12
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  84. 18
      ui-ngx/src/app/core/services/dialog.service.ts
  85. 4
      ui-ngx/src/app/core/services/utils.service.ts
  86. 6
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts
  87. 3
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  88. 2
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html
  89. 13
      ui-ngx/src/app/modules/home/components/event/event-table.component.ts
  90. 3
      ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts
  91. 13
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.ts
  92. 18
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts
  93. 1
      ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts
  94. 26
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  95. 15
      ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts
  96. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts
  97. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts
  98. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts
  99. 5
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  100. 2
      ui-ngx/src/app/modules/home/components/widget/widget.component.html

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>

23
application/src/main/data/upgrade/3.5.0/schema_update.sql

@ -0,0 +1,23 @@
--
-- 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.
--
-- FIX DASHBOARD TEMPLATES AFTER ANGULAR MIGRATION TO VER.15
UPDATE dashboard SET configuration = REPLACE(configuration, 'mat-button mat-icon-button', 'mat-icon-button')
WHERE configuration like '%mat-button mat-icon-button%';
UPDATE widget_type SET descriptor = REPLACE(descriptor, 'mat-button mat-icon-button', 'mat-icon-button')
WHERE descriptor like '%mat-button mat-icon-button%';

16
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -249,22 +249,24 @@ public class ThingsboardInstallService {
case "3.4.4": case "3.4.4":
log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ..."); log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.4"); databaseEntitiesUpgradeService.upgradeDatabase("3.4.4");
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) { if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) {
systemDataLoaderService.createDefaultNotificationConfigs(); systemDataLoaderService.createDefaultNotificationConfigs();
} else { } else {
log.info("Skipping default notification configs creation"); log.info("Skipping default notification configs creation");
} }
installScripts.loadSystemLwm2mResources(); case "3.5.0":
log.info("Upgrading ThingsBoard from version 3.5.0 to 3.5.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.0");
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break; break;
//TODO update CacheCleanupService on the next version upgrade
default: default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
} }
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
installScripts.loadSystemLwm2mResources();
} }
log.info("Upgrade finished successfully!"); log.info("Upgrade finished successfully!");

13
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -713,6 +713,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e); log.error("Failed updating schema!!!", e);
} }
break; break;
case "3.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3005000)) {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005001;");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

3
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -84,7 +84,8 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
break; break;
case "3.4.4": case "3.4.4":
log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0");
clearCacheByName("deviceProfiles"); clearAll();
break;
default: default:
//Do nothing, since cache cleanup is optional. //Do nothing, since cache cleanup is optional.
} }

4
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java

@ -20,6 +20,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo; import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo;
@ -41,6 +42,9 @@ public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotification
@Override @Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceActivityNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceActivityNotificationRuleTriggerConfig triggerConfig) {
if (trigger.getMsg().getOriginator().getEntityType() != EntityType.DEVICE) {
return false;
}
DeviceEvent event = trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? DeviceEvent.ACTIVE : DeviceEvent.INACTIVE; DeviceEvent event = trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? DeviceEvent.ACTIVE : DeviceEvent.INACTIVE;
if (!triggerConfig.getNotifyOn().contains(event)) { if (!triggerConfig.getNotifyOn().contains(event)) {
return false; return false;

4
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -16,8 +16,6 @@
package org.thingsboard.server.service.resource; package org.thingsboard.server.service.resource;
import lombok.extern.slf4j.Slf4j; 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.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType; 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 { public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService {
private final ResourceService resourceService; private final ResourceService resourceService;
private final DDFFileParser ddfFileParser;
public DefaultTbResourceService(ResourceService resourceService) { public DefaultTbResourceService(ResourceService resourceService) {
this.resourceService = resourceService; this.resourceService = resourceService;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
} }
@Override @Override

25
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java

@ -15,14 +15,20 @@
*/ */
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.SerializationUtils; import org.springframework.util.SerializationUtils;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Base64; import java.util.Base64;
import java.util.Optional; import java.util.Optional;
@Slf4j
public class CookieUtils { public class CookieUtils {
public static Optional<Cookie> getCookie(HttpServletRequest request, String name) { public static Optional<Cookie> getCookie(HttpServletRequest request, String name) {
@ -67,7 +73,22 @@ public class CookieUtils {
} }
public static <T> T deserialize(Cookie cookie, Class<T> cls) { public static <T> T deserialize(Cookie cookie, Class<T> cls) {
return cls.cast(SerializationUtils.deserialize( byte[] decodedBytes = Base64.getUrlDecoder().decode(cookie.getValue());
Base64.getUrlDecoder().decode(cookie.getValue()))); try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(decodedBytes)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String name = desc.getName();
if (!cls.getName().equals(name)) {
throw new ClassNotFoundException("Class not allowed for deserialization: " + name);
}
return super.resolveClass(desc);
}
}) {
return cls.cast(ois.readObject());
} catch (Exception e) {
log.debug("Failed to deserialize class from cookie.", e.getCause());
return null;
}
} }
} }

3
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.LwM2mInstance;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.util.TbDDFFileParser;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -41,7 +42,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
@Slf4j @Slf4j
public class LwM2mObjectModelUtils { 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 { public static void toLwm2mResource (TbResource resource) throws ThingsboardException {
try { try {

1
application/src/main/resources/thingsboard.yml

@ -607,6 +607,7 @@ spring:
username: "${SPRING_DATASOURCE_USERNAME:postgres}" username: "${SPRING_DATASOURCE_USERNAME:postgres}"
password: "${SPRING_DATASOURCE_PASSWORD:postgres}" password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
hikari: hikari:
leakDetectionThreshold: "${SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD:0}"
maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:16}" maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:16}"
registerMbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}" # true - enable MBean to diagnose pools state via JMX registerMbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}" # true - enable MBean to diagnose pools state via JMX

85
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 java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy; 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; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
@ -75,6 +77,32 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
"</Object>\n" + "</Object>\n" +
"</LWM2M>"; "</LWM2M>";
private static final String LWM2M_TEST_MODEL_WITH_XXE = "<!DOCTYPE replace [<!ENTITY ObjectVersion SYSTEM \"file:///etc/hostname\"> ]>" +
"<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd\">\n" +
"<Object ObjectType=\"MODefinition\">\n" +
"<Name>My first resource</Name>\n" +
"<Description1></Description1>\n" +
"<ObjectID>0</ObjectID>\n" +
"<ObjectURN></ObjectURN>\n" +
"<ObjectVersion>&ObjectVersion;</ObjectVersion>\n" +
"<MultipleInstances>Multiple</MultipleInstances>\n" +
"<Mandatory>Mandatory</Mandatory>\n" +
"<Resources>\n" +
"<Item ID=\"0\">\n" +
"<Name>LWM2M</Name>\n" +
"<Operations>RW</Operations>\n" +
"<MultipleInstances>Single</MultipleInstances>\n" +
"<Mandatory>Mandatory</Mandatory>\n" +
"<Type>String</Type>\n" +
"<RangeEnumeration>0..255</RangeEnumeration>\n" +
"<Units></Units>\n" +
"<Description></Description>\n" +
"</Item>\n" +
"</Resources>\n" +
"<Description2></Description2>\n" +
"</Object>\n" +
"</LWM2M>";
private static final String DEFAULT_FILE_NAME = "test.jks"; private static final String DEFAULT_FILE_NAME = "test.jks";
private IdComparator<TbResourceInfo> idComparator = new IdComparator<>(); private IdComparator<TbResourceInfo> idComparator = new IdComparator<>();
@ -126,11 +154,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
loginTenantAdmin(); loginTenantAdmin();
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME); createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try { try {
assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME)) assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME))
@ -145,19 +173,19 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
@Test @Test
public void sumDataSizeByTenantId() throws Exception { public void sumDataSizeByTenantId() throws Exception {
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME); createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8; int maxSumDataSize = 8;
for (int i = 2; i <= maxSumDataSize; i++) { for (int i = 2; i <= maxSumDataSize; i++) {
createResource("test" + i, i + DEFAULT_FILE_NAME); 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 { private TbResource createResource(String title, String filename) throws Exception {
@ -184,16 +212,16 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId()); Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0); Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId()); assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals(resource.getTitle(), savedResource.getTitle()); assertEquals(resource.getTitle(), savedResource.getTitle());
Assert.assertEquals(resource.getResourceKey(), savedResource.getResourceKey()); assertEquals(resource.getResourceKey(), savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData()); assertEquals(resource.getData(), savedResource.getData());
savedResource.setTitle("My new resource"); savedResource.setTitle("My new resource");
resourceService.save(savedResource); resourceService.save(savedResource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); assertEquals(foundResource.getTitle(), savedResource.getTitle());
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -211,10 +239,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId()); Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0); Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId()); assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals("My first resource id=0 v1.0", savedResource.getTitle()); assertEquals("My first resource id=0 v1.0", savedResource.getTitle());
Assert.assertEquals("0_1.0", savedResource.getResourceKey()); assertEquals("0_1.0", savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData()); assertEquals(resource.getData(), savedResource.getData());
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -228,7 +256,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setData("Test Data"); resource.setData("Test Data");
TbResource savedResource = resourceService.save(resource); TbResource savedResource = resourceService.save(resource);
Assert.assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
resourceService.delete(savedResource, null); 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 @Test
public void testFindResourceById() throws Exception { public void testFindResourceById() throws Exception {
TbResource resource = new TbResource(); TbResource resource = new TbResource();
@ -296,7 +339,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNotNull(foundResource); Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource); assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -312,7 +355,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey()); TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey());
Assert.assertNotNull(foundResource); Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource); assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -366,7 +409,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator); Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator); Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources); assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId);
@ -427,14 +470,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator); Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator); Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources); assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId);
pageLink = new PageLink(100); pageLink = new PageLink(100);
pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(pageData.getData().size(), 100); assertEquals(pageData.getData().size(), 100);
resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID); resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID);

66
application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepositoryTest.java

@ -0,0 +1,66 @@
/**
* 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.service.security.auth.oauth2;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME;
public class HttpCookieOAuth2AuthorizationRequestRepositoryTest {
private static final String SERIALIZED_ATTACK_STRING =
"rO0ABXNyAHVvcmcudGhpbmdzYm9hcmQuc2VydmVyLnNlcnZpY2Uuc2VjdXJpdHkuYXV0aC5vYXV0aDIuSHR0cENvb2tpZU9BdXRoMkF1dGhvcml6YXRpb25SZXF1ZXN0UmVwb3NpdG9yeVRlc3QkTWFsaWNpb3VzQ2xhc3MAAAAAAAAAAAIAAHhw";
private static int maliciousMethodInvocationCounter;
@Before
public void resetInvocationCounter() {
maliciousMethodInvocationCounter = 0;
}
@Test
public void whenLoadAuthorizationRequest_thenMaliciousMethodNotInvoked() {
HttpCookieOAuth2AuthorizationRequestRepository cookieRequestRepo = new HttpCookieOAuth2AuthorizationRequestRepository();
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Cookie cookie = new Cookie(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, SERIALIZED_ATTACK_STRING);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
cookieRequestRepo.loadAuthorizationRequest(request);
assertEquals(0, maliciousMethodInvocationCounter);
}
private static class MaliciousClass implements Serializable {
private static final long serialVersionUID = 0L;
public void maliciousMethod() {
maliciousMethodInvocationCounter++;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
maliciousMethod();
}
}
}

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

7
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
@ -116,6 +116,11 @@
<groupId>com.google.protobuf</groupId> <groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId> <artifactId>protobuf-java-util</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

272
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<ObjectModel> 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<ObjectModel> 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<Integer, ResourceModel> 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);
}
}

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

5
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.model.ResourceModel;
import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.CodecException;
import org.springframework.stereotype.Component; 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.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -142,9 +143,9 @@ public class LwM2mTransportServerHelper {
.build(); .build();
} }
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) { public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName) {
try { try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator); TbDDFFileParser ddfFileParser = new TbDDFFileParser();
return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0); return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0);
} catch (IOException | InvalidDDFFileException e) { } catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML file [{}]", streamName, e); log.error("Could not parse the XML file [{}]", streamName, e);

3
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> tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key); Optional<TbResource> tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key);
return tbResource.map(resource -> helper.parseFromXmlToObjectModel( return tbResource.map(resource -> helper.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()), Base64.getDecoder().decode(resource.getData()),
key + ".xml", key + ".xml")).orElse(null);
new DefaultDDFFileValidator())).orElse(null);
} }
} }
} }

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

4
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
@ -89,7 +89,7 @@
<artifactId>data</artifactId> <artifactId>data</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.dblock</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

14
common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java

@ -42,7 +42,7 @@ public class SystemUtil {
long total = memory.getTotal(); long total = memory.getTotal();
long available = memory.getAvailable(); long available = memory.getAvailable();
return Optional.of(toPercent(total - available, total)); return Optional.of(toPercent(total - available, total));
} catch (Exception e) { } catch (Throwable e) {
log.debug("Failed to get memory usage!!!", e); log.debug("Failed to get memory usage!!!", e);
} }
return Optional.empty(); return Optional.empty();
@ -51,7 +51,7 @@ public class SystemUtil {
public static Optional<Long> getTotalMemory() { public static Optional<Long> getTotalMemory() {
try { try {
return Optional.of(HARDWARE.getMemory().getTotal()); return Optional.of(HARDWARE.getMemory().getTotal());
} catch (Exception e) { } catch (Throwable e) {
log.debug("Failed to get total memory!!!", e); log.debug("Failed to get total memory!!!", e);
} }
return Optional.empty(); return Optional.empty();
@ -59,8 +59,8 @@ public class SystemUtil {
public static Optional<Integer> getCpuUsage() { public static Optional<Integer> getCpuUsage() {
try { try {
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad() * 100.0)); return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad(1000) * 100.0));
} catch (Exception e) { } catch (Throwable e) {
log.debug("Failed to get cpu usage!!!", e); log.debug("Failed to get cpu usage!!!", e);
} }
return Optional.empty(); return Optional.empty();
@ -69,7 +69,7 @@ public class SystemUtil {
public static Optional<Integer> getCpuCount() { public static Optional<Integer> getCpuCount() {
try { try {
return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount()); return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount());
} catch (Exception e) { } catch (Throwable e) {
log.debug("Failed to get total cpu count!!!", e); log.debug("Failed to get total cpu count!!!", e);
} }
return Optional.empty(); return Optional.empty();
@ -81,7 +81,7 @@ public class SystemUtil {
long total = store.getTotalSpace(); long total = store.getTotalSpace();
long available = store.getUsableSpace(); long available = store.getUsableSpace();
return Optional.of(toPercent(total - available, total)); return Optional.of(toPercent(total - available, total));
} catch (Exception e) { } catch (Throwable e) {
log.debug("Failed to get free disc space!!!", e); log.debug("Failed to get free disc space!!!", e);
} }
return Optional.empty(); return Optional.empty();
@ -91,7 +91,7 @@ public class SystemUtil {
try { try {
FileStore store = Files.getFileStore(Paths.get("/")); FileStore store = Files.getFileStore(Paths.get("/"));
return Optional.of(store.getTotalSpace()); return Optional.of(store.getTotalSpace());
} catch (Exception e) { } catch (Throwable e) {
log.debug("Failed to get total disc space!!!", e); log.debug("Failed to get total disc space!!!", e);
} }
return Optional.empty(); return Optional.empty();

2
common/version-control/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

16
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -19,7 +19,6 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmEntity;
@ -316,7 +315,9 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Query(value = "SELECT a FROM AlarmInfoEntity a WHERE a.tenantId = :tenantId AND a.id = :alarmId") @Query(value = "SELECT a FROM AlarmInfoEntity a WHERE a.tenantId = :tenantId AND a.id = :alarmId")
AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId); AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId);
@Procedure(procedureName = "create_or_update_active_alarm") @Query(value = "SELECT create_or_update_active_alarm(:t_id, :c_id, :a_id, :a_created_ts, :a_o_id, :a_o_type, :a_type, :a_severity, " +
":a_start_ts, :a_end_ts, :a_details, :a_propagate, :a_propagate_to_owner, " +
":a_propagate_to_tenant, :a_propagation_types, :a_creation_enabled)", nativeQuery = true)
String createOrUpdateActiveAlarm(@Param("t_id") UUID tenantId, @Param("c_id") UUID customerId, String createOrUpdateActiveAlarm(@Param("t_id") UUID tenantId, @Param("c_id") UUID customerId,
@Param("a_id") UUID alarmId, @Param("a_created_ts") long createdTime, @Param("a_id") UUID alarmId, @Param("a_created_ts") long createdTime,
@Param("a_o_id") UUID originatorId, @Param("a_o_type") int originatorType, @Param("a_o_id") UUID originatorId, @Param("a_o_type") int originatorType,
@ -326,21 +327,22 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes, @Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes,
@Param("a_creation_enabled") boolean alarmCreationEnabled); @Param("a_creation_enabled") boolean alarmCreationEnabled);
@Procedure(procedureName = "update_alarm") @Query(value = "SELECT update_alarm(:t_id, :a_id, :a_severity, :a_start_ts, :a_end_ts, :a_details, :a_propagate, :a_propagate_to_owner, " +
":a_propagate_to_tenant, :a_propagation_types)", nativeQuery = true)
String updateAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_severity") String severity, String updateAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_severity") String severity,
@Param("a_start_ts") long startTs, @Param("a_end_ts") long endTs, @Param("a_details") String detailsAsString, @Param("a_start_ts") long startTs, @Param("a_end_ts") long endTs, @Param("a_details") String detailsAsString,
@Param("a_propagate") boolean propagate, @Param("a_propagate_to_owner") boolean propagateToOwner, @Param("a_propagate") boolean propagate, @Param("a_propagate_to_owner") boolean propagateToOwner,
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes); @Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes);
@Procedure(procedureName = "acknowledge_alarm") @Query(value = "SELECT acknowledge_alarm(:t_id, :a_id, :a_ts)", nativeQuery = true)
String acknowledgeAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts); String acknowledgeAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts);
@Procedure(procedureName = "clear_alarm") @Query(value = "SELECT clear_alarm(:t_id, :a_id, :a_ts, :a_details)", nativeQuery = true)
String clearAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts, @Param("a_details") String details); String clearAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts, @Param("a_details") String details);
@Procedure(procedureName = "assign_alarm") @Query(value = "SELECT assign_alarm(:t_id, :a_id, :u_id, :a_ts)", nativeQuery = true)
String assignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("u_id") UUID userId, @Param("a_ts") long assignTime); String assignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("u_id") UUID userId, @Param("a_ts") long assignTime);
@Procedure(procedureName = "unassign_alarm") @Query(value = "SELECT unassign_alarm(:t_id, :a_id, :a_ts)", nativeQuery = true)
String unassignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long unassignTime); String unassignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long unassignTime);
} }

2
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -348,7 +348,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override @Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) { public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) {
return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, getDetailsAsString(details))); return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, details != null ? getDetailsAsString(details) : null));
} }
@Override @Override

9
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java

@ -110,9 +110,12 @@ public class SqlPartitioningRepository {
String tablePartition = table + "_" + partitionTs; String tablePartition = table + "_" + partitionTs;
String detachPsqlStmtStr = "ALTER TABLE " + table + " DETACH PARTITION " + tablePartition; String detachPsqlStmtStr = "ALTER TABLE " + table + " DETACH PARTITION " + tablePartition;
if (getCurrentServerVersion() >= PSQL_VERSION_14) {
detachPsqlStmtStr += " CONCURRENTLY"; // hotfix of ERROR: partition "integration_debug_event_1678323600000" already pending detach in partitioned table "public.integration_debug_event"
} // https://github.com/thingsboard/thingsboard/issues/8271
// if (getCurrentServerVersion() >= PSQL_VERSION_14) {
// detachPsqlStmtStr += " CONCURRENTLY";
// }
String dropStmtStr = "DROP TABLE " + tablePartition; String dropStmtStr = "DROP TABLE " + tablePartition;
try { try {

8
dao/src/main/resources/sql/schema-views-and-functions.sql

@ -23,7 +23,7 @@ SELECT d.*
, COALESCE(da.bool_v, FALSE) as active , COALESCE(da.bool_v, FALSE) as active
FROM device d FROM device d
LEFT JOIN customer c ON c.id = d.customer_id LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active'; LEFT JOIN attribute_kv da ON da.entity_type = 'DEVICE' AND da.entity_id = d.id AND da.attribute_type = 'SERVER_SCOPE' AND da.attribute_key = 'active';
DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; DROP VIEW IF EXISTS device_info_active_ts_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_ts_view AS CREATE OR REPLACE VIEW device_info_active_ts_view AS
@ -226,7 +226,11 @@ BEGIN
END IF; END IF;
IF NOT(existing.cleared) THEN IF NOT(existing.cleared) THEN
cleared = TRUE; cleared = TRUE;
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id; IF a_details IS NULL THEN
UPDATE alarm a SET cleared = true, clear_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
ELSE
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
END IF; END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text; RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;

30
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java

@ -202,7 +202,34 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test @Test
public void testClearAlarmProcedure() { public void testClearAlarmProcedure() {
UUID tenantId = UUID.randomUUID(); UUID tenantId = UUID.randomUUID();
; UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
long clearTs = System.currentTimeMillis();
var details = JacksonUtil.newObjectNode().put("test", 123);
AlarmApiCallResult result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs, details);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCleared());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
assertEquals(details, result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isCleared());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
}
@Test
public void testClearAlarmWithoutDetailsProcedure() {
UUID tenantId = UUID.randomUUID();
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b"); UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
@ -216,6 +243,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
assertEquals(afterSave, result.getAlarm()); assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs()); assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared()); assertTrue(result.getAlarm().isCleared());
assertEquals(alarm.getDetails(), result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode()); result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result); assertNotNull(result);
assertNotNull(result.getAlarm()); assertNotNull(result.getAlarm());

2
monitoring/pom.xml

@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>

2
msa/black-box-tests/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "3.5.0", "version": "3.5.1",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/monitoring/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/tb/docker-cassandra/Dockerfile

@ -47,7 +47,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends wget nmap procps gnupg2 \ && apt-get install -y --no-install-recommends wget nmap procps gnupg2 \
&& echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \ && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \
&& wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | apt-key add - \ && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | apt-key add - \
&& echo 'deb http://downloads.apache.org/cassandra/debian 40x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null \ && echo "deb https://debian.cassandra.apache.org 40x main" | tee -a /etc/apt/sources.list.d/cassandra.sources.list > /dev/null \
&& wget -q https://downloads.apache.org/cassandra/KEYS -O- | apt-key add - \ && wget -q https://downloads.apache.org/cassandra/KEYS -O- | apt-key add - \
&& apt-get update \ && apt-get update \
&& apt-get install -y --no-install-recommends cassandra cassandra-tools postgresql-${PG_MAJOR} \ && apt-get install -y --no-install-recommends cassandra cassandra-tools postgresql-${PG_MAJOR} \

4
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name> <tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name> <tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.5.0</pkg.upgradeVersion> <pkg.upgradeVersion>3.5.1</pkg.upgradeVersion>
</properties> </properties>
<dependencies> <dependencies>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/vc-executor-docker/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/vc-executor/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-web-ui", "name": "thingsboard-web-ui",
"private": true, "private": true,
"version": "3.5.0", "version": "3.5.1",
"description": "ThingsBoard Web UI Microservice", "description": "ThingsBoard Web UI Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>netty-mqtt</artifactId> <artifactId>netty-mqtt</artifactId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Netty MQTT Client</name> <name>Netty MQTT Client</name>

6
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Thingsboard</name> <name>Thingsboard</name>
@ -150,7 +150,7 @@
<allure-testng.version>2.21.0</allure-testng.version> <allure-testng.version>2.21.0</allure-testng.version>
<allure-maven.version>2.12.0</allure-maven.version> <allure-maven.version>2.12.0</allure-maven.version>
<slack-api.version>1.12.1</slack-api.version> <slack-api.version>1.12.1</slack-api.version>
<oshi.version>3.4.0</oshi.version> <oshi.version>6.4.2</oshi.version>
</properties> </properties>
<modules> <modules>
@ -2003,7 +2003,7 @@
<version>${exp4j.version}</version> <version>${exp4j.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.dblock</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
<version>${oshi.version}</version> <version>${oshi.version}</version>
</dependency> </dependency>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rest-client</artifactId> <artifactId>rest-client</artifactId>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

17
security.md

@ -0,0 +1,17 @@
# Security Policy
## Reporting a Vulnerability
Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to Thingsboard privately,
to minimize attacks against current users of Thingsboard before they are fixed. Vulnerabilities will be investigated and release as soon as possible.
To report a vulnerability or a security-related issue, please email the private address security@thingsboard.io with the details of the vulnerability.
Emails will be addressed within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime.
Do not report non-security-impacting bugs through this channel. Use GitHub issues instead.
**Proposed Email Content**
Provide a descriptive subject line and in the body of the email include the following information:
- Basic identity information, such as your name and your affiliation or company.
- Detailed steps to reproduce the vulnerability (log errors, screenshots are all helpful to us).
- Description of the effects of the vulnerability on Thingsboard.

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>tools</artifactId> <artifactId>tools</artifactId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>

2
transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>

32
ui-ngx/package.json

@ -1,6 +1,6 @@
{ {
"name": "thingsboard", "name": "thingsboard",
"version": "3.5.0", "version": "3.5.1",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open",
@ -14,17 +14,17 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^15.2.5", "@angular/animations": "^15.2.9",
"@angular/cdk": "^15.2.5", "@angular/cdk": "^15.2.9",
"@angular/common": "^15.2.5", "@angular/common": "^15.2.9",
"@angular/compiler": "^15.2.5", "@angular/compiler": "^15.2.9",
"@angular/core": "^15.2.5", "@angular/core": "^15.2.9",
"@angular/flex-layout": "^15.0.0-beta.42", "@angular/flex-layout": "^15.0.0-beta.42",
"@angular/forms": "^15.2.5", "@angular/forms": "^15.2.9",
"@angular/material": "^15.2.5", "@angular/material": "^15.2.9",
"@angular/platform-browser": "^15.2.5", "@angular/platform-browser": "^15.2.9",
"@angular/platform-browser-dynamic": "^15.2.5", "@angular/platform-browser-dynamic": "^15.2.9",
"@angular/router": "^15.2.5", "@angular/router": "^15.2.9",
"@auth0/angular-jwt": "^5.1.2", "@auth0/angular-jwt": "^5.1.2",
"@date-io/core": "1.3.7", "@date-io/core": "1.3.7",
"@date-io/date-fns": "1.3.7", "@date-io/date-fns": "1.3.7",
@ -108,16 +108,16 @@
}, },
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "~15.0.0", "@angular-builders/custom-webpack": "~15.0.0",
"@angular-devkit/build-angular": "^15.2.4", "@angular-devkit/build-angular": "^15.2.8",
"@angular-eslint/builder": "15.2.1", "@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "15.2.1", "@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1", "@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1", "@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1", "@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.4", "@angular/cli": "^15.2.8",
"@angular/compiler-cli": "^15.2.5", "@angular/compiler-cli": "^15.2.9",
"@angular/language-service": "^15.2.5", "@angular/language-service": "^15.2.9",
"@ngtools/webpack": "^15.1.6", "@ngtools/webpack": "15.2.1",
"@types/ace-diff": "^2.1.1", "@types/ace-diff": "^2.1.1",
"@types/canvas-gauges": "^2.1.4", "@types/canvas-gauges": "^2.1.4",
"@types/flot": "^0.0.32", "@types/flot": "^0.0.32",

20
ui-ngx/patches/@angular+core+15.2.9.patch

@ -0,0 +1,20 @@
diff --git a/node_modules/@angular/core/fesm2020/core.mjs b/node_modules/@angular/core/fesm2020/core.mjs
index 3e93015..9efcb96 100755
--- a/node_modules/@angular/core/fesm2020/core.mjs
+++ b/node_modules/@angular/core/fesm2020/core.mjs
@@ -11053,13 +11053,13 @@ function findDirectiveDefMatches(tView, tNode) {
if (isNodeMatchingSelectorList(tNode, def.selectors, /* isProjectionMode */ false)) {
matches || (matches = []);
if (isComponentDef(def)) {
- if (ngDevMode) {
+ // if (ngDevMode) {
assertTNodeType(tNode, 2 /* TNodeType.Element */, `"${tNode.value}" tags cannot be used as component hosts. ` +
`Please use a different tag to activate the ${stringify(def.type)} component.`);
if (isComponentHost(tNode)) {
throwMultipleComponentError(tNode, matches.find(isComponentDef).type, def.type);
}
- }
+ // }
// Components are inserted at the front of the matches array so that their lifecycle
// hooks run before any directive lifecycle hooks. This appears to be for ViewEngine
// compatibility. This logic doesn't make sense with host directives, because it

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version> <version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

2
ui-ngx/src/app/core/http/widget.service.ts

@ -280,7 +280,7 @@ export class WidgetService {
this.deleteWidgetsBundleFromCache(widgetsBundle.alias, widgetsBundle.tenantId.id === NULL_UUID); this.deleteWidgetsBundleFromCache(widgetsBundle.alias, widgetsBundle.tenantId.id === NULL_UUID);
} }
private deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) { public deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
this.widgetsInfoInMemoryCache.delete(key); this.widgetsInfoInMemoryCache.delete(key);
} }

12
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -30,7 +30,14 @@ import {
WidgetLayout WidgetLayout
} from '@shared/models/dashboard.models'; } from '@shared/models/dashboard.models';
import { isDefined, isString, isUndefined } from '@core/utils'; import { isDefined, isString, isUndefined } from '@core/utils';
import { Datasource, DatasourceType, Widget, WidgetConfig, widgetType } from '@app/shared/models/widget.models'; import {
Datasource,
datasourcesHasOnlyComparisonAggregation,
DatasourceType,
Widget,
WidgetConfig,
widgetType
} from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
import { EntityId } from '@app/shared/models/id/entity-id'; import { EntityId } from '@app/shared/models/id/entity-id';
@ -227,7 +234,8 @@ export class DashboardUtilsService {
} }
}); });
if (type === widgetType.latest) { if (type === widgetType.latest) {
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true, this.timeService); const onlyHistoryTimewindow = datasourcesHasOnlyComparisonAggregation(widgetConfig.datasources);
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true, onlyHistoryTimewindow, this.timeService);
} }
if (type === widgetType.alarm) { if (type === widgetType.alarm) {
if (!widgetConfig.alarmFilterConfig) { if (!widgetConfig.alarmFilterConfig) {

18
ui-ngx/src/app/core/services/dialog.service.ts

@ -29,6 +29,7 @@ import {
} from '@shared/components/dialog/material-icons-dialog.component'; } from '@shared/components/dialog/material-icons-dialog.component';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component';
import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component'; import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component';
@Injectable( @Injectable(
@ -78,6 +79,23 @@ export class DialogService {
return dialogRef.afterClosed(); return dialogRef.afterClosed();
} }
errorAlert(title: string, message: string, error: any, ok: string = null, fullscreen: boolean = false): Observable<any> {
const dialogConfig: MatDialogConfig = {
disableClose: true,
data: {
title,
message,
error,
ok: ok || this.translate.instant('action.ok')
}
};
if (fullscreen) {
dialogConfig.panelClass = ['tb-fullscreen-dialog'];
}
const dialogRef = this.dialog.open(ErrorAlertDialogComponent, dialogConfig);
return dialogRef.afterClosed();
}
colorPicker(color: string): Observable<string> { colorPicker(color: string): Observable<string> {
return this.dialog.open<ColorPickerDialogComponent, ColorPickerDialogData, string>(ColorPickerDialogComponent, return this.dialog.open<ColorPickerDialogComponent, ColorPickerDialogData, string>(ColorPickerDialogComponent,
{ {

4
ui-ngx/src/app/core/services/utils.service.ts

@ -188,6 +188,10 @@ export class UtilsService {
public processWidgetException(exception: any): ExceptionData { public processWidgetException(exception: any): ExceptionData {
const data = this.parseException(exception, -6); const data = this.parseException(exception, -6);
if (data.message?.startsWith('NG0')) {
data.message = `${this.translate.instant('widget.widget-template-error')}<br/>
<br/><i>${this.translate.instant('dialog.error-message-title')}</i><br/><br/>${data.message}`;
}
if (this.widgetEditMode) { if (this.widgetEditMode) {
const message: WindowMessage = { const message: WindowMessage = {
type: 'widgetException', type: 'widgetException',

6
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 { TranslateService } from '@ngx-translate/core';
import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component';
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
import { UtilsService } from '@core/services/utils.service';
export interface AlarmDetailsDialogData { export interface AlarmDetailsDialogData {
alarmId?: string; alarmId?: string;
@ -79,6 +80,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
private translate: TranslateService, private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData, @Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData,
private alarmService: AlarmService, private alarmService: AlarmService,
private utils: UtilsService,
public dialogRef: MatDialogRef<AlarmDetailsDialogComponent, boolean>, public dialogRef: MatDialogRef<AlarmDetailsDialogComponent, boolean>,
public fb: UntypedFormBuilder) { public fb: UntypedFormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
@ -136,7 +138,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
} }
this.alarmFormGroup.get('duration').patchValue(duration); this.alarmFormGroup.get('duration').patchValue(duration);
} }
this.alarmFormGroup.get('type').patchValue(alarm.type); this.alarmFormGroup.get('type').patchValue(this.utils.customTranslation(alarm.type, alarm.type));
this.alarmFormGroup.get('alarmStatus') this.alarmFormGroup.get('alarmStatus')
.patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status))); .patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status)));
this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details); this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details);
@ -175,7 +177,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
onReassign(): void { onReassign(): void {
this.alarmUpdated = true; this.alarmUpdated = true;
this.loadAlarm() this.loadAlarm();
this.alarmCommentComponent.loadAlarmComments(); this.alarmCommentComponent.loadAlarmComments();
} }
} }

3
ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts

@ -116,7 +116,8 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '25%', new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '25%',
(entity) => entity.originatorName, entity => ({}), false)); (entity) => entity.originatorName, entity => ({}), false));
this.columns.push( this.columns.push(
new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '25%')); new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '25%',
entity => this.utilsService.customTranslation(entity.type, entity.type)));
this.columns.push( this.columns.push(
new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '25%', new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '25%',
(entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)),

2
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html

@ -139,7 +139,7 @@
<table mat-table [dataSource]="dataSource" <table mat-table [dataSource]="dataSource"
matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
<ng-container matColumnDef="select" sticky> <ng-container matColumnDef="select" sticky>
<mat-header-cell *matHeaderCellDef style="width: 30px;"> <mat-header-cell *matHeaderCellDef style="width: 40px;">
<mat-checkbox (change)="$event ? dataSource.masterToggle() : null" <mat-checkbox (change)="$event ? dataSource.masterToggle() : null"
[checked]="dataSource.selection.hasValue() && (dataSource.isAllSelected() | async)" [checked]="dataSource.selection.hasValue() && (dataSource.isAllSelected() | async)"
[indeterminate]="dataSource.selection.hasValue() && !(dataSource.isAllSelected() | async)"> [indeterminate]="dataSource.selection.hasValue() && !(dataSource.isAllSelected() | async)">

13
ui-ngx/src/app/modules/home/components/event/event-table.component.ts

@ -14,7 +14,16 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import {
AfterViewInit,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -32,7 +41,7 @@ import { Subscription } from 'rxjs';
templateUrl: './event-table.component.html', templateUrl: './event-table.component.html',
styleUrls: ['./event-table.component.scss'] styleUrls: ['./event-table.component.scss']
}) })
export class EventTableComponent implements OnInit, AfterViewInit { export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() @Input()
tenantId: string; tenantId: string;

3
ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts

@ -53,7 +53,7 @@ import { takeUntil } from 'rxjs/operators';
} }
] ]
}) })
export class TenantProfileQueuesComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { export class TenantProfileQueuesComponent implements ControlValueAccessor, Validator, OnDestroy, OnInit {
tenantProfileQueuesFormGroup: UntypedFormGroup; tenantProfileQueuesFormGroup: UntypedFormGroup;
newQueue = false; newQueue = false;
@ -107,6 +107,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
this.newQueue = false;
if (this.disabled) { if (this.disabled) {
this.tenantProfileQueuesFormGroup.disable({emitEvent: false}); this.tenantProfileQueuesFormGroup.disable({emitEvent: false});
} else { } else {

13
ui-ngx/src/app/modules/home/components/queue/queue-form.component.ts

@ -129,11 +129,6 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr
this.queueFormGroup.get('submitStrategy').get('type').valueChanges.subscribe(() => { this.queueFormGroup.get('submitStrategy').get('type').valueChanges.subscribe(() => {
this.submitStrategyTypeChanged(); this.submitStrategyTypeChanged();
}); });
if (this.newQueue) {
this.queueFormGroup.get('name').enable({emitEvent: false});
} else {
this.queueFormGroup.get('name').disable({emitEvent: false});
}
} }
ngOnDestroy() { ngOnDestroy() {
@ -149,7 +144,11 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr
this.queueFormGroup.disable({emitEvent: false}); this.queueFormGroup.disable({emitEvent: false});
} else { } else {
this.queueFormGroup.enable({emitEvent: false}); 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 type: QueueSubmitStrategyTypes = form.get('type').value;
const batchSizeField = form.get('batchSize'); const batchSizeField = form.get('batchSize');
if (type === QueueSubmitStrategyTypes.BATCH) { if (type === QueueSubmitStrategyTypes.BATCH) {
batchSizeField.patchValue(1000, {emitEvent: false}); batchSizeField.patchValue(batchSizeField.value ?? 1000, {emitEvent: false});
batchSizeField.setValidators([Validators.min(1), Validators.required]); batchSizeField.setValidators([Validators.min(1), Validators.required]);
batchSizeField.updateValueAndValidity({emitEvent: false}); batchSizeField.updateValueAndValidity({emitEvent: false});
this.hideBatchSize = true; this.hideBatchSize = true;

18
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts

@ -33,6 +33,8 @@ import {
CustomDialogComponent, CustomDialogComponent,
CustomDialogData CustomDialogData
} from '@home/components/widget/dialog/custom-dialog.component'; } from '@home/components/widget/dialog/custom-dialog.component';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
export interface CustomDialogContainerData { export interface CustomDialogContainerData {
controller: (instance: CustomDialogComponent) => void; controller: (instance: CustomDialogComponent) => void;
@ -54,6 +56,8 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
protected router: Router, protected router: Router,
public viewContainerRef: ViewContainerRef, public viewContainerRef: ViewContainerRef,
public dialogRef: MatDialogRef<CustomDialogContainerComponent>, public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
private dialogService: DialogService,
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) { @Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) {
super(store, router, dialogRef); super(store, router, dialogRef);
let customDialogData: CustomDialogData = { let customDialogData: CustomDialogData = {
@ -72,7 +76,19 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
useValue: dialogRef useValue: dialogRef
}] }]
}); });
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector); try {
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector);
} catch (e: any) {
let message;
if (e.message?.startsWith('NG0')) {
message = this.translate.instant('widget-action.custom-pretty-template-error');
} else {
message = this.translate.instant('widget-action.custom-pretty-controller-error');
}
dialogRef.close();
console.error(e);
this.dialogService.errorAlert(this.translate.instant('widget-action.custom-pretty-error-title'), message, e);
}
} }
ngOnDestroy(): void { ngOnDestroy(): void {

1
ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts

@ -106,6 +106,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
} }
ngOnDestroy(): void { ngOnDestroy(): void {
super.ngOnDestroy();
if (this.ctx.telemetrySubscribers) { if (this.ctx.telemetrySubscribers) {
this.ctx.telemetrySubscribers.forEach(item => item.unsubscribe()); this.ctx.telemetrySubscribers.forEach(item => item.unsubscribe());
} }

26
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -36,7 +36,13 @@ import { Observable, of } from 'rxjs';
import { Polyline } from './polyline'; import { Polyline } from './polyline';
import { Polygon } from './polygon'; import { Polygon } from './polygon';
import { Circle } from './circle'; import { Circle } from './circle';
import { createTooltip, entitiesParseName, isCutPolygon, isJSON } from '@home/components/widget/lib/maps/maps-utils'; import {
createTooltip,
entitiesParseName,
isCutPolygon,
isJSON,
isValidLatLng
} from '@home/components/widget/lib/maps/maps-utils';
import { checkLngLat, createLoadingDiv } from '@home/components/widget/lib/maps/common-maps-utils'; import { checkLngLat, createLoadingDiv } from '@home/components/widget/lib/maps/common-maps-utils';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { import {
@ -148,7 +154,8 @@ export default abstract class LeafletMap {
if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) { if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) {
const parsedColor = tinycolor(markerColor); const parsedColor = tinycolor(markerColor);
return L.divIcon({ return L.divIcon({
html: `<div style="background-color: ${parsedColor.setAlpha(0.4).toRgbString()};" class="marker-cluster tb-cluster-marker-element">` + html: `<div style="background-color: ${parsedColor.setAlpha(0.4).toRgbString()};" ` +
`class="marker-cluster tb-cluster-marker-element">` +
`<div style="background-color: ${parsedColor.setAlpha(0.9).toRgbString()};"><span>` + childCount + '</span></div></div>', `<div style="background-color: ${parsedColor.setAlpha(0.9).toRgbString()};"><span>` + childCount + '</span></div></div>',
iconSize: new L.Point(40, 40), iconSize: new L.Point(40, 40),
className: 'tb-cluster-marker-container' className: 'tb-cluster-marker-container'
@ -378,7 +385,8 @@ export default abstract class LeafletMap {
}); });
}, },
}); });
this.map.pm.Toolbar.changeControlOrder(['tbMarker', 'tbRectangle', 'tbPolygon', 'tbCircle', 'editMode', 'dragMode', 'tbCut', 'removalMode', 'rotateMode']); this.map.pm.Toolbar.changeControlOrder(['tbMarker', 'tbRectangle', 'tbPolygon', 'tbCircle',
'editMode', 'dragMode', 'tbCut', 'removalMode', 'rotateMode']);
} }
this.map.pm.setLang('en', this.translateService.instant('widgets.maps'), 'en'); this.map.pm.setLang('en', this.translateService.instant('widgets.maps'), 'en');
@ -643,19 +651,19 @@ export default abstract class LeafletMap {
} }
} }
extractPosition(data: FormattedData): {x: number, y: number} { extractPosition(data: FormattedData): {x: number; y: number} {
if (!data) { if (!data) {
return null; return null;
} }
const lat = data[this.options.latKeyName]; const lat = data[this.options.latKeyName];
const lng = data[this.options.lngKeyName]; const lng = data[this.options.lngKeyName];
if (!isDefinedAndNotNull(lat) || isString(lat) || isNaN(lat) || !isDefinedAndNotNull(lng) || isString(lng) || isNaN(lng)) { if (!isValidLatLng(lat, lng)) {
return null; return null;
} }
return {x: lat, y: lng}; return {x: lat, y: lng};
} }
positionToLatLng(position: {x: number, y: number}): L.LatLng { positionToLatLng(position: {x: number; y: number}): L.LatLng {
return L.latLng(position.x, position.y) as L.LatLng; return L.latLng(position.x, position.y) as L.LatLng;
} }
@ -936,7 +944,7 @@ export default abstract class LeafletMap {
return; return;
} }
this.saveLocation(data, this.convertToCustomFormat(e.target._latlng)).subscribe(); this.saveLocation(data, this.convertToCustomFormat(e.target._latlng)).subscribe();
} };
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>, private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>,
updateBounds = true, callback?, snappable = false): Marker { updateBounds = true, callback?, snappable = false): Marker {
@ -1141,7 +1149,7 @@ export default abstract class LeafletMap {
} }
} }
this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {}); this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {});
} };
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetPolygonSettings>, createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetPolygonSettings>,
updateBounds = true, snappable = false) { updateBounds = true, snappable = false) {
@ -1218,7 +1226,7 @@ export default abstract class LeafletMap {
const center = e.layer.getLatLng(); const center = e.layer.getLatLng();
const radius = e.layer.getRadius(); const radius = e.layer.getRadius();
this.saveLocation(data, this.convertCircleToCustomFormat(center, radius)).subscribe(() => {}); this.saveLocation(data, this.convertCircleToCustomFormat(center, radius)).subscribe(() => {});
} };
updateCircle(circlesData: FormattedData[], updateBounds = true) { updateCircle(circlesData: FormattedData[], updateBounds = true) {
const toDelete = new Set(Array.from(this.circles.keys())); const toDelete = new Set(Array.from(this.circles.keys()));

15
ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts

@ -20,7 +20,7 @@ import {
ShowTooltipAction, WidgetToolipSettings ShowTooltipAction, WidgetToolipSettings
} from './map-models'; } from './map-models';
import { Datasource, FormattedData } from '@app/shared/models/widget.models'; import { Datasource, FormattedData } from '@app/shared/models/widget.models';
import { fillDataPattern, processDataPattern, safeExecute } from '@core/utils'; import { fillDataPattern, isDefinedAndNotNull, isString, processDataPattern, safeExecute } from '@core/utils';
import { parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils'; import { parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils';
export function createTooltip(target: L.Layer, export function createTooltip(target: L.Layer,
@ -110,3 +110,16 @@ export function entitiesParseName(entities: FormattedData[], labelSettings: labe
} }
return entities; return entities;
} }
export const isValidLatitude = (latitude: any): boolean =>
isDefinedAndNotNull(latitude) &&
!isString(latitude) &&
!isNaN(latitude) && isFinite(latitude) && Math.abs(latitude) <= 90;
export const isValidLongitude = (longitude: any): boolean =>
isDefinedAndNotNull(longitude) &&
!isString(longitude) &&
!isNaN(longitude) && isFinite(longitude) && Math.abs(longitude) <= 180;
export const isValidLatLng = (latitude: any, longitude: any): boolean =>
isValidLatitude(latitude) && isValidLongitude(longitude);

4
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 { protected prepareOutputSettings(settings: any): WidgetSettings {
return { return {
title: settings.title, ...settings.switchRpcSettings,
...settings.switchRpcSettings title: settings.title
}; };
} }
} }

4
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 { protected prepareOutputSettings(settings: any): WidgetSettings {
return { return {
...settings.switchRpcSettings,
title: settings.title, title: settings.title,
labelPosition: settings.labelPosition, labelPosition: settings.labelPosition,
sliderColor: settings.sliderColor, sliderColor: settings.sliderColor
...settings.switchRpcSettings
}; };
} }
} }

4
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 { protected prepareOutputSettings(settings: any): WidgetSettings {
return { return {
...settings.switchRpcSettings,
title: settings.title, title: settings.title,
showOnOffLabels: settings.showOnOffLabels, showOnOffLabels: settings.showOnOffLabels
...settings.switchRpcSettings
}; };
} }
} }

5
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -224,6 +224,11 @@ export class WidgetComponentService {
); );
} }
public clearWidgetInfo(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): void {
this.dynamicComponentFactoryService.destroyDynamicComponentFactory(widgetInfo.componentFactory);
this.widgetService.deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
}
private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> { private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> {
const widgetInfoSubject = new ReplaySubject<WidgetInfo>(); const widgetInfoSubject = new ReplaySubject<WidgetInfo>();
const widgetInfo = this.widgetService.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); const widgetInfo = this.widgetService.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);

2
ui-ngx/src/app/modules/home/components/widget/widget.component.html

@ -34,7 +34,7 @@
</tb-legend> </tb-legend>
</div> </div>
<div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData"> <div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData">
<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span> <span [innerHtml]="('Widget Error:<br/><br/>' + widgetErrorData.message) | safe:'html'"></span>
</div> </div>
<div class="tb-absolute-fill tb-widget-no-data" *ngIf="displayNoData"> <div class="tb-absolute-fill tb-widget-no-data" *ngIf="displayNoData">
<span fxLayoutAlign="center center" <span fxLayoutAlign="center center"

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save