Browse Source

Merge branch 'master' into release-3.6

release-3.6 v3.6.1
Igor Kulikov 3 years ago
parent
commit
a1067866f0
  1. 22
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  2. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  3. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  4. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  6. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java
  7. 11
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
  8. 117
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
  9. 10
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
  12. 22
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java
  13. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
  14. 10
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java
  15. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
  17. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
  18. 27
      application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java
  19. 51
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  20. 124
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  21. 4
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  22. 11
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  23. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  24. 21
      application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java
  25. 11
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  26. 42
      application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java
  27. 201
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  28. 159
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java
  29. 21
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  30. 62
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  31. 4
      application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java
  32. 4
      application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java
  33. 51
      application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java
  34. 42
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java
  35. 105
      application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java
  36. 8
      common/cluster-api/src/main/proto/queue.proto
  37. 2
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  38. 5
      common/edge-api/src/main/proto/edge.proto
  39. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java
  40. 10
      dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
  41. 33
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java
  42. 3
      packaging/java/scripts/control/deb/preinst
  43. 1
      packaging/js/scripts/control/deb/preinst
  44. 2
      pom.xml
  45. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java
  46. 48
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts
  47. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts
  48. 1
      ui-ngx/src/app/modules/home/components/widget/widget.component.scss
  49. 25
      ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html
  50. 5
      ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts
  51. 2
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  52. 66
      ui-ngx/src/assets/locale/locale.constant-en_US.json

22
application/src/main/java/org/thingsboard/server/controller/AuditLogController.java

@ -99,7 +99,7 @@ public class AuditLogController extends BaseController {
@RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
checkParameter("CustomerId", strCustomerId); checkParameter("CustomerId", strCustomerId);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink)); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink));
} }
@ -133,7 +133,7 @@ public class AuditLogController extends BaseController {
@RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
checkParameter("UserId", strUserId); checkParameter("UserId", strUserId);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink)); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink));
} }
@ -171,7 +171,7 @@ public class AuditLogController extends BaseController {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink)); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink));
} }
@ -202,7 +202,7 @@ public class AuditLogController extends BaseController {
@RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink)); return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink));
} }
@ -214,4 +214,18 @@ public class AuditLogController extends BaseController {
} }
return result; return result;
} }
private Long getStartTime(Long startTime) {
if (startTime == null) {
return 1L;
}
return startTime;
}
private Long getEndTime(Long endTime) {
if (endTime == null) {
return System.currentTimeMillis();
}
return endTime;
}
} }

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

@ -108,7 +108,7 @@ public class ThingsboardInstallService {
log.info("Migrating ThingsBoard entities data from cassandra to SQL database ..."); log.info("Migrating ThingsBoard entities data from cassandra to SQL database ...");
entitiesMigrateService.migrate(); entitiesMigrateService.migrate();
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.loadSystemWidgets();
} else if ("3.0.1-cassandra".equals(upgradeFromVersion)) { } else if ("3.0.1-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ..."); log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate(); latestMigrateService.migrate();
@ -275,7 +275,7 @@ public class ThingsboardInstallService {
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data..."); log.info("Updating system data...");
dataUpdateService.upgradeRuleNodes(); dataUpdateService.upgradeRuleNodes();
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.loadSystemWidgets();
installScripts.loadSystemLwm2mResources(); installScripts.loadSystemLwm2mResources();
} }
log.info("Upgrade finished successfully!"); log.info("Upgrade finished successfully!");

4
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.edge; package org.thingsboard.server.service.edge;
import freemarker.template.Configuration;
import lombok.Data; import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -87,9 +86,6 @@ public class EdgeContextComponent {
@Autowired @Autowired
private AdminSettingsService adminSettingsService; private AdminSettingsService adminSettingsService;
@Autowired
private Configuration freemarkerConfig;
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -641,7 +641,7 @@ public final class EdgeGrpcSession implements Closeable {
case WIDGETS_BUNDLE: case WIDGETS_BUNDLE:
return ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent); return ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent);
case WIDGET_TYPE: case WIDGET_TYPE:
return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent); return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent, this.edgeVersion);
case ADMIN_SETTINGS: case ADMIN_SETTINGS:
return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent); return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent);
case OTA_PACKAGE: case OTA_PACKAGE:
@ -651,9 +651,9 @@ public final class EdgeGrpcSession implements Closeable {
case QUEUE: case QUEUE:
return ctx.getQueueEdgeProcessor().convertQueueEventToDownlink(edgeEvent); return ctx.getQueueEdgeProcessor().convertQueueEventToDownlink(edgeEvent);
case TENANT: case TENANT:
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent); return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.getEdgeVersion());
case TENANT_PROFILE: case TENANT_PROFILE:
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent); return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.getEdgeVersion());
default: default:
log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent); log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent);
return null; return null;

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -57,7 +57,7 @@ public class EdgeSyncCursor {
fetchers.add(new TenantEdgeEventFetcher(ctx.getTenantService())); fetchers.add(new TenantEdgeEventFetcher(ctx.getTenantService()));
fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService()));
fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig())); fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId()); Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId());
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId())); fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId()));

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java

@ -19,10 +19,12 @@ import com.google.protobuf.ByteString;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils;
@Component @Component
@TbCoreComponent @TbCoreComponent
@ -31,7 +33,9 @@ public class TenantProfileMsgConstructor {
@Autowired @Autowired
private DataDecodingEncodingService dataDecodingEncodingService; private DataDecodingEncodingService dataDecodingEncodingService;
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile) { public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_1) ?
ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData()));
TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder() TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(tenantProfile.getId().getId().getMostSignificantBits()) .setIdMSB(tenantProfile.getId().getId().getMostSignificantBits())
@ -39,7 +43,7 @@ public class TenantProfileMsgConstructor {
.setName(tenantProfile.getName()) .setName(tenantProfile.getName())
.setDefault(tenantProfile.isDefault()) .setDefault(tenantProfile.isDefault())
.setIsolatedRuleChain(tenantProfile.isIsolatedTbRuleEngine()) .setIsolatedRuleChain(tenantProfile.isIsolatedTbRuleEngine())
.setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData()))); .setProfileDataBytes(profileData);
if (tenantProfile.getDescription() != null) { if (tenantProfile.getDescription() != null) {
builder.setDescription(tenantProfile.getDescription()); builder.setDescription(tenantProfile.getDescription());
} }

11
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java

@ -20,9 +20,11 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils;
import java.util.Arrays; import java.util.Arrays;
@ -30,7 +32,7 @@ import java.util.Arrays;
@TbCoreComponent @TbCoreComponent
public class WidgetTypeMsgConstructor { public class WidgetTypeMsgConstructor {
public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails) { public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) {
WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits())
@ -58,7 +60,12 @@ public class WidgetTypeMsgConstructor {
builder.setImage(widgetTypeDetails.getImage()); builder.setImage(widgetTypeDetails.getImage());
} }
if (widgetTypeDetails.getDescription() != null) { if (widgetTypeDetails.getDescription() != null) {
builder.setDescription(widgetTypeDetails.getDescription()); if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_0) &&
widgetTypeDetails.getDescription().length() > 255) {
builder.setDescription(widgetTypeDetails.getDescription().substring(0, 254));
} else {
builder.setDescription(widgetTypeDetails.getDescription());
}
} }
builder.setDeprecated(widgetTypeDetails.isDeprecated()); builder.setDeprecated(widgetTypeDetails.isDeprecated());
if (widgetTypeDetails.getTags() != null) { if (widgetTypeDetails.getTags() != null) {

117
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java

@ -15,146 +15,51 @@
*/ */
package org.thingsboard.server.service.edge.rpc.fetch; package org.thingsboard.server.service.edge.rpc.fetch;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.text.WordUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@AllArgsConstructor @AllArgsConstructor
@Slf4j @Slf4j
public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
private final Configuration freemarkerConfig;
private static final Pattern startPattern = Pattern.compile("<div class=\"content\".*?>");
private static final Pattern endPattern = Pattern.compile("<div class=\"footer\".*?>");
private static final List<String> templatesNames = Arrays.asList(
"account.activated.ftl",
"account.lockout.ftl",
"activation.ftl",
"password.was.reset.ftl",
"reset.password.ftl",
"test.ftl");
// TODO: @voba fix format of next templates
// "state.disabled.ftl",
// "state.enabled.ftl",
// "state.warning.ftl",
@Override @Override
public PageLink getPageLink(int pageSize) { public PageLink getPageLink(int pageSize) {
return null; return null;
} }
@Override public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) throws Exception { List<EdgeEvent> result = fetchAdminSettingsForKeys(tenantId, edge.getId(), List.of("general", "mail", "connectivity", "jwt"));
List<EdgeEvent> result = new ArrayList<>();
AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(systemMailSettings)));
AdminSettings tenantMailSettings = convertToTenantAdminSettings(tenantId, systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue());
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(tenantMailSettings)));
AdminSettings systemMailTemplates = loadMailTemplates(tenantId);
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(systemMailTemplates)));
AdminSettings tenantMailTemplates = convertToTenantAdminSettings(tenantId, systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue());
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(tenantMailTemplates)));
// return PageData object to be in sync with other fetchers // return PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false); return new PageData<>(result, 1, result.size(), false);
} }
private AdminSettings loadMailTemplates(TenantId tenantId) throws Exception { private List<EdgeEvent> fetchAdminSettingsForKeys(TenantId tenantId, EdgeId edgeId, List<String> keys) {
Map<String, Object> mailTemplates = new HashMap<>(); List<EdgeEvent> result = new ArrayList<>();
for (String templatesName : templatesNames) { for (String key : keys) {
Template template = freemarkerConfig.getTemplate(templatesName); AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key);
if (template != null) { if (adminSettings != null) {
String name = validateName(template.getName()); result.add(EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.ADMIN_SETTINGS,
Map<String, String> mailTemplate = getMailTemplateFromFile(template.toString()); EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(adminSettings)));
if (mailTemplate != null) {
mailTemplates.put(name, mailTemplate);
} else {
log.error("[{}] Can't load mail template from file {}", tenantId, template.getName());
}
}
}
AdminSettings adminSettings = new AdminSettings();
adminSettings.setId(new AdminSettingsId(Uuids.timeBased()));
adminSettings.setKey("mailTemplates");
adminSettings.setJsonValue(JacksonUtil.convertValue(mailTemplates, JsonNode.class));
return adminSettings;
}
private Map<String, String> getMailTemplateFromFile(String stringTemplate) {
Map<String, String> mailTemplate = new HashMap<>();
Matcher start = startPattern.matcher(stringTemplate);
Matcher end = endPattern.matcher(stringTemplate);
if (start.find() && end.find()) {
String body = StringUtils.substringBetween(stringTemplate, start.group(), end.group()).replaceAll("\t", "");
String subject = StringUtils.substringBetween(body, "<h2>", "</h2>");
mailTemplate.put("subject", subject);
mailTemplate.put("body", body);
} else {
return null;
}
return mailTemplate;
}
private String validateName(String name) throws Exception {
StringBuilder nameBuilder = new StringBuilder();
name = name.replace(".ftl", "");
String[] nameParts = name.split("\\.");
if (nameParts.length >= 1) {
nameBuilder.append(nameParts[0]);
for (int i = 1; i < nameParts.length; i++) {
String word = WordUtils.capitalize(nameParts[i]);
nameBuilder.append(word);
} }
return nameBuilder.toString();
} else {
throw new Exception("Error during filename validation");
} }
} return result;
private AdminSettings convertToTenantAdminSettings(TenantId tenantId, String key, ObjectNode jsonValue) {
AdminSettings tenantMailSettings = new AdminSettings();
tenantMailSettings.setTenantId(tenantId);
jsonValue.put("useSystemMailSettings", true);
tenantMailSettings.setJsonValue(jsonValue);
tenantMailSettings.setKey(key);
return tenantMailSettings;
} }
} }

10
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -467,9 +467,9 @@ public abstract class BaseEdgeProcessor {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getSourceEdgeIdMSB(), edgeNotificationMsg.getSourceEdgeIdLSB()); EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
if (type.isAllEdgesRelated()) { if (type.isAllEdgesRelated()) {
return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId, sourceEdgeId); return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId, originatorEdgeId);
} else { } else {
JsonNode body = JacksonUtil.toJsonNode(edgeNotificationMsg.getBody()); JsonNode body = JacksonUtil.toJsonNode(edgeNotificationMsg.getBody());
EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()); EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB());
@ -481,19 +481,19 @@ public abstract class BaseEdgeProcessor {
if (edgeId != null) { if (edgeId != null) {
return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body); return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body);
} else { } else {
return processNotificationToRelatedEdges(tenantId, entityId, type, actionType, sourceEdgeId); return processNotificationToRelatedEdges(tenantId, entityId, type, actionType, originatorEdgeId);
} }
case DELETED: case DELETED:
EdgeEventActionType deleted = EdgeEventActionType.DELETED; EdgeEventActionType deleted = EdgeEventActionType.DELETED;
if (edgeId != null) { if (edgeId != null) {
return saveEdgeEvent(tenantId, edgeId, type, deleted, entityId, body); return saveEdgeEvent(tenantId, edgeId, type, deleted, entityId, body);
} else { } else {
return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body, sourceEdgeId)), return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body, originatorEdgeId)),
voids -> null, dbCallbackExecutorService); voids -> null, dbCallbackExecutorService);
} }
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE: case UNASSIGNED_FROM_EDGE:
if (sourceEdgeId == null) { if (originatorEdgeId == null) {
ListenableFuture<Void> future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body); ListenableFuture<Void> future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body);
return Futures.transformAsync(future, unused -> { return Futures.transformAsync(future, unused -> {
if (type.equals(EdgeEventType.RULE_CHAIN)) { if (type.equals(EdgeEventType.RULE_CHAIN)) {

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java

@ -71,7 +71,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor {
public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getSourceEdgeIdMSB(), edgeNotificationMsg.getSourceEdgeIdLSB()); EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
switch (actionType) { switch (actionType) {
case DELETED: case DELETED:
Alarm deletedAlarm = JacksonUtil.fromString(edgeNotificationMsg.getBody(), Alarm.class); Alarm deletedAlarm = JacksonUtil.fromString(edgeNotificationMsg.getBody(), Alarm.class);
@ -79,7 +79,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
List<ListenableFuture<Void>> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(), List<ListenableFuture<Void>> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(),
alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), sourceEdgeId); alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), originatorEdgeId);
return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService);
default: default:
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
@ -92,7 +92,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
List<ListenableFuture<Void>> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(), List<ListenableFuture<Void>> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(),
alarmId, actionType, null, sourceEdgeId); alarmId, actionType, null, originatorEdgeId);
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java

@ -71,10 +71,12 @@ public class RelationEdgeProcessor extends BaseRelationProcessor {
if (relation == null || (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE))) { if (relation == null || (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE))) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
Set<EdgeId> uniqueEdgeIds = new HashSet<>(); Set<EdgeId> uniqueEdgeIds = new HashSet<>();
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo()));
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom()));
uniqueEdgeIds.remove(originatorEdgeId);
if (uniqueEdgeIds.isEmpty()) { if (uniqueEdgeIds.isEmpty()) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }

22
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java

@ -18,17 +18,20 @@ package org.thingsboard.server.service.edge.rpc.processor.resource;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@Slf4j @Slf4j
public abstract class BaseResourceProcessor extends BaseEdgeProcessor { public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
protected void saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { protected boolean saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) {
boolean resourceKeyUpdated = false;
try { try {
boolean created = false; boolean created = false;
TbResource resource = resourceService.findResourceById(tenantId, tbResourceId); TbResource resource = resourceService.findResourceById(tenantId, tbResourceId);
@ -42,9 +45,21 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
resource.setCreatedTime(Uuids.unixTimestamp(tbResourceId.getId())); resource.setCreatedTime(Uuids.unixTimestamp(tbResourceId.getId()));
created = true; created = true;
} }
String resourceKey = resourceUpdateMsg.getResourceKey();
ResourceType resourceType = ResourceType.valueOf(resourceUpdateMsg.getResourceType());
PageDataIterable<TbResource> resourcesIterable = new PageDataIterable<>(
link -> resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, resourceType, link), 1024);
for (TbResource tbResource : resourcesIterable) {
if (tbResource.getResourceKey().equals(resourceUpdateMsg.getResourceKey()) && !tbResourceId.equals(tbResource.getId())) {
resourceKey = StringUtils.randomAlphabetic(15) + "_" + resourceKey;
log.warn("[{}] Resource with resource type {} and key {} already exists. Renaming resource key to {}",
tenantId, resourceType, resourceUpdateMsg.getResourceKey(), resourceKey);
resourceKeyUpdated = true;
}
}
resource.setTitle(resourceUpdateMsg.getTitle()); resource.setTitle(resourceUpdateMsg.getTitle());
resource.setResourceKey(resourceUpdateMsg.getResourceKey()); resource.setResourceKey(resourceKey);
resource.setResourceType(ResourceType.valueOf(resourceUpdateMsg.getResourceType())); resource.setResourceType(resourceType);
resource.setFileName(resourceUpdateMsg.getFileName()); resource.setFileName(resourceUpdateMsg.getFileName());
resource.setData(resourceUpdateMsg.hasData() ? resourceUpdateMsg.getData() : null); resource.setData(resourceUpdateMsg.hasData() ? resourceUpdateMsg.getData() : null);
resource.setEtag(resourceUpdateMsg.hasEtag() ? resourceUpdateMsg.getEtag() : null); resource.setEtag(resourceUpdateMsg.hasEtag() ? resourceUpdateMsg.getEtag() : null);
@ -57,5 +72,6 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
log.error("[{}] Failed to process resource update msg [{}]", tenantId, resourceUpdateMsg, e); log.error("[{}] Failed to process resource update msg [{}]", tenantId, resourceUpdateMsg, e);
throw e; throw e;
} }
return resourceKeyUpdated;
} }
} }

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java

@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -46,14 +48,12 @@ public class ResourceEdgeProcessor extends BaseResourceProcessor {
switch (resourceUpdateMsg.getMsgType()) { switch (resourceUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE:
super.saveOrUpdateTbResource(tenantId, tbResourceId, resourceUpdateMsg); boolean resourceKeyUpdated = super.saveOrUpdateTbResource(tenantId, tbResourceId, resourceUpdateMsg);
break; if (resourceKeyUpdated) {
case ENTITY_DELETED_RPC_MESSAGE: saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.TB_RESOURCE, EdgeEventActionType.UPDATED, tbResourceId, null);
TbResource tbResourceToDelete = resourceService.findResourceById(tenantId, tbResourceId);
if (tbResourceToDelete != null) {
resourceService.deleteResource(tenantId, tbResourceId);
} }
break; break;
case ENTITY_DELETED_RPC_MESSAGE:
case UNRECOGNIZED: case UNRECOGNIZED:
return handleUnsupportedMsgType(resourceUpdateMsg.getMsgType()); return handleUnsupportedMsgType(resourceUpdateMsg.getMsgType());
} }

10
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java

@ -21,6 +21,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
@ -37,6 +38,15 @@ public class TelemetryEdgeProcessor extends BaseTelemetryProcessor {
} }
public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException {
if (edgeEvent.getBody() != null) {
String bodyStr = edgeEvent.getBody().toString();
if (bodyStr.length() > 1000) {
log.debug("[{}][{}][{}] Conversion to a DownlinkMsg telemetry event failed due to a size limit violation. " +
"Current size is {}, but the limit is 1000. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(),
edgeEvent.getEntityId(), bodyStr.length(), StringUtils.truncate(bodyStr, 100));
return null;
}
}
EntityType entityType = EntityType.valueOf(edgeEvent.getType().name()); EntityType entityType = EntityType.valueOf(edgeEvent.getType().name());
EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto( EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto(
edgeEvent.getTenantId(), entityType, edgeEvent.getEntityId(), edgeEvent.getTenantId(), entityType, edgeEvent.getEntityId(),

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@TbCoreComponent @TbCoreComponent
public class TenantEdgeProcessor extends BaseEdgeProcessor { public class TenantEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantId tenantId = new TenantId(edgeEvent.getEntityId()); TenantId tenantId = new TenantId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
@ -44,7 +45,7 @@ public class TenantEdgeProcessor extends BaseEdgeProcessor {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
TenantUpdateMsg tenantUpdateMsg = tenantMsgConstructor.constructTenantUpdateMsg(msgType, tenant); TenantUpdateMsg tenantUpdateMsg = tenantMsgConstructor.constructTenantUpdateMsg(msgType, tenant);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId()); TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
TenantProfileUpdateMsg tenantProfileUpdateMsg = tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile); TenantProfileUpdateMsg tenantProfileUpdateMsg = tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addTenantUpdateMsg(tenantUpdateMsg) .addTenantUpdateMsg(tenantUpdateMsg)

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -33,7 +34,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@TbCoreComponent @TbCoreComponent
public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId()); TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
@ -41,7 +42,7 @@ public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
if (tenantProfile != null) { if (tenantProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
TenantProfileUpdateMsg tenantProfileUpdateMsg = TenantProfileUpdateMsg tenantProfileUpdateMsg =
tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile); tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addTenantProfileUpdateMsg(tenantProfileUpdateMsg) .addTenantProfileUpdateMsg(tenantProfileUpdateMsg)

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -32,7 +33,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@TbCoreComponent @TbCoreComponent
public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId()); WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -42,7 +43,7 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
if (widgetTypeDetails != null) { if (widgetTypeDetails != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
WidgetTypeUpdateMsg widgetTypeUpdateMsg = WidgetTypeUpdateMsg widgetTypeUpdateMsg =
widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails); widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails, edgeVersion);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg)

27
application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java

@ -0,0 +1,27 @@
/**
* 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.edge.rpc.utils;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@Slf4j
public final class EdgeVersionUtils {
public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) {
return currentVersion.ordinal() < requiredVersion.ordinal();
}
}

51
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -63,7 +63,6 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
@ -84,9 +83,6 @@ import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
@ -536,58 +532,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
installScripts.loadDashboards(demoTenant.getId(), null); installScripts.loadDashboards(demoTenant.getId(), null);
} }
@Override
public void deleteSystemWidgetBundle(String bundleAlias) throws Exception {
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias);
if (widgetsBundle != null) {
PageData<WidgetTypeInfo> widgetTypes;
var pageLink = new PageLink(1024);
do {
widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink);
for (var widgetType : widgetTypes.getData()) {
widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId());
}
pageLink.nextPageLink();
} while (widgetTypes.hasNext());
widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId());
}
}
@Override @Override
public void loadSystemWidgets() throws Exception { public void loadSystemWidgets() throws Exception {
installScripts.loadSystemWidgets(); installScripts.loadSystemWidgets();
} }
@Override
public void updateSystemWidgets() throws Exception {
this.deleteSystemWidgetBundle("charts");
this.deleteSystemWidgetBundle("cards");
this.deleteSystemWidgetBundle("maps");
this.deleteSystemWidgetBundle("analogue_gauges");
this.deleteSystemWidgetBundle("digital_gauges");
this.deleteSystemWidgetBundle("gpio_widgets");
this.deleteSystemWidgetBundle("alarm_widgets");
this.deleteSystemWidgetBundle("control_widgets");
this.deleteSystemWidgetBundle("maps_v2");
this.deleteSystemWidgetBundle("gateway_widgets");
this.deleteSystemWidgetBundle("input_widgets");
this.deleteSystemWidgetBundle("date");
this.deleteSystemWidgetBundle("entity_admin_widgets");
this.deleteSystemWidgetBundle("navigation_widgets");
this.deleteSystemWidgetBundle("edge_widgets");
this.deleteSystemWidgetBundle("home_page_widgets");
this.deleteSystemWidgetBundle("entity_widgets");
this.deleteSystemWidgetBundle("html_widgets");
this.deleteSystemWidgetBundle("tables");
this.deleteSystemWidgetBundle("count_widgets");
this.deleteSystemWidgetBundle("status_indicators");
this.deleteSystemWidgetBundle("outdoor_environment");
this.deleteSystemWidgetBundle("indoor_environment");
this.deleteSystemWidgetBundle("air_quality");
this.deleteSystemWidgetBundle("liquid_level_tanks");
installScripts.loadSystemWidgets();
}
private User createUser(Authority authority, private User createUser(Authority authority,
TenantId tenantId, TenantId tenantId,
CustomerId customerId, CustomerId customerId,

124
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -29,9 +29,13 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -48,7 +52,9 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
@ -189,6 +195,38 @@ public class InstallScripts {
} }
public void loadSystemWidgets() throws Exception { public void loadSystemWidgets() throws Exception {
Map<Path, JsonNode> widgetsBundlesMap = new HashMap<>();
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
JsonNode widgetsBundleDescriptorJson;
try {
widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile());
} catch (Exception e) {
log.error("Unable to parse widgets bundle from json: [{}]", path);
throw new RuntimeException("Unable to parse widgets bundle from json", e);
}
if (widgetsBundleDescriptorJson == null || !widgetsBundleDescriptorJson.has("widgetsBundle")) {
log.error("Invalid widgets bundle json: [{}]", path);
throw new RuntimeException("Invalid widgets bundle json: [" + path + "]");
}
widgetsBundlesMap.put(path, widgetsBundleDescriptorJson);
JsonNode bundleAliasNode = widgetsBundleDescriptorJson.get("widgetsBundle").get("alias");
if (bundleAliasNode == null || !bundleAliasNode.isTextual()) {
log.error("Invalid widgets bundle json: [{}]", path);
throw new RuntimeException("Invalid widgets bundle json: [" + path + "]");
}
String bundleAlias = bundleAliasNode.asText();
try {
this.deleteSystemWidgetBundle(bundleAlias);
} catch (Exception e) {
log.error("Failed to delete system widgets bundle: [{}]", bundleAlias);
throw new RuntimeException("Failed to delete system widgets bundle: [" + bundleAlias + "]", e);
}
}
);
}
Path widgetTypesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_TYPES_DIR); Path widgetTypesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_TYPES_DIR);
if (Files.exists(widgetTypesDir)) { if (Files.exists(widgetTypesDir)) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetTypesDir, path -> path.toString().endsWith(JSON_EXT))) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetTypesDir, path -> path.toString().endsWith(JSON_EXT))) {
@ -206,44 +244,56 @@ public class InstallScripts {
); );
} }
} }
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); for (var widgetsBundleDescriptorEntry : widgetsBundlesMap.entrySet()) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { Path path = widgetsBundleDescriptorEntry.getKey();
dirStream.forEach( try {
path -> { JsonNode widgetsBundleDescriptorJson = widgetsBundleDescriptorEntry.getValue();
try { JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
JsonNode widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile()); WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class);
JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class); List<String> widgetTypeFqns = new ArrayList<>();
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); if (widgetsBundleDescriptorJson.has("widgetTypes")) {
List<String> widgetTypeFqns = new ArrayList<>(); JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
if (widgetsBundleDescriptorJson.has("widgetTypes")) { widgetTypesArrayJson.forEach(
JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes"); widgetTypeJson -> {
widgetTypesArrayJson.forEach( try {
widgetTypeJson -> { WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class);
try { var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails);
WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); widgetTypeFqns.add(savedWidgetType.getFqn());
var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails); } catch (Exception e) {
widgetTypeFqns.add(savedWidgetType.getFqn()); log.error("Unable to load widget type from json: [{}]", path.toString());
} catch (Exception e) { throw new RuntimeException("Unable to load widget type from json", e);
log.error("Unable to load widget type from json: [{}]", path.toString()); }
throw new RuntimeException("Unable to load widget type from json", e);
}
}
);
}
if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) {
JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns");
widgetFqnsArrayJson.forEach(fqnJson -> {
widgetTypeFqns.add(fqnJson.asText());
});
} }
widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns); );
} catch (Exception e) { }
log.error("Unable to load widgets bundle from json: [{}]", path.toString()); if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) {
throw new RuntimeException("Unable to load widgets bundle from json", e); JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns");
} widgetFqnsArrayJson.forEach(fqnJson -> {
} widgetTypeFqns.add(fqnJson.asText());
); });
}
widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns);
} catch (Exception e) {
log.error("Unable to load widgets bundle from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widgets bundle from json", e);
}
}
}
private void deleteSystemWidgetBundle(String bundleAlias) {
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias);
if (widgetsBundle != null) {
PageData<WidgetTypeInfo> widgetTypes;
var pageLink = new PageLink(1024);
do {
widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink);
for (var widgetType : widgetTypes.getData()) {
widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId());
}
pageLink.nextPageLink();
} while (widgetTypes.hasNext());
widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId());
} }
} }

4
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -31,12 +31,8 @@ public interface SystemDataLoaderService {
void loadSystemWidgets() throws Exception; void loadSystemWidgets() throws Exception;
void updateSystemWidgets() throws Exception;
void loadDemoData() throws Exception; void loadDemoData() throws Exception;
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
void createQueues(); void createQueues();
void createDefaultNotificationConfigs(); void createDefaultNotificationConfigs();

11
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -27,7 +27,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
@ -67,7 +66,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.audit.AuditLogDao; import org.thingsboard.server.dao.audit.AuditLogDao;
@ -90,6 +88,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.component.RuleNodeClassInfo; import org.thingsboard.server.service.component.RuleNodeClassInfo;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -291,17 +290,11 @@ public class DefaultDataUpdateService implements DataUpdateService {
continue; continue;
} }
var ruleNodeId = ruleNode.getId(); var ruleNodeId = ruleNode.getId();
var oldConfiguration = ruleNode.getConfiguration();
int fromVersion = ruleNode.getConfigurationVersion(); int fromVersion = ruleNode.getConfigurationVersion();
log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, fromVersion, toVersion); ruleNodeId, ruleNodeType, fromVersion, toVersion);
try { try {
var tbVersionedNode = (TbNode) ruleNodeClassInfo.getClazz().getDeclaredConstructor().newInstance(); TbNodeUpgradeUtils.upgradeConfigurationAndVersion(ruleNode, ruleNodeClassInfo);
TbPair<Boolean, JsonNode> upgradeRuleNodeConfigurationResult = tbVersionedNode.upgrade(fromVersion, oldConfiguration);
if (upgradeRuleNodeConfigurationResult.getFirst()) {
ruleNode.setConfiguration(upgradeRuleNodeConfigurationResult.getSecond());
}
ruleNode.setConfigurationVersion(toVersion);
saveFutures.add(jpaExecutorService.submit(() -> { saveFutures.add(jpaExecutorService.submit(() -> {
ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode); ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode);
log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",

8
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -486,7 +486,7 @@ public class DefaultTbClusterService implements TbClusterService {
} }
@Override @Override
public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId sourceEdgeId) { public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId originatorEdgeId) {
if (!edgesEnabled) { if (!edgesEnabled) {
return; return;
} }
@ -519,9 +519,9 @@ public class DefaultTbClusterService implements TbClusterService {
if (body != null) { if (body != null) {
builder.setBody(body); builder.setBody(body);
} }
if (sourceEdgeId != null) { if (originatorEdgeId != null) {
builder.setSourceEdgeIdMSB(sourceEdgeId.getId().getMostSignificantBits()); builder.setOriginatorEdgeIdMSB(originatorEdgeId.getId().getMostSignificantBits());
builder.setSourceEdgeIdLSB(sourceEdgeId.getId().getLeastSignificantBits()); builder.setOriginatorEdgeIdLSB(originatorEdgeId.getId().getLeastSignificantBits());
} }
TransportProtos.EdgeNotificationMsgProto msg = builder.build(); TransportProtos.EdgeNotificationMsgProto msg = builder.build();
log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg); log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);

21
application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java

@ -152,22 +152,29 @@ public class ProtoUtils {
} }
private static TransportProtos.DeviceEdgeUpdateMsgProto toProto(DeviceEdgeUpdateMsg msg) { private static TransportProtos.DeviceEdgeUpdateMsgProto toProto(DeviceEdgeUpdateMsg msg) {
return TransportProtos.DeviceEdgeUpdateMsgProto.newBuilder() TransportProtos.DeviceEdgeUpdateMsgProto.Builder builder = TransportProtos.DeviceEdgeUpdateMsgProto.newBuilder()
.setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits()) .setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits()) .setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceId().getId().getMostSignificantBits()) .setDeviceIdMSB(msg.getDeviceId().getId().getMostSignificantBits())
.setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits()) .setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits());
.setEdgeIdMSB(msg.getEdgeId().getId().getMostSignificantBits())
.setEdgeIdLSB(msg.getEdgeId().getId().getLeastSignificantBits()) if (msg.getEdgeId() != null) {
.build(); builder.setEdgeIdMSB(msg.getEdgeId().getId().getMostSignificantBits())
.setEdgeIdLSB(msg.getEdgeId().getId().getLeastSignificantBits());
}
return builder.build();
} }
private static DeviceEdgeUpdateMsg fromProto(TransportProtos.DeviceEdgeUpdateMsgProto proto) { private static DeviceEdgeUpdateMsg fromProto(TransportProtos.DeviceEdgeUpdateMsgProto proto) {
EdgeId edgeId = null;
if (proto.hasEdgeIdMSB() && proto.hasEdgeIdLSB()) {
edgeId = new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()));
}
return new DeviceEdgeUpdateMsg( return new DeviceEdgeUpdateMsg(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB())), new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB())),
new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB())) edgeId);
);
} }
private static TransportProtos.DeviceNameOrTypeUpdateMsgProto toProto(DeviceNameOrTypeUpdateMsg msg) { private static TransportProtos.DeviceNameOrTypeUpdateMsgProto toProto(DeviceNameOrTypeUpdateMsg msg) {

11
application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java

@ -15,12 +15,10 @@
*/ */
package org.thingsboard.server.service.rule; package org.thingsboard.server.service.rule;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
@ -44,13 +42,13 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult; import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult; import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -402,18 +400,13 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
var ruleNodeClass = componentDiscoveryService.getRuleNodeInfo(ruleNodeType) var ruleNodeClass = componentDiscoveryService.getRuleNodeInfo(ruleNodeType)
.orElseThrow(() -> new RuntimeException("Rule node " + ruleNodeType + " is not supported!")); .orElseThrow(() -> new RuntimeException("Rule node " + ruleNodeType + " is not supported!"));
if (ruleNodeClass.isVersioned()) { if (ruleNodeClass.isVersioned()) {
TbNode tbVersionedNode = (TbNode) ruleNodeClass.getClazz().getDeclaredConstructor().newInstance();
int fromVersion = node.getConfigurationVersion(); int fromVersion = node.getConfigurationVersion();
int toVersion = ruleNodeClass.getCurrentVersion(); int toVersion = ruleNodeClass.getCurrentVersion();
if (fromVersion < toVersion) { if (fromVersion < toVersion) {
log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, fromVersion, toVersion); ruleNodeId, ruleNodeType, fromVersion, toVersion);
try { try {
TbPair<Boolean, JsonNode> upgradeResult = tbVersionedNode.upgrade(fromVersion, node.getConfiguration()); TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, ruleNodeClass);
if (upgradeResult.getFirst()) {
node.setConfiguration(upgradeResult.getSecond());
}
node.setConfigurationVersion(toVersion);
log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}", log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion); ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion);
} catch (TbNodeException e) { } catch (TbNodeException e) {

42
application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java

@ -0,0 +1,42 @@
/**
* 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.utils;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
public class TbNodeUpgradeUtils {
public static void upgradeConfigurationAndVersion(RuleNode node, RuleNodeClassInfo nodeInfo) throws Exception {
JsonNode oldConfiguration = node.getConfiguration();
if (oldConfiguration == null || !oldConfiguration.isObject()) {
var configClass = nodeInfo.getAnnotation().configClazz();
node.setConfiguration(JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration()));
} else {
var tbVersionedNode = (TbNode) nodeInfo.getClazz().getDeclaredConstructor().newInstance();
TbPair<Boolean, JsonNode> upgradeResult = tbVersionedNode.upgrade(node.getConfigurationVersion(), oldConfiguration);
if (upgradeResult.getFirst()) {
node.setConfiguration(upgradeResult.getSecond());
}
}
node.setConfigurationVersion(nodeInfo.getCurrentVersion());
}
}

201
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -24,7 +24,6 @@ import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.AdditionalAnswers; import org.mockito.AdditionalAnswers;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -100,8 +99,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"C3swkUdrR3pezg==\n" + "C3swkUdrR3pezg==\n" +
"-----END PRIVATE KEY-----\n"; "-----END PRIVATE KEY-----\n";
ListeningExecutorService executor;
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
private DeviceProfileId mqttDeviceProfileId; private DeviceProfileId mqttDeviceProfileId;
@ -117,8 +114,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin(); loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode(); ObjectNode config = JacksonUtil.newObjectNode();
@ -208,8 +203,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
executor.shutdownNow();
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId()) doDelete("/api/tenant/" + savedTenant.getId().getId())
@ -248,19 +241,25 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId())); "-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
" -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", " -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId())); credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"", "mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
credentials.getCredentialsId())); credentials.getCredentialsId()));
JsonNode linuxCoapCommands = commands.get(COAP); JsonNode linuxCoapCommands = commands.get(COAP);
assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry " +
"-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); "-t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry" +
" -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); " -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
} }
@Test @Test
@ -286,10 +285,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t %s -u \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); "-t %s -u \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
" -p 1883 -t %s -u \"%s\" -m \"{temperature:25}\"", " -p 1883 -t %s -u \"%s\" -m \"{temperature:25}\"",
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -u \"%s\" -m \"{temperature:25}\"\"", "mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -u \"%s\" -m \"{temperature:25}\"\"",
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
@ -330,10 +329,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); "-t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
" -p 1883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", " -p 1883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"",
DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"\"", "mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"\"",
DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
@ -378,9 +377,9 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
assertThat(commands).hasSize(1); assertThat(commands).hasSize(1);
JsonNode linuxCommands = commands.get(COAP); JsonNode linuxCommands = commands.get(COAP);
assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"",
credentials.getCredentialsId())); credentials.getCredentialsId()));
assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"",
credentials.getCredentialsId())); credentials.getCredentialsId()));
} }
@ -445,4 +444,172 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
public void testDownloadCertWithUnknownProtocol() throws Exception { public void testDownloadCertWithUnknownProtocol() throws Exception {
doGet("/api/device-connectivity/unknownProtocol/certificate/download").andExpect(status().isNotFound()); doGet("/api/device-connectivity/unknownProtocol/certificate/download").andExpect(status().isNotFound());
} }
@Test
public void testFetchPublishTelemetryCommandsForDefaultDeviceIfPortsSetToDefault() throws Exception {
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
ObjectNode http = JacksonUtil.newObjectNode();
http.put("enabled", true);
http.put("host", "");
http.put("port", 80);
config.set("http", http);
ObjectNode https = JacksonUtil.newObjectNode();
https.put("enabled", true);
https.put("host", "");
https.put("port", 443);
config.set("https", https);
ObjectNode mqtt = JacksonUtil.newObjectNode();
mqtt.put("enabled", false);
mqtt.put("host", "");
mqtt.put("port", 1883);
config.set("mqtt", mqtt);
ObjectNode mqtts = JacksonUtil.newObjectNode();
mqtts.put("enabled", false);
mqtts.put("host", "");
mqtts.put("port", 8883);
config.set("mqtts", mqtts);
ObjectNode coap = JacksonUtil.newObjectNode();
coap.put("enabled", false);
coap.put("host", "");
coap.put("port", 5683);
config.set("coap", coap);
ObjectNode coaps = JacksonUtil.newObjectNode();
coaps.put("enabled", false);
coaps.put("host", "");
coaps.put("port", 5684);
config.set("coaps", coaps);
AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
adminSettings.setJsonValue(config);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
login("tenant2@thingsboard.org", "testPassword1");
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(1);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
}
@Test
public void testFetchPublishTelemetryCommandsForDefaultDeviceIfHostIsNotLocalhost() throws Exception {
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
ObjectNode http = JacksonUtil.newObjectNode();
http.put("enabled", true);
http.put("host", "test.domain");
http.put("port", 8080);
config.set("http", http);
ObjectNode https = JacksonUtil.newObjectNode();
https.put("enabled", true);
https.put("host", "test.domain");
https.put("port", 443);
config.set("https", https);
ObjectNode mqtt = JacksonUtil.newObjectNode();
mqtt.put("enabled", true);
mqtt.put("host", "test.domain");
mqtt.put("port", 1883);
config.set("mqtt", mqtt);
ObjectNode mqtts = JacksonUtil.newObjectNode();
mqtts.put("enabled", true);
mqtts.put("host", "test.domain");
mqtts.put("port", 8883);
config.set("mqtts", mqtts);
ObjectNode coap = JacksonUtil.newObjectNode();
coap.put("enabled", true);
coap.put("host", "test.domain");
coap.put("port", 5683);
config.set("coap", coap);
ObjectNode coaps = JacksonUtil.newObjectNode();
coaps.put("enabled", true);
coaps.put("host", "test.domain");
coaps.put("port", 5684);
config.set("coaps", coaps);
AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
adminSettings.setJsonValue(config);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
login("tenant2@thingsboard.org", "testPassword1");
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(3);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://test.domain:8080/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://test.domain/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
JsonNode mqttCommands = commands.get(MQTT);
assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h test.domain -p 1883 -t v1/devices/me/telemetry " +
"-u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download");
assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile ca-root.pem -h test.domain -p 8883 " +
"-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h test.domain" +
" -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h test.domain -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
credentials.getCredentialsId()));
JsonNode linuxCoapCommands = commands.get(COAP);
assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://test.domain:5683/api/v1/%s/telemetry " +
"-t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry" +
" -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " +
"thingsboard/coap-clients coap-client -v 6 -m POST coap://test.domain:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it " +
"thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
}
} }

159
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java

@ -1,159 +0,0 @@
/**
* 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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS;
@ContextConfiguration(classes = {DeviceConnectivityControllerWithDefaultPortTest.Config.class})
@DaoSqlTest
public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractControllerTest {
ListeningExecutorService executor;
private Tenant savedTenant;
static class Config {
@Bean
@Primary
public DeviceDao deviceDao(DeviceDao deviceDao) {
return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao));
}
}
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
ObjectNode http = JacksonUtil.newObjectNode();
http.put("enabled", true);
http.put("host", "");
http.put("port", 80);
config.set("http", http);
ObjectNode https = JacksonUtil.newObjectNode();
https.put("enabled", true);
https.put("host", "");
https.put("port", 443);
config.set("https", https);
ObjectNode mqtt = JacksonUtil.newObjectNode();
mqtt.put("enabled", false);
mqtt.put("host", "");
mqtt.put("port", 1883);
config.set("mqtt", mqtt);
ObjectNode mqtts = JacksonUtil.newObjectNode();
mqtts.put("enabled", false);
mqtts.put("host", "");
mqtts.put("port", 8883);
config.set("mqtts", mqtts);
ObjectNode coap = JacksonUtil.newObjectNode();
coap.put("enabled", false);
coap.put("host", "");
coap.put("port", 5683);
config.set("coap", coap);
ObjectNode coaps = JacksonUtil.newObjectNode();
coaps.put("enabled", false);
coaps.put("host", "");
coaps.put("port", 5684);
config.set("coaps", coaps);
AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
adminSettings.setJsonValue(config);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
executor.shutdownNow();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
}
@Test
public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(1);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
}
}

21
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
@ -843,6 +844,13 @@ public class EdgeControllerTest extends AbstractControllerTest {
@Test @Test
public void testSyncEdge() throws Exception { public void testSyncEdge() throws Exception {
loginSysAdmin();
// get jwt settings from yaml config
JwtSettings settings = doGet("/api/admin/jwtSettings", JwtSettings.class);
// save jwt settings into db
doPost("/api/admin/jwtSettings", settings).andExpect(status().isOk());
loginTenantAdmin();
Asset asset = new Asset(); Asset asset = new Asset();
asset.setName("Test Sync Edge Asset 1"); asset.setName("Test Sync Edge Asset 1");
asset.setType("test"); asset.setType("test");
@ -904,10 +912,10 @@ public class EdgeControllerTest extends AbstractControllerTest {
private void verifyFetchersMsgs(EdgeImitator edgeImitator) { private void verifyFetchersMsgs(EdgeImitator edgeImitator) {
Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main")); Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main"));
Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain")); Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", true)); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "general"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", false)); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", true)); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "connectivity"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", false)); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "jwt"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
@ -953,12 +961,11 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key, boolean isSystem) { private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AdminSettingsUpdateMsg) { if (message instanceof AdminSettingsUpdateMsg) {
AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message; AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message;
if (key.equals(adminSettingsUpdateMsg.getKey()) if (key.equals(adminSettingsUpdateMsg.getKey())) {
&& isSystem == adminSettingsUpdateMsg.getIsSystem()) {
messages.remove(message); messages.remove(message);
return true; return true;
} }

62
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -71,6 +71,7 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.edge.imitator.EdgeImitator;
@ -98,6 +99,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -127,6 +129,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
@Before @Before
public void setupEdgeTest() throws Exception { public void setupEdgeTest() throws Exception {
loginSysAdmin();
// get jwt settings from yaml config
JwtSettings settings = doGet("/api/admin/jwtSettings", JwtSettings.class);
// save jwt settings into db
doPost("/api/admin/jwtSettings", settings).andExpect(status().isOk());
loginTenantAdmin(); loginTenantAdmin();
installation(); installation();
@ -173,7 +182,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
private void installation() { private void installation() throws Exception {
thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME,
createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false)); createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false));
extendDeviceProfileData(thermostatDeviceProfile); extendDeviceProfileData(thermostatDeviceProfile);
@ -189,6 +198,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
+ "/device/" + savedDevice.getUuidId(), Device.class); + "/device/" + savedDevice.getUuidId(), Device.class);
doPost("/api/edge/" + edge.getUuidId() doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Asset.class); + "/asset/" + savedAsset.getUuidId(), Asset.class);
// wait until assign device and asset events are fully processed by edge notification service
TimeUnit.MILLISECONDS.sleep(500);
} }
protected void extendDeviceProfileData(DeviceProfile deviceProfile) { protected void extendDeviceProfileData(DeviceProfile deviceProfile) {
@ -237,25 +249,23 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 1); validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 1);
validateRuleChainMetadataUpdates(ruleChainUUID); validateRuleChainMetadataUpdates(ruleChainUUID);
// 4 messages // 4 messages ('general', 'mail', 'connectivity', 'jwt')
// - 2 from fetcher - system level ('mail', 'mailTemplates')
// - 2 from fetcher - admin level ('mail', 'mailTemplates')
validateMsgsCnt(AdminSettingsUpdateMsg.class, 4); validateMsgsCnt(AdminSettingsUpdateMsg.class, 4);
validateAdminSettings(); validateAdminSettings(4);
// 4 messages // 4 messages
// - 1 from default profile fetcher // - 1 from default profile fetcher
// - 2 from device profile fetcher (default and thermostat) // - 2 from device profile fetcher (default and thermostat)
// - 1 from device fetcher // - 1 from device fetcher
validateMsgsCnt(DeviceProfileUpdateMsg.class, 4); validateMsgsCnt(DeviceProfileUpdateMsg.class, 4);
validateDeviceProfiles(); validateDeviceProfiles(4);
// 3 messages // 3 messages
// - 1 from default profile fetcher // - 1 from default profile fetcher
// - 1 message from asset profile fetcher // - 1 message from asset profile fetcher
// - 1 message from asset fetcher // - 1 message from asset fetcher
validateMsgsCnt(AssetProfileUpdateMsg.class, 3); validateMsgsCnt(AssetProfileUpdateMsg.class, 3);
validateAssetProfiles(); validateAssetProfiles(3);
// 1 from device fetcher // 1 from device fetcher
validateMsgsCnt(DeviceUpdateMsg.class, 1); validateMsgsCnt(DeviceUpdateMsg.class, 1);
@ -326,13 +336,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(tenantProfileUpdateMsg); testAutoGeneratedCodeByProtobuf(tenantProfileUpdateMsg);
} }
private void validateDeviceProfiles() throws Exception { private void validateDeviceProfiles(int expectedMsgCnt) throws Exception {
List<DeviceProfileUpdateMsg> deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); List<DeviceProfileUpdateMsg> deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class);
// default msg default device profile from fetcher // default msg default device profile from fetcher
// default msg device profile from fetcher // default msg device profile from fetcher
// thermostat msg from device profile fetcher // thermostat msg from device profile fetcher
// thermostat msg from device fetcher // thermostat msg from device fetcher
Assert.assertEquals(4, deviceProfileUpdateMsgList.size()); Assert.assertEquals(expectedMsgCnt, deviceProfileUpdateMsgList.size());
Optional<DeviceProfileUpdateMsg> thermostatProfileUpdateMsgOpt = Optional<DeviceProfileUpdateMsg> thermostatProfileUpdateMsgOpt =
deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny();
Assert.assertTrue(thermostatProfileUpdateMsgOpt.isPresent()); Assert.assertTrue(thermostatProfileUpdateMsgOpt.isPresent());
@ -413,20 +423,28 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID); Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID);
} }
private void validateAdminSettings() { private void validateAdminSettings(int expectedMsgCnt) {
List<AdminSettingsUpdateMsg> adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class); List<AdminSettingsUpdateMsg> adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class);
Assert.assertEquals(4, adminSettingsUpdateMsgs.size()); Assert.assertEquals(expectedMsgCnt, adminSettingsUpdateMsgs.size());
for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) { for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) {
if (adminSettingsUpdateMsg.getKey().equals("general")) {
validateGeneralAdminSettings(adminSettingsUpdateMsg);
}
if (adminSettingsUpdateMsg.getKey().equals("mail")) { if (adminSettingsUpdateMsg.getKey().equals("mail")) {
validateMailAdminSettings(adminSettingsUpdateMsg); validateMailAdminSettings(adminSettingsUpdateMsg);
} }
if (adminSettingsUpdateMsg.getKey().equals("mailTemplates")) { if (adminSettingsUpdateMsg.getKey().equals("connectivity")) {
validateMailTemplatesAdminSettings(adminSettingsUpdateMsg); validateConnectivityAdminSettings(adminSettingsUpdateMsg);
} }
} }
} }
private void validateGeneralAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("baseUrl"));
}
private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("mailFrom")); Assert.assertNotNull(jsonNode.get("mailFrom"));
@ -436,19 +454,19 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Assert.assertNotNull(jsonNode.get("timeout")); Assert.assertNotNull(jsonNode.get("timeout"));
} }
private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { private void validateConnectivityAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("accountActivated")); Assert.assertNotNull(jsonNode.get("http"));
Assert.assertNotNull(jsonNode.get("accountLockout")); Assert.assertNotNull(jsonNode.get("https"));
Assert.assertNotNull(jsonNode.get("activation")); Assert.assertNotNull(jsonNode.get("mqtt"));
Assert.assertNotNull(jsonNode.get("passwordWasReset")); Assert.assertNotNull(jsonNode.get("mqtts"));
Assert.assertNotNull(jsonNode.get("resetPassword")); Assert.assertNotNull(jsonNode.get("coap"));
Assert.assertNotNull(jsonNode.get("test")); Assert.assertNotNull(jsonNode.get("coaps"));
} }
private void validateAssetProfiles() throws Exception { private void validateAssetProfiles(int expectedMsgCnt) throws Exception {
List<AssetProfileUpdateMsg> assetProfileUpdateMsgs = edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class); List<AssetProfileUpdateMsg> assetProfileUpdateMsgs = edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class);
Assert.assertEquals(3, assetProfileUpdateMsgs.size()); Assert.assertEquals(expectedMsgCnt, assetProfileUpdateMsgs.size());
AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileUpdateMsgs.get(0); AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileUpdateMsgs.get(0);
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType());
UUID assetProfileUUID = new UUID(assetProfileUpdateMsg.getIdMSB(), assetProfileUpdateMsg.getIdLSB()); UUID assetProfileUUID = new UUID(assetProfileUpdateMsg.getIdMSB(), assetProfileUpdateMsg.getIdLSB());

4
application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java

@ -173,7 +173,7 @@ public class AssetEdgeTest extends AbstractEdgeTest {
assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetUpdateMsgBuilder.setName("Asset Edge 2"); assetUpdateMsgBuilder.setName("Asset Edge 2");
assetUpdateMsgBuilder.setType("test"); assetUpdateMsgBuilder.setType("default");
assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder); testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder);
uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build()); uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build());
@ -205,7 +205,7 @@ public class AssetEdgeTest extends AbstractEdgeTest {
assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetUpdateMsgBuilder.setName(assetOnCloudName); assetUpdateMsgBuilder.setName(assetOnCloudName);
assetUpdateMsgBuilder.setType("test"); assetUpdateMsgBuilder.setType("default");
assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder); testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder);
uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build()); uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build());

4
application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java

@ -492,7 +492,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceUpdateMsgBuilder.setName(deviceOnCloudName); deviceUpdateMsgBuilder.setName(deviceOnCloudName);
deviceUpdateMsgBuilder.setType("test"); deviceUpdateMsgBuilder.setType("default");
deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder); testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder);
uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());
@ -541,7 +541,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceUpdateMsgBuilder.setName("Edge Device 2"); deviceUpdateMsgBuilder.setName("Edge Device 2");
deviceUpdateMsgBuilder.setType("test"); deviceUpdateMsgBuilder.setType("default");
deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());

51
application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java

@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -120,4 +121,54 @@ public class ResourceEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(tbResource); Assert.assertNotNull(tbResource);
Assert.assertEquals("Edge Test Resource", tbResource.getName()); Assert.assertEquals("Edge Test Resource", tbResource.getName());
} }
@Test
public void testResourceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle("Edge Test Resource");
resource.setFileName(FILE_NAME);
resource.setData(TEST_DATA);
edgeImitator.expectMessageAmount(1);
TbResource savedResource = doPost("/api/resource", resource, TbResource.class);
Assert.assertTrue(edgeImitator.waitForMessages());
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder();
resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
resourceUpdateMsgBuilder.setTitle("Edge Test Resource");
resourceUpdateMsgBuilder.setResourceType(ResourceType.JKS.name());
resourceUpdateMsgBuilder.setResourceKey(FILE_NAME);
resourceUpdateMsgBuilder.setFileName(FILE_NAME);
resourceUpdateMsgBuilder.setData(TEST_DATA);
resourceUpdateMsgBuilder.setIsSystem(false);
resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder);
uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<ResourceUpdateMsg> resourceUpdateMsgOpt = edgeImitator.findMessageByType(ResourceUpdateMsg.class);
Assert.assertTrue(resourceUpdateMsgOpt.isPresent());
ResourceUpdateMsg latestResourceUpdateMsg = resourceUpdateMsgOpt.get();
Assert.assertNotEquals(FILE_NAME, latestResourceUpdateMsg.getResourceKey());
Assert.assertNotEquals(savedResource.getUuidId(), uuid);
TbResource tbResource = doGet("/api/resource/" + uuid, TbResource.class);
Assert.assertNotNull(tbResource);
Assert.assertNotEquals(FILE_NAME, tbResource.getName());
}
} }

42
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java

@ -0,0 +1,42 @@
/**
* 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.edge.rpc.processor.telemetry;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class TelemetryEdgeProcessorTest {
@Test
public void testConvert_maxSizeLimit() throws Exception {
EdgeEvent edgeEvent = new EdgeEvent();
ObjectNode body = JacksonUtil.newObjectNode();
body.put("value", StringUtils.randomAlphanumeric(10000));
edgeEvent.setBody(body);
DownlinkMsg downlinkMsg = new TelemetryEdgeProcessor().convertTelemetryEventToDownlink(edgeEvent);
Assert.assertNull(downlinkMsg);
}
}

105
application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java

@ -0,0 +1,105 @@
/**
* 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.utils;
import com.fasterxml.jackson.databind.node.NullNode;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TbNodeUpgradeUtilsTest {
@Test
public void testUpgradeRuleNodeConfigurationWithNullConfig() throws Exception {
// GIVEN
var node = new RuleNode();
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetAttributesNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
@Test
public void testUpgradeRuleNodeConfigurationWithNullNodeConfig() throws Exception {
// GIVEN
var node = new RuleNode();
node.setConfiguration(NullNode.instance);
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetAttributesNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
@Test
public void testUpgradeRuleNodeConfigurationWithNonNullConfig() throws Exception {
// GIVEN
var node = new RuleNode();
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetAttributesNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
String versionZeroDefaultConfigStr = "{\"fetchToData\":false," +
"\"clientAttributeNames\":[]," +
"\"sharedAttributeNames\":[]," +
"\"serverAttributeNames\":[]," +
"\"latestTsKeyNames\":[]," +
"\"tellFailureIfAbsent\":true," +
"\"getLatestValueWithTs\":false}";
node.setConfiguration(JacksonUtil.toJsonNode(versionZeroDefaultConfigStr));
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
}

8
common/cluster-api/src/main/proto/queue.proto

@ -910,8 +910,8 @@ message EdgeNotificationMsgProto {
string body = 10; string body = 10;
PostTelemetryMsg postTelemetryMsg = 11; PostTelemetryMsg postTelemetryMsg = 11;
PostAttributeMsg postAttributesMsg = 12; PostAttributeMsg postAttributesMsg = 12;
int64 sourceEdgeIdMSB = 13; int64 originatorEdgeIdMSB = 13;
int64 sourceEdgeIdLSB = 14; int64 originatorEdgeIdLSB = 14;
} }
message EdgeEventUpdateMsgProto { message EdgeEventUpdateMsgProto {
@ -945,8 +945,8 @@ message DeviceEdgeUpdateMsgProto {
int64 tenantIdLSB = 2; int64 tenantIdLSB = 2;
int64 deviceIdMSB = 3; int64 deviceIdMSB = 3;
int64 deviceIdLSB = 4; int64 deviceIdLSB = 4;
int64 edgeIdMSB = 5; optional int64 edgeIdMSB = 5;
int64 edgeIdLSB = 6; optional int64 edgeIdLSB = 6;
} }
message DeviceNameOrTypeUpdateMsgProto { message DeviceNameOrTypeUpdateMsgProto {

2
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setConnectRequestMsg(ConnectRequestMsg.newBuilder()
.setEdgeRoutingKey(edgeKey) .setEdgeRoutingKey(edgeKey)
.setEdgeSecret(edgeSecret) .setEdgeSecret(edgeSecret)
.setEdgeVersion(EdgeVersion.V_3_6_0) .setEdgeVersion(EdgeVersion.V_3_6_1)
.setMaxInboundMessageSize(maxInboundMessageSize) .setMaxInboundMessageSize(maxInboundMessageSize)
.build()) .build())
.build()); .build());

5
common/edge-api/src/main/proto/edge.proto

@ -35,6 +35,7 @@ enum EdgeVersion {
V_3_3_3 = 1; V_3_3_3 = 1;
V_3_4_0 = 2; V_3_4_0 = 2;
V_3_6_0 = 3; V_3_6_0 = 3;
V_3_6_1 = 4;
} }
/** /**
@ -361,8 +362,8 @@ message WidgetsBundleUpdateMsg {
optional bytes image = 6; optional bytes image = 6;
bool isSystem = 7; bool isSystem = 7;
optional string description = 8; optional string description = 8;
optional int32 order = 9; optional string widgets = 9;
optional string widgets = 10; optional int32 order = 10;
} }
message WidgetTypeUpdateMsg { message WidgetTypeUpdateMsg {

8
dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java

@ -91,10 +91,10 @@ public interface EdgeRepository extends JpaRepository<EdgeEntity, UUID> {
"LEFT JOIN CustomerEntity c on c.id = a.customerId " + "LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"WHERE a.tenantId = :tenantId " + "WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId " + "AND a.customerId = :customerId " +
"AND (:searchText IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)") "AND (:textSearch IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)")
Page<EdgeInfoEntity> findEdgeInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, Page<EdgeInfoEntity> findEdgeInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.EdgeInfoEntity(a, c.title, c.additionalInfo) " + @Query("SELECT new org.thingsboard.server.dao.model.sql.EdgeInfoEntity(a, c.title, c.additionalInfo) " +
@ -113,11 +113,11 @@ public interface EdgeRepository extends JpaRepository<EdgeEntity, UUID> {
@Query("SELECT ee FROM EdgeEntity ee, RelationEntity re WHERE ee.tenantId = :tenantId " + @Query("SELECT ee FROM EdgeEntity ee, RelationEntity re WHERE ee.tenantId = :tenantId " +
"AND ee.id = re.fromId AND re.fromType = 'EDGE' AND re.relationTypeGroup = 'EDGE' " + "AND ee.id = re.fromId AND re.fromType = 'EDGE' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.toId = :entityId AND re.toType = :entityType " + "AND re.relationType = 'Contains' AND re.toId = :entityId AND re.toType = :entityType " +
"AND (:searchText IS NULL OR ilike(ee.name, CONCAT('%', :searchText, '%')) = true)") "AND (:textSearch IS NULL OR ilike(ee.name, CONCAT('%', :textSearch, '%')) = true)")
Page<EdgeEntity> findByTenantIdAndEntityId(@Param("tenantId") UUID tenantId, Page<EdgeEntity> findByTenantIdAndEntityId(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId, @Param("entityId") UUID entityId,
@Param("entityType") String entityType, @Param("entityType") String entityType,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT ee FROM EdgeEntity ee, TenantEntity te WHERE ee.tenantId = te.id AND te.tenantProfileId = :tenantProfileId ") @Query("SELECT ee FROM EdgeEntity ee, TenantEntity te WHERE ee.tenantId = te.id AND te.tenantProfileId = :tenantProfileId ")

10
dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java

@ -20,6 +20,9 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import java.util.Arrays;
import java.util.List;
public class DeviceConnectivityUtil { public class DeviceConnectivityUtil {
public static final String HTTP = "http"; public static final String HTTP = "http";
@ -38,6 +41,7 @@ public class DeviceConnectivityUtil {
public static final String GATEWAY_DOCKER_RUN = "docker run -it "; public static final String GATEWAY_DOCKER_RUN = "docker run -it ";
public static final String MQTT_IMAGE = "thingsboard/mosquitto-clients "; public static final String MQTT_IMAGE = "thingsboard/mosquitto-clients ";
public static final String COAP_IMAGE = "thingsboard/coap-clients "; public static final String COAP_IMAGE = "thingsboard/coap-clients ";
public static final List<String> LOCAL_HOSTS = Arrays.asList("localhost", "127.0.0.1");
public static String getHttpPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { public static String getHttpPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) {
return String.format("curl -v -X POST %s://%s%s/api/v1/%s/telemetry --header Content-Type:application/json --data " + JSON_EXAMPLE_PAYLOAD, return String.format("curl -v -X POST %s://%s%s/api/v1/%s/telemetry --header Content-Type:application/json --data " + JSON_EXAMPLE_PAYLOAD,
@ -135,7 +139,7 @@ public class DeviceConnectivityUtil {
} }
StringBuilder mqttDockerCommand = new StringBuilder(); StringBuilder mqttDockerCommand = new StringBuilder();
mqttDockerCommand.append(DOCKER_RUN).append(MQTT_IMAGE); mqttDockerCommand.append(DOCKER_RUN).append(LOCAL_HOSTS.contains(host) ? "--network=host ":"").append(MQTT_IMAGE);
if (MQTTS.equals(protocol)) { if (MQTTS.equals(protocol)) {
mqttDockerCommand.append("/bin/sh -c \"") mqttDockerCommand.append("/bin/sh -c \"")
@ -158,7 +162,7 @@ public class DeviceConnectivityUtil {
switch (deviceCredentials.getCredentialsType()) { switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN: case ACCESS_TOKEN:
String client = COAPS.equals(protocol) ? "coap-client-openssl" : "coap-client"; String client = COAPS.equals(protocol) ? "coap-client-openssl" : "coap-client";
return String.format("%s -m POST %s://%s%s/api/v1/%s/telemetry -t json -e %s", return String.format("%s -v 6 -m POST %s://%s%s/api/v1/%s/telemetry -t json -e %s",
client, protocol, host, port, deviceCredentials.getCredentialsId(), JSON_EXAMPLE_PAYLOAD); client, protocol, host, port, deviceCredentials.getCredentialsId(), JSON_EXAMPLE_PAYLOAD);
default: default:
return null; return null;
@ -167,6 +171,6 @@ public class DeviceConnectivityUtil {
public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) {
String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials); String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials);
return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN, COAP_IMAGE, coapCommand) : null; return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN + (LOCAL_HOSTS.contains(host) ? "--network=host ":""), COAP_IMAGE, coapCommand) : null;
} }
} }

33
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java

@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -74,8 +75,8 @@ public class ThingsBoardDbInstaller {
new File("./../../docker/docker-compose.yml"), new File("./../../docker/docker-compose.yml"),
new File("./../../docker/docker-compose.volumes.yml"), new File("./../../docker/docker-compose.volumes.yml"),
IS_HYBRID_MODE IS_HYBRID_MODE
? new File("./../../docker/docker-compose.hybrid.yml") ? new File("./../../docker/docker-compose.hybrid.yml")
: new File("./../../docker/docker-compose.postgres.yml"), : new File("./../../docker/docker-compose.postgres.yml"),
new File("./../../docker/docker-compose.postgres.volumes.yml"), new File("./../../docker/docker-compose.postgres.volumes.yml"),
resolveRedisComposeFile(), resolveRedisComposeFile(),
resolveRedisComposeVolumesFile() resolveRedisComposeVolumesFile()
@ -156,7 +157,7 @@ public class ThingsBoardDbInstaller {
return env; return env;
} }
public void createVolumes() { public void createVolumes() {
try { try {
dockerCompose.withCommand("volume create " + postgresDataVolume); dockerCompose.withCommand("volume create " + postgresDataVolume);
@ -200,7 +201,7 @@ public class ThingsBoardDbInstaller {
} }
} else if (IS_REDIS_SENTINEL) { } else if (IS_REDIS_SENTINEL) {
additionalServices.append(" redis-master"); additionalServices.append(" redis-master");
dockerCompose.withCommand("volume create " + redisSentinelDataVolume +"-" + "master"); dockerCompose.withCommand("volume create " + redisSentinelDataVolume + "-" + "master");
dockerCompose.invokeDocker(); dockerCompose.invokeDocker();
additionalServices.append(" redis-slave"); additionalServices.append(" redis-slave");
@ -226,7 +227,8 @@ public class ThingsBoardDbInstaller {
try { try {
dockerCompose.withCommand("down -v"); dockerCompose.withCommand("down -v");
dockerCompose.invokeCompose(); dockerCompose.invokeCompose();
} catch (Exception ignored) {} } catch (Exception ignored) {
}
} }
} }
@ -239,15 +241,24 @@ public class ThingsBoardDbInstaller {
copyLogs(tbSnmpTransportLogVolume, "./target/tb-snmp-transport-logs/"); copyLogs(tbSnmpTransportLogVolume, "./target/tb-snmp-transport-logs/");
copyLogs(tbVcExecutorLogVolume, "./target/tb-vc-executor-logs/"); copyLogs(tbVcExecutorLogVolume, "./target/tb-vc-executor-logs/");
dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume + StringJoiner rmVolumesCommand = new StringJoiner(" ")
" " + tbCoapTransportLogVolume + " " + tbLwm2mTransportLogVolume + " " + tbHttpTransportLogVolume + .add("volume rm -f")
" " + tbMqttTransportLogVolume + " " + tbSnmpTransportLogVolume + " " + tbVcExecutorLogVolume + resolveRedisComposeVolumeLog()); .add(postgresDataVolume)
dockerCompose.invokeDocker(); .add(tbLogVolume)
.add(tbCoapTransportLogVolume)
.add(tbLwm2mTransportLogVolume)
.add(tbHttpTransportLogVolume)
.add(tbMqttTransportLogVolume)
.add(tbSnmpTransportLogVolume)
.add(tbVcExecutorLogVolume)
.add(resolveRedisComposeVolumeLog());
dockerCompose.withCommand(rmVolumesCommand.toString());
} }
private String resolveRedisComposeVolumeLog() { private String resolveRedisComposeVolumeLog() {
if (IS_REDIS_CLUSTER) { if (IS_REDIS_CLUSTER) {
return IntStream.range(0, 6).mapToObj(i -> " " + redisClusterDataVolume + "-" + i).collect(Collectors.joining()); return IntStream.range(0, 6).mapToObj(i -> redisClusterDataVolume + "-" + i).collect(Collectors.joining());
} }
if (IS_REDIS_SENTINEL) { if (IS_REDIS_SENTINEL) {
return redisSentinelDataVolume + "-" + "master " + " " + return redisSentinelDataVolume + "-" + "master " + " " +
@ -266,7 +277,7 @@ public class ThingsBoardDbInstaller {
dockerCompose.withCommand("run -d --rm --name " + logsContainerName + " -v " + volumeName + ":/root alpine tail -f /dev/null"); dockerCompose.withCommand("run -d --rm --name " + logsContainerName + " -v " + volumeName + ":/root alpine tail -f /dev/null");
dockerCompose.invokeDocker(); dockerCompose.invokeDocker();
dockerCompose.withCommand("cp " + logsContainerName + ":/root/. "+tbLogsDir.getAbsolutePath()); dockerCompose.withCommand("cp " + logsContainerName + ":/root/. " + tbLogsDir.getAbsolutePath());
dockerCompose.invokeDocker(); dockerCompose.invokeDocker();
dockerCompose.withCommand("rm -f " + logsContainerName); dockerCompose.withCommand("rm -f " + logsContainerName);

3
packaging/java/scripts/control/deb/preinst

@ -10,7 +10,6 @@ if ! getent passwd ${pkg.user} >/dev/null; then
adduser --quiet \ adduser --quiet \
--system \ --system \
--ingroup ${pkg.user} \ --ingroup ${pkg.user} \
--quiet \
--disabled-login \ --disabled-login \
--disabled-password \ --disabled-password \
--home ${pkg.installFolder} \ --home ${pkg.installFolder} \
@ -19,4 +18,4 @@ if ! getent passwd ${pkg.user} >/dev/null; then
${pkg.user} ${pkg.user}
fi fi
exit 0 exit 0

1
packaging/js/scripts/control/deb/preinst

@ -8,7 +8,6 @@ if ! getent passwd ${pkg.user} >/dev/null; then
adduser --quiet \ adduser --quiet \
--system \ --system \
--ingroup ${pkg.user} \ --ingroup ${pkg.user} \
--quiet \
--disabled-login \ --disabled-login \
--disabled-password \ --disabled-password \
--home ${pkg.installFolder} \ --home ${pkg.installFolder} \

2
pom.xml

@ -78,7 +78,7 @@
<zookeeper.version>3.8.1</zookeeper.version> <zookeeper.version>3.8.1</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version> <protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version> <grpc.version>1.42.1</grpc.version>
<tbel.version>1.1.3</tbel.version> <tbel.version>1.1.4</tbel.version>
<lombok.version>1.18.18</lombok.version> <lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version> <paho.client.version>1.2.4</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version> <paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.rule.engine.api.util; package org.thingsboard.rule.engine.api.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
@ -85,6 +84,7 @@ public class TbNodeUtils {
} }
} }
@Deprecated(since = "3.6.1", forRemoval = true)
public static List<String> processPatterns(List<String> patterns, TbMsgMetaData metaData) { public static List<String> processPatterns(List<String> patterns, TbMsgMetaData metaData) {
if (!CollectionUtils.isEmpty(patterns)) { if (!CollectionUtils.isEmpty(patterns)) {
return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList()); return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList());

48
ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts

@ -160,26 +160,30 @@ export class LiquidLevelWidgetComponent implements OnInit {
} }
private getData(): Observable<{ svg: string; volume: number; units: string }> { private getData(): Observable<{ svg: string; volume: number; units: string }> {
const entityId: EntityId = { if (this.ctx.datasources?.length) {
entityType: this.ctx.datasources[0].entityType, const entityId: EntityId = {
id: this.ctx.datasources[0].entityId entityType: this.ctx.datasources[0].entityType,
}; id: this.ctx.datasources[0].entityId
};
return this.getShape(entityId).pipe(
switchMap(shape => {
this.shape = shape;
this.svgParams = svgMapping.get(shape);
if (this.svgParams) {
return forkJoin([
this.resourcesService.loadJsonResource<string>(this.svgParams.svg),
this.getTankersParams(entityId)
]).pipe(
map(params => ({svg: params[0], ...params[1]}))
);
}
return of(null);
})
);
}
return this.getShape(entityId).pipe( return of(null);
switchMap(shape => {
this.shape = shape;
this.svgParams = svgMapping.get(shape);
if (this.svgParams) {
return forkJoin([
this.resourcesService.loadJsonResource<string>(this.svgParams.svg),
this.getTankersParams(entityId)
]).pipe(
map(params => ({svg: params[0], ...params[1]}))
);
}
return of(null);
})
);
} }
public onInit() { public onInit() {
@ -541,13 +545,13 @@ export class LiquidLevelWidgetComponent implements OnInit {
</div>`; </div>`;
} }
private convertInputData(value: number): number { private convertInputData(value: any): number {
if (this.settings.datasourceUnits !== CapacityUnits.percent) { if (this.settings.datasourceUnits !== CapacityUnits.percent) {
return (convertLiters(value, this.settings.datasourceUnits, ConversionType.to) / return (convertLiters(Number(value), this.settings.datasourceUnits, ConversionType.to) /
convertLiters(this.volume, this.settings.volumeUnits, ConversionType.to)) * 100; convertLiters(this.volume, this.settings.volumeUnits, ConversionType.to)) * 100;
} }
return value; return Number(value);
} }
private convertOutputData(value: number): number { private convertOutputData(value: number): number {

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts

@ -65,7 +65,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
ngOnInit(): void { ngOnInit(): void {
this.colorSettingsFormGroup = this.fb.group( this.colorSettingsFormGroup = this.fb.group(
{ {
type: [this.colorSettings?.type, []], type: [this.colorSettings?.type || ColorType.constant, []],
color: [this.colorSettings?.color, []], color: [this.colorSettings?.color, []],
rangeList: [this.colorSettings?.rangeList, []], rangeList: [this.colorSettings?.rangeList, []],
colorFunction: [this.colorSettings?.colorFunction, []] colorFunction: [this.colorSettings?.colorFunction, []]

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

@ -30,6 +30,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: rgba(255, 255, 255, .75); background: rgba(255, 255, 255, .75);
z-index: 2;
span { span {
color: #000; color: #000;

25
ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html

@ -226,31 +226,6 @@
<ng-template [ngSwitchCase]="DeviceTransportType.COAP"> <ng-template [ngSwitchCase]="DeviceTransportType.COAP">
<div class="tb-no-data-text" translate>device.connectivity.use-following-instructions</div> <div class="tb-no-data-text" translate>device.connectivity.use-following-instructions</div>
<mat-tab-group [(selectedIndex)]="coapTabIndex"> <mat-tab-group [(selectedIndex)]="coapTabIndex">
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tabs-icon" svgIcon="macos"></mat-icon>
MacOS
</ng-template>
<ng-template matTabContent>
<div class="tb-form-panel no-padding no-border tb-tab-body">
<div class="tb-form-panel stroked" *ngIf="commands.coap.coaps !== 'Check documentation'">
<div class="tb-form-panel-title" translate>device.connectivity.install-necessary-client-tools</div>
<div class="tb-install-instruction-text">
<ng-container *ngTemplateOutlet="seeDocumentation; context:
{doc: {text: 'device.connectivity.install-coap-client',
href: 'https://thingsboard.io/docs/user-guide/ssl/coap-access-token/'}}">
</ng-container>
</div>
</div>
<ng-container
*ngTemplateOutlet="executeCommand; context: {cmd: {
noSecLabel: 'CoAP', noSec: commands.coap.coap,
secLabel: 'CoAPs', sec: commands.coap.coaps,
doc: {text: 'device.connectivity.coaps-x509-command', href: 'https://thingsboard.io/docs/user-guide/ssl/coap-x509-certificates/'}}}">
</ng-container>
</div>
</ng-template>
</mat-tab>
<mat-tab> <mat-tab>
<ng-template mat-tab-label> <ng-template mat-tab-label>
<mat-icon class="tabs-icon" svgIcon="linux"></mat-icon> <mat-icon class="tabs-icon" svgIcon="linux"></mat-icon>

5
ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts

@ -199,20 +199,21 @@ export class DeviceCheckConnectivityDialogComponent extends
case 'android': case 'android':
this.httpTabIndex = 2; this.httpTabIndex = 2;
this.mqttTabIndex = 2; this.mqttTabIndex = 2;
this.coapTabIndex = 1;
break; break;
case 'macos': case 'macos':
case 'ios': case 'ios':
this.httpTabIndex = 1; this.httpTabIndex = 1;
this.mqttTabIndex = 1; this.mqttTabIndex = 1;
this.coapTabIndex = 1;
break; break;
case 'windows': case 'windows':
this.httpTabIndex = 0; this.httpTabIndex = 0;
this.mqttTabIndex = 0; this.mqttTabIndex = 0;
this.coapTabIndex = 1;
break; break;
default: default:
this.mqttTabIndex = this.commands.mqtt?.docker ? 3 : 0; this.mqttTabIndex = this.commands.mqtt?.docker ? 3 : 0;
this.coapTabIndex = this.commands.coap?.docker ? 2 : 1; this.coapTabIndex = this.commands.coap?.docker ? 1 : 0;
} }
} }

2
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -161,6 +161,8 @@ export abstract class ColorProcessor {
return new RangeColorProcessor(settings); return new RangeColorProcessor(settings);
case ColorType.function: case ColorType.function:
return new FunctionColorProcessor(settings); return new FunctionColorProcessor(settings);
default:
return new ConstantColorProcessor(settings);
} }
} }

66
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -97,7 +97,7 @@
"general-settings": "General Settings", "general-settings": "General Settings",
"home-settings": "Home Settings", "home-settings": "Home Settings",
"home": "Home", "home": "Home",
"outgoing-mail": "Mail Server", "outgoing-mail": "Mail server",
"outgoing-mail-settings": "Outgoing Mail Server Settings", "outgoing-mail-settings": "Outgoing Mail Server Settings",
"system-settings": "System Settings", "system-settings": "System Settings",
"test-mail-sent": "Test mail was successfully sent!", "test-mail-sent": "Test mail was successfully sent!",
@ -621,7 +621,7 @@
"filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"filter-type-entity-view-search-query": "Entity view search query", "filter-type-entity-view-search-query": "Entity view search query",
"filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"filter-type-apiUsageState": "Api Usage State", "filter-type-apiUsageState": "API Usage State",
"filter-type-edge-search-query": "Edge search query", "filter-type-edge-search-query": "Edge search query",
"filter-type-edge-search-query-description": "Edges with types {{edgeTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-edge-search-query-description": "Edges with types {{edgeTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"entity-filter": "Entity filter", "entity-filter": "Entity filter",
@ -647,7 +647,7 @@
"assets": "Assets", "assets": "Assets",
"management": "Asset management", "management": "Asset management",
"view-assets": "View Assets", "view-assets": "View Assets",
"add": "Add Asset", "add": "Add asset",
"asset-type-max-length": "Asset type should be less than 256", "asset-type-max-length": "Asset type should be less than 256",
"assign-to-customer": "Assign to customer", "assign-to-customer": "Assign to customer",
"assign-asset-to-customer": "Assign Asset(s) To Customer", "assign-asset-to-customer": "Assign Asset(s) To Customer",
@ -772,7 +772,7 @@
}, },
"api-usage": { "api-usage": {
"api-features": "API features", "api-features": "API features",
"api-usage": "Api Usage", "api-usage": "API usage",
"alarm": "Alarm", "alarm": "Alarm",
"alarms-created": "Alarms created", "alarms-created": "Alarms created",
"alarms-created-daily-activity": "Alarms created daily activity", "alarms-created-daily-activity": "Alarms created daily activity",
@ -847,10 +847,10 @@
}, },
"audit-log": { "audit-log": {
"audit": "Audit", "audit": "Audit",
"audit-logs": "Audit Logs", "audit-logs": "Audit logs",
"timestamp": "Timestamp", "timestamp": "Timestamp",
"entity-type": "Entity Type", "entity-type": "Entity type",
"entity-name": "Entity Name", "entity-name": "Entity name",
"user": "User", "user": "User",
"type": "Type", "type": "Type",
"status": "Status", "status": "Status",
@ -951,7 +951,7 @@
"public-assets": "Public Assets", "public-assets": "Public Assets",
"public-edges": "Public Edges", "public-edges": "Public Edges",
"public-entity-views": "Public Entity Views", "public-entity-views": "Public Entity Views",
"add": "Add Customer", "add": "Add customer",
"delete": "Delete customer", "delete": "Delete customer",
"manage-customer-users": "Manage customer users", "manage-customer-users": "Manage customer users",
"manage-customer-devices": "Manage customer devices", "manage-customer-devices": "Manage customer devices",
@ -1011,7 +1011,7 @@
"dashboards": "Dashboards", "dashboards": "Dashboards",
"management": "Dashboard management", "management": "Dashboard management",
"view-dashboards": "View Dashboards", "view-dashboards": "View Dashboards",
"add": "Add Dashboard", "add": "Add dashboard",
"assign-dashboard-to-customer": "Assign Dashboard(s) To Customer", "assign-dashboard-to-customer": "Assign Dashboard(s) To Customer",
"assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer", "assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer",
"assign-dashboard-to-edge-title": "Assign Dashboard(s) To Edge", "assign-dashboard-to-edge-title": "Assign Dashboard(s) To Edge",
@ -1299,7 +1299,7 @@
"device-list-empty": "No devices selected.", "device-list-empty": "No devices selected.",
"device-name-filter-required": "Device name filter is required.", "device-name-filter-required": "Device name filter is required.",
"device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.", "device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.",
"add": "Add Device", "add": "Add device",
"assign-to-customer": "Assign to customer", "assign-to-customer": "Assign to customer",
"assign-device-to-customer": "Assign Device(s) To Customer", "assign-device-to-customer": "Assign Device(s) To Customer",
"assign-device-to-customer-text": "Please select the devices to assign to the customer", "assign-device-to-customer-text": "Please select the devices to assign to the customer",
@ -1933,7 +1933,7 @@
"type-max-length": "Type should be less than 256", "type-max-length": "Type should be less than 256",
"management": "Edge management", "management": "Edge management",
"no-edges-matching": "No edges matching '{{entity}}' were found.", "no-edges-matching": "No edges matching '{{entity}}' were found.",
"add": "Add Edge", "add": "Add edge",
"no-edges-text": "No edges found", "no-edges-text": "No edges found",
"edge-details": "Edge details", "edge-details": "Edge details",
"add-edge-text": "Add new edge", "add-edge-text": "Add new edge",
@ -2192,7 +2192,7 @@
"no-entities-prompt": "No entities found", "no-entities-prompt": "No entities found",
"no-data": "No data to display", "no-data": "No data to display",
"columns-to-display": "Columns to Display", "columns-to-display": "Columns to Display",
"type-api-usage-state": "Api Usage State", "type-api-usage-state": "API Usage State",
"type-edge": "Edge", "type-edge": "Edge",
"type-edges": "Edges", "type-edges": "Edges",
"list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }", "list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }",
@ -2231,9 +2231,9 @@
"label": "Label" "label": "Label"
}, },
"entity-view": { "entity-view": {
"entity-view": "Entity View", "entity-view": "Entity view",
"entity-view-required": "Entity view is required.", "entity-view-required": "Entity view is required.",
"entity-views": "Entity Views", "entity-views": "Entity views",
"management": "Entity View management", "management": "Entity View management",
"view-entity-views": "View Entity Views", "view-entity-views": "View Entity Views",
"entity-view-alias": "Entity View alias", "entity-view-alias": "Entity View alias",
@ -2259,7 +2259,7 @@
"entity-view-list-empty": "No entity views selected.", "entity-view-list-empty": "No entity views selected.",
"entity-view-name-filter-required": "Entity view name filter is required.", "entity-view-name-filter-required": "Entity view name filter is required.",
"entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.",
"add": "Add Entity View", "add": "Add entity view",
"entity-view-public": "Entity view is public", "entity-view-public": "Entity view is public",
"assign-to-customer": "Assign to customer", "assign-to-customer": "Assign to customer",
"assign-entity-view-to-customer": "Assign Entity View(s) To Customer", "assign-entity-view-to-customer": "Assign Entity View(s) To Customer",
@ -2287,14 +2287,14 @@
"unassign-entity-view": "Unassign entity view", "unassign-entity-view": "Unassign entity view",
"unassign-entity-views-title": "Are you sure you want to unassign { count, plural, =1 {1 entity view} other {# entity views} }?", "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, =1 {1 entity view} other {# entity views} }?",
"unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.", "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.",
"entity-view-type": "Entity View type", "entity-view-type": "Entity view type",
"entity-view-type-required": "Entity View type is required.", "entity-view-type-required": "Entity view type is required.",
"select-entity-view-type": "Select entity view type", "select-entity-view-type": "Select entity view type",
"enter-entity-view-type": "Enter entity view type", "enter-entity-view-type": "Enter entity view type",
"any-entity-view": "Any entity view", "any-entity-view": "Any entity view",
"no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.",
"entity-view-type-list-empty": "No entity view types selected.", "entity-view-type-list-empty": "No entity view types selected.",
"entity-view-types": "Entity View types", "entity-view-types": "Entity view types",
"created-time": "Created time", "created-time": "Created time",
"name": "Name", "name": "Name",
"name-required": "Name is required.", "name-required": "Name is required.",
@ -3457,7 +3457,7 @@
"use-external-url": "Use external URL", "use-external-url": "Use external URL",
"version": "Version", "version": "Version",
"version-required": "Version is required.", "version-required": "Version is required.",
"version-tag": "Version Tag", "version-tag": "Version tag",
"version-tag-hint": "Custom tag should match the package version reported by your device.", "version-tag-hint": "Custom tag should match the package version reported by your device.",
"version-max-length": "Version should be less than 256", "version-max-length": "Version should be less than 256",
"warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type."
@ -3608,7 +3608,7 @@
"no-relations-text": "No relations found" "no-relations-text": "No relations found"
}, },
"resource": { "resource": {
"add": "Add Resource", "add": "Add resource",
"all-types": "All", "all-types": "All",
"copyId": "Copy resource Id", "copyId": "Copy resource Id",
"delete": "Delete resource", "delete": "Delete resource",
@ -3653,7 +3653,7 @@
"name-required": "Name is required.", "name-required": "Name is required.",
"name-max-length": "Name should be less than 256", "name-max-length": "Name should be less than 256",
"description": "Description", "description": "Description",
"add": "Add Rule Chain", "add": "Add rule chain",
"set-root": "Make rule chain root", "set-root": "Make rule chain root",
"set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
"set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
@ -3685,8 +3685,8 @@
"selected-rulechains": "{ count, plural, =1 {1 rule chain} other {# rule chains} } selected", "selected-rulechains": "{ count, plural, =1 {1 rule chain} other {# rule chains} } selected",
"open-rulechain": "Open rule chain", "open-rulechain": "Open rule chain",
"assign-new-rulechain": "Assign new rulechain", "assign-new-rulechain": "Assign new rulechain",
"edge-template-root": "Template Root", "edge-template-root": "Template root",
"assign-to-edge": "Assign to Edge", "assign-to-edge": "Assign to edge",
"edge-rulechain": "Edge rule chain", "edge-rulechain": "Edge rule chain",
"unassign-rulechain-from-edge-text": "After the confirmation the rulechain will be unassigned and won't be accessible by the edge.", "unassign-rulechain-from-edge-text": "After the confirmation the rulechain will be unassigned and won't be accessible by the edge.",
"unassign-rulechains-from-edge-title": "Are you sure you want to unassign { count, plural, =1 {1 rulechain} other {# rulechains} }?", "unassign-rulechains-from-edge-title": "Are you sure you want to unassign { count, plural, =1 {1 rulechain} other {# rulechains} }?",
@ -3896,7 +3896,7 @@
"tenant": "Tenant", "tenant": "Tenant",
"tenants": "Tenants", "tenants": "Tenants",
"management": "Tenant management", "management": "Tenant management",
"add": "Add Tenant", "add": "Add tenant",
"admins": "Admins", "admins": "Admins",
"manage-tenant-admins": "Manage tenant admins", "manage-tenant-admins": "Manage tenant admins",
"delete": "Delete tenant", "delete": "Delete tenant",
@ -4623,13 +4623,13 @@
"user": { "user": {
"user": "User", "user": "User",
"users": "Users", "users": "Users",
"customer-users": "Customer Users", "customer-users": "Customer users",
"tenant-admins": "Tenant Admins", "tenant-admins": "Tenant admins",
"sys-admin": "System administrator", "sys-admin": "System administrator",
"tenant-admin": "Tenant administrator", "tenant-admin": "Tenant administrator",
"customer": "Customer", "customer": "Customer",
"anonymous": "Anonymous", "anonymous": "Anonymous",
"add": "Add User", "add": "Add user",
"delete": "Delete user", "delete": "Delete user",
"add-user-text": "Add new user", "add-user-text": "Add new user",
"no-users-text": "No users found", "no-users-text": "No users found",
@ -4644,8 +4644,8 @@
"email": "Email", "email": "Email",
"email-required": "Email is required.", "email-required": "Email is required.",
"invalid-email-format": "Invalid email format.", "invalid-email-format": "Invalid email format.",
"first-name": "First Name", "first-name": "First name",
"last-name": "Last Name", "last-name": "Last name",
"description": "Description", "description": "Description",
"default-dashboard": "Default dashboard", "default-dashboard": "Default dashboard",
"always-fullscreen": "Always fullscreen", "always-fullscreen": "Always fullscreen",
@ -4764,7 +4764,7 @@
"auto-commit-settings-read-only-hint": "Auto-commit feature doesn't work with enabled read-only option in Repository settings." "auto-commit-settings-read-only-hint": "Auto-commit feature doesn't work with enabled read-only option in Repository settings."
}, },
"widget": { "widget": {
"widget-library": "Widgets Library", "widget-library": "Widgets library",
"widget-bundle": "Widgets Bundle", "widget-bundle": "Widgets Bundle",
"all-bundles": "All bundles", "all-bundles": "All bundles",
"select-widgets-bundle": "Select widgets bundle", "select-widgets-bundle": "Select widgets bundle",
@ -4849,7 +4849,7 @@
"widget-template-load-failed-error": "Failed to load widget template!", "widget-template-load-failed-error": "Failed to load widget template!",
"details": "Details", "details": "Details",
"widget-details": "Widget details", "widget-details": "Widget details",
"add": "Add Widget", "add": "Add widget",
"add-existing-widget": "Add existing widget", "add-existing-widget": "Add existing widget",
"add-new-widget": "Add new widget", "add-new-widget": "Add new widget",
"search-widgets": "Search widgets", "search-widgets": "Search widgets",
@ -4925,9 +4925,9 @@
}, },
"widgets-bundle": { "widgets-bundle": {
"current": "Current bundle", "current": "Current bundle",
"widgets-bundles": "Widgets Bundles", "widgets-bundles": "Widgets bundles",
"widgets-bundle-widgets": "Widgets Bundle Widgets", "widgets-bundle-widgets": "Widgets Bundle Widgets",
"add": "Add Widgets Bundle", "add": "Add widgets bundle",
"delete": "Delete widgets bundle", "delete": "Delete widgets bundle",
"title": "Title", "title": "Title",
"title-required": "Title is required.", "title-required": "Title is required.",

Loading…
Cancel
Save