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>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<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":
log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ...");
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)) {
systemDataLoaderService.createDefaultNotificationConfigs();
} else {
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;
//TODO update CacheCleanupService on the next version upgrade
default:
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!");

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);
}
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:
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;
case "3.4.4":
log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0");
clearCacheByName("deviceProfiles");
clearAll();
break;
default:
//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.thingsboard.server.common.data.DataConstants;
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.TenantId;
import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo;
@ -41,6 +42,9 @@ public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotification
@Override
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;
if (!triggerConfig.getNotifyOn().contains(event)) {
return false;

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

@ -16,8 +16,6 @@
package org.thingsboard.server.service.resource;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType;
@ -53,11 +51,9 @@ import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource
public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService {
private final ResourceService resourceService;
private final DDFFileParser ddfFileParser;
public DefaultTbResourceService(ResourceService resourceService) {
this.resourceService = resourceService;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
}
@Override

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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.SerializationUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
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.Optional;
@Slf4j
public class CookieUtils {
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) {
return cls.cast(SerializationUtils.deserialize(
Base64.getUrlDecoder().decode(cookie.getValue())));
byte[] decodedBytes = 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.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.util.TbDDFFileParser;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.io.ByteArrayInputStream;
@ -41,7 +42,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
@Slf4j
public class LwM2mObjectModelUtils {
private static final DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
private static final TbDDFFileParser ddfFileParser = new TbDDFFileParser();
public static void toLwm2mResource (TbResource resource) throws ThingsboardException {
try {

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

@ -607,6 +607,7 @@ spring:
username: "${SPRING_DATASOURCE_USERNAME:postgres}"
password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
hikari:
leakDetectionThreshold: "${SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD:0}"
maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:16}"
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 static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@ -75,6 +77,32 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
"</Object>\n" +
"</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 IdComparator<TbResourceInfo> idComparator = new IdComparator<>();
@ -126,11 +154,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
loginTenantAdmin();
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try {
assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME))
@ -145,19 +173,19 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
@Test
public void sumDataSizeByTenantId() throws Exception {
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8;
for (int i = 2; i <= maxSumDataSize; i++) {
createResource("test" + i, i + DEFAULT_FILE_NAME);
Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId));
}
Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId));
}
private TbResource createResource(String title, String filename) throws Exception {
@ -184,16 +212,16 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals(resource.getTitle(), savedResource.getTitle());
Assert.assertEquals(resource.getResourceKey(), savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData());
assertEquals(resource.getTenantId(), savedResource.getTenantId());
assertEquals(resource.getTitle(), savedResource.getTitle());
assertEquals(resource.getResourceKey(), savedResource.getResourceKey());
assertEquals(resource.getData(), savedResource.getData());
savedResource.setTitle("My new resource");
resourceService.save(savedResource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle());
assertEquals(foundResource.getTitle(), savedResource.getTitle());
resourceService.delete(savedResource, null);
}
@ -211,10 +239,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals("My first resource id=0 v1.0", savedResource.getTitle());
Assert.assertEquals("0_1.0", savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData());
assertEquals(resource.getTenantId(), savedResource.getTenantId());
assertEquals("My first resource id=0 v1.0", savedResource.getTitle());
assertEquals("0_1.0", savedResource.getResourceKey());
assertEquals(resource.getData(), savedResource.getData());
resourceService.delete(savedResource, null);
}
@ -228,7 +256,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setData("Test Data");
TbResource savedResource = resourceService.save(resource);
Assert.assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
resourceService.delete(savedResource, null);
}
@ -285,6 +313,21 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
});
}
@Test
public void testSaveLwm2mTbResourceWithXXE() {
TbResource resource = new TbResource();
resource.setTenantId(tenantId);
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setFileName("xxe_test_model.xml");
resource.setData(Base64.getEncoder().encodeToString(LWM2M_TEST_MODEL_WITH_XXE.getBytes()));
DataValidationException thrown = assertThrows(DataValidationException.class, () -> {
resourceService.save(resource);
});
assertEquals("Failed to parse file xxe_test_model.xml", thrown.getMessage());
}
@Test
public void testFindResourceById() throws Exception {
TbResource resource = new TbResource();
@ -296,7 +339,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource);
assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null);
}
@ -312,7 +355,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey());
Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource);
assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null);
}
@ -366,7 +409,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources);
assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId);
@ -427,14 +470,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources);
assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId);
pageLink = new PageLink(100);
pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(pageData.getData().size(), 100);
assertEquals(pageData.getData().size(), 100);
resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID);

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>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

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

2
common/cluster-api/pom.xml

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

2
common/coap-server/pom.xml

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

2
common/dao-api/pom.xml

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

7
common/data/pom.xml

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

2
common/message/pom.xml

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

2
common/pom.xml

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

2
common/queue/pom.xml

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

2
common/script/pom.xml

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

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

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

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

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

2
common/stats/pom.xml

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

2
common/transport/coap/pom.xml

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

2
common/transport/http/pom.xml

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

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<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.node.codec.CodecException;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.util.TbDDFFileParser;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -142,9 +143,9 @@ public class LwM2mTransportServerHelper {
.build();
}
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) {
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator);
TbDDFFileParser ddfFileParser = new TbDDFFileParser();
return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0);
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML file [{}]", streamName, e);

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);
return tbResource.map(resource -> helper.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()),
key + ".xml",
new DefaultDDFFileValidator())).orElse(null);
key + ".xml")).orElse(null);
}
}
}

2
common/transport/mqtt/pom.xml

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

2
common/transport/pom.xml

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

2
common/transport/snmp/pom.xml

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

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

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

4
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
@ -89,7 +89,7 @@
<artifactId>data</artifactId>
</dependency>
<dependency>
<groupId>com.github.dblock</groupId>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
</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 available = memory.getAvailable();
return Optional.of(toPercent(total - available, total));
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get memory usage!!!", e);
}
return Optional.empty();
@ -51,7 +51,7 @@ public class SystemUtil {
public static Optional<Long> getTotalMemory() {
try {
return Optional.of(HARDWARE.getMemory().getTotal());
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get total memory!!!", e);
}
return Optional.empty();
@ -59,8 +59,8 @@ public class SystemUtil {
public static Optional<Integer> getCpuUsage() {
try {
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad() * 100.0));
} catch (Exception e) {
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad(1000) * 100.0));
} catch (Throwable e) {
log.debug("Failed to get cpu usage!!!", e);
}
return Optional.empty();
@ -69,7 +69,7 @@ public class SystemUtil {
public static Optional<Integer> getCpuCount() {
try {
return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount());
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get total cpu count!!!", e);
}
return Optional.empty();
@ -81,7 +81,7 @@ public class SystemUtil {
long total = store.getTotalSpace();
long available = store.getUsableSpace();
return Optional.of(toPercent(total - available, total));
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get free disc space!!!", e);
}
return Optional.empty();
@ -91,7 +91,7 @@ public class SystemUtil {
try {
FileStore store = Files.getFileStore(Paths.get("/"));
return Optional.of(store.getTotalSpace());
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get total disc space!!!", e);
}
return Optional.empty();

2
common/version-control/pom.xml

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

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<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.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
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")
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,
@Param("a_id") UUID alarmId, @Param("a_created_ts") long createdTime,
@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_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,
@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_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);
@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);
@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);
@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);
}

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
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

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 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;
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
FROM device d
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;
CREATE OR REPLACE VIEW device_info_active_ts_view AS
@ -226,7 +226,11 @@ BEGIN
END IF;
IF NOT(existing.cleared) THEN
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;
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;

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

@ -202,7 +202,34 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test
public void testClearAlarmProcedure() {
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 alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
@ -216,6 +243,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
assertEquals(alarm.getDetails(), result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
assertNotNull(result.getAlarm());

2
monitoring/pom.xml

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

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

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

2
msa/js-executor/package.json

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

2
msa/js-executor/pom.xml

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

2
msa/monitoring/pom.xml

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

2
msa/pom.xml

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

2
msa/tb-node/pom.xml

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

2
msa/transport/coap/pom.xml

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

2
msa/transport/http/pom.xml

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

2
msa/transport/lwm2m/pom.xml

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

2
msa/transport/mqtt/pom.xml

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

2
msa/transport/pom.xml

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

2
msa/transport/snmp/pom.xml

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

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

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

2
msa/vc-executor/pom.xml

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

2
msa/web-ui/package.json

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

2
msa/web-ui/pom.xml

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

4
netty-mqtt/pom.xml

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

6
pom.xml

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

2
rest-client/pom.xml

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

2
rule-engine/pom.xml

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

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

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

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

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

2
transport/coap/pom.xml

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

2
transport/http/pom.xml

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

2
transport/lwm2m/pom.xml

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

2
transport/mqtt/pom.xml

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

2
transport/pom.xml

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

2
transport/snmp/pom.xml

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

32
ui-ngx/package.json

@ -1,6 +1,6 @@
{
"name": "thingsboard",
"version": "3.5.0",
"version": "3.5.1",
"scripts": {
"ng": "ng",
"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,
"dependencies": {
"@angular/animations": "^15.2.5",
"@angular/cdk": "^15.2.5",
"@angular/common": "^15.2.5",
"@angular/compiler": "^15.2.5",
"@angular/core": "^15.2.5",
"@angular/animations": "^15.2.9",
"@angular/cdk": "^15.2.9",
"@angular/common": "^15.2.9",
"@angular/compiler": "^15.2.9",
"@angular/core": "^15.2.9",
"@angular/flex-layout": "^15.0.0-beta.42",
"@angular/forms": "^15.2.5",
"@angular/material": "^15.2.5",
"@angular/platform-browser": "^15.2.5",
"@angular/platform-browser-dynamic": "^15.2.5",
"@angular/router": "^15.2.5",
"@angular/forms": "^15.2.9",
"@angular/material": "^15.2.9",
"@angular/platform-browser": "^15.2.9",
"@angular/platform-browser-dynamic": "^15.2.9",
"@angular/router": "^15.2.9",
"@auth0/angular-jwt": "^5.1.2",
"@date-io/core": "1.3.7",
"@date-io/date-fns": "1.3.7",
@ -108,16 +108,16 @@
},
"devDependencies": {
"@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/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.4",
"@angular/compiler-cli": "^15.2.5",
"@angular/language-service": "^15.2.5",
"@ngtools/webpack": "^15.1.6",
"@angular/cli": "^15.2.8",
"@angular/compiler-cli": "^15.2.9",
"@angular/language-service": "^15.2.9",
"@ngtools/webpack": "15.2.1",
"@types/ace-diff": "^2.1.1",
"@types/canvas-gauges": "^2.1.4",
"@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>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<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);
}
private deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
public deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
this.widgetsInfoInMemoryCache.delete(key);
}

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

@ -30,7 +30,14 @@ import {
WidgetLayout
} from '@shared/models/dashboard.models';
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 { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
import { EntityId } from '@app/shared/models/id/entity-id';
@ -227,7 +234,8 @@ export class DashboardUtilsService {
}
});
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 (!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';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-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';
@Injectable(
@ -78,6 +79,23 @@ export class DialogService {
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> {
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 {
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) {
const message: WindowMessage = {
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 { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component';
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
import { UtilsService } from '@core/services/utils.service';
export interface AlarmDetailsDialogData {
alarmId?: string;
@ -79,6 +80,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData,
private alarmService: AlarmService,
private utils: UtilsService,
public dialogRef: MatDialogRef<AlarmDetailsDialogComponent, boolean>,
public fb: UntypedFormBuilder) {
super(store, router, dialogRef);
@ -136,7 +138,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
}
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')
.patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status)));
this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details);
@ -175,7 +177,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
onReassign(): void {
this.alarmUpdated = true;
this.loadAlarm()
this.loadAlarm();
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%',
(entity) => entity.originatorName, entity => ({}), false));
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(
new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '25%',
(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"
matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
<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"
[checked]="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.
///
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 { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog';
@ -32,7 +41,7 @@ import { Subscription } from 'rxjs';
templateUrl: './event-table.component.html',
styleUrls: ['./event-table.component.scss']
})
export class EventTableComponent implements OnInit, AfterViewInit {
export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy {
@Input()
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;
newQueue = false;
@ -107,6 +107,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this.newQueue = false;
if (this.disabled) {
this.tenantProfileQueuesFormGroup.disable({emitEvent: false});
} else {

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

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

@ -33,6 +33,8 @@ import {
CustomDialogComponent,
CustomDialogData
} from '@home/components/widget/dialog/custom-dialog.component';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
export interface CustomDialogContainerData {
controller: (instance: CustomDialogComponent) => void;
@ -54,6 +56,8 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
protected router: Router,
public viewContainerRef: ViewContainerRef,
public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
private dialogService: DialogService,
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) {
super(store, router, dialogRef);
let customDialogData: CustomDialogData = {
@ -72,7 +76,19 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
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 {

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 {
super.ngOnDestroy();
if (this.ctx.telemetrySubscribers) {
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 { Polygon } from './polygon';
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 { WidgetContext } from '@home/models/widget-component.models';
import {
@ -148,7 +154,8 @@ export default abstract class LeafletMap {
if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) {
const parsedColor = tinycolor(markerColor);
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>',
iconSize: new L.Point(40, 40),
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');
@ -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) {
return null;
}
const lat = data[this.options.latKeyName];
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 {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;
}
@ -936,7 +944,7 @@ export default abstract class LeafletMap {
return;
}
this.saveLocation(data, this.convertToCustomFormat(e.target._latlng)).subscribe();
}
};
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>,
updateBounds = true, callback?, snappable = false): Marker {
@ -1141,7 +1149,7 @@ export default abstract class LeafletMap {
}
}
this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {});
}
};
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetPolygonSettings>,
updateBounds = true, snappable = false) {
@ -1218,7 +1226,7 @@ export default abstract class LeafletMap {
const center = e.layer.getLatLng();
const radius = e.layer.getRadius();
this.saveLocation(data, this.convertCircleToCustomFormat(center, radius)).subscribe(() => {});
}
};
updateCircle(circlesData: FormattedData[], updateBounds = true) {
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
} from './map-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';
export function createTooltip(target: L.Layer,
@ -110,3 +110,16 @@ export function entitiesParseName(entities: FormattedData[], labelSettings: labe
}
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 {
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 {
return {
...settings.switchRpcSettings,
title: settings.title,
labelPosition: settings.labelPosition,
sliderColor: settings.sliderColor,
...settings.switchRpcSettings
sliderColor: settings.sliderColor
};
}
}

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 {
return {
...settings.switchRpcSettings,
title: settings.title,
showOnOffLabels: settings.showOnOffLabels,
...settings.switchRpcSettings
showOnOffLabels: settings.showOnOffLabels
};
}
}

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> {
const widgetInfoSubject = new ReplaySubject<WidgetInfo>();
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>
</div>
<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 class="tb-absolute-fill tb-widget-no-data" *ngIf="displayNoData">
<span fxLayoutAlign="center center"

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

Loading…
Cancel
Save