Browse Source

Upgrade scripts for migrating to image resources

pull/9542/head
ViacheslavKlimov 3 years ago
parent
commit
9889dc148f
  1. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  2. 61
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  3. 180
      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/dashboard/DashboardService.java
  6. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  7. 22
      common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java
  8. 2
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
  9. 5
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  10. 7
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  11. 4
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java
  12. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
  13. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java
  14. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
  15. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java
  16. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java

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

@ -280,6 +280,7 @@ 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.loadSystemLwm2mResources();
}
log.info("Upgrade finished successfully!");

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

@ -17,18 +17,20 @@ package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
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.StringUtils;
import org.thingsboard.server.common.data.TbResource;
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.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
@ -40,6 +42,7 @@ import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
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.DirectoryStream;
@ -102,6 +105,9 @@ public class InstallScripts {
@Autowired
private ResourceService resourceService;
@Autowired
private ImagesUpdater imagesUpdater;
Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
@ -146,16 +152,14 @@ public class InstallScripts {
}
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException {
findRuleChainsFromPath(ruleChainsPath)
.forEach(
path -> {
try {
createRuleChainFromFile(tenantId, path, null);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
}
});
findRuleChainsFromPath(ruleChainsPath).forEach(path -> {
try {
createRuleChainFromFile(tenantId, path, null);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
}
});
}
List<Path> findRuleChainsFromPath(Path ruleChainsPath) throws IOException {
@ -188,6 +192,7 @@ public class InstallScripts {
}
public void loadSystemWidgets() throws Exception {
log.info("Loading system widgets");
Path widgetTypesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_TYPES_DIR);
if (Files.exists(widgetTypesDir)) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetTypesDir, path -> path.toString().endsWith(JSON_EXT))) {
@ -195,8 +200,7 @@ public class InstallScripts {
path -> {
try {
JsonNode widgetTypeJson = JacksonUtil.toJsonNode(path.toFile());
WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class);
widgetTypeService.saveWidgetType(widgetTypeDetails);
saveWidgetType(widgetTypeJson);
} 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);
@ -213,6 +217,7 @@ 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")) {
@ -220,8 +225,7 @@ public class InstallScripts {
widgetTypesArrayJson.forEach(
widgetTypeJson -> {
try {
WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class);
var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails);
var savedWidgetType = saveWidgetType(widgetTypeJson);
widgetTypeFqns.add(savedWidgetType.getFqn());
} catch (Exception e) {
log.error("Unable to load widget type from json: [{}]", path.toString());
@ -246,6 +250,32 @@ 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);
}
public void updateDashboards() {
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);
if (updated) {
dashboardService.saveDashboard(dashboard);
}
} catch (Exception e) {
log.error("Failed to update images for dashboard '{}' ({})", dashboard.getTitle(), dashboardId, e);
}
}
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
@ -339,4 +369,3 @@ public class InstallScripts {
}
}
}

180
application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java

@ -0,0 +1,180 @@
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
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.TbResourceInfo;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.util.MediaTypeUtils;
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.util.List;
import java.util.Optional;
@Component
//@Profile("install")
@Slf4j
@RequiredArgsConstructor
public class ImagesUpdater {
private final ResourceService resourceService;
// 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);
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());
}
public void updateWidgetsBundleImages(WidgetsBundle widgetsBundle) {
String imageLink = saveImage(widgetsBundle.getTitle(), widgetsBundle.getAlias(), widgetsBundle.getImage());
widgetsBundle.setImage(imageLink);
}
public boolean updateDashboardImages(Dashboard dashboard) {
String imageNamePrefix = dashboard.getTitle();
String imageKeyPrefix = "dashboard_" + dashboard.getUuidId();
boolean updated = false;
String imageLink = saveImage(dashboard.getTenantId(), imageNamePrefix + " - image", imageKeyPrefix + ".image", dashboard.getImage(), null);
dashboard.setImage(imageLink);
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);
}
return updated;
}
private JsonNode updateWidgetConfig(TenantId tenantId, JsonNode widgetConfig,
String imageNamePrefix, String imageKeyPrefix,
String fqn) {
ObjectNode widgetSettings = (ObjectNode) widgetConfig.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 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 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);
}
JsonNode backgroundConfigNode = widgetSettings.get("background");
if (backgroundConfigNode != null && backgroundConfigNode.isObject()) {
ObjectNode backgroundConfig = (ObjectNode) backgroundConfigNode;
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);
backgroundConfig.set("imageBase64", null);
backgroundConfig.put("imageUrl", imageLink);
backgroundConfig.put("type", "imageUrl");
}
}
}
return widgetConfig;
}
private String getText(JsonNode jsonNode, String field) {
return Optional.ofNullable(jsonNode.get(field))
.filter(JsonNode::isTextual)
.map(JsonNode::asText).orElse(null);
}
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) {
if (data == null) {
return null;
}
String base64Data = StringUtils.substringAfter(data, "base64,");
if (base64Data.isEmpty()) {
return data;
}
String imageMediaType = StringUtils.substringBetween(data, "data:", ";base64");
String extension = MediaTypeUtils.getFileExtension(imageMediaType);
key += "." + extension;
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) {
log.warn("Found more than one system image resources for key {}", existingImageQuery);
}
log.info("Using system image {} for {}", resourceInfo.getLink(), key);
return resourceInfo.getLink();
}
}
TbResource resource;
if (resourceInfo == null) {
resource = new TbResource();
resource.setTenantId(tenantId);
resource.setResourceType(ResourceType.IMAGE);
resource.setResourceKey(key);
} else if (tenantId.isSysTenantId()) {
resource = new TbResource(resourceInfo);
} else {
return resourceInfo.getLink();
}
resource.setTitle(title);
resource.setFileName(key);
resource.setMediaType(imageMediaType);
resource.setBase64Data(base64Data);
resource = resourceService.saveResource(resource);
log.info("[{}] {} image '{}' {} ({})", tenantId, resourceInfo == null ? "Created" : "Updated",
resource.getTitle(), resource.getResourceKey(), resource.getLink());
return resource.getLink();
}
}

536
application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java

@ -23,6 +23,9 @@ 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;
@ -36,6 +39,7 @@ 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;
@ -46,6 +50,8 @@ 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})
@ -72,12 +78,27 @@ 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
@ -116,4 +137,519 @@ 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/dashboard/DashboardService.java

@ -74,4 +74,6 @@ public interface DashboardService extends EntityDaoService {
List<Dashboard> findTenantDashboardsByTitle(TenantId tenantId, String title);
PageData<DashboardId> findAllDashboardsIds(PageLink pageLink);
}

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

@ -60,4 +60,6 @@ public interface ResourceService extends EntityDaoService {
long sumDataSizeByTenantId(TenantId tenantId);
List<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, String base64Data, String query);
}

22
common/data/src/main/java/org/thingsboard/server/common/data/util/MediaTypeUtils.java

@ -0,0 +1,22 @@
package org.thingsboard.server.common.data.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.MimeTypeUtils;
import java.util.Map;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MediaTypeUtils {
private static final Map<String, String> mappings = Map.of(
"jpeg", "jpg",
"svg+xml", "svg"
);
public static String getFileExtension(String mimeType) {
String subtype = MimeTypeUtils.parseMimeType(mimeType).getSubtype();
return mappings.getOrDefault(subtype, subtype);
}
}

2
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java

@ -44,4 +44,6 @@ public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao, Exportabl
PageData<DashboardId> findIdsByTenantId(TenantId tenantId, PageLink pageLink);
PageData<DashboardId> findAllIds(PageLink pageLink);
}

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

@ -364,6 +364,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardDao.findByTenantIdAndTitle(tenantId.getId(), title);
}
@Override
public PageData<DashboardId> findAllDashboardsIds(PageLink pageLink) {
return dashboardDao.findAllIds(pageLink);
}
private final PaginatedRemover<TenantId, DashboardId> tenantDashboardsRemover = new PaginatedRemover<>() {
@Override

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

@ -44,6 +44,7 @@ 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;
@ -220,6 +221,12 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return resourceDao.sumDataSizeByTenantId(tenantId);
}
@Override
public List<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, String base64Data, String query) {
String etag = calculateEtag(Base64.getDecoder().decode(base64Data));
return resourceInfoDao.findByTenantIdAndEtagAndKeyStartingWith(tenantId, etag, query);
}
private String calculateEtag(byte[] data) {
return Hashing.sha256().hashBytes(data).toString();
}

4
dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java

@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface TbResourceInfoDao extends Dao<TbResourceInfo> {
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
@ -31,4 +33,6 @@ public interface TbResourceInfoDao extends Dao<TbResourceInfo> {
TbResourceInfo findByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query);
}

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java

@ -64,7 +64,8 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
@Override
protected TbResource validateUpdate(TenantId tenantId, TbResource resource) {
if (resource.getData() != null && !resource.getResourceType().isUpdatable()) {
if (resource.getData() != null && !resource.getResourceType().isUpdatable() &&
tenantId != null && !tenantId.isSysTenantId()) {
throw new DataValidationException("This type of resource can't be updated");
}
return resource;

3
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java

@ -43,4 +43,7 @@ public interface DashboardRepository extends JpaRepository<DashboardEntity, UUID
@Query("SELECT d.id FROM DashboardEntity d WHERE d.tenantId = :tenantId")
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
@Query("SELECT id FROM DashboardEntity")
Page<UUID> findAllIds(Pageable pageable);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java

@ -85,6 +85,11 @@ public class JpaDashboardDao extends JpaAbstractDao<DashboardEntity, Dashboard>
return DaoUtil.pageToPageData(dashboardRepository.findIdsByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)).map(DashboardId::new));
}
@Override
public PageData<DashboardId> findAllIds(PageLink pageLink) {
return DaoUtil.pageToPageData(dashboardRepository.findAllIds(DaoUtil.toPageable(pageLink)).map(DashboardId::new));
}
@Override
public EntityType getEntityType() {
return EntityType.DASHBOARD;

6
dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java

@ -31,6 +31,7 @@ import org.thingsboard.server.dao.resource.TbResourceInfoDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@ -80,4 +81,9 @@ public class JpaTbResourceInfoDao extends JpaAbstractDao<TbResourceInfoEntity, T
return DaoUtil.getData(resourceInfoRepository.findByTenantIdAndResourceTypeAndResourceKey(tenantId.getId(), resourceType.name(), resourceKey));
}
@Override
public List<TbResourceInfo> findByTenantIdAndEtagAndKeyStartingWith(TenantId tenantId, String etag, String query) {
return DaoUtil.convertDataList(resourceInfoRepository.findByTenantIdAndHashCodeAndResourceKeyStartingWith(tenantId.getId(), etag, query));
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java

@ -22,6 +22,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.TbResourceInfoEntity;
import java.util.List;
import java.util.UUID;
public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEntity, UUID> {
@ -53,4 +54,6 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
TbResourceInfoEntity findByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey);
List<TbResourceInfoEntity> findByTenantIdAndHashCodeAndResourceKeyStartingWith(UUID tenantId, String hashCode, String query);
}

Loading…
Cancel
Save