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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <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.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import lombok.Getter; import lombok.Getter;
@ -28,6 +29,7 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; 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.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -194,6 +198,7 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; 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.StringUtils.isNotEmpty;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; 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) { protected <T> ResponseEntity<T> response(HttpStatus status) {
return ResponseEntity.status(status).build(); 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.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; 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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@ -67,6 +70,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; 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;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_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')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/dashboard/{dashboardId}") @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, @PathVariable(DASHBOARD_ID) String strDashboardId,
@Parameter(description = INCLUDE_RESOURCES_DESCRIPTION) @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); checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ); Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
if (includeResources) { if (includeResources) {
dashboard.setResources(tbResourceService.exportResources(dashboard, getCurrentUser())); 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)", @ApiOperation(value = "Create Or Update Dashboard (saveDashboard)",
@ -171,11 +178,15 @@ public class DashboardController extends BaseController {
TENANT_AUTHORITY_PARAGRAPH) TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@PostMapping(value = "/dashboard") @PostMapping(value = "/dashboard")
public Dashboard saveDashboard(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.") public void saveDashboard(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard) throws Exception { @RequestBody Dashboard dashboard,
@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
HttpServletResponse response) throws Exception {
dashboard.setTenantId(getTenantId()); dashboard.setTenantId(getTenantId());
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD); 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)", @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. " "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) + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) @GetMapping(value = "/dashboard/home")
@ResponseBody public void getHomeDashboard(@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
public HomeDashboard getHomeDashboard() throws ThingsboardException { HttpServletResponse response) throws Exception {
SecurityUser securityUser = getCurrentUser(); SecurityUser securityUser = getCurrentUser();
response.setContentType(APPLICATION_JSON_VALUE);
if (securityUser.isSystemAdmin()) { if (securityUser.isSystemAdmin()) {
return null; return;
} }
User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId());
JsonNode additionalInfo = user.getAdditionalInfo(); JsonNode additionalInfo = user.getAdditionalInfo();
@ -431,7 +443,9 @@ public class DashboardController extends BaseController {
homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo);
} }
} }
return homeDashboard; if (homeDashboard != null) {
compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(homeDashboard));
}
} }
@ApiOperation(value = "Get Home Dashboard Info (getHomeDashboardInfo)", @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.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.TimeUnit; 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_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_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.RESOURCE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_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.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.dao.util.ImageUtils.mediaTypeToFileExtension;
@Slf4j @Slf4j
@RestController @RestController
@ -179,15 +182,17 @@ public class ImageController extends BaseController {
@PathVariable String type, @PathVariable String type,
@Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true) @Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true)
@PathVariable String key, @PathVariable String key,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception { @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
return downloadIfChanged(type, key, etag, false); @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/*") @GetMapping(value = "/api/images/public/{publicResourceKey}", produces = "image/*")
public ResponseEntity<ByteArrayResource> downloadPublicImage(@PathVariable String publicResourceKey, 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); 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')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -213,8 +218,9 @@ public class ImageController extends BaseController {
@PathVariable String type, @PathVariable String type,
@Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true) @Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true)
@PathVariable String key, @PathVariable String key,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception { @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
return downloadIfChanged(type, key, etag, true); @RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
return downloadIfChanged(type, key, etag, acceptEncodingHeader, true);
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -268,12 +274,12 @@ public class ImageController extends BaseController {
return (result.isSuccess() ? ResponseEntity.ok() : ResponseEntity.badRequest()).body(result); 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); 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)) { if (StringUtils.isNotEmpty(etag)) {
etag = StringUtils.remove(etag, '\"'); // etag is wrapped in double quotes due to HTTP specification etag = StringUtils.remove(etag, '\"'); // etag is wrapped in double quotes due to HTTP specification
if (etag.equals(tbImageService.getETag(cacheKey))) { if (etag.equals(tbImageService.getETag(cacheKey))) {
@ -294,7 +300,6 @@ public class ImageController extends BaseController {
tbImageService.putETag(cacheKey, descriptor.getEtag()); tbImageService.putETag(cacheKey, descriptor.getEtag());
var result = ResponseEntity.ok() var result = ResponseEntity.ok()
.header("Content-Type", descriptor.getMediaType()) .header("Content-Type", descriptor.getMediaType())
.contentLength(data.length)
.eTag(descriptor.getEtag()); .eTag(descriptor.getEtag());
if (!cacheKey.isPublic()) { if (!cacheKey.isPublic()) {
result result
@ -308,7 +313,19 @@ public class ImageController extends BaseController {
} else { } else {
result.cacheControl(CacheControl.noCache()); 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 { 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) SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/deliveryMethods") @GetMapping("/notification/deliveryMethods")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @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()); 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) { for (DashboardId dashboardId : dashboards) {
executor.submit(() -> { executor.submit(() -> {
Dashboard dashboard = dashboardService.findDashboardById(TenantId.SYS_TENANT_ID, dashboardId); 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) { if (updated) {
dashboardService.saveDashboard(dashboard); dashboardService.saveDashboard(dashboard);
updatedCount.incrementAndGet(); updatedCount.incrementAndGet();
@ -130,7 +130,7 @@ public class ResourcesUpdater {
for (WidgetTypeId widgetTypeId : widgets) { for (WidgetTypeId widgetTypeId : widgets) {
executor.submit(() -> { executor.submit(() -> {
WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(TenantId.SYS_TENANT_ID, widgetTypeId); WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(TenantId.SYS_TENANT_ID, widgetTypeId);
boolean updated = resourceService.updateResourcesUsage(widgetTypeDetails); boolean updated = resourceService.updateResourcesUsage(widgetTypeDetails.getTenantId(), widgetTypeDetails);
if (updated) { if (updated) {
widgetTypeService.saveWidgetType(widgetTypeDetails); widgetTypeService.saveWidgetType(widgetTypeDetails);
updatedCount.incrementAndGet(); 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 @Override
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) { public List<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
return channels.values().stream() return channels.values().stream()
.filter(channel -> { .filter(channel -> {
try { try {
@ -428,7 +428,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
}) })
.map(NotificationChannel::getDeliveryMethod) .map(NotificationChannel::getDeliveryMethod)
.collect(Collectors.toSet()); .sorted().toList();
} }
@Override @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.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -132,12 +132,12 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
@Override @Override
public List<ResourceExportData> exportResources(Dashboard dashboard, SecurityUser user) throws ThingsboardException { 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 @Override
public List<ResourceExportData> exportResources(WidgetTypeDetails widgetTypeDetails, SecurityUser user) throws ThingsboardException { 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 @Override
@ -153,13 +153,12 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
} }
} }
private <T> List<ResourceExportData> exportResources(T entity, private <T> List<ResourceExportData> exportResources(Supplier<Collection<TbResourceInfo>> imagesProcessor,
Function<T, Collection<TbResourceInfo>> imagesProcessor, Supplier<Collection<TbResourceInfo>> resourcesProcessor,
Function<T, Collection<TbResourceInfo>> resourcesProcessor,
SecurityUser user) throws ThingsboardException { SecurityUser user) throws ThingsboardException {
List<TbResourceInfo> resources = new ArrayList<>(); List<TbResourceInfo> resources = new ArrayList<>();
resources.addAll(imagesProcessor.apply(entity)); resources.addAll(imagesProcessor.get());
resources.addAll(resourcesProcessor.apply(entity)); resources.addAll(resourcesProcessor.get());
for (TbResourceInfo resourceInfo : resources) { for (TbResourceInfo resourceInfo : resources) {
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.READ, resourceInfo.getId(), resourceInfo); 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.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -602,13 +603,14 @@ public class DashboardControllerTest extends AbstractControllerTest {
dashboard.setTitle("My dashboard"); dashboard.setTitle("My dashboard");
dashboard.setConfiguration(JacksonUtil.newObjectNode() dashboard.setConfiguration(JacksonUtil.newObjectNode()
.put("someImage", "tb-image;/api/images/tenant/" + imageInfo.getResourceKey()) .put("someImage", "tb-image;/api/images/tenant/" + imageInfo.getResourceKey())
.set("widgets", JacksonUtil.toJsonNode(""" .<ObjectNode>set("widgets", JacksonUtil.toJsonNode("""
{"xxx": {"xxx":
{"config":{"actions":{"elementClick":[ {"config":{"actions":{"elementClick":[
{"customResources":[{"url":{"entityType":"TB_RESOURCE","id": {"customResources":[{"url":{"entityType":"TB_RESOURCE","id":
"tb-resource;/api/resource/js_module/tenant/gateway-management-extension.js"},"isModule":true}, "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}]}]}}}} {"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 = doPost("/api/dashboard", dashboard, Dashboard.class);
Dashboard exportedDashboard = doGet("/api/dashboard/" + dashboard.getUuidId() + "?includeResources=true", 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); doPost("/api/resource", resource, TbResourceInfo.class);
Dashboard importedDashboard = doPost("/api/dashboard", exportedDashboard, Dashboard.class); Dashboard importedDashboard = doPost("/api/dashboard", exportedDashboard, Dashboard.class);
String newResourceKey = "gateway-management-extension_(1).js";
imageRef = importedDashboard.getConfiguration().get("someImage").asText(); imageRef = importedDashboard.getConfiguration().get("someImage").asText();
assertThat(imageRef).isEqualTo("tb-image;/api/images/tenant/" + imageInfo.getResourceKey()); 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(); List<String> resourcesRefs = new ArrayList<>();
String newResourceKey = "gateway-management-extension_(1).js"; resourcesRefs.add(importedDashboard.getConfiguration().get("widgets").get("xxx").get("config")
assertThat(resourceRef).isEqualTo("tb-resource;/api/resource/js_module/tenant/" + newResourceKey); .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); TbResourceInfo importedImageInfo = doGet("/api/images/tenant/" + imageInfo.getResourceKey() + "/info", TbResourceInfo.class);
assertThat(importedImageInfo.getEtag()).isEqualTo(imageInfo.getEtag()); assertThat(importedImageInfo.getEtag()).isEqualTo(imageInfo.getEtag());

2
common/actor/pom.xml

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

2
common/cache/pom.xml

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

2
common/cluster-api/pom.xml

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

2
common/coap-server/pom.xml

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

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <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); 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); TbResource createOrUpdateSystemResource(ResourceType resourceType, String resourceKey, byte[] data);

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <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 @JsonIgnore
public JsonNode getDefaultConfig() { public JsonNode getDefaultConfig() {
return Optional.ofNullable(descriptor.get("defaultConfig")) return Optional.ofNullable(descriptor)
.map(descriptor -> descriptor.get("defaultConfig"))
.filter(JsonNode::isTextual).map(JsonNode::asText) .filter(JsonNode::isTextual).map(JsonNode::asText)
.map(json -> { .map(json -> {
try { try {

2
common/edge-api/pom.xml

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

2
common/message/pom.xml

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

2
common/pom.xml

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

2
common/proto/pom.xml

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

2
common/queue/pom.xml

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

2
common/script/pom.xml

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

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

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

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

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

2
common/stats/pom.xml

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

2
common/transport/coap/pom.xml

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

2
common/transport/http/pom.xml

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

2
common/transport/lwm2m/pom.xml

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

2
common/transport/mqtt/pom.xml

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

2
common/transport/pom.xml

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

2
common/transport/snmp/pom.xml

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

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

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

2
common/util/pom.xml

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

2
common/version-control/pom.xml

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

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <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); dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
} }
try { try {
TenantId tenantId = dashboard.getTenantId();
if (CollectionUtils.isNotEmpty(dashboard.getResources())) { if (CollectionUtils.isNotEmpty(dashboard.getResources())) {
resourceService.importResources(dashboard.getTenantId(), dashboard.getResources()); resourceService.importResources(tenantId, dashboard.getResources());
} }
imageService.updateImagesUsage(dashboard); 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())); 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()); .entityId(saved.getId()).created(dashboard.getId() == null).build());
if (dashboard.getId() == null) { if (dashboard.getId() == null) {
countService.publishCountEntityEvictEvent(saved.getTenantId(), EntityType.DASHBOARD); countService.publishCountEntityEvictEvent(tenantId, EntityType.DASHBOARD);
} }
return saved; return saved;
} catch (Exception e) { } 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 @Override
public List<OAuth2ClientLoginInfo> findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType) { 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) return oauth2ClientDao.findEnabledByPkgNameAndPlatformType(pkgName, platformType)
.stream() .stream()
.map(OAuth2Utils::toClientLoginInfo) .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 org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -401,18 +402,33 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
} }
@Override @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()); 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 @Override
public boolean updateResourcesUsage(WidgetTypeDetails widgetTypeDetails) { public boolean updateResourcesUsage(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
Map<String, String> links = getResourcesLinks(widgetTypeDetails.getResources()); 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(); JsonNode defaultConfig = widgetTypeDetails.getDefaultConfig();
if (defaultConfig != null) { 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); widgetTypeDetails.setDefaultConfig(defaultConfig);
} }
return updated; return updated;
@ -433,8 +449,9 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return links; return links;
} }
private boolean updateResourcesUsage(TenantId tenantId, JsonNode jsonNode, Map<String, String> mapping, Map<String, String> links) { private boolean updateResourcesUsage(TenantId tenantId, List<JsonNode> jsonNodes, List<Map<String, String>> mappings, Map<String, String> links) {
return processResources(jsonNode, mapping, value -> { log.trace("[{}] updateResourcesUsage (new links: {}) for {}", tenantId, links, jsonNodes);
return processResources(jsonNodes, mappings, value -> {
String link = getResourceLink(value); String link = getResourceLink(value);
if (link != null) { if (link != null) {
String newLink = links.get(link); String newLink = links.get(link);
@ -462,23 +479,31 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
} }
@Override @Override
public Collection<TbResourceInfo> getUsedResources(Dashboard dashboard) { public Collection<TbResourceInfo> getUsedResources(TenantId tenantId, Dashboard dashboard) {
return getUsedResources(dashboard.getTenantId(), dashboard.getConfiguration(), DASHBOARD_RESOURCES_MAPPING).values(); return getUsedResources(tenantId, List.of(dashboard.getConfiguration()), List.of(DASHBOARD_RESOURCES_MAPPING)).values();
} }
@Override @Override
public Collection<TbResourceInfo> getUsedResources(WidgetTypeDetails widgetTypeDetails) { public Collection<TbResourceInfo> getUsedResources(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
Map<TbResourceId, TbResourceInfo> resources = getUsedResources(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor(), WIDGET_RESOURCES_MAPPING); 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(); JsonNode defaultConfig = widgetTypeDetails.getDefaultConfig();
if (defaultConfig != null) { 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<>(); Map<TbResourceId, TbResourceInfo> resources = new HashMap<>();
processResources(jsonNode, mapping, value -> { log.trace("[{}] getUsedResources for {}", tenantId, jsonNodes);
processResources(jsonNodes, mappings, value -> {
String link = getResourceLink(value); String link = getResourceLink(value);
if (link == null) { if (link == null) {
return value; 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); 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)) { for (int i = 0; i < jsonNodes.size(); i++) {
value = processor.apply(value); JsonNode jsonNode = jsonNodes.get(i);
} else { // processing by mappings first
value = ""; 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 (StringUtils.isNotBlank(value)) {
if (!newValue.toString().equals(urlNode.toString())) { value = processor.apply(value);
updated.set(true); } else {
log.trace("Replaced '{}' with '{}'", urlNode, newValue); 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(); return updated.get();
} }
@ -555,7 +605,7 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
importResources(dashboard.getTenantId(), dashboard.getResources()); importResources(dashboard.getTenantId(), dashboard.getResources());
} }
imageService.updateImagesUsage(dashboard); imageService.updateImagesUsage(dashboard);
updateResourcesUsage(dashboard); updateResourcesUsage(dashboard.getTenantId(), dashboard);
data = JacksonUtil.writeValueAsBytes(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.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -65,8 +66,17 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao<OAuth2ClientEntity, OAuth
@Override @Override
public List<OAuth2Client> findEnabledByPkgNameAndPlatformType(String pkgName, PlatformType platformType) { public List<OAuth2Client> findEnabledByPkgNameAndPlatformType(String pkgName, PlatformType platformType) {
return DaoUtil.convertDataList(repository.findEnabledByPkgNameAndPlatformType(pkgName, List<OAuth2ClientEntity> clientEntities;
platformType != null ? platformType.name() : null)); 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 @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 " + "FROM OAuth2ClientEntity c " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity ac ON c.id = ac.oauth2ClientId " + "LEFT JOIN MobileAppBundleOauth2ClientEntity ac ON c.id = ac.oauth2ClientId " +
"LEFT JOIN MobileAppBundleEntity b ON ac.mobileAppBundleId = b.id " + "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 " + "LEFT JOIN MobileAppEntity andApp ON b.androidAppId = andApp.id " +
"WHERE andApp.pkgName = :pkgName OR iosApp.pkgName = :pkgName AND b.oauth2Enabled = true " + "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)") "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, List<OAuth2ClientEntity> findEnabledByAndroidPkgNameAndPlatformType(@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter); @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 " + @Query("SELECT c " +
"FROM OAuth2ClientEntity 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); log.trace("Executing saveWidgetType [{}]", widgetTypeDetails);
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId); widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
try { try {
TenantId tenantId = widgetTypeDetails.getTenantId();
if (CollectionUtils.isNotEmpty(widgetTypeDetails.getResources())) { if (CollectionUtils.isNotEmpty(widgetTypeDetails.getResources())) {
resourceService.importResources(widgetTypeDetails.getTenantId(), widgetTypeDetails.getResources()); resourceService.importResources(tenantId, widgetTypeDetails.getResources());
} }
imageService.updateImagesUsage(widgetTypeDetails); imageService.updateImagesUsage(widgetTypeDetails);
resourceService.updateResourcesUsage(widgetTypeDetails); resourceService.updateResourcesUsage(tenantId, widgetTypeDetails);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails); WidgetTypeDetails result = widgetTypeDao.save(tenantId, widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(result.getId()).created(widgetTypeDetails.getId() == null).build()); .entityId(result.getId()).created(widgetTypeDetails.getId() == null).build());
return result; return result;
} catch (Exception t) { } catch (Exception t) {

2
monitoring/pom.xml

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

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

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

2
msa/js-executor/pom.xml

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

2
msa/monitoring/pom.xml

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

2
msa/pom.xml

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

2
msa/tb-node/pom.xml

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

2
msa/tb/pom.xml

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

2
msa/transport/coap/pom.xml

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

2
msa/transport/http/pom.xml

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

2
msa/transport/lwm2m/pom.xml

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

2
msa/transport/mqtt/pom.xml

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

2
msa/transport/pom.xml

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

2
msa/transport/snmp/pom.xml

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

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

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

2
msa/vc-executor/pom.xml

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

2
msa/web-ui/pom.xml

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

4
netty-mqtt/pom.xml

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

4
pom.xml

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

2
rule-engine/pom.xml

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

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

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <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.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import java.util.Set; import java.util.List;
public interface NotificationCenter { public interface NotificationCenter {
@ -48,6 +48,6 @@ public interface NotificationCenter {
void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId); 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

2
tools/pom.xml

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

2
transport/coap/pom.xml

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

2
transport/http/pom.xml

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

2
transport/lwm2m/pom.xml

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

2
transport/mqtt/pom.xml

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

2
transport/pom.xml

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

2
transport/snmp/pom.xml

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

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.9.0-SNAPSHOT</version> <version>3.9.0-RC</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <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;"> <mat-checkbox *ngIf="entityId.entityType === entityTypes.DEVICE" formControlName="saveCredentials" style="margin-bottom: 16px;">
{{ 'version-control.export-credentials' | translate }} {{ 'version-control.export-credentials' | translate }}
</mat-checkbox> </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 }} {{ 'version-control.export-attributes' | translate }}
</mat-checkbox> </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 }} {{ 'version-control.export-relations' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </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 { PageComponent } from '@shared/components/page.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { import {
entityTypesWithoutRelatedData,
SingleEntityVersionCreateRequest, SingleEntityVersionCreateRequest,
VersionCreateRequestType, VersionCreateRequestType,
VersionCreationResult VersionCreationResult
@ -62,6 +63,8 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
entityTypes = EntityType; entityTypes = EntityType;
entityTypesWithoutRelatedData = entityTypesWithoutRelatedData;
resultMessage: string; resultMessage: string;
versionCreateResult$: Observable<VersionCreationResult>; versionCreateResult$: Observable<VersionCreationResult>;
@ -108,8 +111,10 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
branch: this.createVersionFormGroup.get('branch').value, branch: this.createVersionFormGroup.get('branch').value,
versionName: this.createVersionFormGroup.get('versionName').value, versionName: this.createVersionFormGroup.get('versionName').value,
config: { config: {
saveRelations: this.createVersionFormGroup.get('saveRelations').value, saveRelations: !entityTypesWithoutRelatedData.has(this.entityId.entityType)
saveAttributes: this.createVersionFormGroup.get('saveAttributes').value, ? 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 saveCredentials: this.entityId.entityType === EntityType.DEVICE ? this.createVersionFormGroup.get('saveCredentials').value : false
}, },
type: VersionCreateRequestType.SINGLE_ENTITY 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. 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> <ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-segmented-button-container"> <div class="tb-segmented-button-container">
<tb-widget-button-toggle <tb-widget-button-toggle
@ -23,6 +23,7 @@
[appearance]="appearance" [appearance]="appearance"
[autoScale]="autoScale" [autoScale]="autoScale"
[disabled]="disabled || (loading$ | async)" [disabled]="disabled || (loading$ | async)"
[disableEvents]="ctx.isEdit"
[value]="value" [value]="value"
[ctx]="ctx" [ctx]="ctx"
(clicked)="onClick($event)"> (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 { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component';
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.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 { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component';
import { QueueComponent } from '@home/pages/admin/queue/queue.component'; import { QueueComponent } from '@home/pages/admin/queue/queue.component';
import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component';
@ -47,6 +48,7 @@ import { NgxFlowModule } from '@flowjs/ngx-flow';
SecuritySettingsComponent, SecuritySettingsComponent,
HomeSettingsComponent, HomeSettingsComponent,
ResourcesLibraryComponent, ResourcesLibraryComponent,
ResourceTabsComponent,
ResourcesTableHeaderComponent, ResourcesTableHeaderComponent,
JsResourceComponent, JsResourceComponent,
JsLibraryTableHeaderComponent, 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 { JsLibraryTableHeaderComponent } from '@home/pages/admin/resource/js-library-table-header.component';
import { JsResourceComponent } from '@home/pages/admin/resource/js-resource.component'; import { JsResourceComponent } from '@home/pages/admin/resource/js-resource.component';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { ResourceTabsComponent } from '@home/pages/admin/resource/resource-tabs.component';
@Injectable() @Injectable()
export class JsLibraryTableConfigResolver { export class JsLibraryTableConfigResolver {
@ -57,6 +58,7 @@ export class JsLibraryTableConfigResolver {
this.config.entityType = EntityType.TB_RESOURCE; this.config.entityType = EntityType.TB_RESOURCE;
this.config.entityComponent = JsResourceComponent; this.config.entityComponent = JsResourceComponent;
this.config.entityTabsComponent = ResourceTabsComponent;
this.config.entityTranslations = { this.config.entityTranslations = {
details: 'javascript.javascript-resource-details', details: 'javascript.javascript-resource-details',
add: 'javascript.add', 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) { if (form.condition) {
this.hasConditions = true; this.hasConditions = true;
if (!this.conditionFunction) { if (!form.conditionFunction) {
this.conditionFunction = new Function('form', 'model', 'index', `return ${form.condition};`); 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; 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; required: boolean;
default?: any; default?: any;
condition?: string; condition?: string;
conditionFunction?: Function;
style?: any; style?: any;
rows?: number; rows?: number;
rowsMax?: 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 { 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 { ExportableEntity } from '@shared/models/base-data';
import { EntityRelation } from '@shared/models/relation.models'; import { EntityRelation } from '@shared/models/relation.models';
import { Device, DeviceCredentials } from '@shared/models/device.models'; import { Device, DeviceCredentials } from '@shared/models/device.models';
@ -38,7 +38,7 @@ export const exportableEntityTypes: Array<EntityType> = [
EntityType.NOTIFICATION_RULE EntityType.NOTIFICATION_RULE
]; ];
export const entityTypesWithoutRelatedData: Set<EntityType> = new Set([ export const entityTypesWithoutRelatedData: Set<EntityType | AliasEntityType> = new Set([
EntityType.NOTIFICATION_TEMPLATE, EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_TARGET,
EntityType.NOTIFICATION_RULE, EntityType.NOTIFICATION_RULE,
@ -104,8 +104,8 @@ export function createDefaultEntityTypesVersionCreate(): {[entityType: string]:
for (const entityType of exportableEntityTypes) { for (const entityType of exportableEntityTypes) {
res[entityType] = { res[entityType] = {
syncStrategy: null, syncStrategy: null,
saveAttributes: true, saveAttributes: !entityTypesWithoutRelatedData.has(entityType),
saveRelations: true, saveRelations: !entityTypesWithoutRelatedData.has(entityType),
saveCredentials: true, saveCredentials: true,
allEntities: true, allEntities: true,
entityIds: [] entityIds: []
@ -151,8 +151,8 @@ export function createDefaultEntityTypesVersionLoad(): {[entityType: string]: En
const res: {[entityType: string]: EntityTypeVersionLoadConfig} = {}; const res: {[entityType: string]: EntityTypeVersionLoadConfig} = {};
for (const entityType of exportableEntityTypes) { for (const entityType of exportableEntityTypes) {
res[entityType] = { res[entityType] = {
loadAttributes: true, loadAttributes: !entityTypesWithoutRelatedData.has(entityType),
loadRelations: true, loadRelations: !entityTypesWithoutRelatedData.has(entityType),
loadCredentials: true, loadCredentials: true,
removeOtherEntities: false, removeOtherEntities: false,
findExistingEntityByName: true findExistingEntityByName: true

Loading…
Cancel
Save