From 9f102175384534e2c4a815f11cd42e0c168dbec5 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 29 Oct 2024 12:20:19 +0200 Subject: [PATCH] Reference resources by link in dashboards and widgets --- .../controller/ControllerConstants.java | 3 + .../controller/DashboardController.java | 35 ++--- .../controller/TbResourceController.java | 15 +++ .../controller/WidgetTypeController.java | 65 ++++------ .../install/ThingsboardInstallService.java | 1 + .../entitiy/SimpleTbEntityService.java | 3 +- .../profile/DefaultTbAssetProfileService.java | 3 +- .../customer/DefaultTbCustomerService.java | 3 +- .../dashboard/DefaultTbDashboardService.java | 69 +--------- .../entitiy/dashboard/TbDashboardService.java | 6 - .../DefaultTbDeviceProfileService.java | 3 +- .../bundle/DefaultWidgetsBundleService.java | 3 +- .../type/DefaultWidgetTypeService.java | 110 +--------------- .../widgets/type/TbWidgetTypeService.java | 18 +-- .../resource/DefaultTbResourceService.java | 64 ++++++++- .../service/resource/TbResourceService.java | 9 +- .../rule/DefaultTbRuleChainService.java | 3 +- .../impl/DashboardExportService.java | 1 + .../impl/DefaultEntityExportService.java | 3 + .../impl/WidgetTypeExportService.java | 1 + .../impl/DashboardImportService.java | 1 - .../impl/WidgetTypeImportService.java | 4 +- .../server/controller/AbstractWebTest.java | 12 +- .../controller/DashboardControllerTest.java | 58 +++++++-- .../src/test/resources/logback-test.xml | 4 +- .../dao/dashboard/DashboardService.java | 2 + .../server/dao/resource/ResourceService.java | 9 +- .../server/dao/widget/WidgetTypeService.java | 2 + .../server/common/data/Dashboard.java | 6 + .../server/common/data/DataConstants.java | 2 + .../common/data/ResourceExportData.java | 2 - .../server/common/data/TbResourceInfo.java | 7 +- .../data/dashboard/DashboardExportData.java | 28 ---- .../common/data/widget/WidgetExportData.java | 27 ---- .../common/data/widget/WidgetTypeDetails.java | 7 + .../common/data/widget/WidgetsExportData.java | 28 ---- .../dao/dashboard/DashboardServiceImpl.java | 11 ++ .../server/dao/resource/BaseImageService.java | 19 --- .../dao/resource/BaseResourceService.java | 122 +++++++++++++----- .../dao/widget/WidgetTypeServiceImpl.java | 11 ++ ui-ngx/src/app/core/http/dashboard.service.ts | 6 +- .../import-export/import-export.service.ts | 7 +- .../src/app/shared/models/dashboard.models.ts | 6 +- ui-ngx/src/app/shared/models/widget.models.ts | 1 + 44 files changed, 359 insertions(+), 441 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardExportData.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetExportData.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index a774e9f889..c3666501b5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/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'"; diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index e9efd4d226..4328a2b51c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/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')") diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 1161403e39..3671cec189 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/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) diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index 70f56583e7..e4973a71c3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/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 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 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 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)", diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 4ae8d117be..215f834e27 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/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: diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java index fee468d078..e31c79c98b 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java +++ b/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 { @@ -23,7 +24,7 @@ public interface SimpleTbEntityService { 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); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java index 03850880ae..d11ca22d03 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java +++ b/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 { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java index b14e605b15..ec651b669f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java +++ b/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 { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index 1cfabed966..66794c5ee2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/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 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 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); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java index f923372643..10a727e160 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java +++ b/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 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; - } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java index 35b12d721e..c71b8ce6d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java +++ b/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 { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java index a2c001a018..29b6ae6353 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java +++ b/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 { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java index 9b732a4872..d32fd1bd8f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java +++ b/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 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 widgetTypes = widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(tenantId, bundleId); - List resources = processWidgetTypesForExport(widgetTypes, user); - - WidgetsExportData exportData = new WidgetsExportData(); - exportData.setWidgetTypesDetails(widgetTypes); - exportData.setResources(resources); - return exportData; - } - - private List processWidgetTypesForExport(List widgetTypes, SecurityUser user) throws ThingsboardException { - Map 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 importWidgetTypes(WidgetsExportData exportData, SecurityUser user) throws Exception { - return importWidgetTypes(exportData.getWidgetTypesDetails(), exportData.getResources(), user); - } - - private List importWidgetTypes(List widgetTypesDetails, List resources, SecurityUser user) throws Exception { - Map 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 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; - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java index e5a39910d3..f36ae0b92f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/TbWidgetTypeService.java +++ b/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 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 importWidgetTypes(WidgetsExportData exportData, SecurityUser user) throws Exception; + WidgetTypeDetails save(WidgetTypeDetails widgetTypeDetails, boolean updateExistingByFqn, SecurityUser user) throws Exception; } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 32a51db57c..4f6bcd8405 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/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 exportResources(Dashboard dashboard, SecurityUser user) throws ThingsboardException { + return exportResources(dashboard, imageService::inlineImages, resourceService::replaceResourcesUrlsWithTags, user); + } + + @Override + public List exportResources(WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException { + return exportResources(widgetTypeDetails, imageService::inlineImages, resourceService::replaceResourcesUrlsWithTags, user); + } + + @Override + public void importResources(List 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 List exportResources(T entity, + Function> imagesProcessor, + Function> resourcesProcessor, + SecurityUser user) throws ThingsboardException { + Map 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"); diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 8cbb33db02..0de2cded00 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/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 { String sortOrder, PageLink pageLink); - ResourceExportData exportResource(TbResourceInfo resourceInfo); + List exportResources(Dashboard dashboard, SecurityUser user) throws ThingsboardException; - TbResourceInfo importResource(ResourceExportData exportData, boolean checkExisting, SecurityUser user) throws ThingsboardException; + List exportResources(WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException; + + void importResources(List resources, SecurityUser user) throws Exception; } diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 36841ebe41..732eb29ff3 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/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 { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java index 3b2cb626ac..7e0bcd51ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java @@ -49,6 +49,7 @@ public class DashboardExportService extends BaseEntityExportService ctx, I entityId) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java index 85ac3984fc..c2d78791da 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java @@ -37,6 +37,7 @@ public class WidgetTypeExportService extends BaseEntityExportService 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 TbResourceInfo uploadResource(HttpMethod httpMethod, String url, String filename, String mediaType, byte[] content, List 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); diff --git a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java index 6f8d62a207..94ebc2ba14 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java +++ b/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 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> 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) { diff --git a/application/src/test/resources/logback-test.xml b/application/src/test/resources/logback-test.xml index a9ac276f4d..f43144a24e 100644 --- a/application/src/test/resources/logback-test.xml +++ b/application/src/test/resources/logback-test.xml @@ -17,9 +17,7 @@ - - - + diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index c823add6e2..5f0beb993e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/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 findAllDashboardsIds(PageLink pageLink); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java index cde0d4421e..d69d64889a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java +++ b/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 processResourcesForExport(Dashboard dashboard); + boolean replaceResourcesUsageWithUrls(Dashboard dashboard); - List processResourcesForExport(WidgetTypeDetails widgetTypeDetails); + boolean replaceResourcesUsageWithUrls(WidgetTypeDetails widgetTypeDetails); - void processResourcesForImport(Dashboard dashboard, Map importedResources); + List replaceResourcesUrlsWithTags(Dashboard dashboard); - void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map importedResources); + List replaceResourcesUrlsWithTags(WidgetTypeDetails widgetTypeDetails); TbResource createOrUpdateSystemResource(ResourceType resourceType, String resourceKey, String data); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java index 34b27a7504..9cad249787 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java +++ b/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 findAllWidgetTypesIds(PageLink pageLink); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java index e3db68563e..20495f4ae7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java +++ b/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 resources; + public Dashboard() { super(); } @@ -57,6 +62,7 @@ public class Dashboard extends DashboardInfo implements ExportableEntity(dashboard.getResources()) : null; } @Schema(description = "JSON object with main configuration of the dashboard: layouts, widgets, aliases, etc. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index aef3751f0c..209b0c46ee 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/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() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java index d2c815cc2c..14d27bde6a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceExportData.java +++ b/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; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index b89ee23057..5b634a13d3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -114,11 +114,12 @@ public class TbResourceInfo extends BaseData 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) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardExportData.java deleted file mode 100644 index 91e9f59328..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardExportData.java +++ /dev/null @@ -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 resources; -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetExportData.java deleted file mode 100644 index 04e710510d..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetExportData.java +++ /dev/null @@ -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 resources; -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java index 55bd1cf27a..f69a564936 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java +++ b/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 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; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java deleted file mode 100644 index 44a09e7cab..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsExportData.java +++ /dev/null @@ -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 widgetTypesDetails; - private List resources; - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 6fc75c03df..96db46c1d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/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 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 findAllDashboardsIds(PageLink pageLink) { + return dashboardDao.findAllIds(pageLink); + } + private final PaginatedRemover tenantDashboardsRemover = new PaginatedRemover<>() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java index 88b4ed234a..379fe615a5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java +++ b/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; diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 9ae1dd2298..f684285ef6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/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 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 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 processResourcesForExport(TenantId tenantId, JsonNode jsonNode, Map mapping) { - List resources = new ArrayList<>(); - JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> { - if (StringUtils.isBlank(value)) { + private boolean replaceResourcesUsageWithUrls(TenantId tenantId, JsonNode jsonNode, Map 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 importedResources) { - processResourcesForImport(dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING, importedResources); + public List replaceResourcesUrlsWithTags(Dashboard dashboard) { + return replaceResourcesUrlsWithTags(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING); } @Override - public void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map importedResources) { - processResourcesForImport(widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING, importedResources); + public List replaceResourcesUrlsWithTags(WidgetTypeDetails widgetTypeDetails) { + return replaceResourcesUrlsWithTags(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING); } - private void processResourcesForImport(JsonNode jsonNode, Map mapping, Map importedResources) { - JacksonUtil.replaceAllByMapping(jsonNode, mapping, Collections.emptyMap(), (name, value) -> { - if (StringUtils.isBlank(value)) { + private List replaceResourcesUrlsWithTags(TenantId tenantId, JsonNode jsonNode, Map mapping) { + List 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 mapping, UnaryOperator 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 tenantResourcesRemover = new PaginatedRemover<>() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java index 37d0c4c4ac..882b543400 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java +++ b/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 findAllWidgetTypesIds(PageLink pageLink) { + return widgetTypeDao.findAllWidgetTypesIds(pageLink); + } + @Override public void deleteByTenantId(TenantId tenantId) { deleteWidgetTypesByTenantId(tenantId); diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 0ee4386f7e..3a5025a9ba 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/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(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config)); } - public exportDashboard(dashboardId: string, config?: RequestConfig): Observable { - return this.http.get(`/api/dashboard/${dashboardId}/export`, defaultHttpOptionsFromConfig(config)); + public exportDashboard(dashboardId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/dashboard/${dashboardId}?inlineImages=true`, defaultHttpOptionsFromConfig(config)); } public getDashboardInfo(dashboardId: string, config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index 3cc3debe93..3963a81c1e 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/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 { return this.openImportDialog('dashboard.import', 'dashboard.dashboard-file').pipe( mergeMap((dashboard: Dashboard) => { diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index ad8f56bca6..5ccae04fda 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/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; + resources?: Array; } export interface HomeDashboard extends Dashboard { @@ -209,11 +210,6 @@ export interface DashboardSetup extends Dashboard { assignedCustomerIds?: Array; } -export interface DashboardExportData { - dashboard?: Dashboard; - resources: any; -} - export const isPublicDashboard = (dashboard: DashboardInfo): boolean => { if (dashboard && dashboard.assignedCustomers) { return dashboard.assignedCustomers diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index e8b00ebab4..11332e5acb 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -249,6 +249,7 @@ export interface WidgetTypeDetails extends WidgetType, ExportableEntity; } export enum DeprecatedFilter {