Browse Source

Upgrade scripts for images migration

pull/9542/head
ViacheslavKlimov 3 years ago
parent
commit
55df765916
  1. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  2. 66
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  3. 219
      application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java
  4. 536
      application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java
  5. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  6. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java
  7. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java
  8. 25
      common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java
  9. 5
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  10. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java
  11. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java
  12. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java
  13. 2
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java
  14. 5
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  15. 2
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java
  16. 5
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

4
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) {

66
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<String> 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<String, String> 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);
}
}
}

219
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<String, String> 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<TbResourceInfo> existing = resourceService.findByTenantIdAndDataAndKeyStartingWith(TenantId.SYS_TENANT_ID, base64Data, existingImageQuery);
if (!existing.isEmpty()) {
resourceInfo = existing.get(0);
if (existing.size() > 1) {
List<TbResourceInfo> 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;
}
}

536
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\": \"<b>${entityName}</b><br/><br/><b>X Pos:</b> ${xPos:2}<br/><b>Y Pos:</b> ${yPos:2}<br/><b>Temperature:</b> ${temperature} °C<br/><small>See advanced settings for details</small>\",\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\": \"<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${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\": \"<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${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" +
"}";
}

2
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<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, String base64Data, String query);
List<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, byte[] data, String query);
}

2
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<WidgetTypeId> findAllWidgetTypesIds(PageLink pageLink);
}

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

@ -46,6 +46,8 @@ public interface WidgetsBundleService extends EntityDaoService {
List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId);
PageData<WidgetsBundle> findAllWidgetsBundles(PageLink pageLink);
void deleteWidgetsBundlesByTenantId(TenantId tenantId);
}

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

5
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<ResourceInf
}
@Override
public List<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, String base64Data, String query) {
String etag = calculateEtag(Base64.getDecoder().decode(base64Data));
public List<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, byte[] data, String query) {
String etag = calculateEtag(data);
return resourceInfoDao.findByTenantIdAndEtagAndKeyStartingWith(tenantId, etag, query);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java

@ -225,6 +225,11 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
widgetsBundleWidgetRepository.deleteById(new WidgetsBundleWidgetCompositeKey(widgetsBundleId, widgetTypeId));
}
@Override
public PageData<WidgetTypeId> findAllWidgetTypesIds(PageLink pageLink) {
return DaoUtil.pageToPageData(widgetTypeRepository.findAllIds(DaoUtil.toPageable(pageLink)).map(WidgetTypeId::new));
}
@Override
public EntityType getEntityType() {
return EntityType.WIDGET_TYPE;

6
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<WidgetsBundleEntity, Wid
return findTenantWidgetsBundlesByTenantIds(Collections.singletonList(tenantId), fullSearch, pageLink);
}
@Override
public PageData<WidgetsBundle> findAllWidgetsBundles(PageLink pageLink) {
return DaoUtil.toPageData(widgetsBundleRepository.findAll(DaoUtil.toPageable(pageLink)));
}
private PageData<WidgetsBundle> findTenantWidgetsBundlesByTenantIds(List<UUID> tenantIds, boolean fullSearch, PageLink pageLink) {
if (fullSearch) {
return DaoUtil.toPageData(

3
dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java

@ -76,4 +76,7 @@ public interface WidgetTypeRepository extends JpaRepository<WidgetTypeDetailsEnt
@Query("SELECT externalId FROM WidgetTypeDetailsEntity WHERE id = :id")
UUID getExternalIdById(@Param("id") UUID id);
@Query("SELECT id FROM WidgetTypeEntity")
Page<UUID> findAllIds(Pageable pageable);
}

2
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java

@ -108,4 +108,6 @@ public interface WidgetTypeDao extends Dao<WidgetTypeDetails>, ExportableEntityD
void removeWidgetTypeFromWidgetsBundle(UUID widgetsBundleId, UUID widgetTypeId);
PageData<WidgetTypeId> findAllWidgetTypesIds(PageLink pageLink);
}

5
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<WidgetTypeId> findAllWidgetTypesIds(PageLink pageLink) {
return widgetTypeDao.findAllWidgetTypesIds(pageLink);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findWidgetTypeById(tenantId, new WidgetTypeId(entityId.getId())));

2
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java

@ -83,5 +83,7 @@ public interface WidgetsBundleDao extends Dao<WidgetsBundle>, ExportableEntityDa
*/
PageData<WidgetsBundle> findTenantWidgetsBundlesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink);
PageData<WidgetsBundle> findAllWidgetsBundles(PageLink pageLink);
}

5
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<WidgetsBundle> findAllWidgetsBundles(PageLink pageLink) {
return widgetsBundleDao.findAllWidgetsBundles(pageLink);
}
@Override
public void deleteWidgetsBundlesByTenantId(TenantId tenantId) {
log.trace("Executing deleteWidgetsBundlesByTenantId, tenantId [{}]", tenantId);

Loading…
Cancel
Save