Browse Source

Merge branch 'rc' into fix/debug-settings-ui-fixes

pull/12212/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
6f728e8fbb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/pom.xml
  2. 21
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 36
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  4. 39
      application/src/main/java/org/thingsboard/server/controller/ImageController.java
  5. 2
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  6. 4
      application/src/main/java/org/thingsboard/server/service/install/update/ResourcesUpdater.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  8. 15
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  9. 20
      application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
  10. 2
      common/actor/pom.xml
  11. 2
      common/cache/pom.xml
  12. 2
      common/cluster-api/pom.xml
  13. 2
      common/coap-server/pom.xml
  14. 2
      common/dao-api/pom.xml
  15. 8
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  16. 2
      common/data/pom.xml
  17. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java
  18. 2
      common/edge-api/pom.xml
  19. 2
      common/message/pom.xml
  20. 2
      common/pom.xml
  21. 2
      common/proto/pom.xml
  22. 2
      common/queue/pom.xml
  23. 2
      common/script/pom.xml
  24. 2
      common/script/remote-js-client/pom.xml
  25. 2
      common/script/script-api/pom.xml
  26. 2
      common/stats/pom.xml
  27. 2
      common/transport/coap/pom.xml
  28. 2
      common/transport/http/pom.xml
  29. 2
      common/transport/lwm2m/pom.xml
  30. 2
      common/transport/mqtt/pom.xml
  31. 2
      common/transport/pom.xml
  32. 2
      common/transport/snmp/pom.xml
  33. 2
      common/transport/transport-api/pom.xml
  34. 2
      common/util/pom.xml
  35. 2
      common/version-control/pom.xml
  36. 2
      dao/pom.xml
  37. 11
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  38. 2
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java
  39. 126
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  40. 14
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java
  41. 18
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java
  42. 9
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  43. 2
      monitoring/pom.xml
  44. 2
      msa/black-box-tests/pom.xml
  45. 2
      msa/js-executor/pom.xml
  46. 2
      msa/monitoring/pom.xml
  47. 2
      msa/pom.xml
  48. 2
      msa/tb-node/pom.xml
  49. 2
      msa/tb/pom.xml
  50. 2
      msa/transport/coap/pom.xml
  51. 2
      msa/transport/http/pom.xml
  52. 2
      msa/transport/lwm2m/pom.xml
  53. 2
      msa/transport/mqtt/pom.xml
  54. 2
      msa/transport/pom.xml
  55. 2
      msa/transport/snmp/pom.xml
  56. 2
      msa/vc-executor-docker/pom.xml
  57. 2
      msa/vc-executor/pom.xml
  58. 2
      msa/web-ui/pom.xml
  59. 4
      netty-mqtt/pom.xml
  60. 4
      pom.xml
  61. 2
      rest-client/pom.xml
  62. 2
      rule-engine/pom.xml
  63. 2
      rule-engine/rule-engine-api/pom.xml
  64. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java
  65. 2
      rule-engine/rule-engine-components/pom.xml
  66. 2
      tools/pom.xml
  67. 2
      transport/coap/pom.xml
  68. 2
      transport/http/pom.xml
  69. 2
      transport/lwm2m/pom.xml
  70. 2
      transport/mqtt/pom.xml
  71. 2
      transport/pom.xml
  72. 2
      transport/snmp/pom.xml
  73. 2
      ui-ngx/pom.xml
  74. 4
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.html
  75. 9
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts
  76. 3
      ui-ngx/src/app/modules/home/components/widget/lib/button/two-segment-button-widget.component.html
  77. 2
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  78. 2
      ui-ngx/src/app/modules/home/pages/admin/resource/js-library-table-config.resolver.ts
  79. 27
      ui-ngx/src/app/modules/home/pages/admin/resource/resource-tabs.component.html
  80. 36
      ui-ngx/src/app/modules/home/pages/admin/resource/resource-tabs.component.ts
  81. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx
  82. 1
      ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts
  83. 12
      ui-ngx/src/app/shared/models/vc.models.ts

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

21
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import jakarta.mail.MessagingException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import lombok.Getter;
@ -28,6 +29,7 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -182,7 +184,9 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -194,6 +198,7 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import static org.thingsboard.server.common.data.StringUtils.isNotEmpty;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
@ -1008,6 +1013,22 @@ public abstract class BaseController {
}
}
protected void compressResponseWithGzipIFAccepted(String acceptEncodingHeader, HttpServletResponse response, byte[] content) throws IOException {
if (StringUtils.isNotEmpty(acceptEncodingHeader) && acceptEncodingHeader.contains("gzip")) {
response.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.getOutputStream())) {
gzipOutputStream.write(content);
gzipOutputStream.finish();
}
} else {
try (ServletOutputStream outputStream = response.getOutputStream()) {
outputStream.write(content);
outputStream.flush();
}
}
}
protected <T> ResponseEntity<T> response(HttpStatus status) {
return ResponseEntity.status(status).build();
}

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

@ -22,8 +22,10 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@ -31,6 +33,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@ -67,6 +70,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION;
@ -149,17 +153,20 @@ public class DashboardController extends BaseController {
)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/dashboard/{dashboardId}")
public Dashboard getDashboardById(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
public void getDashboardById(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId,
@Parameter(description = INCLUDE_RESOURCES_DESCRIPTION)
@RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources) throws ThingsboardException {
@RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources,
@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
HttpServletResponse response) throws Exception {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
if (includeResources) {
dashboard.setResources(tbResourceService.exportResources(dashboard, getCurrentUser()));
}
return dashboard;
response.setContentType(APPLICATION_JSON_VALUE);
compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(dashboard));
}
@ApiOperation(value = "Create Or Update Dashboard (saveDashboard)",
@ -171,11 +178,15 @@ public class DashboardController extends BaseController {
TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@PostMapping(value = "/dashboard")
public Dashboard saveDashboard(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard) throws Exception {
public void saveDashboard(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard,
@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
HttpServletResponse response) throws Exception {
dashboard.setTenantId(getTenantId());
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
return tbDashboardService.save(dashboard, getCurrentUser());
var savedDashboard = tbDashboardService.save(dashboard, getCurrentUser());
response.setContentType(APPLICATION_JSON_VALUE);
compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(savedDashboard));
}
@ApiOperation(value = "Delete the Dashboard (deleteDashboard)",
@ -408,12 +419,13 @@ public class DashboardController extends BaseController {
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. "
+ DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
@ResponseBody
public HomeDashboard getHomeDashboard() throws ThingsboardException {
@GetMapping(value = "/dashboard/home")
public void getHomeDashboard(@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
HttpServletResponse response) throws Exception {
SecurityUser securityUser = getCurrentUser();
response.setContentType(APPLICATION_JSON_VALUE);
if (securityUser.isSystemAdmin()) {
return null;
return;
}
User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId());
JsonNode additionalInfo = user.getAdditionalInfo();
@ -431,7 +443,9 @@ public class DashboardController extends BaseController {
homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo);
}
}
return homeDashboard;
if (homeDashboard != null) {
compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(homeDashboard));
}
}
@ApiOperation(value = "Get Home Dashboard Info (getHomeDashboardInfo)",

39
application/src/main/java/org/thingsboard/server/controller/ImageController.java

@ -61,7 +61,9 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -70,6 +72,7 @@ import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_INC
import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.dao.util.ImageUtils.mediaTypeToFileExtension;
@Slf4j
@RestController
@ -179,15 +182,17 @@ public class ImageController extends BaseController {
@PathVariable String type,
@Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true)
@PathVariable String key,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
return downloadIfChanged(type, key, etag, false);
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
return downloadIfChanged(type, key, etag, acceptEncodingHeader, false);
}
@GetMapping(value = "/api/images/public/{publicResourceKey}", produces = "image/*")
public ResponseEntity<ByteArrayResource> downloadPublicImage(@PathVariable String publicResourceKey,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
ImageCacheKey cacheKey = ImageCacheKey.forPublicImage(publicResourceKey);
return downloadIfChanged(cacheKey, etag, () -> imageService.getPublicImageInfoByKey(publicResourceKey));
return downloadIfChanged(cacheKey, etag, acceptEncodingHeader, () -> imageService.getPublicImageInfoByKey(publicResourceKey));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -213,8 +218,9 @@ public class ImageController extends BaseController {
@PathVariable String type,
@Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true)
@PathVariable String key,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
return downloadIfChanged(type, key, etag, true);
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
return downloadIfChanged(type, key, etag, acceptEncodingHeader, true);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -268,12 +274,12 @@ public class ImageController extends BaseController {
return (result.isSuccess() ? ResponseEntity.ok() : ResponseEntity.badRequest()).body(result);
}
private ResponseEntity<ByteArrayResource> downloadIfChanged(String type, String key, String etag, boolean preview) throws Exception {
private ResponseEntity<ByteArrayResource> downloadIfChanged(String type, String key, String etag, String acceptEncodingHeader, boolean preview) throws Exception {
ImageCacheKey cacheKey = ImageCacheKey.forImage(getTenantId(type), key, preview);
return downloadIfChanged(cacheKey, etag, () -> checkImageInfo(type, key, Operation.READ));
return downloadIfChanged(cacheKey, etag, acceptEncodingHeader, () -> checkImageInfo(type, key, Operation.READ));
}
private ResponseEntity<ByteArrayResource> downloadIfChanged(ImageCacheKey cacheKey, String etag, ThrowingSupplier<TbResourceInfo> imageInfoSupplier) throws Exception {
private ResponseEntity<ByteArrayResource> downloadIfChanged(ImageCacheKey cacheKey, String etag, String acceptEncodingHeader, ThrowingSupplier<TbResourceInfo> imageInfoSupplier) throws Exception {
if (StringUtils.isNotEmpty(etag)) {
etag = StringUtils.remove(etag, '\"'); // etag is wrapped in double quotes due to HTTP specification
if (etag.equals(tbImageService.getETag(cacheKey))) {
@ -294,7 +300,6 @@ public class ImageController extends BaseController {
tbImageService.putETag(cacheKey, descriptor.getEtag());
var result = ResponseEntity.ok()
.header("Content-Type", descriptor.getMediaType())
.contentLength(data.length)
.eTag(descriptor.getEtag());
if (!cacheKey.isPublic()) {
result
@ -308,7 +313,19 @@ public class ImageController extends BaseController {
} else {
result.cacheControl(CacheControl.noCache());
}
return result.body(new ByteArrayResource(data));
var responseData = data;
if (mediaTypeToFileExtension(descriptor.getMediaType()).equals("svg") &&
StringUtils.isNotEmpty(acceptEncodingHeader) && acceptEncodingHeader.contains("gzip")) {
result.header(HttpHeaders.CONTENT_ENCODING, "gzip");
var outputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
gzipOutputStream.write(data);
gzipOutputStream.finish();
}
responseData = outputStream.toByteArray();
}
result.contentLength(responseData.length);
return result.body(new ByteArrayResource(responseData));
}
private TbResourceInfo checkImageInfo(String imageType, String key, Operation operation) throws ThingsboardException {

2
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -477,7 +477,7 @@ public class NotificationController extends BaseController {
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/deliveryMethods")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
public List<NotificationDeliveryMethod> getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
return notificationCenter.getAvailableDeliveryMethods(user.getTenantId());
}

4
application/src/main/java/org/thingsboard/server/service/install/update/ResourcesUpdater.java

@ -101,7 +101,7 @@ public class ResourcesUpdater {
for (DashboardId dashboardId : dashboards) {
executor.submit(() -> {
Dashboard dashboard = dashboardService.findDashboardById(TenantId.SYS_TENANT_ID, dashboardId);
boolean updated = resourceService.updateResourcesUsage(dashboard); // will convert resources ids to new structure
boolean updated = resourceService.updateResourcesUsage(dashboard.getTenantId(), dashboard); // will convert resources ids to new structure
if (updated) {
dashboardService.saveDashboard(dashboard);
updatedCount.incrementAndGet();
@ -130,7 +130,7 @@ public class ResourcesUpdater {
for (WidgetTypeId widgetTypeId : widgets) {
executor.submit(() -> {
WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(TenantId.SYS_TENANT_ID, widgetTypeId);
boolean updated = resourceService.updateResourcesUsage(widgetTypeDetails);
boolean updated = resourceService.updateResourcesUsage(widgetTypeDetails.getTenantId(), widgetTypeDetails);
if (updated) {
widgetTypeService.saveWidgetType(widgetTypeDetails);
updatedCount.incrementAndGet();

4
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -417,7 +417,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
@Override
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
public List<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
return channels.values().stream()
.filter(channel -> {
try {
@ -428,7 +428,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
})
.map(NotificationChannel::getDeliveryMethod)
.collect(Collectors.toSet());
.sorted().toList();
}
@Override

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

@ -46,7 +46,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -132,12 +132,12 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
@Override
public List<ResourceExportData> exportResources(Dashboard dashboard, SecurityUser user) throws ThingsboardException {
return exportResources(dashboard, imageService::getUsedImages, resourceService::getUsedResources, user);
return exportResources(() -> imageService.getUsedImages(dashboard), () -> resourceService.getUsedResources(user.getTenantId(), dashboard), user);
}
@Override
public List<ResourceExportData> exportResources(WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException {
return exportResources(widgetTypeDetails, imageService::getUsedImages, resourceService::getUsedResources, user);
return exportResources(() -> imageService.getUsedImages(widgetTypeDetails), () -> resourceService.getUsedResources(user.getTenantId(), widgetTypeDetails), user);
}
@Override
@ -153,13 +153,12 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
}
}
private <T> List<ResourceExportData> exportResources(T entity,
Function<T, Collection<TbResourceInfo>> imagesProcessor,
Function<T, Collection<TbResourceInfo>> resourcesProcessor,
private <T> List<ResourceExportData> exportResources(Supplier<Collection<TbResourceInfo>> imagesProcessor,
Supplier<Collection<TbResourceInfo>> resourcesProcessor,
SecurityUser user) throws ThingsboardException {
List<TbResourceInfo> resources = new ArrayList<>();
resources.addAll(imagesProcessor.apply(entity));
resources.addAll(resourcesProcessor.apply(entity));
resources.addAll(imagesProcessor.get());
resources.addAll(resourcesProcessor.get());
for (TbResourceInfo resourceInfo : resources) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo);
}

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

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -602,13 +603,14 @@ public class DashboardControllerTest extends AbstractControllerTest {
dashboard.setTitle("My dashboard");
dashboard.setConfiguration(JacksonUtil.newObjectNode()
.put("someImage", "tb-image;/api/images/tenant/" + imageInfo.getResourceKey())
.set("widgets", JacksonUtil.toJsonNode("""
.<ObjectNode>set("widgets", JacksonUtil.toJsonNode("""
{"xxx":
{"config":{"actions":{"elementClick":[
{"customResources":[{"url":{"entityType":"TB_RESOURCE","id":
"tb-resource;/api/resource/js_module/tenant/gateway-management-extension.js"},"isModule":true},
{"url":"tb-resource;/api/resource/js_module/tenant/gateway-management-extension.js","isModule":true}]}]}}}}
""")));
"""))
.put("someResource", "tb-resource;/api/resource/js_module/tenant/gateway-management-extension.js"));
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
Dashboard exportedDashboard = doGet("/api/dashboard/" + dashboard.getUuidId() + "?includeResources=true", Dashboard.class);
@ -637,12 +639,18 @@ public class DashboardControllerTest extends AbstractControllerTest {
doPost("/api/resource", resource, TbResourceInfo.class);
Dashboard importedDashboard = doPost("/api/dashboard", exportedDashboard, Dashboard.class);
String newResourceKey = "gateway-management-extension_(1).js";
imageRef = importedDashboard.getConfiguration().get("someImage").asText();
assertThat(imageRef).isEqualTo("tb-image;/api/images/tenant/" + imageInfo.getResourceKey());
resourceRef = importedDashboard.getConfiguration().get("widgets").get("xxx").get("config")
.get("actions").get("elementClick").get(0).get("customResources").get(0).get("url").asText();
String newResourceKey = "gateway-management-extension_(1).js";
assertThat(resourceRef).isEqualTo("tb-resource;/api/resource/js_module/tenant/" + newResourceKey);
List<String> resourcesRefs = new ArrayList<>();
resourcesRefs.add(importedDashboard.getConfiguration().get("widgets").get("xxx").get("config")
.get("actions").get("elementClick").get(0).get("customResources").get(0).get("url").asText());
resourcesRefs.add(importedDashboard.getConfiguration().get("someResource").asText());
assertThat(resourcesRefs).allSatisfy(ref -> {
assertThat(ref).isEqualTo("tb-resource;/api/resource/js_module/tenant/" + newResourceKey);
});
TbResourceInfo importedImageInfo = doGet("/api/images/tenant/" + imageInfo.getResourceKey() + "/info", TbResourceInfo.class);
assertThat(importedImageInfo.getEtag()).isEqualTo(imageInfo.getEtag());

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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

@ -80,13 +80,13 @@ public interface ResourceService extends EntityDaoService {
TbResourceInfo findSystemOrTenantResourceByEtag(TenantId tenantId, ResourceType resourceType, String etag);
boolean updateResourcesUsage(Dashboard dashboard);
boolean updateResourcesUsage(TenantId tenantId, Dashboard dashboard);
boolean updateResourcesUsage(WidgetTypeDetails widgetTypeDetails);
boolean updateResourcesUsage(TenantId tenantId, WidgetTypeDetails widgetTypeDetails);
Collection<TbResourceInfo> getUsedResources(Dashboard dashboard);
Collection<TbResourceInfo> getUsedResources(TenantId tenantId, Dashboard dashboard);
Collection<TbResourceInfo> getUsedResources(WidgetTypeDetails widgetTypeDetails);
Collection<TbResourceInfo> getUsedResources(TenantId tenantId, WidgetTypeDetails widgetTypeDetails);
TbResource createOrUpdateSystemResource(ResourceType resourceType, String resourceKey, byte[] data);

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java

@ -50,7 +50,8 @@ public class WidgetType extends BaseWidgetType {
@JsonIgnore
public JsonNode getDefaultConfig() {
return Optional.ofNullable(descriptor.get("defaultConfig"))
return Optional.ofNullable(descriptor)
.map(descriptor -> descriptor.get("defaultConfig"))
.filter(JsonNode::isTextual).map(JsonNode::asText)
.map(json -> {
try {

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>common</artifactId>

2
common/proto/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/version-control/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>dao</artifactId>

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

@ -162,18 +162,19 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
}
try {
TenantId tenantId = dashboard.getTenantId();
if (CollectionUtils.isNotEmpty(dashboard.getResources())) {
resourceService.importResources(dashboard.getTenantId(), dashboard.getResources());
resourceService.importResources(tenantId, dashboard.getResources());
}
imageService.updateImagesUsage(dashboard);
resourceService.updateResourcesUsage(dashboard);
resourceService.updateResourcesUsage(tenantId, dashboard);
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
var saved = dashboardDao.save(tenantId, dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(saved.getId()).created(dashboard.getId() == null).build());
if (dashboard.getId() == null) {
countService.publishCountEntityEvictEvent(saved.getTenantId(), EntityType.DASHBOARD);
countService.publishCountEntityEvictEvent(tenantId, EntityType.DASHBOARD);
}
return saved;
} catch (Exception e) {

2
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java

@ -64,7 +64,7 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA
@Override
public List<OAuth2ClientLoginInfo> findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType) {
log.trace("Executing findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType pkgName=[{}] platformType=[{}]",pkgName, platformType);
log.trace("Executing findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType pkgName=[{}] platformType=[{}]", pkgName, platformType);
return oauth2ClientDao.findEnabledByPkgNameAndPlatformType(pkgName, platformType)
.stream()
.map(OAuth2Utils::toClientLoginInfo)

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

@ -57,6 +57,7 @@ import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
@ -401,18 +402,33 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
}
@Override
public boolean updateResourcesUsage(Dashboard dashboard) {
public boolean updateResourcesUsage(TenantId tenantId, Dashboard dashboard) {
if (dashboard.getConfiguration() == null) {
return false;
}
Map<String, String> links = getResourcesLinks(dashboard.getResources());
return updateResourcesUsage(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING, links);
return updateResourcesUsage(tenantId, List.of(dashboard.getConfiguration()), List.of(DASHBOARD_RESOURCES_MAPPING), links);
}
@Override
public boolean updateResourcesUsage(WidgetTypeDetails widgetTypeDetails) {
public boolean updateResourcesUsage(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
Map<String, String> links = getResourcesLinks(widgetTypeDetails.getResources());
boolean updated = updateResourcesUsage(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING, links);
List<JsonNode> jsonNodes = new ArrayList<>(2);
List<Map<String, String>> mappings = new ArrayList<>(2);
if (widgetTypeDetails.getDescriptor() != null) {
jsonNodes.add(widgetTypeDetails.getDescriptor());
mappings.add(WIDGET_RESOURCES_MAPPING);
}
JsonNode defaultConfig = widgetTypeDetails.getDefaultConfig();
if (defaultConfig != null) {
updated |= updateResourcesUsage(widgetTypeDetails.getTenantId(), defaultConfig, WIDGET_DEFAULT_CONFIG_RESOURCES_MAPPING, links);
jsonNodes.add(defaultConfig);
mappings.add(WIDGET_DEFAULT_CONFIG_RESOURCES_MAPPING);
}
boolean updated = updateResourcesUsage(tenantId, jsonNodes, mappings, links);
if (defaultConfig != null) {
widgetTypeDetails.setDefaultConfig(defaultConfig);
}
return updated;
@ -433,8 +449,9 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return links;
}
private boolean updateResourcesUsage(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping, Map<String, String> links) {
return processResources(jsonNode, mapping, value -> {
private boolean updateResourcesUsage(TenantId tenantId, List<JsonNode> jsonNodes, List<Map<String, String>> mappings, Map<String, String> links) {
log.trace("[{}] updateResourcesUsage (new links: {}) for {}", tenantId, links, jsonNodes);
return processResources(jsonNodes, mappings, value -> {
String link = getResourceLink(value);
if (link != null) {
String newLink = links.get(link);
@ -462,23 +479,31 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
}
@Override
public Collection<TbResourceInfo> getUsedResources(Dashboard dashboard) {
return getUsedResources(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING).values();
public Collection<TbResourceInfo> getUsedResources(TenantId tenantId, Dashboard dashboard) {
return getUsedResources(tenantId, List.of(dashboard.getConfiguration()), List.of(DASHBOARD_RESOURCES_MAPPING)).values();
}
@Override
public Collection<TbResourceInfo> getUsedResources(WidgetTypeDetails widgetTypeDetails) {
Map<TbResourceId, TbResourceInfo> resources = getUsedResources(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING);
public Collection<TbResourceInfo> getUsedResources(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
List<JsonNode> jsonNodes = new ArrayList<>(2);
List<Map<String, String>> mappings = new ArrayList<>(2);
jsonNodes.add(widgetTypeDetails.getDescriptor());
mappings.add(WIDGET_RESOURCES_MAPPING);
JsonNode defaultConfig = widgetTypeDetails.getDefaultConfig();
if (defaultConfig != null) {
resources.putAll(getUsedResources(widgetTypeDetails.getTenantId(), defaultConfig, WIDGET_DEFAULT_CONFIG_RESOURCES_MAPPING));
jsonNodes.add(defaultConfig);
mappings.add(WIDGET_DEFAULT_CONFIG_RESOURCES_MAPPING);
}
return resources.values();
return getUsedResources(tenantId, jsonNodes, mappings).values();
}
private Map<TbResourceId, TbResourceInfo> getUsedResources(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping) {
private Map<TbResourceId, TbResourceInfo> getUsedResources(TenantId tenantId, List<JsonNode> jsonNodes, List<Map<String, String>> mappings) {
Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
processResources(jsonNode, mapping, value -> {
log.trace("[{}] getUsedResources for {}", tenantId, jsonNodes);
processResources(jsonNodes, mappings, value -> {
String link = getResourceLink(value);
if (link == null) {
return value;
@ -517,32 +542,57 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
}
}
private boolean processResources(JsonNode jsonNode, Map<String, String> mapping, UnaryOperator<String> processor) {
private boolean processResources(List<JsonNode> jsonNodes, List<Map<String, String>> mappings, UnaryOperator<String> processor) {
AtomicBoolean updated = new AtomicBoolean(false);
JacksonUtil.replaceByMapping(jsonNode, mapping, Collections.emptyMap(), (name, urlNode) -> {
String value = null;
if (urlNode.isTextual()) { // link is in the right place
value = urlNode.asText();
} else {
JsonNode id = urlNode.get("id"); // old structure is used
if (id != null && id.isTextual()) {
value = id.asText();
}
}
if (StringUtils.isNotBlank(value)) {
value = processor.apply(value);
} else {
value = "";
}
for (int i = 0; i < jsonNodes.size(); i++) {
JsonNode jsonNode = jsonNodes.get(i);
// processing by mappings first
if (i <= mappings.size() - 1) {
JacksonUtil.replaceByMapping(jsonNode, mappings.get(i), Collections.emptyMap(), (name, urlNode) -> {
String value = null;
if (urlNode.isTextual()) { // link is in the right place
value = urlNode.asText();
} else {
JsonNode id = urlNode.get("id"); // old structure is used
if (id != null && id.isTextual()) {
value = id.asText();
}
}
JsonNode newValue = new TextNode(value);
if (!newValue.toString().equals(urlNode.toString())) {
updated.set(true);
log.trace("Replaced '{}' with '{}'", urlNode, newValue);
if (StringUtils.isNotBlank(value)) {
value = processor.apply(value);
} else {
value = "";
}
JsonNode newValue = new TextNode(value);
if (!newValue.toString().equals(urlNode.toString())) {
updated.set(true);
log.trace("Replaced by mapping '{}' ({}) with '{}'", value, name, newValue);
}
return newValue;
});
}
return newValue;
});
// processing all
JacksonUtil.replaceAll(jsonNode, "", (name, value) -> {
if (!StringUtils.startsWith(value, DataConstants.TB_RESOURCE_PREFIX + "/api/resource/")) {
return value;
}
String newValue = processor.apply(value);
if (StringUtils.equals(value, newValue)) {
return value;
} else {
updated.set(true);
log.trace("Replaced '{}' ({}) with '{}'", value, name, newValue);
return newValue;
}
});
}
return updated.get();
}
@ -555,7 +605,7 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
importResources(dashboard.getTenantId(), dashboard.getResources());
}
imageService.updateImagesUsage(dashboard);
updateResourcesUsage(dashboard);
updateResourcesUsage(dashboard.getTenantId(), dashboard);
data = JacksonUtil.writeValueAsBytes(dashboard);
}

14
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java

@ -31,6 +31,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2ClientDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -65,8 +66,17 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao<OAuth2ClientEntity, OAuth
@Override
public List<OAuth2Client> findEnabledByPkgNameAndPlatformType(String pkgName, PlatformType platformType) {
return DaoUtil.convertDataList(repository.findEnabledByPkgNameAndPlatformType(pkgName,
platformType != null ? platformType.name() : null));
List<OAuth2ClientEntity> clientEntities;
if (platformType != null) {
clientEntities = switch (platformType) {
case ANDROID -> repository.findEnabledByAndroidPkgNameAndPlatformType(pkgName, platformType.name());
case IOS -> repository.findEnabledByIosPkgNameAndPlatformType(pkgName, platformType.name());
default -> Collections.emptyList();
};
} else {
clientEntities = Collections.emptyList();
}
return DaoUtil.convertDataList(clientEntities);
}
@Override

18
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java

@ -49,11 +49,21 @@ public interface OAuth2ClientRepository extends JpaRepository<OAuth2ClientEntity
"FROM OAuth2ClientEntity c " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity ac ON c.id = ac.oauth2ClientId " +
"LEFT JOIN MobileAppBundleEntity b ON ac.mobileAppBundleId = b.id " +
"LEFT JOIN MobileAppEntity andApp ON b.androidAppId = andApp.id LEFT JOIN MobileAppEntity iosApp ON b.iosAppID = iosApp.id " +
"WHERE andApp.pkgName = :pkgName OR iosApp.pkgName = :pkgName AND b.oauth2Enabled = true " +
"LEFT JOIN MobileAppEntity andApp ON b.androidAppId = andApp.id " +
"WHERE andApp.pkgName = :pkgName AND b.oauth2Enabled = true " +
"AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR ilike(c.platforms, CONCAT('%', :platformFilter, '%')) = true)")
List<OAuth2ClientEntity> findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter);
List<OAuth2ClientEntity> findEnabledByAndroidPkgNameAndPlatformType(@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter);
@Query("SELECT c " +
"FROM OAuth2ClientEntity c " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity ac ON c.id = ac.oauth2ClientId " +
"LEFT JOIN MobileAppBundleEntity b ON ac.mobileAppBundleId = b.id " +
"LEFT JOIN MobileAppEntity iosApp ON b.iosAppID = iosApp.id " +
"WHERE iosApp.pkgName = :pkgName AND b.oauth2Enabled = true " +
"AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR ilike(c.platforms, CONCAT('%', :platformFilter, '%')) = true)")
List<OAuth2ClientEntity> findEnabledByIosPkgNameAndPlatformType(@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter);
@Query("SELECT c " +
"FROM OAuth2ClientEntity c " +

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

@ -99,14 +99,15 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
log.trace("Executing saveWidgetType [{}]", widgetTypeDetails);
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
try {
TenantId tenantId = widgetTypeDetails.getTenantId();
if (CollectionUtils.isNotEmpty(widgetTypeDetails.getResources())) {
resourceService.importResources(widgetTypeDetails.getTenantId(), widgetTypeDetails.getResources());
resourceService.importResources(tenantId, widgetTypeDetails.getResources());
}
imageService.updateImagesUsage(widgetTypeDetails);
resourceService.updateResourcesUsage(widgetTypeDetails);
resourceService.updateResourcesUsage(tenantId, widgetTypeDetails);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
WidgetTypeDetails result = widgetTypeDao.save(tenantId, widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(result.getId()).created(widgetTypeDetails.getId() == null).build());
return result;
} catch (Exception t) {

2
monitoring/pom.xml

@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>

2
msa/black-box-tests/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/monitoring/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.msa</groupId>
<artifactId>transport</artifactId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/vc-executor-docker/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/vc-executor/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>netty-mqtt</artifactId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<packaging>jar</packaging>
<name>Netty MQTT Client</name>

4
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<packaging>pom</packaging>
<name>Thingsboard</name>
@ -83,7 +83,7 @@
<zookeeper.version>3.9.2</zookeeper.version>
<protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.63.0</grpc.version>
<tbel.version>1.2.4</tbel.version>
<tbel.version>1.2.5</tbel.version>
<lombok.version>1.18.32</lombok.version>
<paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rest-client</artifactId>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java

@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import java.util.Set;
import java.util.List;
public interface NotificationCenter {
@ -48,6 +48,6 @@ public interface NotificationCenter {
void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId);
Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId);
List<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId);
}

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>tools</artifactId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>transport</artifactId>

2
transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>transport</artifactId>
</parent>

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version>
<version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

4
ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.html

@ -41,10 +41,10 @@
<mat-checkbox *ngIf="entityId.entityType === entityTypes.DEVICE" formControlName="saveCredentials" style="margin-bottom: 16px;">
{{ 'version-control.export-credentials' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes" style="margin-bottom: 16px;">
<mat-checkbox *ngIf="!entityTypesWithoutRelatedData.has(entityId.entityType)" formControlName="saveAttributes" style="margin-bottom: 16px;">
{{ 'version-control.export-attributes' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveRelations" style="margin-bottom: 16px;">
<mat-checkbox *ngIf="!entityTypesWithoutRelatedData.has(entityId.entityType)" formControlName="saveRelations" style="margin-bottom: 16px;">
{{ 'version-control.export-relations' | translate }}
</mat-checkbox>
</div>

9
ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts

@ -18,6 +18,7 @@ import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular
import { PageComponent } from '@shared/components/page.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import {
entityTypesWithoutRelatedData,
SingleEntityVersionCreateRequest,
VersionCreateRequestType,
VersionCreationResult
@ -62,6 +63,8 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
entityTypes = EntityType;
entityTypesWithoutRelatedData = entityTypesWithoutRelatedData;
resultMessage: string;
versionCreateResult$: Observable<VersionCreationResult>;
@ -108,8 +111,10 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
branch: this.createVersionFormGroup.get('branch').value,
versionName: this.createVersionFormGroup.get('versionName').value,
config: {
saveRelations: this.createVersionFormGroup.get('saveRelations').value,
saveAttributes: this.createVersionFormGroup.get('saveAttributes').value,
saveRelations: !entityTypesWithoutRelatedData.has(this.entityId.entityType)
? this.createVersionFormGroup.get('saveRelations').value : false,
saveAttributes: !entityTypesWithoutRelatedData.has(this.entityId.entityType)
? this.createVersionFormGroup.get('saveAttributes').value : false,
saveCredentials: this.entityId.entityType === EntityType.DEVICE ? this.createVersionFormGroup.get('saveCredentials').value : false
},
type: VersionCreateRequestType.SINGLE_ENTITY

3
ui-ngx/src/app/modules/home/components/widget/lib/button/two-segment-button-widget.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="tb-segmented-button-widget" [class.no-pointer-events]="ctx.isEdit">
<div class="tb-segmented-button-widget">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-segmented-button-container">
<tb-widget-button-toggle
@ -23,6 +23,7 @@
[appearance]="appearance"
[autoScale]="autoScale"
[disabled]="disabled || (loading$ | async)"
[disableEvents]="ctx.isEdit"
[value]="value"
[ctx]="ctx"
(clicked)="onClick($event)">

2
ui-ngx/src/app/modules/home/pages/admin/admin.module.ts

@ -27,6 +27,7 @@ import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component';
import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component';
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component';
import { ResourceTabsComponent } from '@home/pages/admin/resource/resource-tabs.component';
import { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component';
import { QueueComponent } from '@home/pages/admin/queue/queue.component';
import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component';
@ -47,6 +48,7 @@ import { NgxFlowModule } from '@flowjs/ngx-flow';
SecuritySettingsComponent,
HomeSettingsComponent,
ResourcesLibraryComponent,
ResourceTabsComponent,
ResourcesTableHeaderComponent,
JsResourceComponent,
JsLibraryTableHeaderComponent,

2
ui-ngx/src/app/modules/home/pages/admin/resource/js-library-table-config.resolver.ts

@ -43,6 +43,7 @@ import { EntityAction } from '@home/models/entity/entity-component.models';
import { JsLibraryTableHeaderComponent } from '@home/pages/admin/resource/js-library-table-header.component';
import { JsResourceComponent } from '@home/pages/admin/resource/js-resource.component';
import { switchMap } from 'rxjs/operators';
import { ResourceTabsComponent } from '@home/pages/admin/resource/resource-tabs.component';
@Injectable()
export class JsLibraryTableConfigResolver {
@ -57,6 +58,7 @@ export class JsLibraryTableConfigResolver {
this.config.entityType = EntityType.TB_RESOURCE;
this.config.entityComponent = JsResourceComponent;
this.config.entityTabsComponent = ResourceTabsComponent;
this.config.entityTranslations = {
details: 'javascript.javascript-resource-details',
add: 'javascript.add',

27
ui-ngx/src/app/modules/home/pages/admin/resource/resource-tabs.component.html

@ -0,0 +1,27 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-tab *ngIf="entity && entity.tenantId.id !== NULL_UUID && authUser.authority === authorities.TENANT_ADMIN && !isEdit" #auditLogsTab="matTab"
label="{{ 'audit-log.audit-logs' | translate }}">
<tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table>
</mat-tab>
<mat-tab *ngIf="entity && entity.tenantId.id !== NULL_UUID && authUser.authority === authorities.TENANT_ADMIN && !isEdit"
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

36
ui-ngx/src/app/modules/home/pages/admin/resource/resource-tabs.component.ts

@ -0,0 +1,36 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component';
import { Resource } from '@shared/models/resource.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@Component({
selector: 'tb-resource-tabs',
templateUrl: './resource-tabs.component.html',
styleUrls: []
})
export class ResourceTabsComponent extends EntityTabsComponent<Resource> {
readonly NULL_UUID = NULL_UUID;
constructor(protected store: Store<AppState>) {
super(store);
}
}

6
ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx

@ -131,10 +131,10 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
}
if (form.condition) {
this.hasConditions = true;
if (!this.conditionFunction) {
this.conditionFunction = new Function('form', 'model', 'index', `return ${form.condition};`);
if (!form.conditionFunction) {
form.conditionFunction = new Function('form', 'model', 'index', `return ${form.condition};`);
}
if (this.conditionFunction(form, model, index) === false) {
if (form.conditionFunction(form, model, index) === false) {
return null;
}
}

1
ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts

@ -87,6 +87,7 @@ export interface JsonFormData {
required: boolean;
default?: any;
condition?: string;
conditionFunction?: Function;
style?: any;
rows?: number;
rowsMax?: number;

12
ui-ngx/src/app/shared/models/vc.models.ts

@ -15,7 +15,7 @@
///
import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models';
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
import { ExportableEntity } from '@shared/models/base-data';
import { EntityRelation } from '@shared/models/relation.models';
import { Device, DeviceCredentials } from '@shared/models/device.models';
@ -38,7 +38,7 @@ export const exportableEntityTypes: Array<EntityType> = [
EntityType.NOTIFICATION_RULE
];
export const entityTypesWithoutRelatedData: Set<EntityType> = new Set([
export const entityTypesWithoutRelatedData: Set<EntityType | AliasEntityType> = new Set([
EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET,
EntityType.NOTIFICATION_RULE,
@ -104,8 +104,8 @@ export function createDefaultEntityTypesVersionCreate(): {[entityType: string]:
for (const entityType of exportableEntityTypes) {
res[entityType] = {
syncStrategy: null,
saveAttributes: true,
saveRelations: true,
saveAttributes: !entityTypesWithoutRelatedData.has(entityType),
saveRelations: !entityTypesWithoutRelatedData.has(entityType),
saveCredentials: true,
allEntities: true,
entityIds: []
@ -151,8 +151,8 @@ export function createDefaultEntityTypesVersionLoad(): {[entityType: string]: En
const res: {[entityType: string]: EntityTypeVersionLoadConfig} = {};
for (const entityType of exportableEntityTypes) {
res[entityType] = {
loadAttributes: true,
loadRelations: true,
loadAttributes: !entityTypesWithoutRelatedData.has(entityType),
loadRelations: !entityTypesWithoutRelatedData.has(entityType),
loadCredentials: true,
removeOtherEntities: false,
findExistingEntityByName: true

Loading…
Cancel
Save