From 55df765916c741173ad95a18ff3fe5221d4ee41b Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 8 Nov 2023 21:30:08 +0200 Subject: [PATCH] Upgrade scripts for images migration --- .../install/ThingsboardInstallService.java | 4 +- .../service/install/InstallScripts.java | 66 ++- .../service/install/update/ImagesUpdater.java | 219 ++++--- .../service/install/InstallScriptsTest.java | 536 ------------------ .../server/dao/resource/ResourceService.java | 2 +- .../server/dao/widget/WidgetTypeService.java | 2 + .../dao/widget/WidgetsBundleService.java | 2 + .../common/data/util/MediaTypeUtils.java | 25 +- .../dao/resource/BaseResourceService.java | 5 +- .../dao/sql/widget/JpaWidgetTypeDao.java | 5 + .../dao/sql/widget/JpaWidgetsBundleDao.java | 6 +- .../dao/sql/widget/WidgetTypeRepository.java | 3 + .../server/dao/widget/WidgetTypeDao.java | 2 + .../dao/widget/WidgetTypeServiceImpl.java | 5 + .../server/dao/widget/WidgetsBundleDao.java | 2 + .../dao/widget/WidgetsBundleServiceImpl.java | 5 + 16 files changed, 256 insertions(+), 633 deletions(-) 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 f37d1f37bc..f95ffdf620 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -280,7 +280,8 @@ public class ThingsboardInstallService { log.info("Updating system data..."); dataUpdateService.upgradeRuleNodes(); systemDataLoaderService.updateSystemWidgets(); - installScripts.updateDashboards(); // fixme: can't work properly if widgets not updated first + installScripts.updateSystemImages(); +// installScripts.migrateTenantImages(); installScripts.loadSystemLwm2mResources(); } log.info("Upgrade finished successfully!"); @@ -323,6 +324,7 @@ public class ThingsboardInstallService { // systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemRules(); + installScripts.updateSystemImages(); installScripts.loadSystemLwm2mResources(); if (loadDemo) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 62232a1637..838d09ed5a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.service.install; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +31,7 @@ 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.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.rule.RuleChain; @@ -51,6 +54,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -80,6 +84,7 @@ public class InstallScripts { public static final String DASHBOARDS_DIR = "dashboards"; public static final String MODELS_LWM2M_DIR = "lwm2m-registry"; public static final String CREDENTIALS_DIR = "credentials"; + public static final String IMAGES_DIR = "images"; public static final String JSON_EXT = ".json"; public static final String XML_EXT = ".xml"; @@ -200,7 +205,8 @@ public class InstallScripts { path -> { try { JsonNode widgetTypeJson = JacksonUtil.toJsonNode(path.toFile()); - saveWidgetType(widgetTypeJson); + WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); + widgetTypeService.saveWidgetType(widgetTypeDetails); } catch (Exception e) { log.error("Unable to load widget type from json: [{}]", path.toString()); throw new RuntimeException("Unable to load widget type from json", e); @@ -217,7 +223,6 @@ public class InstallScripts { JsonNode widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile()); JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class); - imagesUpdater.updateWidgetsBundleImages(widgetsBundle); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); List widgetTypeFqns = new ArrayList<>(); if (widgetsBundleDescriptorJson.has("widgetTypes")) { @@ -225,7 +230,8 @@ public class InstallScripts { widgetTypesArrayJson.forEach( widgetTypeJson -> { try { - var savedWidgetType = saveWidgetType(widgetTypeJson); + WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); + var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails); widgetTypeFqns.add(savedWidgetType.getFqn()); } catch (Exception e) { log.error("Unable to load widget type from json: [{}]", path.toString()); @@ -250,28 +256,58 @@ public class InstallScripts { } } - private WidgetTypeDetails saveWidgetType(JsonNode widgetTypeJson) { - WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); - try { - imagesUpdater.updateWidgetImages(widgetTypeDetails); - } catch (Exception e) { - log.warn("Failed to process images for widget type {}", widgetTypeDetails.getName(), e); - } - return widgetTypeService.saveWidgetType(widgetTypeDetails); + @SneakyThrows + public void updateSystemImages() { + Path imagesDir = Paths.get(getDataDir(), IMAGES_DIR); + Map imageNames = JacksonUtil.OBJECT_MAPPER.readValue(imagesDir.resolve("names.json").toFile(), new TypeReference<>() {}); + + Files.walk(imagesDir) + .filter(path -> path.toFile().isFile()) + .filter(path -> !path.getFileName().toString().endsWith("json")) + .forEach(imageFile -> { + imagesUpdater.updateSystemImage(imageFile, imageNames); + }); } - public void updateDashboards() { + public void migrateTenantImages() { + var widgetsBundles = new PageDataIterable<>(widgetsBundleService::findAllWidgetsBundles, 100); + for (WidgetsBundle widgetsBundle : widgetsBundles) { + try { + boolean updated = imagesUpdater.updateWidgetsBundle(widgetsBundle); + if (updated) { + widgetsBundleService.saveWidgetsBundle(widgetsBundle); + log.info("[{}][{}][{}] Migrated widgets bundle images", widgetsBundle.getTenantId(), widgetsBundle.getId(), widgetsBundle.getTitle()); + } + } catch (Exception e) { + log.error("[{}][{}][{}] Failed to migrate widgets bundle images", widgetsBundle.getTenantId(), widgetsBundle.getId(), widgetsBundle.getTitle(), e); + } + } + + var widgetTypes = new PageDataIterable<>(widgetTypeService::findAllWidgetTypesIds, 1024); + for (WidgetTypeId widgetTypeId : widgetTypes) { + WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(TenantId.SYS_TENANT_ID, widgetTypeId); + try { + boolean updated = imagesUpdater.updateWidget(widgetTypeDetails); + if (updated) { + widgetTypeService.saveWidgetType(widgetTypeDetails); + log.info("[{}][{}][{}] Migrated widget type images", widgetTypeDetails.getTenantId(), widgetTypeDetails.getId(), widgetTypeDetails.getName()); + } + } catch (Exception e) { + log.error("[{}][{}][{}] Failed to migrate widget type images", widgetTypeDetails.getTenantId(), widgetTypeDetails.getId(), widgetTypeDetails.getName(), e); + } + } + var dashboards = new PageDataIterable<>(dashboardService::findAllDashboardsIds, 1024); for (DashboardId dashboardId : dashboards) { Dashboard dashboard = dashboardService.findDashboardById(TenantId.SYS_TENANT_ID, dashboardId); - log.debug("Updating images for dashboard '{}' ({})", dashboard.getTitle(), dashboardId); try { - boolean updated = imagesUpdater.updateDashboardImages(dashboard); + boolean updated = imagesUpdater.updateDashboard(dashboard); if (updated) { dashboardService.saveDashboard(dashboard); + log.info("[{}][{}][{}] Migrated dashboard images", dashboard.getTenantId(), dashboardId, dashboard.getTitle()); } } catch (Exception e) { - log.error("Failed to update images for dashboard '{}' ({})", dashboard.getTitle(), dashboardId, e); + log.error("[{}][{}][{}] Failed to migrate dashboard images", dashboard.getTenantId(), dashboardId, dashboard.getTitle(), e); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java index 051c4eb054..e4b8defc56 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java @@ -1,9 +1,26 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.install.update; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Data; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -18,86 +35,129 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.resource.ResourceService; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; import java.util.List; +import java.util.Map; import java.util.Optional; @Component -//@Profile("install") @Slf4j @RequiredArgsConstructor public class ImagesUpdater { private final ResourceService resourceService; + private static final String IMAGE_NAME_SUFFIX = " - image"; + private static final String BACKGROUND_IMAGE_NAME_SUFFIX = " - background image"; + private static final String BACKGROUND_IMAGE_KEY_SUFFIX = "#background_image"; + private static final String MAP_IMAGE_NAME_SUFFIX = " - map image"; + private static final String MAP_IMAGE_KEY_SUFFIX = "#map_image"; + private static final String MARKER_IMAGE_NAME_SUFFIX = " - marker image "; + private static final String MARKER_IMAGE_KEY_SUFFIX = "#marker_image_"; + + @SneakyThrows + public void updateSystemImage(Path imageFile, Map imageNames) { + String imageKey = imageFile.getFileName().toString(); + String imageName = imageNames.get(imageKey); + if (imageName == null) { + throw new IllegalArgumentException("Image name is missing for " + imageKey + ". Please add it to names.json file"); + } + byte[] imageData = Files.readAllBytes(imageFile); + String mediaType = MediaTypeUtils.fileExtensionToMediaType("image", StringUtils.substringAfterLast(imageKey, ".")); + try { + saveImage(TenantId.SYS_TENANT_ID, imageName, imageKey, imageData, mediaType, null); + } catch (Exception e) { + log.error("Failed to save system image {}", imageKey, e); + } + } - // TODO: solution templates - - - public void updateWidgetImages(WidgetTypeDetails widgetTypeDetails) { - String imageName = widgetTypeDetails.getName() + " - image"; - String imageKey = widgetTypeDetails.getFqn(); - String imageLink = saveImage(imageName, imageKey, widgetTypeDetails.getImage()); - widgetTypeDetails.setImage(imageLink); + public boolean updateDashboard(Dashboard dashboard) { + String imageKeyPrefix = "dashboard_" + dashboard.getUuidId(); + String image = dashboard.getImage(); + ImageSaveResult result = saveImage(dashboard.getTenantId(), dashboard.getTitle() + IMAGE_NAME_SUFFIX, + imageKeyPrefix + ".image", image, null); + dashboard.setImage(result.getLink()); + boolean updated = result.isUpdated(); - JsonNode defaultConfig = JacksonUtil.toJsonNode(widgetTypeDetails.getDescriptor().get("defaultConfig").asText()); - defaultConfig = updateWidgetConfig(TenantId.SYS_TENANT_ID, defaultConfig, imageName, imageKey, imageKey); - ((ObjectNode) widgetTypeDetails.getDescriptor()).put("defaultConfig", defaultConfig.toString()); + for (ObjectNode widgetConfig : dashboard.getWidgetsConfig()) { + String fqn; + if (widgetConfig.has("typeFullFqn")) { + fqn = StringUtils.substringAfter(widgetConfig.get("typeFullFqn").asText(), "."); // removing prefix ('system' or 'tenant') + } else { + fqn = widgetConfig.get("bundleAlias").asText() + "." + widgetConfig.get("typeAlias").asText(); + } + String widgetName = widgetConfig.get("config").get("title").asText(); + updated |= updateWidgetConfig(dashboard.getTenantId(), widgetConfig.get("config"), + dashboard.getTitle() + " - " + widgetName + " widget", + imageKeyPrefix + "." + fqn, fqn); + } + return updated; } - public void updateWidgetsBundleImages(WidgetsBundle widgetsBundle) { - String imageLink = saveImage(widgetsBundle.getTitle(), widgetsBundle.getAlias(), widgetsBundle.getImage()); + public boolean updateWidgetsBundle(WidgetsBundle widgetsBundle) { + String bundleName = widgetsBundle.getTitle(); + String bundleAlias = widgetsBundle.getAlias(); + + String image = widgetsBundle.getImage(); + ImageSaveResult result = saveImage(widgetsBundle.getTenantId(), bundleName + IMAGE_NAME_SUFFIX, bundleAlias, image, bundleAlias); + String imageLink = result.getLink(); widgetsBundle.setImage(imageLink); + + return result.isUpdated(); } - public boolean updateDashboardImages(Dashboard dashboard) { - String imageNamePrefix = dashboard.getTitle(); - String imageKeyPrefix = "dashboard_" + dashboard.getUuidId(); - boolean updated = false; + public boolean updateWidget(WidgetTypeDetails widgetType) { + String widgetName = widgetType.getName(); + String widgetFqn = widgetType.getFqn(); + boolean updated; - String imageLink = saveImage(dashboard.getTenantId(), imageNamePrefix + " - image", imageKeyPrefix + ".image", dashboard.getImage(), null); - dashboard.setImage(imageLink); + String previewImage = widgetType.getImage(); + ImageSaveResult result = saveImage(widgetType.getTenantId(), widgetName + IMAGE_NAME_SUFFIX, widgetFqn, previewImage, widgetFqn); + updated = result.isUpdated(); + widgetType.setImage(result.getLink()); - for (ObjectNode widgetConfig : dashboard.getWidgetsConfig()) { - String alias = widgetConfig.get("bundleAlias").asText() + "." + widgetConfig.get("typeAlias").asText(); - String widgetName = widgetConfig.get("config").get("title").asText(); - updateWidgetConfig(dashboard.getTenantId(), widgetConfig.get("config"), - imageNamePrefix + " - " + widgetName + " widget", - imageKeyPrefix + ".widget." + alias, alias); + JsonNode descriptor = widgetType.getDescriptor(); + if (!descriptor.isObject()) { + return updated; } - + JsonNode defaultConfig = JacksonUtil.toJsonNode(descriptor.get("defaultConfig").asText()); + updated |= updateWidgetConfig(widgetType.getTenantId(), defaultConfig, widgetName, widgetFqn, widgetFqn); + ((ObjectNode) descriptor).put("defaultConfig", defaultConfig.toString()); return updated; } - private JsonNode updateWidgetConfig(TenantId tenantId, JsonNode widgetConfig, - String imageNamePrefix, String imageKeyPrefix, - String fqn) { - ObjectNode widgetSettings = (ObjectNode) widgetConfig.get("settings"); + private boolean updateWidgetConfig(TenantId tenantId, JsonNode widgetConfigJson, String imageNamePrefix, String imageKeyPrefix, String widgetFqn) { + boolean updated = false; + ObjectNode widgetSettings = (ObjectNode) widgetConfigJson.get("settings"); ArrayNode markerImages = (ArrayNode) widgetSettings.get("markerImages"); if (markerImages != null && !markerImages.isEmpty()) { for (int i = 0; i < markerImages.size(); i++) { - String imageName = imageNamePrefix + " - marker image " + (i + 1); - String imageKey = imageKeyPrefix + ".marker_image_" + (i + 1); - String imageLink = saveImage(tenantId, imageName, imageKey, markerImages.get(i).asText(), fqn + ".marker_image_"); - markerImages.set(i, imageLink); + String imageName = imageNamePrefix + MARKER_IMAGE_NAME_SUFFIX + (i + 1); + String imageKey = imageKeyPrefix + MARKER_IMAGE_KEY_SUFFIX + (i + 1); + ImageSaveResult result = saveImage(tenantId, imageName, imageKey, markerImages.get(i).asText(), widgetFqn); + markerImages.set(i, result.getLink()); + updated |= result.isUpdated(); } } String mapImage = getText(widgetSettings, "mapImageUrl"); if (mapImage != null) { - String imageName = imageNamePrefix + " - map image"; - String imageKeySuffix = ".map_image"; - String imageKey = imageKeyPrefix + imageKeySuffix; - String imageLink = saveImage(tenantId, imageName, imageKey, mapImage, fqn + imageKeySuffix); - widgetSettings.put("mapImageUrl", imageLink); + String imageName = imageNamePrefix + MAP_IMAGE_NAME_SUFFIX; + String imageKey = imageKeyPrefix + MAP_IMAGE_KEY_SUFFIX; + ImageSaveResult result = saveImage(tenantId, imageName, imageKey, mapImage, widgetFqn); + widgetSettings.put("mapImageUrl", result.getLink()); + updated |= result.isUpdated(); } String backgroundImage = getText(widgetSettings, "backgroundImageUrl"); if (backgroundImage != null) { - String imageName = imageNamePrefix + " - background image"; - String imageKeySuffix = ".background_image"; - String imageKey = imageKeyPrefix + imageKeySuffix; - String imageLink = saveImage(tenantId, imageName, imageKey, backgroundImage, fqn + imageKeySuffix); - widgetSettings.put("backgroundImageUrl", imageLink); + String imageName = imageNamePrefix + BACKGROUND_IMAGE_NAME_SUFFIX; + String imageKey = imageKeyPrefix + BACKGROUND_IMAGE_KEY_SUFFIX; + ImageSaveResult result = saveImage(tenantId, imageName, imageKey, backgroundImage, widgetFqn); + widgetSettings.put("backgroundImageUrl", result.getLink()); + updated |= result.isUpdated(); } JsonNode backgroundConfigNode = widgetSettings.get("background"); @@ -106,49 +166,47 @@ public class ImagesUpdater { if ("image".equals(getText(backgroundConfig, "type"))) { String imageBase64 = getText(backgroundConfig, "imageBase64"); if (imageBase64 != null) { - String imageName = imageNamePrefix + " - background image"; - String imageKeySuffix = ".background_image"; - String imageKey = imageKeyPrefix + imageKeySuffix; - String imageLink = saveImage(tenantId, imageName, imageKey, imageBase64, fqn + imageKeySuffix); + String imageName = imageNamePrefix + BACKGROUND_IMAGE_NAME_SUFFIX; + String imageKey = imageKeyPrefix + BACKGROUND_IMAGE_KEY_SUFFIX; + ImageSaveResult result = saveImage(tenantId, imageName, imageKey, imageBase64, widgetFqn); backgroundConfig.set("imageBase64", null); - backgroundConfig.put("imageUrl", imageLink); + backgroundConfig.put("imageUrl", result.getLink()); backgroundConfig.put("type", "imageUrl"); + updated |= result.isUpdated(); } } } - - return widgetConfig; - } - - private String getText(JsonNode jsonNode, String field) { - return Optional.ofNullable(jsonNode.get(field)) - .filter(JsonNode::isTextual) - .map(JsonNode::asText).orElse(null); + return updated; } - private String saveImage(String title, String key, String data) { - return saveImage(TenantId.SYS_TENANT_ID, title, key, data, null); - } - private String saveImage(TenantId tenantId, String title, String key, String data, - String existingImageQuery) { + private ImageSaveResult saveImage(TenantId tenantId, String name, String key, String data, + String existingImageQuery) { if (data == null) { - return null; + return new ImageSaveResult(null, false); } String base64Data = StringUtils.substringAfter(data, "base64,"); if (base64Data.isEmpty()) { - return data; + return new ImageSaveResult(data, false); } + String imageMediaType = StringUtils.substringBetween(data, "data:", ";base64"); - String extension = MediaTypeUtils.getFileExtension(imageMediaType); + String extension = MediaTypeUtils.mediaTypeToFileExtension(imageMediaType); key += "." + extension; + byte[] imageData = Base64.getDecoder().decode(base64Data); + String imageLink = saveImage(tenantId, name, key, imageData, imageMediaType, existingImageQuery); + return new ImageSaveResult(imageLink, !imageLink.equals(data)); + } + + private String saveImage(TenantId tenantId, String name, String key, byte[] imageData, String mediaType, + String existingImageQuery) { TbResourceInfo resourceInfo = resourceService.findResourceInfoByTenantIdAndKey(tenantId, ResourceType.IMAGE, key); if (resourceInfo == null && !tenantId.isSysTenantId() && existingImageQuery != null) { - List existing = resourceService.findByTenantIdAndDataAndKeyStartingWith(TenantId.SYS_TENANT_ID, base64Data, existingImageQuery); - if (!existing.isEmpty()) { - resourceInfo = existing.get(0); - if (existing.size() > 1) { + List existingSystemImages = resourceService.findByTenantIdAndDataAndKeyStartingWith(TenantId.SYS_TENANT_ID, imageData, existingImageQuery); + if (!existingSystemImages.isEmpty()) { + resourceInfo = existingSystemImages.get(0); + if (existingSystemImages.size() > 1) { log.warn("Found more than one system image resources for key {}", existingImageQuery); } log.info("Using system image {} for {}", resourceInfo.getLink(), key); @@ -166,15 +224,26 @@ public class ImagesUpdater { } else { return resourceInfo.getLink(); } - resource.setTitle(title); + resource.setTitle(name); resource.setFileName(key); - resource.setMediaType(imageMediaType); - resource.setBase64Data(base64Data); + resource.setMediaType(mediaType); + resource.setData(imageData); resource = resourceService.saveResource(resource); - log.info("[{}] {} image '{}' {} ({})", tenantId, resourceInfo == null ? "Created" : "Updated", - resource.getTitle(), resource.getResourceKey(), resource.getLink()); + log.info("[{}] {} image '{}' ({})", tenantId, resourceInfo == null ? "Created" : "Updated", + resource.getTitle(), resource.getResourceKey()); return resource.getLink(); + } + + private String getText(JsonNode jsonNode, String field) { + return Optional.ofNullable(jsonNode.get(field)) + .filter(JsonNode::isTextual) + .map(JsonNode::asText).orElse(null); + } + @Data + public static class ImageSaveResult { + private final String link; + private final boolean updated; } } diff --git a/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java b/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java index af9ec8267d..26eead0ec7 100644 --- a/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java +++ b/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java @@ -23,9 +23,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.ResourceType; -import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; @@ -39,7 +36,6 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.usagerecord.ApiLimitService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; -import org.thingsboard.server.service.install.update.ImagesUpdater; import java.io.IOException; import java.nio.file.Path; @@ -50,8 +46,6 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @Slf4j @SpringBootTest(classes = {InstallScripts.class, RuleChainDataValidator.class}) @@ -78,27 +72,12 @@ class InstallScriptsTest { ApiLimitService apiLimitService; @SpyBean RuleChainDataValidator ruleChainValidator; - @SpyBean - ImagesUpdater imagesUpdater; - TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da")); @BeforeEach void setUp() { willReturn(true).given(tenantService).tenantExists(tenantId); willReturn(true).given(apiLimitService).checkEntitiesLimit(any(), any()); - - when(resourceService.saveResource(any())).thenAnswer(inv -> { - TbResource resource = inv.getArgument(0); - if (resource.getResourceType() == ResourceType.IMAGE) { - resource.setLink(String.format("/api/images/%s/%s", - resource.getTenantId().isSysTenantId() ? - "system" : resource.getTenantId().toString(), - resource.getResourceKey() - )); - } - return resource; - }); } @Test @@ -137,519 +116,4 @@ class InstallScriptsTest { .containsExactlyInAnyOrderElementsOf(Collections.emptyList()); } - @Test - public void testWidgetsUpdate() throws Exception { - installScripts.loadSystemWidgets(); - } - - @Test - public void testImagesUpdater() { - ImagesUpdater imagesUpdater = new ImagesUpdater(resourceService); - Dashboard dashboard = new Dashboard(); - dashboard.setConfiguration(JacksonUtil.toJsonNode(dashboardConfig)); - dashboard.setTenantId(tenantId); - imagesUpdater.updateDashboardImages(dashboard); - System.err.println("Updated config: " + dashboard.getConfiguration().toPrettyString()); - } - - - public static final String dashboardConfig = "{\n" + - " \"description\": \"\",\n" + - " \"widgets\": {\n" + - " \"24b26b1e-bdd0-8b2b-2a96-be150b21ae5e\": {\n" + - " \"typeFullFqn\": \"system.cards.value_card\",\n" + - " \"type\": \"latest\",\n" + - " \"sizeX\": 3,\n" + - " \"sizeY\": 3,\n" + - " \"config\": {\n" + - " \"datasources\": [\n" + - " {\n" + - " \"type\": \"device\",\n" + - " \"name\": \"\",\n" + - " \"deviceId\": \"09bfc8f0-b376-11ed-af9a-5f9d1fc4febb\",\n" + - " \"dataKeys\": [\n" + - " {\n" + - " \"name\": \"temperature\",\n" + - " \"type\": \"timeseries\",\n" + - " \"label\": \"Temperature\",\n" + - " \"color\": \"#2196f3\",\n" + - " \"settings\": {},\n" + - " \"_hash\": 0.790442736163091\n" + - " }\n" + - " ],\n" + - " \"alarmFilterConfig\": {\n" + - " \"statusList\": [\n" + - " \"ACTIVE\"\n" + - " ]\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"timewindow\": {\n" + - " \"displayValue\": \"\",\n" + - " \"selectedTab\": 0,\n" + - " \"realtime\": {\n" + - " \"realtimeType\": 1,\n" + - " \"interval\": 1000,\n" + - " \"timewindowMs\": 60000,\n" + - " \"quickInterval\": \"CURRENT_DAY\"\n" + - " },\n" + - " \"history\": {\n" + - " \"historyType\": 0,\n" + - " \"interval\": 1000,\n" + - " \"timewindowMs\": 60000,\n" + - " \"fixedTimewindow\": {\n" + - " \"startTimeMs\": 1698052629317,\n" + - " \"endTimeMs\": 1698139029317\n" + - " },\n" + - " \"quickInterval\": \"CURRENT_DAY\"\n" + - " },\n" + - " \"aggregation\": {\n" + - " \"type\": \"AVG\",\n" + - " \"limit\": 25000\n" + - " }\n" + - " },\n" + - " \"showTitle\": false,\n" + - " \"backgroundColor\": \"rgba(0, 0, 0, 0)\",\n" + - " \"color\": \"rgba(0, 0, 0, 0.87)\",\n" + - " \"padding\": \"0px\",\n" + - " \"settings\": {\n" + - " \"labelPosition\": \"top\",\n" + - " \"layout\": \"square\",\n" + - " \"showLabel\": true,\n" + - " \"labelFont\": {\n" + - " \"family\": \"Roboto\",\n" + - " \"size\": 16,\n" + - " \"sizeUnit\": \"px\",\n" + - " \"style\": \"normal\",\n" + - " \"weight\": \"500\"\n" + - " },\n" + - " \"labelColor\": {\n" + - " \"type\": \"constant\",\n" + - " \"color\": \"rgba(0, 0, 0, 0.87)\",\n" + - " \"colorFunction\": \"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"\n" + - " },\n" + - " \"showIcon\": true,\n" + - " \"iconSize\": 40,\n" + - " \"iconSizeUnit\": \"px\",\n" + - " \"icon\": \"thermostat\",\n" + - " \"iconColor\": {\n" + - " \"type\": \"constant\",\n" + - " \"color\": \"#5469FF\",\n" + - " \"colorFunction\": \"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"\n" + - " },\n" + - " \"valueFont\": {\n" + - " \"family\": \"Roboto\",\n" + - " \"size\": 52,\n" + - " \"sizeUnit\": \"px\",\n" + - " \"style\": \"normal\",\n" + - " \"weight\": \"500\"\n" + - " },\n" + - " \"valueColor\": {\n" + - " \"type\": \"constant\",\n" + - " \"color\": \"rgba(0, 0, 0, 0.87)\",\n" + - " \"colorFunction\": \"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"\n" + - " },\n" + - " \"showDate\": true,\n" + - " \"dateFormat\": {\n" + - " \"format\": null,\n" + - " \"lastUpdateAgo\": true,\n" + - " \"custom\": false\n" + - " },\n" + - " \"dateFont\": {\n" + - " \"family\": \"Roboto\",\n" + - " \"size\": 12,\n" + - " \"sizeUnit\": \"px\",\n" + - " \"style\": \"normal\",\n" + - " \"weight\": \"500\"\n" + - " },\n" + - " \"dateColor\": {\n" + - " \"type\": \"constant\",\n" + - " \"color\": \"rgba(0, 0, 0, 0.38)\",\n" + - " \"colorFunction\": \"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"\n" + - " },\n" + - " \"background\": {\n" + - " \"type\": \"image\",\n" + - " \"imageBase64\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wCEAAQEBAQEBAUFBQUHBwYHBwoJCAgJCg8KCwoLCg8WDhAODhAOFhQYExITGBQjHBgYHCMpIiAiKTEsLDE+Oz5RUW0BBAQEBAQEBQUFBQcHBgcHCgkICAkKDwoLCgsKDxYOEA4OEA4WFBgTEhMYFCMcGBgcIykiICIpMSwsMT47PlFRbf/CABEIAtAEsAMBEQACEQEDEQH/xAA2AAEAAgIDAQEAAAAAAAAAAAAABQYEBwIDCAEJAQEAAwEBAQEAAAAAAAAAAAAAAQIDBAUGB//aAAwDAQACEAMQAAAA9/AAAAAAAXwsX42s7RHUMhntDzk8zlsOrsWZoeolQ8zbG7KF+Bf/Z\",\n" + - " \"imageUrl\": \"http://localhost:8080/api/resource/myimage\",\n" + - " \"color\": \"#fff\",\n" + - " \"overlay\": {\n" + - " \"enabled\": false,\n" + - " \"color\": \"rgba(255,255,255,0.72)\",\n" + - " \"blur\": 3\n" + - " }\n" + - " },\n" + - " \"autoScale\": true\n" + - " },\n" + - " \"title\": \"Value card\",\n" + - " \"dropShadow\": true,\n" + - " \"enableFullscreen\": false,\n" + - " \"titleStyle\": {\n" + - " \"fontSize\": \"16px\",\n" + - " \"fontWeight\": 400\n" + - " },\n" + - " \"units\": \"°C\",\n" + - " \"decimals\": 0,\n" + - " \"useDashboardTimewindow\": true,\n" + - " \"showLegend\": false,\n" + - " \"widgetStyle\": {},\n" + - " \"actions\": {},\n" + - " \"configMode\": \"basic\",\n" + - " \"displayTimewindow\": true,\n" + - " \"margin\": \"0px\",\n" + - " \"borderRadius\": \"0px\",\n" + - " \"widgetCss\": \"\",\n" + - " \"pageSize\": 1024,\n" + - " \"noDataDisplayMessage\": \"\",\n" + - " \"showTitleIcon\": false,\n" + - " \"titleTooltip\": \"\",\n" + - " \"titleFont\": {\n" + - " \"size\": 12,\n" + - " \"sizeUnit\": \"px\",\n" + - " \"family\": null,\n" + - " \"weight\": null,\n" + - " \"style\": null,\n" + - " \"lineHeight\": \"1.6\"\n" + - " },\n" + - " \"titleIcon\": \"\",\n" + - " \"iconColor\": \"rgba(0, 0, 0, 0.87)\",\n" + - " \"iconSize\": \"14px\",\n" + - " \"timewindowStyle\": {\n" + - " \"showIcon\": true,\n" + - " \"iconSize\": \"14px\",\n" + - " \"icon\": \"query_builder\",\n" + - " \"iconPosition\": \"left\",\n" + - " \"font\": {\n" + - " \"size\": 12,\n" + - " \"sizeUnit\": \"px\",\n" + - " \"family\": null,\n" + - " \"weight\": null,\n" + - " \"style\": null,\n" + - " \"lineHeight\": \"1\"\n" + - " },\n" + - " \"color\": null\n" + - " }\n" + - " },\n" + - " \"row\": 0,\n" + - " \"col\": 0,\n" + - " \"id\": \"24b26b1e-bdd0-8b2b-2a96-be150b21ae5e\"\n" + - " },\n" + - " \"01ad2980-87c8-5813-18a2-47833d8f6df7\": {\n" + - " \"typeFullFqn\": \"system.date.date_range_navigator\",\n" + - " \"type\": \"static\",\n" + - " \"sizeX\": 5,\n" + - " \"sizeY\": 5.5,\n" + - " \"config\": {\n" + - " \"datasources\": [\n" + - " {\n" + - " \"type\": \"static\",\n" + - " \"name\": \"function\",\n" + - " \"dataKeys\": [\n" + - " {\n" + - " \"name\": \"f(x)\",\n" + - " \"type\": \"function\",\n" + - " \"label\": \"Random\",\n" + - " \"color\": \"#2196f3\",\n" + - " \"settings\": {},\n" + - " \"_hash\": 0.15479322438769105,\n" + - " \"funcBody\": \"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"timewindow\": {\n" + - " \"realtime\": {\n" + - " \"timewindowMs\": 60000\n" + - " }\n" + - " },\n" + - " \"showTitle\": true,\n" + - " \"backgroundColor\": \"#C32F2F\",\n" + - " \"color\": \"rgba(0, 0, 0, 0.87)\",\n" + - " \"padding\": \"8px\",\n" + - " \"settings\": {\n" + - " \"defaultInterval\": \"week\",\n" + - " \"stepSize\": \"day\",\n" + - " \"useSessionStorage\": true\n" + - " },\n" + - " \"title\": \"Date-range-navigator\",\n" + - " \"dropShadow\": true,\n" + - " \"enableFullscreen\": true,\n" + - " \"widgetStyle\": {},\n" + - " \"titleStyle\": {\n" + - " \"fontSize\": \"16px\",\n" + - " \"fontWeight\": 400\n" + - " },\n" + - " \"useDashboardTimewindow\": true,\n" + - " \"showLegend\": false,\n" + - " \"actions\": {},\n" + - " \"showTitleIcon\": false,\n" + - " \"titleTooltip\": \"\",\n" + - " \"widgetCss\": \"\",\n" + - " \"pageSize\": 1024,\n" + - " \"noDataDisplayMessage\": \"\"\n" + - " },\n" + - " \"row\": 0,\n" + - " \"col\": 0,\n" + - " \"id\": \"01ad2980-87c8-5813-18a2-47833d8f6df7\"\n" + - " },\n" + - " \"6dc68681-97fe-dca5-5df6-b5006797c9eb\": {\n" + - " \"typeFullFqn\": \"system.maps_v2.image_map\",\n" + - " \"type\": \"latest\",\n" + - " \"sizeX\": 8.5,\n" + - " \"sizeY\": 6.5,\n" + - " \"config\": {\n" + - " \"datasources\": [\n" + - " {\n" + - " \"type\": \"entity\",\n" + - " \"name\": \"\",\n" + - " \"entityAliasId\": \"50e40fe7-fd33-e93e-88f6-212acfa1b311\",\n" + - " \"filterId\": null,\n" + - " \"dataKeys\": [\n" + - " {\n" + - " \"name\": \"pressure\",\n" + - " \"type\": \"timeseries\",\n" + - " \"label\": \"pressure\",\n" + - " \"color\": \"#2196f3\",\n" + - " \"settings\": {},\n" + - " \"_hash\": 0.38936754782620264\n" + - " }\n" + - " ],\n" + - " \"alarmFilterConfig\": {\n" + - " \"statusList\": [\n" + - " \"ACTIVE\"\n" + - " ]\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"timewindow\": {\n" + - " \"displayValue\": \"\",\n" + - " \"selectedTab\": 0,\n" + - " \"realtime\": {\n" + - " \"realtimeType\": 1,\n" + - " \"interval\": 1000,\n" + - " \"timewindowMs\": 60000,\n" + - " \"quickInterval\": \"CURRENT_DAY\"\n" + - " },\n" + - " \"history\": {\n" + - " \"historyType\": 0,\n" + - " \"interval\": 1000,\n" + - " \"timewindowMs\": 60000,\n" + - " \"fixedTimewindow\": {\n" + - " \"startTimeMs\": 1698064891779,\n" + - " \"endTimeMs\": 1698151291779\n" + - " },\n" + - " \"quickInterval\": \"CURRENT_DAY\"\n" + - " },\n" + - " \"aggregation\": {\n" + - " \"type\": \"AVG\",\n" + - " \"limit\": 25000\n" + - " }\n" + - " },\n" + - " \"showTitle\": true,\n" + - " \"backgroundColor\": \"#fff\",\n" + - " \"color\": \"rgba(0, 0, 0, 0.87)\",\n" + - " \"padding\": \"8px\",\n" + - " \"settings\": {\n" + - " \"provider\": \"image-map\",\n" + - " \"gmApiKey\": \"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\n" + - " \"gmDefaultMapType\": \"roadmap\",\n" + - " \"mapProvider\": \"OpenStreetMap.Mapnik\",\n" + - " \"useCustomProvider\": false,\n" + - " \"customProviderTileUrl\": \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\n" + - " \"mapProviderHere\": \"HERE.normalDay\",\n" + - " \"credentials\": {\n" + - " \"useV3\": true,\n" + - " \"app_id\": \"AhM6TzD9ThyK78CT3ptx\",\n" + - " \"app_code\": \"p6NPiITB3Vv0GMUFnkLOOg\",\n" + - " \"apiKey\": \"kVXykxAfZ6LS4EbCTO02soFVfjA7HoBzNVVH9u7nzoE\"\n" + - " },\n" + - " \"mapImageUrl\": \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyIAAAKqCAYAAADG2epfAAAABHNCSVQICAgIfAhkiAAAIABJREFUeJzs3Xd4VFX+x/H3nZ6e0ORK5CYII=\",\n" + - " \"tmApiKey\": \"84d6d83e0e51e481e50454ccbe8986b\",\n" + - " \"tmDefaultMapType\": \"roadmap\",\n" + - " \"latKeyName\": \"latitude\",\n" + - " \"lngKeyName\": \"longitude\",\n" + - " \"xPosKeyName\": \"xPos\",\n" + - " \"yPosKeyName\": \"yPos\",\n" + - " \"defaultCenterPosition\": \"0,0\",\n" + - " \"disableScrollZooming\": false,\n" + - " \"disableDoubleClickZooming\": false,\n" + - " \"disableZoomControl\": false,\n" + - " \"fitMapBounds\": true,\n" + - " \"useDefaultCenterPosition\": false,\n" + - " \"mapPageSize\": 16384,\n" + - " \"markerOffsetX\": 0.5,\n" + - " \"markerOffsetY\": 1,\n" + - " \"posFunction\": \"return {x: origXPos, y: origYPos};\",\n" + - " \"draggableMarker\": false,\n" + - " \"showLabel\": true,\n" + - " \"useLabelFunction\": false,\n" + - " \"label\": \"${entityName}\",\n" + - " \"showTooltip\": true,\n" + - " \"showTooltipAction\": \"click\",\n" + - " \"autocloseTooltip\": true,\n" + - " \"useTooltipFunction\": false,\n" + - " \"tooltipPattern\": \"${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See advanced settings for details\",\n" + - " \"tooltipOffsetX\": 0,\n" + - " \"tooltipOffsetY\": -1,\n" + - " \"color\": \"#fe7569\",\n" + - " \"useColorFunction\": true,\n" + - " \"colorFunction\": \"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\n" + - " \"useMarkerImageFunction\": true,\n" + - " \"markerImageSize\": 34,\n" + - " \"markerImageFunction\": \"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\n" + - " \"markerImages\": [\n" + - " \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAB/CAYAAAD4mHJdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACWAAAAlgB7MGOJQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAwgSURBVGiB7Zt5cBT3lce/v18fc89oRoPEIRBCHIUxp2ywCAgIxLExvoidZIFNxXE2VXHirIO3aqtSseM43qpNeZfYKecox3bhpJykYgd2w45TMcyQHIAOgBcBbAUUJI5uOM/wcaHmf3g9UM7QAAAABJRU5ErkJggg==\",\n" + - " \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAB/CAYAAAD4mHJdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACWAAAAlgB7MGOJQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAA3vSURBVGiB7Vt7cFzVef+dc+/d90OrJyO/JSOFqAtyOKzKo83MLgAkgA2AAQB+ADgCfAzjBGIsPxfh/6wbDK7xbMFYAAAAASUVORK5CYII=\",\n" + - " \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAB/CAYAAAD4mHJdg7EC8/8BoAc0AekgE+B/cAWpVTqSMb/AlY1WXIncMcxAAAAAElFTkSuQmCC\",\n" + - " \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAB/CAYAAAD4mHJdJRU5ErkJggg==\"\n" + - " ],\n" + - " \"showPolygon\": false,\n" + - " \"polygonKeyName\": \"perimeter\",\n" + - " \"editablePolygon\": false,\n" + - " \"showPolygonLabel\": false,\n" + - " \"usePolygonLabelFunction\": false,\n" + - " \"polygonLabel\": \"${entityName}\",\n" + - " \"showPolygonTooltip\": false,\n" + - " \"showPolygonTooltipAction\": \"click\",\n" + - " \"autoClosePolygonTooltip\": true,\n" + - " \"usePolygonTooltipFunction\": false,\n" + - " \"polygonTooltipPattern\": \"${entityName}

TimeStamp: ${ts:7}\",\n" + - " \"polygonColor\": \"#3388ff\",\n" + - " \"polygonOpacity\": 0.2,\n" + - " \"usePolygonColorFunction\": false,\n" + - " \"polygonStrokeColor\": \"#3388ff\",\n" + - " \"polygonStrokeOpacity\": 1,\n" + - " \"polygonStrokeWeight\": 3,\n" + - " \"usePolygonStrokeColorFunction\": false,\n" + - " \"showCircle\": false,\n" + - " \"circleKeyName\": \"perimeter\",\n" + - " \"editableCircle\": false,\n" + - " \"showCircleLabel\": false,\n" + - " \"useCircleLabelFunction\": false,\n" + - " \"circleLabel\": \"${entityName}\",\n" + - " \"showCircleTooltip\": false,\n" + - " \"showCircleTooltipAction\": \"click\",\n" + - " \"autoCloseCircleTooltip\": true,\n" + - " \"useCircleTooltipFunction\": false,\n" + - " \"circleTooltipPattern\": \"${entityName}

TimeStamp: ${ts:7}\",\n" + - " \"circleFillColor\": \"#3388ff\",\n" + - " \"circleFillColorOpacity\": 0.2,\n" + - " \"useCircleFillColorFunction\": false,\n" + - " \"circleStrokeColor\": \"#3388ff\",\n" + - " \"circleStrokeOpacity\": 1,\n" + - " \"circleStrokeWeight\": 3,\n" + - " \"useCircleStrokeColorFunction\": false\n" + - " },\n" + - " \"title\": \"Image Map\",\n" + - " \"dropShadow\": true,\n" + - " \"enableFullscreen\": true,\n" + - " \"titleStyle\": {\n" + - " \"fontSize\": \"16px\",\n" + - " \"fontWeight\": 400\n" + - " },\n" + - " \"useDashboardTimewindow\": true,\n" + - " \"showLegend\": false,\n" + - " \"widgetStyle\": {},\n" + - " \"actions\": {},\n" + - " \"displayTimewindow\": true\n" + - " },\n" + - " \"row\": 0,\n" + - " \"col\": 0,\n" + - " \"id\": \"6dc68681-97fe-dca5-5df6-b5006797c9eb\"\n" + - " }\n" + - " },\n" + - " \"states\": {\n" + - " \"default\": {\n" + - " \"name\": \"Images\",\n" + - " \"root\": true,\n" + - " \"layouts\": {\n" + - " \"main\": {\n" + - " \"widgets\": {\n" + - " \"24b26b1e-bdd0-8b2b-2a96-be150b21ae5e\": {\n" + - " \"sizeX\": 6,\n" + - " \"sizeY\": 5,\n" + - " \"row\": 0,\n" + - " \"col\": 18\n" + - " },\n" + - " \"01ad2980-87c8-5813-18a2-47833d8f6df7\": {\n" + - " \"sizeX\": 5,\n" + - " \"sizeY\": 5,\n" + - " \"row\": 0,\n" + - " \"col\": 13\n" + - " },\n" + - " \"6dc68681-97fe-dca5-5df6-b5006797c9eb\": {\n" + - " \"sizeX\": 8,\n" + - " \"sizeY\": 6,\n" + - " \"row\": 6,\n" + - " \"col\": 13\n" + - " }\n" + - " },\n" + - " \"gridSettings\": {\n" + - " \"backgroundColor\": \"#eeeeee\",\n" + - " \"columns\": 24,\n" + - " \"margin\": 10,\n" + - " \"outerMargin\": true,\n" + - " \"backgroundSizeMode\": \"auto\",\n" + - " \"autoFillHeight\": false,\n" + - " \"backgroundImageUrl\": \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyIAAAKqCAYAAADG2epfAAAABHNCSVQICAgIfAhkiAAAIABJREFUeJzscvuw4MRvxaz5gVL8owhtF0n7gs8zcmnLxWDL3sejVwbSPaER4uzt4fl4saeVj6pOIiIjcIP4fn4t1aCx7Vb0AAAAASUVORK5CYII=\",\n" + - " \"mobileAutoFillHeight\": false,\n" + - " \"mobileRowHeight\": 70\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " },\n" + - " \"entityAliases\": {\n" + - " \"50e40fe7-fd33-e93e-88f6-212acfa1b311\": {\n" + - " \"id\": \"50e40fe7-fd33-e93e-88f6-212acfa1b311\",\n" + - " \"alias\": \"aa\",\n" + - " \"filter\": {\n" + - " \"type\": \"entityList\",\n" + - " \"resolveMultiple\": true,\n" + - " \"entityType\": \"DEVICE\",\n" + - " \"entityList\": [\n" + - " \"5a6c89e0-5ea9-11ed-8ee5-3570b5c0f66a\"\n" + - " ]\n" + - " }\n" + - " }\n" + - " },\n" + - " \"filters\": {},\n" + - " \"timewindow\": {\n" + - " \"hideInterval\": false,\n" + - " \"hideLastInterval\": false,\n" + - " \"hideQuickInterval\": false,\n" + - " \"hideAggregation\": false,\n" + - " \"hideAggInterval\": false,\n" + - " \"hideTimezone\": false,\n" + - " \"selectedTab\": 1,\n" + - " \"history\": {\n" + - " \"historyType\": 1,\n" + - " \"fixedTimewindow\": {\n" + - " \"startTimeMs\": 1697576400000,\n" + - " \"endTimeMs\": 1698181199999\n" + - " },\n" + - " \"interval\": 1210000\n" + - " },\n" + - " \"aggregation\": {\n" + - " \"type\": \"AVG\",\n" + - " \"limit\": 25000\n" + - " }\n" + - " },\n" + - " \"settings\": {\n" + - " \"stateControllerId\": \"entity\",\n" + - " \"showTitle\": false,\n" + - " \"showDashboardsSelect\": true,\n" + - " \"showEntitiesSelect\": true,\n" + - " \"showDashboardTimewindow\": true,\n" + - " \"showDashboardExport\": true,\n" + - " \"toolbarAlwaysOpen\": true,\n" + - " \"titleColor\": \"rgba(0,0,0,0.870588)\",\n" + - " \"showDashboardLogo\": true,\n" + - " \"dashboardLogoUrl\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wCEAAQEBAQEBAUFBQUHBwYHBwoJCAgJCg8KCwoLCg8WDhAODhAOFhQYExITGBQjHBgYHCMpIiAiKTEsLDE+Oz5RlQ8zbG7KF+Bf/Z\",\n" + - " \"hideToolbar\": false,\n" + - " \"showFilters\": true,\n" + - " \"showUpdateDashboardImage\": true,\n" + - " \"dashboardCss\": \"\"\n" + - " }\n" + - "}"; - } 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 33fece3292..7240b1b742 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 @@ -60,6 +60,6 @@ public interface ResourceService extends EntityDaoService { long sumDataSizeByTenantId(TenantId tenantId); - List findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, String base64Data, String query); + List findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, byte[] data, String query); } 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 d8df5d61a3..6db79821b9 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 @@ -62,4 +62,6 @@ public interface WidgetTypeService extends EntityDaoService { void deleteWidgetTypesByTenantId(TenantId tenantId); + PageData findAllWidgetTypesIds(PageLink pageLink); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java index a836fb893d..224cb9511f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java @@ -46,6 +46,8 @@ public interface WidgetsBundleService extends EntityDaoService { List findAllTenantWidgetsBundlesByTenantId(TenantId tenantId); + PageData findAllWidgetsBundles(PageLink pageLink); + void deleteWidgetsBundlesByTenantId(TenantId tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java index 2858293499..4170cc738d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java @@ -1,7 +1,23 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.util; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import java.util.Map; @@ -14,9 +30,16 @@ public class MediaTypeUtils { "svg+xml", "svg" ); - public static String getFileExtension(String mimeType) { + public static String mediaTypeToFileExtension(String mimeType) { String subtype = MimeTypeUtils.parseMimeType(mimeType).getSubtype(); return mappings.getOrDefault(subtype, subtype); } + public static String fileExtensionToMediaType(String type, String extension) { + String subtype = mappings.entrySet().stream() + .filter(mapping -> mapping.getValue().equals(extension)) + .map(Map.Entry::getKey).findFirst().orElse(extension); + return new MimeType(type, subtype).toString(); + } + } 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 c896376bc4..a045a06290 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 @@ -44,7 +44,6 @@ import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.validator.ResourceDataValidator; -import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -222,8 +221,8 @@ public class BaseResourceService extends AbstractCachedEntityService findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, String base64Data, String query) { - String etag = calculateEtag(Base64.getDecoder().decode(base64Data)); + public List findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, byte[] data, String query) { + String etag = calculateEtag(data); return resourceInfoDao.findByTenantIdAndEtagAndKeyStartingWith(tenantId, etag, query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java index 7700e71bc4..a821191629 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java @@ -225,6 +225,11 @@ public class JpaWidgetTypeDao extends JpaAbstractDao findAllWidgetTypesIds(PageLink pageLink) { + return DaoUtil.pageToPageData(widgetTypeRepository.findAllIds(DaoUtil.toPageable(pageLink)).map(WidgetTypeId::new)); + } + @Override public EntityType getEntityType() { return EntityType.WIDGET_TYPE; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java index a5051a2747..2475da8fb7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java @@ -33,7 +33,6 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -103,6 +102,11 @@ public class JpaWidgetsBundleDao extends JpaAbstractDao findAllWidgetsBundles(PageLink pageLink) { + return DaoUtil.toPageData(widgetsBundleRepository.findAll(DaoUtil.toPageable(pageLink))); + } + private PageData findTenantWidgetsBundlesByTenantIds(List tenantIds, boolean fullSearch, PageLink pageLink) { if (fullSearch) { return DaoUtil.toPageData( diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java index f4b3dec5bb..d85f486a05 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java @@ -76,4 +76,7 @@ public interface WidgetTypeRepository extends JpaRepository findAllIds(Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java index 96d93a34f1..55fb5bcff4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java @@ -108,4 +108,6 @@ public interface WidgetTypeDao extends Dao, ExportableEntityD void removeWidgetTypeFromWidgetsBundle(UUID widgetsBundleId, UUID widgetTypeId); + PageData findAllWidgetTypesIds(PageLink pageLink); + } 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 2e347f2b62..975e0cc9a1 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 @@ -221,6 +221,11 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { tenantWidgetTypeRemover.removeEntities(tenantId, tenantId); } + @Override + public PageData findAllWidgetTypesIds(PageLink pageLink) { + return widgetTypeDao.findAllWidgetTypesIds(pageLink); + } + @Override public Optional> findEntity(TenantId tenantId, EntityId entityId) { return Optional.ofNullable(findWidgetTypeById(tenantId, new WidgetTypeId(entityId.getId()))); diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java index 9e0d70dcd8..e83da6cc8e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java @@ -83,5 +83,7 @@ public interface WidgetsBundleDao extends Dao, ExportableEntityDa */ PageData findTenantWidgetsBundlesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink); + PageData findAllWidgetsBundles(PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java index 87a507873d..82acae9fff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java @@ -167,6 +167,11 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { return widgetsBundles; } + @Override + public PageData findAllWidgetsBundles(PageLink pageLink) { + return widgetsBundleDao.findAllWidgetsBundles(pageLink); + } + @Override public void deleteWidgetsBundlesByTenantId(TenantId tenantId) { log.trace("Executing deleteWidgetsBundlesByTenantId, tenantId [{}]", tenantId);