Browse Source

Merge remote-tracking branch 'upstream/master' into AD/imp/rule-chain-selector

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

4
application/src/main/java/org/thingsboard/server/controller/QueueController.java

@ -83,7 +83,7 @@ public class QueueController extends BaseController {
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("serviceType", serviceType);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
ServiceType type = ServiceType.valueOf(serviceType);
ServiceType type = ServiceType.of(serviceType);
switch (type) {
case TB_RULE_ENGINE:
return queueService.findQueuesByTenantId(getTenantId(), pageLink);
@ -136,7 +136,7 @@ public class QueueController extends BaseController {
checkEntity(queue.getId(), queue, Resource.QUEUE);
ServiceType type = ServiceType.valueOf(serviceType);
ServiceType type = ServiceType.of(serviceType);
switch (type) {
case TB_RULE_ENGINE:
queue.setTenantId(getTenantId());

2
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -406,7 +406,7 @@ public class UserController extends BaseController {
public void setUserCredentialsEnabled(
@ApiParam(value = USER_ID_PARAM_DESCRIPTION)
@PathVariable(USER_ID) String strUserId,
@ApiParam(value = "Disable (\"true\") or enable (\"false\") the credentials.", defaultValue = "true")
@ApiParam(value = "Enable (\"true\") or disable (\"false\") the credentials.", defaultValue = "true")
@RequestParam(required = false, defaultValue = "true") boolean userCredentialsEnabled) throws ThingsboardException {
checkParameter(USER_ID, strUserId);
UserId userId = new UserId(toUUID(strUserId));

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

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

@ -15,16 +15,30 @@
*/
package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.util.SerializationUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
@Slf4j
public class CookieUtils {
private static final ObjectMapper OBJECT_MAPPER;
static {
ClassLoader loader = CookieUtils.class.getClassLoader();
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.registerModules(SecurityJackson2Modules.getModules(loader));
}
public static Optional<Cookie> getCookie(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
@ -50,7 +64,7 @@ public class CookieUtils {
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie: cookies) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
cookie.setValue("");
cookie.setPath("/");
@ -62,12 +76,22 @@ public class CookieUtils {
}
public static String serialize(Object object) {
return Base64.getUrlEncoder()
.encodeToString(SerializationUtils.serialize(object));
try {
return Base64.getUrlEncoder()
.encodeToString(OBJECT_MAPPER.writeValueAsBytes(object));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("The given Json object value: "
+ object + " cannot be transformed to a String", e);
}
}
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 {
return OBJECT_MAPPER.readValue(decodedBytes, cls);
} catch (IOException e) {
throw new IllegalArgumentException("The given string value: "
+ Arrays.toString(decodedBytes) + " cannot be transformed to Json object", e);
}
}
}

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 {

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

@ -159,7 +159,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.5}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.5.1}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -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

96
application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java

@ -0,0 +1,96 @@
/**
* 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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class BaseQueueControllerTest extends AbstractControllerTest {
@Test
public void testQueueWithServiceTypeRE() throws Exception {
loginSysAdmin();
// create queue
Queue queue = new Queue();
queue.setName("qwerty");
queue.setTopic("tb_rule_engine.qwerty");
queue.setPollInterval(25);
queue.setPartitions(10);
queue.setTenantId(TenantId.SYS_TENANT_ID);
queue.setConsumerPerPartition(false);
queue.setPackProcessingTimeout(2000);
SubmitStrategy submitStrategy = new SubmitStrategy();
submitStrategy.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR);
queue.setSubmitStrategy(submitStrategy);
ProcessingStrategy processingStrategy = new ProcessingStrategy();
processingStrategy.setType(ProcessingStrategyType.RETRY_ALL);
processingStrategy.setRetries(3);
processingStrategy.setFailurePercentage(0.7);
processingStrategy.setPauseBetweenRetries(3);
processingStrategy.setMaxPauseBetweenRetries(5);
queue.setProcessingStrategy(processingStrategy);
// create queue
Queue queue2 = new Queue();
queue2.setName("qwerty2");
queue2.setTopic("tb_rule_engine.qwerty2");
queue2.setPollInterval(25);
queue2.setPartitions(10);
queue2.setTenantId(TenantId.SYS_TENANT_ID);
queue2.setConsumerPerPartition(false);
queue2.setPackProcessingTimeout(2000);
submitStrategy.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR);
queue2.setSubmitStrategy(submitStrategy);
processingStrategy.setType(ProcessingStrategyType.RETRY_ALL);
processingStrategy.setRetries(3);
processingStrategy.setFailurePercentage(0.7);
processingStrategy.setPauseBetweenRetries(3);
processingStrategy.setMaxPauseBetweenRetries(5);
queue2.setProcessingStrategy(processingStrategy);
Queue savedQueue = doPost("/api/queues?serviceType=" + "TB-RULE-ENGINE", queue, Queue.class);
Queue savedQueue2 = doPost("/api/queues?serviceType=" + "TB_RULE_ENGINE", queue2, Queue.class);
PageLink pageLink = new PageLink(10);
PageData<Queue> pageData;
pageData = doGetTypedWithPageLink("/api/queues?serviceType=TB-RULE-ENGINE&", new TypeReference<>() {
}, pageLink);
Assert.assertFalse(pageData.getData().isEmpty());
doDelete("/api/queues/" + savedQueue.getUuidId())
.andExpect(status().isOk());
pageData = doGetTypedWithPageLink("/api/queues?serviceType=TB_RULE_ENGINE&", new TypeReference<>() {
}, pageLink);
Assert.assertFalse(pageData.getData().isEmpty());
doDelete("/api/queues/" + savedQueue2.getUuidId())
.andExpect(status().isOk());
}
}

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

58
application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtilsTest.java

@ -0,0 +1,58 @@
/**
* 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.Test;
import org.mockito.Mockito;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME;
public class CookieUtilsTest {
@Test
public void serializeDeserializeOAuth2AuthorizationRequestTest() {
HttpCookieOAuth2AuthorizationRequestRepository cookieRequestRepo = new HttpCookieOAuth2AuthorizationRequestRepository();
HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("param1", "value1");
additionalParameters.put("param2", "value2");
var request = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri("testUri").clientId("testId")
.scope("read", "write")
.additionalParameters(additionalParameters).build();
Cookie cookie = new Cookie(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(request));
Mockito.when(servletRequest.getCookies()).thenReturn(new Cookie[]{cookie});
OAuth2AuthorizationRequest deserializedRequest = cookieRequestRepo.loadAuthorizationRequest(servletRequest);
assertNotNull(deserializedRequest);
assertEquals(request.getGrantType(), deserializedRequest.getGrantType());
assertEquals(request.getAuthorizationUri(), deserializedRequest.getAuthorizationUri());
assertEquals(request.getClientId(), deserializedRequest.getClientId());
}
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/client/AbstractMqttClientConnectionTest.java

@ -34,7 +34,7 @@ public abstract class AbstractMqttClientConnectionTest extends AbstractMqttInteg
try {
client.connectAndWait("wrongAccessToken");
} catch (MqttException e) {
Assert.assertEquals(MqttException.REASON_CODE_FAILED_AUTHENTICATION, e.getReasonCode());
Assert.assertEquals(MqttException.REASON_CODE_NOT_AUTHORIZED, e.getReasonCode());
}
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/credentials/BasicMqttCredentialsTest.java

@ -124,7 +124,7 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest {
mqttTestClient.connectAndWait(USER_NAME3, "WRONG PASSWORD");
Assert.fail(); // This should not happens, because we have a wrong password
} catch (MqttException e) {
Assert.assertEquals(4, e.getReasonCode()); // 4 - Reason code for bad username or password in MQTT v3
Assert.assertEquals(5, e.getReasonCode()); // 4 - Reason code not authorized in MQTT v3
}
Assertions.assertThrows(MqttException.class, () -> {
testTelemetryIsNotDelivered(clientIdAndUserNameAndPasswordDevice3, mqttTestClient);

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>

4
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -326,7 +326,7 @@ public class HashPartitionService implements PartitionService {
final Map<QueueKey, List<ServiceInfo>> currentMap = new HashMap<>();
services.forEach(serviceInfo -> {
for (String serviceTypeStr : serviceInfo.getServiceTypesList()) {
ServiceType serviceType = ServiceType.valueOf(serviceTypeStr.toUpperCase());
ServiceType serviceType = ServiceType.of(serviceTypeStr);
if (ServiceType.TB_RULE_ENGINE.equals(serviceType)) {
partitionTopicsMap.keySet().forEach(queueKey ->
currentMap.computeIfAbsent(queueKey, key -> new ArrayList<>()).add(serviceInfo));
@ -389,7 +389,7 @@ public class HashPartitionService implements PartitionService {
private void addNode(Map<QueueKey, List<ServiceInfo>> queueServiceList, ServiceInfo instance) {
for (String serviceTypeStr : instance.getServiceTypesList()) {
ServiceType serviceType = ServiceType.valueOf(serviceTypeStr.toUpperCase());
ServiceType serviceType = ServiceType.of(serviceTypeStr);
if (ServiceType.TB_RULE_ENGINE.equals(serviceType)) {
partitionTopicsMap.keySet().forEach(key -> {
if (key.getType().equals(ServiceType.TB_RULE_ENGINE)) {

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>

1
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/ReturnCodeResolver.java

@ -27,7 +27,6 @@ public class ReturnCodeResolver {
if (!MqttVersion.MQTT_5.equals(mqttVersion) && !ReturnCode.SUCCESS.equals(returnCode)) {
switch (returnCode) {
case BAD_USERNAME_OR_PASSWORD:
return MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD;
case NOT_AUTHORIZED_5:
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
case SERVER_UNAVAILABLE_5:

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>

8
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>
@ -74,7 +74,7 @@
<gson.version>2.9.0</gson.version>
<freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version>
<curator.version>4.2.0</curator.version>
<curator.version>5.5.0</curator.version>
<zookeeper.version>3.8.1</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version>
@ -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>

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

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();
}
}

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

@ -86,7 +86,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
private utilsService: UtilsService,
pageMode = false) {
super();
this.loadDataOnInit = false;
this.loadDataOnInit = pageMode;
this.tableTitle = '';
this.useTimePageLink = true;
this.forAllTimeEnabled = true;
@ -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)">

2
ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<form (ngSubmit)="add()" style="min-width: 400px; width: 700px;" [ngStyle]="entitiesTableConfig.addDialogStyle">
<form (ngSubmit)="add()" style="min-width: 400px; width: 750px;" [ngStyle]="entitiesTableConfig.addDialogStyle">
<mat-toolbar color="primary">
<h2 translate>{{ translations.add }}</h2>
<span fxFlex></span>

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;

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

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
@ -53,7 +53,7 @@ import { takeUntil } from 'rxjs/operators';
}
]
})
export class TenantProfileQueuesComponent implements ControlValueAccessor, Validator, OnDestroy {
export class TenantProfileQueuesComponent implements ControlValueAccessor, Validator, OnDestroy, OnInit {
tenantProfileQueuesFormGroup: UntypedFormGroup;
newQueue = false;
@ -107,6 +107,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this.newQueue = false;
if (this.disabled) {
this.tenantProfileQueuesFormGroup.disable({emitEvent: false});
} else {
@ -115,7 +116,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
}
writeValue(queues: Array<QueueInfo> | null): void {
if (queues.length === this.queuesFormArray.length) {
if (queues?.length === this.queuesFormArray.length) {
this.queuesFormArray.patchValue(queues, {emitEvent: false});
} else {
const queuesControls: Array<AbstractControl> = [];

64
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -21,7 +21,7 @@
{{ 'tenant-profile.entities' | translate }} <span translate>tenant-profile.unlimited</span>
</legend>
<div class="fields-element" fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-devices</mat-label>
<input matInput required min="0" step="1"
formControlName="maxDevices"
@ -32,8 +32,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDevices').hasError('min')">
{{ 'tenant-profile.maximum-devices-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-dashboards</mat-label>
<input matInput required min="0" step="1"
formControlName="maxDashboards"
@ -44,10 +45,11 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('min')">
{{ 'tenant-profile.maximum-dashboards-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-assets</mat-label>
<input matInput required min="0" step="1"
formControlName="maxAssets"
@ -58,8 +60,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxAssets').hasError('min')">
{{ 'tenant-profile.maximum-assets-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-users</mat-label>
<input matInput required min="0" step="1"
formControlName="maxUsers"
@ -70,6 +73,7 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('min')">
{{ 'tenant-profile.maximum-users-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
<mat-expansion-panel class="configuration-panel">
@ -80,7 +84,7 @@
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-customers</mat-label>
<input matInput required min="0" step="1"
formControlName="maxCustomers"
@ -91,8 +95,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('min')">
{{ 'tenant-profile.maximum-customers-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-rule-chains</mat-label>
<input matInput required min="0" step="1"
formControlName="maxRuleChains"
@ -103,6 +108,7 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('min')">
{{ 'tenant-profile.maximum-rule-chains-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
</ng-template>
@ -114,7 +120,7 @@
{{ 'tenant-profile.rule-engine' | translate }} <span translate>tenant-profile.unlimited</span>
</legend>
<div class="fields-element" fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-r-e-executions</mat-label>
<input matInput required min="0" step="1"
formControlName="maxREExecutions"
@ -125,8 +131,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxREExecutions').hasError('min')">
{{ 'tenant-profile.max-r-e-executions-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-transport-messages</mat-label>
<input matInput required min="0" step="1"
formControlName="maxTransportMessages"
@ -137,6 +144,7 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxTransportMessages').hasError('min')">
{{ 'tenant-profile.max-transport-messages-required' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
<mat-expansion-panel class="configuration-panel">
@ -147,7 +155,7 @@
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-j-s-executions</mat-label>
<input matInput required min="0" step="1"
formControlName="maxJSExecutions"
@ -158,8 +166,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxJSExecutions').hasError('min')">
{{ 'tenant-profile.max-j-s-executions-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-transport-data-points</mat-label>
<input matInput required min="0" step="1"
formControlName="maxTransportDataPoints"
@ -170,10 +179,11 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxTransportDataPoints').hasError('min')">
{{ 'tenant-profile.max-transport-data-points-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-rule-node-executions-per-message</mat-label>
<input matInput required min="0" step="1"
formControlName="maxRuleNodeExecutionsPerMessage"
@ -184,6 +194,7 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleNodeExecutionsPerMessage').hasError('min')">
{{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<div fxFlex></div>
</div>
@ -196,7 +207,7 @@
{{ 'tenant-profile.time-to-live' | translate }} <span translate>tenant-profile.unlimited</span>
</legend>
<div class="fields-element" fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-d-p-storage-days</mat-label>
<input matInput required min="0" step="1"
formControlName="maxDPStorageDays"
@ -207,8 +218,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDPStorageDays').hasError('min')">
{{ 'tenant-profile.max-d-p-storage-days-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.alarms-ttl-days</mat-label>
<input matInput required min="0" step="1"
formControlName="alarmsTtlDays"
@ -219,10 +231,11 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('alarmsTtlDays').hasError('min')">
{{ 'tenant-profile.alarms-ttl-days-days-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.default-storage-ttl-days</mat-label>
<input matInput required min="0" step="1"
formControlName="defaultStorageTtlDays"
@ -233,8 +246,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('defaultStorageTtlDays').hasError('min')">
{{ 'tenant-profile.default-storage-ttl-days-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rpc-ttl-days</mat-label>
<input matInput required min="0" step="1"
formControlName="rpcTtlDays"
@ -245,6 +259,7 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('rpcTtlDays').hasError('min')">
{{ 'tenant-profile.rpc-ttl-days-days-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
</fieldset>
@ -254,7 +269,7 @@
{{ 'tenant-profile.alarms-and-notifications' | translate }} <span translate>tenant-profile.unlimited</span>
</legend>
<div class="fields-element" fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-emails</mat-label>
<input matInput required min="0" step="1"
formControlName="maxEmails"
@ -265,8 +280,9 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxEmails').hasError('min')">
{{ 'tenant-profile.max-emails-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-created-alarms</mat-label>
<input matInput required min="0" step="1"
formControlName="maxCreatedAlarms"
@ -277,10 +293,11 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCreatedAlarms').hasError('min')">
{{ 'tenant-profile.max-created-alarms-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-sms</mat-label>
<input matInput required min="0" step="1"
formControlName="maxSms"
@ -291,6 +308,7 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxSms').hasError('min')">
{{ 'tenant-profile.max-sms-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<div fxFlex></div>
</div>
@ -301,7 +319,7 @@
{{ 'tenant-profile.ota-files-in-bytes' | translate }} <span translate>tenant-profile.unlimited</span>
</legend>
<div class="fields-element" fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-resources-sum-data-size</mat-label>
<input matInput required min="0" step="1"
formControlName="maxResourcesInBytes"
@ -312,18 +330,20 @@
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('min')">
{{ 'tenant-profile.maximum-resources-sum-data-size-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" appearance="fill">
<mat-form-field fxFlex class="mat-block" appearance="fill" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-ota-packages-sum-data-size</mat-label>
<input matInput required min="0" step="1"
formControlName="maxOtaPackagesInBytes"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('required')">
{{ 'tenant-profile.maximum-ota-packages-sum-data-size-required' | translate}}
{{ 'tenant-profile.maximum-ota-package-sum-data-size-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('min')">
{{ 'tenant-profile.maximum-ota-packages-sum-data-size-range' | translate}}
{{ 'tenant-profile.maximum-ota-package-sum-data-size-range' | translate}}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
</div>
</fieldset>

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

Loading…
Cancel
Save