Browse Source

Reference resources by link in dashboards and widgets

pull/11873/head
ViacheslavKlimov 2 years ago
parent
commit
9f10217538
  1. 3
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  2. 35
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  3. 15
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  4. 65
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  5. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java
  9. 69
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java
  12. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java
  13. 110
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java
  14. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java
  15. 64
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  16. 9
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  17. 3
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  18. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
  19. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java
  20. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java
  21. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java
  22. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetTypeImportService.java
  23. 12
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  24. 58
      application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
  25. 4
      application/src/test/resources/logback-test.xml
  26. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  27. 9
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  28. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java
  29. 6
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  30. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  31. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java
  32. 7
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  33. 28
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardExportData.java
  34. 27
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetExportData.java
  35. 7
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  36. 28
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java
  37. 11
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  38. 19
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  39. 122
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  40. 11
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  41. 6
      ui-ngx/src/app/core/http/dashboard.service.ts
  42. 7
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  43. 6
      ui-ngx/src/app/shared/models/dashboard.models.ts
  44. 1
      ui-ngx/src/app/shared/models/widget.models.ts

3
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -34,6 +34,9 @@ public class ControllerConstants {
"See the 'Model' tab of the Response Class for more details. ";
protected static final String INLINE_IMAGES = "inlineImages";
protected static final String INLINE_IMAGES_DESCRIPTION = "Inline images as a data URL (Base64)";
protected static final String INCLUDE_RESOURCES = "includeResources";
protected static final String INCLUDE_RESOURCES_DESCRIPTION = "Export used resources and replace resource links with resource metadata";
protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";

35
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -45,7 +45,6 @@ import org.thingsboard.server.common.data.HomeDashboard;
import org.thingsboard.server.common.data.HomeDashboardInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.dashboard.DashboardExportData;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
@ -55,9 +54,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -78,6 +77,8 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INCLUDE_RESOURCES;
import static org.thingsboard.server.controller.ControllerConstants.INCLUDE_RESOURCES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -97,7 +98,8 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class DashboardController extends BaseController {
private final TbDashboardService tbDashboardService;
private final ImageService imageService;
private final TbResourceService tbResourceService;
public static final String DASHBOARD_ID = "dashboardId";
private static final String HOME_DASHBOARD_ID = "homeDashboardId";
@ -149,19 +151,16 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/dashboard/{dashboardId}")
public Dashboard getDashboardById(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
@PathVariable(DASHBOARD_ID) String strDashboardId,
@Parameter(description = INCLUDE_RESOURCES_DESCRIPTION)
@RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
return checkDashboardId(dashboardId, Operation.READ);
}
@GetMapping(value = "/dashboard/{dashboardId}/export")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
public DashboardExportData exportDashboard(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) UUID id) throws ThingsboardException {
DashboardId dashboardId = new DashboardId(id);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
return tbDashboardService.exportDashboard(getTenantId(), dashboard, getCurrentUser());
if (includeResources) {
dashboard.setResources(tbResourceService.exportResources(dashboard, getCurrentUser()));
}
return dashboard;
}
@ApiOperation(value = "Create Or Update Dashboard (saveDashboard)",
@ -180,16 +179,6 @@ public class DashboardController extends BaseController {
return tbDashboardService.save(dashboard, getCurrentUser());
}
@PostMapping(value = "/dashboard/import")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
public Dashboard importDashboard(@RequestBody DashboardExportData exportData) throws Exception {
Dashboard dashboard = exportData.getDashboard();
dashboard.setTenantId(getTenantId());
dashboard.setId(null);
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
return tbDashboardService.importDashboard(exportData, getCurrentUser());
}
@ApiOperation(value = "Delete the Dashboard (deleteDashboard)",
notes = "Delete the Dashboard." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")

15
application/src/main/java/org/thingsboard/server/controller/TbResourceController.java

@ -173,6 +173,21 @@ public class TbResourceController extends BaseController {
return checkResourceInfoId(resourceId, Operation.READ);
}
@ApiOperation(value = "Get resource info (getResourceInfo)",
notes = "Get info for the resource with the given type, scope and key. " +
RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource/{resourceType}/{scope}/{key}/info")
public TbResourceInfo getResourceInfo(@Parameter(description = "Type of the resource", schema = @Schema(allowableValues = {"lwm2m_model", "jks", "pkcs_12", "js_module", "dashboard"}))
@PathVariable("resourceType") String resourceTypeStr,
@Parameter(description = "Scope of the resource", schema = @Schema(allowableValues = {"system", "tenant"}))
@PathVariable String scope,
@Parameter(description = "Key of the resource, e.g. 'extension.js'")
@PathVariable String key) throws ThingsboardException {
ResourceType resourceType = ResourceType.valueOf(resourceTypeStr.toUpperCase());
return checkResourceInfo(scope, resourceType, key, Operation.READ);
}
@ApiOperation(value = "Get Resource (getResourceById)",
notes = "Fetch the Resource object based on the provided Resource Id. " +
RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, hidden = true)

65
application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java

@ -23,7 +23,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -41,26 +40,27 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
import org.thingsboard.server.common.data.widget.WidgetExportData;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeFilter;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.data.widget.WidgetsExportData;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.widgets.type.TbWidgetTypeService;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.INCLUDE_RESOURCES;
import static org.thingsboard.server.controller.ControllerConstants.INCLUDE_RESOURCES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -78,6 +78,7 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_TYPE_
public class WidgetTypeController extends AutoCommitController {
private final TbWidgetTypeService tbWidgetTypeService;
private final TbResourceService tbResourceService;
private static final String WIDGET_TYPE_DESCRIPTION = "Widget Type represents the template for widget creation. Widget Type and Widget are similar to class and object in OOP theory.";
private static final String WIDGET_TYPE_DETAILS_DESCRIPTION = "Widget Type Details extend Widget Type and add image and description properties. " +
@ -95,18 +96,16 @@ public class WidgetTypeController extends AutoCommitController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/widgetType/{widgetTypeId}")
public WidgetTypeDetails getWidgetTypeById(@Parameter(description = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException {
@PathVariable("widgetTypeId") String strWidgetTypeId,
@Parameter(description = INCLUDE_RESOURCES_DESCRIPTION)
@RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources) throws ThingsboardException {
checkParameter("widgetTypeId", strWidgetTypeId);
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
return checkWidgetTypeId(widgetTypeId, Operation.READ);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/widgetType/{widgetTypeId}/export")
public WidgetExportData exportWidgetType(@Parameter(description = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetTypeId") UUID widgetTypeId) throws ThingsboardException {
WidgetTypeDetails widgetTypeDetails = checkWidgetTypeId(new WidgetTypeId(widgetTypeId), Operation.READ);
return tbWidgetTypeService.exportWidgetType(getTenantId(), widgetTypeDetails, getCurrentUser());
WidgetTypeDetails widgetTypeDetails = checkWidgetTypeId(widgetTypeId, Operation.READ);
if (includeResources) {
widgetTypeDetails.setResources(tbResourceService.exportResources(widgetTypeDetails, getCurrentUser()));
}
return widgetTypeDetails;
}
@ApiOperation(value = "Get Widget Type Info (getWidgetTypeInfoById)",
@ -151,15 +150,6 @@ public class WidgetTypeController extends AutoCommitController {
return tbWidgetTypeService.save(widgetTypeDetails, updateExistingByFqn != null && updateExistingByFqn, currentUser);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PostMapping(value = "/widgetType/import")
public WidgetTypeDetails importWidgetType(@RequestBody WidgetExportData exportData) throws Exception {
WidgetTypeDetails widgetTypeDetails = exportData.getWidgetTypeDetails();
widgetTypeDetails.setTenantId(getTenantId());
checkEntity(widgetTypeDetails.getId(), widgetTypeDetails, Resource.WIDGET_TYPE);
return tbWidgetTypeService.importWidgetType(exportData, getCurrentUser());
}
@ApiOperation(value = "Delete widget type (deleteWidgetType)",
notes = "Deletes the Widget Type. Referencing non-existing Widget Type Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -283,26 +273,19 @@ public class WidgetTypeController extends AutoCommitController {
@ResponseBody
public List<WidgetTypeDetails> getBundleWidgetTypesDetails(
@Parameter(description = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId
@RequestParam("widgetsBundleId") String strWidgetsBundleId,
@Parameter(description = INCLUDE_RESOURCES_DESCRIPTION)
@RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources
) throws ThingsboardException {
SecurityUser user = getCurrentUser();
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
return checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/widgetTypes/export", params = {"widgetsBundleId"})
public WidgetsExportData exportBundleWidgetTypes(@Parameter(description = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
return tbWidgetTypeService.exportBundleWidgetTypes(getTenantId(), widgetsBundleId, getCurrentUser());
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PostMapping(value = "/widgetTypes/import", params = {"widgetsBundleId"})
public List<WidgetTypeDetails> importBundleWidgetTypes(@Parameter(description = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
// FIXME: determine how bundle widget types were imported before
throw new UnsupportedOperationException();
List<WidgetTypeDetails> result = checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
if (includeResources) {
for (WidgetTypeDetails widgetTypeDetails : result) {
widgetTypeDetails.setResources(tbResourceService.exportResources(widgetTypeDetails, user));
}
}
return result;
}
@ApiOperation(value = "Get all Widget type fqns for specified Bundle (getBundleWidgetTypeFqns)",

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

@ -145,6 +145,7 @@ public class ThingsboardInstallService {
case "3.8.1":
log.info("Upgrading ThingsBoard from version 3.8.1 to 3.9.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.8.1");
dataUpdateService.updateData("3.8.1");
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:

3
application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.entitiy;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface SimpleTbEntityService<T> {
@ -23,7 +24,7 @@ public interface SimpleTbEntityService<T> {
return save(entity, null);
}
T save(T entity, User user) throws Exception;
T save(T entity, SecurityUser user) throws Exception;
void delete(T entity, User user);

3
application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
@Service
@TbCoreComponent
@ -38,7 +39,7 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem
private final AssetProfileService assetProfileService;
@Override
public AssetProfile save(AssetProfile assetProfile, User user) throws Exception {
public AssetProfile save(AssetProfile assetProfile, SecurityUser user) throws Exception {
ActionType actionType = assetProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = assetProfile.getTenantId();
try {

3
application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java

@ -24,13 +24,14 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
@Service
@AllArgsConstructor
public class DefaultTbCustomerService extends AbstractTbEntityService implements TbCustomerService {
@Override
public Customer save(Customer customer, User user) throws Exception {
public Customer save(Customer customer, SecurityUser user) throws Exception {
ActionType actionType = customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = customer.getTenantId();
try {

69
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java

@ -16,38 +16,27 @@
package org.thingsboard.server.service.entitiy.dashboard;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.dashboard.DashboardExportData;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Service
@ -56,15 +45,17 @@ import java.util.Set;
public class DefaultTbDashboardService extends AbstractTbEntityService implements TbDashboardService {
private final DashboardService dashboardService;
private final ImageService imageService;
private final TbImageService tbImageService;
private final ResourceService resourceService;
private final TbResourceService tbResourceService;
@Override
public Dashboard save(Dashboard dashboard, User user) throws Exception {
public Dashboard save(Dashboard dashboard, SecurityUser user) throws Exception {
ActionType actionType = dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = dashboard.getTenantId();
if (CollectionUtils.isNotEmpty(dashboard.getResources())) {
tbResourceService.importResources(dashboard.getResources(), user);
}
try {
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
autoCommit(user, savedDashboard.getId());
@ -301,50 +292,4 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement
}
}
@Override
public DashboardExportData exportDashboard(TenantId tenantId, Dashboard dashboard, SecurityUser user) throws ThingsboardException {
Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
for (TbResourceInfo imageInfo : imageService.inlineImages(dashboard)) {
resources.putIfAbsent(imageInfo.getId(), imageInfo);
}
for (TbResourceInfo resourceInfo : resourceService.processResourcesForExport(dashboard)) {
resources.putIfAbsent(resourceInfo.getId(), resourceInfo);
}
for (TbResourceInfo resourceInfo : resources.values()) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo);
}
DashboardExportData exportData = new DashboardExportData();
exportData.setDashboard(dashboard);
exportData.setResources(resources.values().stream()
.map(resourceInfo -> {
if (resourceInfo.getResourceType() == ResourceType.IMAGE) {
ResourceExportData imageExportData = tbImageService.exportImage(resourceInfo);
imageExportData.setResourceKey(null); // so that the image is not updated by resource key on import
return imageExportData;
} else {
return tbResourceService.exportResource(resourceInfo);
}
})
.toList());
return exportData;
}
@Override
public Dashboard importDashboard(DashboardExportData exportData, SecurityUser user) throws Exception {
Map<TbResourceId, TbResourceId> importedResources = new HashMap<>();
for (ResourceExportData resourceExportData : exportData.getResources()) {
if (resourceExportData.getType() == ResourceType.IMAGE) {
tbImageService.importImage(resourceExportData, true, user);
} else {
TbResourceInfo importedResource = tbResourceService.importResource(resourceExportData, true, user);
importedResources.put(resourceExportData.getId(), importedResource.getId());
}
}
Dashboard dashboard = exportData.getDashboard();
resourceService.processResourcesForImport(dashboard, importedResources);
return save(dashboard, user);
}
}

6
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java

@ -18,14 +18,12 @@ package org.thingsboard.server.service.entitiy.dashboard;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.dashboard.DashboardExportData;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Set;
@ -49,8 +47,4 @@ public interface TbDashboardService extends SimpleTbEntityService<Dashboard> {
Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, User user) throws ThingsboardException;
DashboardExportData exportDashboard(TenantId tenantId, Dashboard dashboard, SecurityUser user) throws ThingsboardException;
Dashboard importDashboard(DashboardExportData exportData, SecurityUser user) throws Exception;
}

3
application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
@Service
@TbCoreComponent
@ -38,7 +39,7 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple
private final DeviceProfileService deviceProfileService;
@Override
public DeviceProfile save(DeviceProfile deviceProfile, User user) throws Exception {
public DeviceProfile save(DeviceProfile deviceProfile, SecurityUser user) throws Exception {
ActionType actionType = deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = deviceProfile.getTenantId();
try {

3
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java

@ -29,6 +29,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List;
@ -42,7 +43,7 @@ public class DefaultWidgetsBundleService extends AbstractTbEntityService impleme
private final WidgetTypeService widgetTypeService;
@Override
public WidgetsBundle save(WidgetsBundle widgetsBundle, User user) throws Exception {
public WidgetsBundle save(WidgetsBundle widgetsBundle, SecurityUser user) throws Exception {
ActionType actionType = widgetsBundle.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = widgetsBundle.getTenantId();
try {

110
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java

@ -16,57 +16,36 @@
package org.thingsboard.server.service.entitiy.widgets.type;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.widget.WidgetExportData;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsExportData;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@TbCoreComponent
@AllArgsConstructor
public class DefaultWidgetTypeService extends AbstractTbEntityService implements TbWidgetTypeService {
private final WidgetTypeService widgetTypeService;
private final ImageService imageService;
private final TbImageService tbImageService;
private final ResourceService resourceService;
private final TbResourceService tbResourceService;
@Override
public WidgetTypeDetails save(WidgetTypeDetails entity, User user) throws Exception {
public WidgetTypeDetails save(WidgetTypeDetails entity, SecurityUser user) throws Exception {
return this.save(entity, false, user);
}
@Override
public WidgetTypeDetails save(WidgetTypeDetails widgetTypeDetails, boolean updateExistingByFqn, User user) throws Exception {
public WidgetTypeDetails save(WidgetTypeDetails widgetTypeDetails, boolean updateExistingByFqn, SecurityUser user) throws Exception {
TenantId tenantId = widgetTypeDetails.getTenantId();
if (widgetTypeDetails.getId() == null && StringUtils.isNotEmpty(widgetTypeDetails.getFqn()) && updateExistingByFqn) {
WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdAndFqn(tenantId, widgetTypeDetails.getFqn());
@ -74,6 +53,10 @@ public class DefaultWidgetTypeService extends AbstractTbEntityService implements
widgetTypeDetails.setId(widgetType.getId());
}
}
if (CollectionUtils.isNotEmpty(widgetTypeDetails.getResources())) {
tbResourceService.importResources(widgetTypeDetails.getResources(), user);
}
ActionType actionType = widgetTypeDetails.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
try {
WidgetTypeDetails savedWidgetTypeDetails = checkNotNull(widgetTypeService.saveWidgetType(widgetTypeDetails));
@ -100,83 +83,4 @@ public class DefaultWidgetTypeService extends AbstractTbEntityService implements
}
}
@Override
public WidgetExportData exportWidgetType(TenantId tenantId, WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException {
List<ResourceExportData> resources = processWidgetTypesForExport(List.of(widgetTypeDetails), user);
WidgetExportData exportData = new WidgetExportData();
exportData.setWidgetTypeDetails(widgetTypeDetails);
exportData.setResources(resources);
return exportData;
}
@Override
public WidgetsExportData exportBundleWidgetTypes(TenantId tenantId, WidgetsBundleId bundleId, SecurityUser user) throws ThingsboardException {
List<WidgetTypeDetails> widgetTypes = widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(tenantId, bundleId);
List<ResourceExportData> resources = processWidgetTypesForExport(widgetTypes, user);
WidgetsExportData exportData = new WidgetsExportData();
exportData.setWidgetTypesDetails(widgetTypes);
exportData.setResources(resources);
return exportData;
}
private List<ResourceExportData> processWidgetTypesForExport(List<WidgetTypeDetails> widgetTypes, SecurityUser user) throws ThingsboardException {
Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
for (WidgetTypeDetails widgetTypeDetails : widgetTypes) {
for (TbResourceInfo imageInfo : imageService.inlineImages(widgetTypeDetails)) {
resources.putIfAbsent(imageInfo.getId(), imageInfo);
}
for (TbResourceInfo resourceInfo : resourceService.processResourcesForExport(widgetTypeDetails)) {
resources.putIfAbsent(resourceInfo.getId(), resourceInfo);
}
}
for (TbResourceInfo resourceInfo : resources.values()) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo);
}
return resources.values().stream()
.map(resourceInfo -> {
if (resourceInfo.getResourceType() == ResourceType.IMAGE) {
ResourceExportData imageExportData = tbImageService.exportImage(resourceInfo);
imageExportData.setResourceKey(null); // so that the image is not updated by resource key on import
return imageExportData;
} else {
return tbResourceService.exportResource(resourceInfo);
}
})
.toList();
}
@Override
public WidgetTypeDetails importWidgetType(WidgetExportData exportData, SecurityUser user) throws Exception {
return importWidgetTypes(List.of(exportData.getWidgetTypeDetails()), exportData.getResources(), user).get(0);
}
@Override
public List<WidgetTypeDetails> importWidgetTypes(WidgetsExportData exportData, SecurityUser user) throws Exception {
return importWidgetTypes(exportData.getWidgetTypesDetails(), exportData.getResources(), user);
}
private List<WidgetTypeDetails> importWidgetTypes(List<WidgetTypeDetails> widgetTypesDetails, List<ResourceExportData> resources, SecurityUser user) throws Exception {
Map<TbResourceId, TbResourceId> importedResources = new HashMap<>();
for (ResourceExportData resourceExportData : resources) {
if (resourceExportData.getType() == ResourceType.IMAGE) {
tbImageService.importImage(resourceExportData, true, user);
} else {
TbResourceInfo importedResource = tbResourceService.importResource(resourceExportData, true, user);
importedResources.put(resourceExportData.getId(), importedResource.getId());
}
}
List<WidgetTypeDetails> result = new ArrayList<>();
for (WidgetTypeDetails widgetTypeDetails : widgetTypesDetails) {
resourceService.processResourcesForImport(widgetTypeDetails, importedResources);
// FIXME: shouldn't just save it, have to be processed somehow
widgetTypeDetails = save(widgetTypeDetails, user);
result.add(widgetTypeDetails);
}
return result;
}
}

18
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java

@ -15,28 +15,12 @@
*/
package org.thingsboard.server.service.entitiy.widgets.type;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.widget.WidgetExportData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsExportData;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List;
public interface TbWidgetTypeService extends SimpleTbEntityService<WidgetTypeDetails> {
WidgetTypeDetails save(WidgetTypeDetails widgetTypeDetails, boolean updateExistingByFqn, User user) throws Exception;
WidgetExportData exportWidgetType(TenantId tenantId, WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException;
WidgetsExportData exportBundleWidgetTypes(TenantId tenantId, WidgetsBundleId bundleId, SecurityUser user) throws ThingsboardException;
WidgetTypeDetails importWidgetType(WidgetExportData exportData, SecurityUser user) throws Exception;
List<WidgetTypeDetails> importWidgetTypes(WidgetsExportData exportData, SecurityUser user) throws Exception;
WidgetTypeDetails save(WidgetTypeDetails widgetTypeDetails, boolean updateExistingByFqn, SecurityUser user) throws Exception;
}

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

@ -18,6 +18,7 @@ package org.thingsboard.server.service.resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.ResourceSubType;
@ -32,6 +33,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -41,7 +44,10 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -58,9 +64,11 @@ import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource
public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService {
private final ResourceService resourceService;
private final ImageService imageService;
private final TbImageService tbImageService;
@Override
public TbResource save(TbResource resource, User user) throws ThingsboardException {
public TbResource save(TbResource resource, SecurityUser user) throws ThingsboardException {
if (resource.getResourceType() == ResourceType.IMAGE) {
throw new IllegalArgumentException("Image resource type is not supported");
}
@ -124,10 +132,57 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
}
@Override
public ResourceExportData exportResource(TbResourceInfo resourceInfo) {
public List<ResourceExportData> exportResources(Dashboard dashboard, SecurityUser user) throws ThingsboardException {
return exportResources(dashboard, imageService::inlineImages, resourceService::replaceResourcesUrlsWithTags, user);
}
@Override
public List<ResourceExportData> exportResources(WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException {
return exportResources(widgetTypeDetails, imageService::inlineImages, resourceService::replaceResourcesUrlsWithTags, user);
}
@Override
public void importResources(List<ResourceExportData> resources, SecurityUser user) throws Exception {
for (ResourceExportData resourceExportData : resources) {
if (resourceExportData.getType() == ResourceType.IMAGE) {
tbImageService.importImage(resourceExportData, true, user);
} else {
importResource(resourceExportData, true, user);
}
}
}
private <T> List<ResourceExportData> exportResources(T entity,
Function<T, List<TbResourceInfo>> imagesProcessor,
Function<T, List<TbResourceInfo>> resourcesProcessor,
SecurityUser user) throws ThingsboardException {
Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
for (TbResourceInfo imageInfo : imagesProcessor.apply(entity)) {
resources.putIfAbsent(imageInfo.getId(), imageInfo);
}
for (TbResourceInfo resourceInfo : resourcesProcessor.apply(entity)) {
resources.putIfAbsent(resourceInfo.getId(), resourceInfo);
}
for (TbResourceInfo resourceInfo : resources.values()) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo);
}
return resources.values().stream()
.map(resourceInfo -> {
if (resourceInfo.getResourceType() == ResourceType.IMAGE) {
ResourceExportData imageExportData = tbImageService.exportImage(resourceInfo);
imageExportData.setResourceKey(null); // so that the image is not updated by resource key on import
return imageExportData;
} else {
return exportResource(resourceInfo);
}
})
.toList();
}
private ResourceExportData exportResource(TbResourceInfo resourceInfo) {
byte[] data = resourceService.getResourceData(resourceInfo.getTenantId(), resourceInfo.getId());
return ResourceExportData.builder()
.id(resourceInfo.getId())
.mediaType(resourceInfo.getResourceType().getMediaType())
.fileName(resourceInfo.getFileName())
.title(resourceInfo.getTitle())
@ -138,8 +193,7 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
.build();
}
@Override
public TbResourceInfo importResource(ResourceExportData exportData, boolean checkExisting, SecurityUser user) throws ThingsboardException {
private TbResourceInfo importResource(ResourceExportData exportData, boolean checkExisting, SecurityUser user) throws ThingsboardException {
if (exportData.getType() == ResourceType.IMAGE || exportData.getSubType() == ResourceSubType.IMAGE
|| exportData.getSubType() == ResourceSubType.SCADA_SYMBOL) {
throw new IllegalArgumentException("Image import not supported");

9
application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -15,13 +15,14 @@
*/
package org.thingsboard.server.service.resource;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -39,8 +40,10 @@ public interface TbResourceService extends SimpleTbEntityService<TbResource> {
String sortOrder,
PageLink pageLink);
ResourceExportData exportResource(TbResourceInfo resourceInfo);
List<ResourceExportData> exportResources(Dashboard dashboard, SecurityUser user) throws ThingsboardException;
TbResourceInfo importResource(ResourceExportData exportData, boolean checkExisting, SecurityUser user) throws ThingsboardException;
List<ResourceExportData> exportResources(WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException;
void importResources(List<ResourceExportData> resources, SecurityUser user) throws Exception;
}

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

@ -47,6 +47,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList;
@ -170,7 +171,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
}
@Override
public RuleChain save(RuleChain ruleChain, User user) throws Exception {
public RuleChain save(RuleChain ruleChain, SecurityUser user) throws Exception {
ActionType actionType = ruleChain.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = ruleChain.getTenantId();
try {

1
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java

@ -49,6 +49,7 @@ public class DashboardExportService extends BaseEntityExportService<DashboardId,
replaceUuidsRecursively(ctx, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.emptySet(), WIDGET_CONFIG_PROCESSED_FIELDS_PATTERN);
}
imageService.inlineImages(dashboard);
resourceService.replaceResourcesUrlsWithTags(dashboard);
}
@Override

3
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
@ -62,6 +63,8 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
private AttributesService attributesService;
@Autowired
protected ImageService imageService;
@Autowired
protected ResourceService resourceService;
@Override
public final D getExportData(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException {

1
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java

@ -37,6 +37,7 @@ public class WidgetTypeExportService extends BaseEntityExportService<WidgetTypeI
throw new IllegalArgumentException("Export of system Widget Type is not allowed");
}
imageService.inlineImages(widgetTypeDetails);
resourceService.replaceResourcesUrlsWithTags(widgetTypeDetails);
}
@Override

1
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java

@ -49,7 +49,6 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
private final DashboardService dashboardService;
@Override
protected void setOwner(TenantId tenantId, Dashboard dashboard, IdProvider idProvider) {
dashboard.setTenantId(tenantId);

4
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetTypeImportService.java

@ -39,8 +39,8 @@ public class WidgetTypeImportService extends BaseEntityImportService<WidgetTypeI
}
@Override
protected WidgetTypeDetails prepare(EntitiesImportCtx ctx, WidgetTypeDetails widgetsBundle, WidgetTypeDetails old, WidgetTypeExportData exportData, IdProvider idProvider) {
return widgetsBundle;
protected WidgetTypeDetails prepare(EntitiesImportCtx ctx, WidgetTypeDetails widgetTypeDetails, WidgetTypeDetails old, WidgetTypeExportData exportData, IdProvider idProvider) {
return widgetTypeDetails;
}
@Override

12
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -1173,11 +1173,17 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
}
protected <R> TbResourceInfo uploadImage(HttpMethod httpMethod, String url, String subType, String filename, String mediaType, byte[] content) throws Exception {
return uploadResource(httpMethod, url, filename, mediaType, content, StringUtils.isNotEmpty(subType) ?
List.of(new MockPart("imageSubType", subType.getBytes(StandardCharsets.UTF_8))) : null);
}
protected <R> TbResourceInfo uploadResource(HttpMethod httpMethod, String url, String filename, String mediaType, byte[] content, List<MockPart> otherParts) throws Exception {
MockMultipartFile file = new MockMultipartFile("file", filename, mediaType, content);
var request = MockMvcRequestBuilders.multipart(httpMethod, url).file(file);
if (StringUtils.isNotEmpty(subType)) {
var imageSubTypePart = new MockPart("imageSubType", subType.getBytes(StandardCharsets.UTF_8));
request.part(imageSubTypePart);
if (otherParts != null && !otherParts.isEmpty()) {
for (MockPart otherPart : otherParts) {
request.part(otherPart);
}
}
setJwtToken(request);
return readResponse(mockMvc.perform(request).andExpect(status().isOk()), TbResourceInfo.class);

58
application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java

@ -34,14 +34,15 @@ import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.dashboard.DashboardExportData;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@ -55,6 +56,8 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
@ -583,34 +586,67 @@ public class DashboardControllerTest extends AbstractControllerTest {
}
@Test
public void testExportImportDashboard() throws Exception {
public void testExportImportDashboardWithResources() throws Exception {
TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", "image12.png", "image/png", ImageControllerTest.PNG_IMAGE);
TbResource resource = new TbResource();
resource.setResourceKey("gateway-management-extension.js");
resource.setFileName(resource.getResourceKey());
resource.setTitle(resource.getResourceKey());
resource.setResourceType(ResourceType.JS_MODULE);
byte[] resourceData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
resource.setData(resourceData);
TbResourceInfo resourceInfo = doPost("/api/resource", resource, TbResourceInfo.class);
assertThat(resourceInfo.getLink()).isEqualTo("/api/resource/js_module/tenant/gateway-management-extension.js");
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
dashboard.setConfiguration(JacksonUtil.newObjectNode()
.put("someImage", "tb-image;/api/images/tenant/" + imageInfo.getResourceKey()));
.put("someImage", "tb-image;/api/images/tenant/" + imageInfo.getResourceKey())
.set("widgets", JacksonUtil.toJsonNode("""
{"xxx":
{"config":{"actions":{"elementClick":[
{"customResources":[{"url":{"entityType":"TB_RESOURCE","id":
"tb-resource;/api/resource/js_module/tenant/gateway-management-extension.js"},"isModule":true}]}]}}}}
""")));
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
DashboardExportData dashboardExportData = doGet("/api/dashboard/" + dashboard.getUuidId() + "/export", DashboardExportData.class);
String imageRef = dashboardExportData.getDashboard().getConfiguration().get("someImage").asText();
Dashboard exportedDashboard = doGet("/api/dashboard/" + dashboard.getUuidId() + "?includeResources=true", Dashboard.class);
exportedDashboard.setId(null);
String imageRef = exportedDashboard.getConfiguration().get("someImage").asText();
assertThat(imageRef).isEqualTo("tb-image:" + Base64.getEncoder().encodeToString(imageInfo.getResourceKey().getBytes()) + ":"
+ Base64.getEncoder().encodeToString(imageInfo.getName().getBytes()) + ":"
+ Base64.getEncoder().encodeToString(imageInfo.getResourceSubType().name().getBytes()) + ":"
+ imageInfo.getEtag() + ";data:image/png;base64,");
List<ResourceExportData> resources = dashboardExportData.getResources();
assertThat(resources).singleElement().satisfies(resource -> {
assertThat(resource.getResourceKey()).isEqualTo(imageInfo.getResourceKey());
assertThat(resource.getData()).isEqualTo(Base64.getEncoder().encodeToString(ImageControllerTest.PNG_IMAGE));
String resourceRef = exportedDashboard.getConfiguration().get("widgets").get("xxx").get("config")
.get("actions").get("elementClick").get(0).get("customResources").get(0).get("url").get("id").asText();
assertThat(resourceRef).isEqualTo("tb-resource:" + Base64.getEncoder().encodeToString(resourceInfo.getResourceType().name().getBytes())
+ ":" + resourceInfo.getEtag());
Map<ResourceType, List<ResourceExportData>> resources = exportedDashboard.getResources().stream()
.collect(Collectors.groupingBy(ResourceExportData::getType));
assertThat(resources.get(ResourceType.IMAGE)).singleElement().satisfies(exportedImage -> {
assertThat(exportedImage.getFileName()).isEqualTo(imageInfo.getResourceKey());
assertThat(exportedImage.getData()).isEqualTo(Base64.getEncoder().encodeToString(ImageControllerTest.PNG_IMAGE));
});
assertThat(resources.get(ResourceType.JS_MODULE)).singleElement().satisfies(exportedJsModule -> {
assertThat(exportedJsModule.getFileName()).isEqualTo(resource.getResourceKey());
assertThat(exportedJsModule.getData()).isEqualTo(Base64.getEncoder().encodeToString(resourceData));
});
doDelete("/api/dashboard/" + dashboard.getId()).andExpect(status().isOk());
doDelete("/api/images/tenant/" + imageInfo.getResourceKey()).andExpect(status().isOk());
doDelete("/api/resource/" + resourceInfo.getId()).andExpect(status().isOk());
Dashboard importedDashboard = doPost("/api/dashboard/import", dashboardExportData, Dashboard.class);
Dashboard importedDashboard = doPost("/api/dashboard", exportedDashboard, Dashboard.class);
assertThat(importedDashboard.getConfiguration().get("someImage").asText()).isEqualTo("tb-image;/api/images/tenant/" + imageInfo.getResourceKey());
TbResourceInfo importedImageInfo = doGet("/api/images/tenant/" + imageInfo.getResourceKey() + "/info", TbResourceInfo.class);
assertThat(importedImageInfo.getEtag()).isEqualTo(imageInfo.getEtag());
assertThat(importedImageInfo.getResourceKey()).isEqualTo(imageInfo.getResourceKey());
TbResourceInfo importedResourceInfo = doGet(resourceInfo.getLink() + "/info", TbResourceInfo.class);
assertThat(importedResourceInfo.getEtag()).isEqualTo(resourceInfo.getEtag());
assertThat(importedResourceInfo.getResourceKey()).isEqualTo(resourceInfo.getResourceKey());
}
private Dashboard createDashboard(String title) {

4
application/src/test/resources/logback-test.xml

@ -17,9 +17,7 @@
<logger name="org.eclipse.leshan" level="INFO"/>
<logger name="org.thingsboard.server.controller.AbstractWebTest" level="INFO"/>
<logger name="org.thingsboard.server.service.script" level="INFO"/>
<logger name="org.thingsboard.server.service.entitiy.dashboard" level="TRACE"/>
<logger name="org.thingsboard.server.service.entitiy.widgets" level="TRACE"/>
<logger name="org.thingsboard.server.service.sync" level="TRACE"/>
<!-- mute TelemetryEdgeSqlTest that causes a lot of randomly generated errors -->
<logger name="org.thingsboard.server.service.edge.rpc.EdgeGrpcSession" level="OFF"/>

2
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -78,4 +78,6 @@ public interface DashboardService extends EntityDaoService {
boolean existsById(TenantId tenantId, DashboardId dashboardId);
PageData<DashboardId> findAllDashboardsIds(PageLink pageLink);
}

9
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
import java.util.Map;
public interface ResourceService extends EntityDaoService {
@ -71,13 +70,13 @@ public interface ResourceService extends EntityDaoService {
TbResourceInfo findSystemOrTenantResourceByEtag(TenantId tenantId, ResourceType resourceType, String etag);
List<TbResourceInfo> processResourcesForExport(Dashboard dashboard);
boolean replaceResourcesUsageWithUrls(Dashboard dashboard);
List<TbResourceInfo> processResourcesForExport(WidgetTypeDetails widgetTypeDetails);
boolean replaceResourcesUsageWithUrls(WidgetTypeDetails widgetTypeDetails);
void processResourcesForImport(Dashboard dashboard, Map<TbResourceId, TbResourceId> importedResources);
List<TbResourceInfo> replaceResourcesUrlsWithTags(Dashboard dashboard);
void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map<TbResourceId, TbResourceId> importedResources);
List<TbResourceInfo> replaceResourcesUrlsWithTags(WidgetTypeDetails widgetTypeDetails);
TbResource createOrUpdateSystemResource(ResourceType resourceType, String resourceKey, String data);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java

@ -67,4 +67,6 @@ public interface WidgetTypeService extends EntityDaoService {
void deleteWidgetTypesByBundleId(TenantId tenantId, WidgetsBundleId bundleId);
PageData<WidgetTypeId> findAllWidgetTypesIds(PageLink pageLink);
}

6
common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java

@ -25,6 +25,7 @@ import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.id.DashboardId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -41,6 +42,10 @@ public class Dashboard extends DashboardInfo implements ExportableEntity<Dashboa
@Setter
private DashboardId externalId;
@Getter
@Setter
private List<ResourceExportData> resources;
public Dashboard() {
super();
}
@ -57,6 +62,7 @@ public class Dashboard extends DashboardInfo implements ExportableEntity<Dashboa
super(dashboard);
this.configuration = dashboard.getConfiguration();
this.externalId = dashboard.getExternalId();
this.resources = dashboard.getResources() != null ? new ArrayList<>(dashboard.getResources()) : null;
}
@Schema(description = "JSON object with main configuration of the dashboard: layouts, widgets, aliases, etc. " +

2
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -52,7 +52,9 @@ public class DataConstants {
public static final String HTTP_TRANSPORT_NAME = "HTTP";
public static final String SNMP_TRANSPORT_NAME = "SNMP";
public static final String MAXIMUM_NUMBER_OF_DEVICES_REACHED = "Maximum number of devices reached!";
public static final String TB_IMAGE_PREFIX = "tb-image;";
public static final String TB_RESOURCE_PREFIX = "tb-resource;";
public static String[] allScopes() {

2
common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java

@ -22,7 +22,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TbResourceId;
@Schema
@Slf4j
@ -33,7 +32,6 @@ import org.thingsboard.server.common.data.id.TbResourceId;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResourceExportData {
private TbResourceId id;
private String title;
private ResourceType type;
private ResourceSubType subType;

7
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -114,11 +114,12 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getLink() {
String scope = (tenantId != null && tenantId.isSysTenantId()) ? "system" : "tenant"; // tenantId is null in case of export to git
if (resourceType == ResourceType.IMAGE) {
String type = (tenantId != null && tenantId.isSysTenantId()) ? "system" : "tenant"; // tenantId is null in case of export to git
return "/api/images/" + type + "/" + resourceKey;
return "/api/images/" + scope + "/" + resourceKey;
} else {
return "/api/resource/" + resourceType.name().toLowerCase() + "/" + scope + "/" + resourceKey;
}
return null;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)

28
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardExportData.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.dashboard;
import lombok.Data;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ResourceExportData;
import java.util.List;
@Data
public class DashboardExportData {
private Dashboard dashboard;
private List<ResourceExportData> resources;
}

27
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetExportData.java

@ -1,27 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.widget;
import lombok.Data;
import org.thingsboard.server.common.data.ResourceExportData;
import java.util.List;
@Data
public class WidgetExportData {
private WidgetTypeDetails widgetTypeDetails;
private List<ResourceExportData> resources;
}

7
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java

@ -23,10 +23,14 @@ import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.ArrayList;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JsonPropertyOrder({"fqn", "name", "deprecated", "image", "description", "descriptor", "externalId"})
@ -44,6 +48,8 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI
private WidgetTypeId externalId;
private List<ResourceExportData> resources;
public WidgetTypeDetails() {
super();
}
@ -62,6 +68,7 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI
this.description = widgetTypeDetails.getDescription();
this.tags = widgetTypeDetails.getTags();
this.externalId = widgetTypeDetails.getExternalId();
this.resources = widgetTypeDetails.getResources() != null ? new ArrayList<>(widgetTypeDetails.getResources()) : null;
}
}

28
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.widget;
import lombok.Data;
import org.thingsboard.server.common.data.ResourceExportData;
import java.util.List;
@Data
public class WidgetsExportData {
private List<WidgetTypeDetails> widgetTypesDetails;
private List<ResourceExportData> resources;
}

11
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -50,6 +50,7 @@ import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -83,6 +84,9 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private ImageService imageService;
@Autowired
private ResourceService resourceService;
@Autowired
private DataValidator<Dashboard> dashboardValidator;
@ -158,6 +162,8 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
}
try {
imageService.replaceBase64WithImageUrl(dashboard);
resourceService.replaceResourcesUsageWithUrls(dashboard);
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
@ -390,6 +396,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardDao.existsById(tenantId, dashboardId.getId());
}
@Override
public PageData<DashboardId> findAllDashboardsIds(PageLink pageLink) {
return dashboardDao.findAllIds(pageLink);
}
private final PaginatedRemover<TenantId, DashboardId> tenantDashboardsRemover = new PaginatedRemover<>() {
@Override

19
dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java

@ -56,7 +56,6 @@ import org.thingsboard.server.dao.util.ImageUtils.ProcessedImage;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
@ -568,24 +567,6 @@ public class BaseImageService extends BaseResourceService implements ImageServic
return null;
}
private String encode(String data) {
return encode(data.getBytes(StandardCharsets.UTF_8));
}
private String encode(byte[] data) {
if (data == null || data.length == 0) {
return "";
}
return Base64.getEncoder().encodeToString(data);
}
private String decode(String value) {
if (value == null) {
return null;
}
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
}
@Data(staticConstructor = "of")
private static class UpdateResult {
private final boolean updated;

122
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -30,6 +30,7 @@ import org.thingsboard.common.util.RegexUtils;
import org.thingsboard.server.cache.resourceInfo.ResourceInfoCacheKey;
import org.thingsboard.server.cache.resourceInfo.ResourceInfoEvictEvent;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
@ -52,12 +53,15 @@ import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.UnaryOperator;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -282,64 +286,104 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
}
@Override
public List<TbResourceInfo> processResourcesForExport(Dashboard dashboard) {
return processResourcesForExport(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING);
public boolean replaceResourcesUsageWithUrls(Dashboard dashboard) {
return replaceResourcesUsageWithUrls(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING);
}
@Override
public List<TbResourceInfo> processResourcesForExport(WidgetTypeDetails widgetTypeDetails) {
return processResourcesForExport(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING);
public boolean replaceResourcesUsageWithUrls(WidgetTypeDetails widgetTypeDetails) {
return replaceResourcesUsageWithUrls(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING);
}
private List<TbResourceInfo> processResourcesForExport(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping) {
List<TbResourceInfo> resources = new ArrayList<>();
JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> {
if (StringUtils.isBlank(value)) {
private boolean replaceResourcesUsageWithUrls(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping) {
AtomicBoolean updated = new AtomicBoolean(false);
processResources(jsonNode, mapping, value -> {
if (value.startsWith(DataConstants.TB_RESOURCE_PREFIX + "/api/resource")) { // already a link, ignoring
return value;
}
TbResourceId resourceId;
try {
resourceId = new TbResourceId(UUID.fromString(value));
} catch (IllegalArgumentException e) {
return value;
TbResourceInfo resourceInfo;
if (value.startsWith("tb-resource:")) { // tag with metadata, probably importing
String[] metadata = StringUtils.removeStart(value, "tb-resource:").split(":");
if (metadata.length < 2) {
return value;
}
ResourceType resourceType = ResourceType.valueOf(decode(metadata[0]));
String etag = metadata[1];
resourceInfo = findSystemOrTenantResourceByEtag(tenantId, resourceType, etag);
if (resourceInfo == null) {
return value;
}
} else { // probably importing an old dashboard json where resources are referenced by ids
TbResourceId resourceId;
try {
resourceId = new TbResourceId(UUID.fromString(value));
} catch (IllegalArgumentException e) {
return value;
}
resourceInfo = findResourceInfoById(tenantId, resourceId);
if (resourceInfo == null) {
updated.set(true);
return "";
}
}
TbResourceInfo resourceInfo = findResourceInfoById(tenantId, resourceId);
resources.add(resourceInfo);
return value;
updated.set(true);
return DataConstants.TB_RESOURCE_PREFIX + resourceInfo.getLink();
});
return resources;
return updated.get();
}
@Override
public void processResourcesForImport(Dashboard dashboard, Map<TbResourceId, TbResourceId> importedResources) {
processResourcesForImport(dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING, importedResources);
public List<TbResourceInfo> replaceResourcesUrlsWithTags(Dashboard dashboard) {
return replaceResourcesUrlsWithTags(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING);
}
@Override
public void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map<TbResourceId, TbResourceId> importedResources) {
processResourcesForImport(widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING, importedResources);
public List<TbResourceInfo> replaceResourcesUrlsWithTags(WidgetTypeDetails widgetTypeDetails) {
return replaceResourcesUrlsWithTags(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING);
}
private void processResourcesForImport(JsonNode jsonNode, Map<String, String> mapping, Map<TbResourceId, TbResourceId> importedResources) {
JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> {
if (StringUtils.isBlank(value)) {
private List<TbResourceInfo> replaceResourcesUrlsWithTags(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping) {
List<TbResourceInfo> resources = new ArrayList<>();
processResources(jsonNode, mapping, value -> {
if (!value.startsWith(DataConstants.TB_RESOURCE_PREFIX + "/api/resource/")) {
return value;
}
TbResourceId oldResourceId;
ResourceType resourceType;
String resourceKey;
TenantId resourceTenantId;
try {
oldResourceId = new TbResourceId(UUID.fromString(value));
} catch (IllegalArgumentException e) {
String[] parts = StringUtils.removeStart(value, DataConstants.TB_RESOURCE_PREFIX + "/api/resource/").split("/");
resourceType = ResourceType.valueOf(parts[0].toUpperCase());
String scope = parts[1];
resourceKey = parts[2];
resourceTenantId = scope.equals("system") ? TenantId.SYS_TENANT_ID : tenantId;
} catch (Exception e) {
log.warn("[{}] Invalid resource link '{}'", tenantResourcesRemover, value);
return value;
}
TbResourceId importedResourceId = importedResources.get(oldResourceId);
if (importedResourceId != null) {
return importedResourceId.toString();
TbResourceInfo resourceInfo = findResourceInfoByTenantIdAndKey(resourceTenantId, resourceType, resourceKey);
if (resourceInfo != null) {
resources.add(resourceInfo);
return "tb-resource:" + String.join(":", encode(resourceType.name()), resourceInfo.getEtag());
} else {
return value;
}
});
return resources;
}
private void processResources(JsonNode jsonNode, Map<String, String> mapping, UnaryOperator<String> processor) {
JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> {
if (StringUtils.isBlank(value)) {
return value;
}
return processor.apply(value);
});
}
@Override
@ -391,6 +435,24 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return resourceInfoDao.findSystemOrTenantResourceByEtag(tenantId, resourceType, etag);
}
protected String encode(String data) {
return encode(data.getBytes(StandardCharsets.UTF_8));
}
protected String encode(byte[] data) {
if (data == null || data.length == 0) {
return "";
}
return Base64.getEncoder().encodeToString(data);
}
protected String decode(String value) {
if (value == null) {
return null;
}
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
}
private final PaginatedRemover<TenantId, TbResourceId> tenantResourcesRemover = new PaginatedRemover<>() {
@Override

11
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java

@ -37,6 +37,7 @@ import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -68,6 +69,9 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
@Autowired
protected ImageService imageService;
@Autowired
private ResourceService resourceService;
@Override
public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) {
log.trace("Executing findWidgetTypeById [{}]", widgetTypeId);
@ -95,6 +99,8 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
try {
imageService.replaceBase64WithImageUrl(widgetTypeDetails);
resourceService.replaceResourcesUsageWithUrls(widgetTypeDetails);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).created(widgetTypeDetails.getId() == null).build());
@ -248,6 +254,11 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
bundleWidgetTypesRemover.removeEntities(tenantId, bundleId);
}
@Override
public PageData<WidgetTypeId> findAllWidgetTypesIds(PageLink pageLink) {
return widgetTypeDao.findAllWidgetTypesIds(pageLink);
}
@Override
public void deleteByTenantId(TenantId tenantId) {
deleteWidgetTypesByTenantId(tenantId);

6
ui-ngx/src/app/core/http/dashboard.service.ts

@ -20,7 +20,7 @@ import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { Dashboard, DashboardExportData, DashboardInfo, HomeDashboard, HomeDashboardInfo } from '@shared/models/dashboard.models';
import { Dashboard, DashboardInfo, HomeDashboard, HomeDashboardInfo } from '@shared/models/dashboard.models';
import { WINDOW } from '@core/services/window.service';
import { NavigationEnd, Router } from '@angular/router';
import { filter, map, publishReplay, refCount } from 'rxjs/operators';
@ -71,8 +71,8 @@ export class DashboardService {
return this.http.get<Dashboard>(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config));
}
public exportDashboard(dashboardId: string, config?: RequestConfig): Observable<DashboardExportData> {
return this.http.get<DashboardExportData>(`/api/dashboard/${dashboardId}/export`, defaultHttpOptionsFromConfig(config));
public exportDashboard(dashboardId: string, config?: RequestConfig): Observable<Dashboard> {
return this.http.get<Dashboard>(`/api/dashboard/${dashboardId}?inlineImages=true`, defaultHttpOptionsFromConfig(config));
}
public getDashboardInfo(dashboardId: string, config?: RequestConfig): Observable<DashboardInfo> {

7
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -144,12 +144,10 @@ export class ImportExportService {
public exportDashboard(dashboardId: string) {
this.dashboardService.exportDashboard(dashboardId).subscribe({
next: (exportData) => {
let dashboard = exportData.dashboard;
next: (dashboard) => {
let name = dashboard.title;
name = name.toLowerCase().replace(/\W/g, '_');
exportData.dashboard = this.prepareDashboardExport(dashboard)
this.exportToPc(exportData, name);
this.exportToPc(this.prepareDashboardExport(dashboard), name);
},
error: (e) => {
this.handleExportError(e, 'dashboard.export-failed-error');
@ -157,7 +155,6 @@ export class ImportExportService {
});
}
// FIXME: backward compatibility - support old export structure
public importDashboard(onEditMissingAliases: editMissingAliasesFunction): Observable<Dashboard> {
return this.openImportDialog('dashboard.import', 'dashboard.dashboard-file').pipe(
mergeMap((dashboard: Dashboard) => {

6
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -194,6 +194,7 @@ export interface DashboardConfiguration {
export interface Dashboard extends DashboardInfo {
configuration?: DashboardConfiguration;
dialogRef?: MatDialogRef<any>;
resources?: Array<any>;
}
export interface HomeDashboard extends Dashboard {
@ -209,11 +210,6 @@ export interface DashboardSetup extends Dashboard {
assignedCustomerIds?: Array<string>;
}
export interface DashboardExportData {
dashboard?: Dashboard;
resources: any;
}
export const isPublicDashboard = (dashboard: DashboardInfo): boolean => {
if (dashboard && dashboard.assignedCustomers) {
return dashboard.assignedCustomers

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -249,6 +249,7 @@ export interface WidgetTypeDetails extends WidgetType, ExportableEntity<WidgetTy
image: string;
description: string;
tags: string[];
resources?: Array<any>;
}
export enum DeprecatedFilter {

Loading…
Cancel
Save