diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index cbc6ff2bca..470ec738af 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -29,24 +29,23 @@ import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; +import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService; import org.thingsboard.server.service.update.UpdateService; import static org.thingsboard.server.controller.ControllerConstants.*; -import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; @RestController @TbCoreComponent @@ -68,6 +67,9 @@ public class AdminController extends BaseController { @Autowired private EntitiesVersionControlService versionControlService; + @Autowired + private TbAutoCommitSettingsService autoCommitSettingsService; + @Autowired private UpdateService updateService; @@ -189,15 +191,15 @@ public class AdminController extends BaseController { } } - @ApiOperation(value = "Get version control settings (getVersionControlSettings)", - notes = "Get the version control settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Get repository settings (getRepositorySettings)", + notes = "Get the repository settings object. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @GetMapping("/vcSettings") + @GetMapping("/repositorySettings") @ResponseBody - public EntitiesVersionControlSettings getVersionControlSettings() throws ThingsboardException { + public RepositorySettings getRepositorySettings() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); - EntitiesVersionControlSettings versionControlSettings = checkNotNull(versionControlService.getVersionControlSettings(getTenantId())); + RepositorySettings versionControlSettings = checkNotNull(versionControlService.getVersionControlSettings(getTenantId())); versionControlSettings.setPassword(null); versionControlSettings.setPrivateKey(null); versionControlSettings.setPrivateKeyPassword(null); @@ -207,12 +209,12 @@ public class AdminController extends BaseController { } } - @ApiOperation(value = "Check version control settings exists (versionControlSettingsExists)", - notes = "Check whether the version control settings exists. " + TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Check repository settings exists (repositorySettingsExists)", + notes = "Check whether the repository settings exists. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @GetMapping("/vcSettings/exists") + @GetMapping("/repositorySettings/exists") @ResponseBody - public Boolean versionControlSettingsExists() throws ThingsboardException { + public Boolean repositorySettingsExists() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); return versionControlService.getVersionControlSettings(getTenantId()) != null; @@ -221,13 +223,13 @@ public class AdminController extends BaseController { } } - @ApiOperation(value = "Creates or Updates the version control settings (saveVersionControlSettings)", - notes = "Creates or Updates the version control settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Creates or Updates the repository settings (saveRepositorySettings)", + notes = "Creates or Updates the repository settings object. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @PostMapping("/vcSettings") - public DeferredResult saveVersionControlSettings(@RequestBody EntitiesVersionControlSettings settings) throws ThingsboardException { + @PostMapping("/repositorySettings") + public DeferredResult saveRepositorySettings(@RequestBody RepositorySettings settings) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); - ListenableFuture future = versionControlService.saveVersionControlSettings(getTenantId(), settings); + ListenableFuture future = versionControlService.saveVersionControlSettings(getTenantId(), settings); return wrapFuture(Futures.transform(future, savedSettings -> { savedSettings.setPassword(null); savedSettings.setPrivateKey(null); @@ -236,13 +238,13 @@ public class AdminController extends BaseController { }, MoreExecutors.directExecutor())); } - @ApiOperation(value = "Delete version control settings (deleteVersionControlSettings)", - notes = "Deletes the version control settings." + @ApiOperation(value = "Delete repository settings (deleteRepositorySettings)", + notes = "Deletes the repository settings." + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/vcSettings", method = RequestMethod.DELETE) + @RequestMapping(value = "/repositorySettings", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public DeferredResult deleteVersionControlSettings() throws ThingsboardException { + public DeferredResult deleteRepositorySettings() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.DELETE); return wrapFuture(versionControlService.deleteVersionControlSettings(getTenantId())); @@ -251,13 +253,14 @@ public class AdminController extends BaseController { } } - @ApiOperation(value = "Check version control access (checkVersionControlAccess)", - notes = "Attempts to check version control access. " + TENANT_AUTHORITY_PARAGRAPH) + + @ApiOperation(value = "Check repository access (checkRepositoryAccess)", + notes = "Attempts to check repository access. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/vcSettings/checkAccess", method = RequestMethod.POST) - public DeferredResult checkVersionControlAccess( - @ApiParam(value = "A JSON value representing the Entities Version Control Settings.") - @RequestBody EntitiesVersionControlSettings settings) throws ThingsboardException { + @RequestMapping(value = "/repositorySettings/checkAccess", method = RequestMethod.POST) + public DeferredResult checkRepositoryAccess( + @ApiParam(value = "A JSON value representing the Repository Settings.") + @RequestBody RepositorySettings settings) throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); settings = checkNotNull(settings); @@ -267,6 +270,58 @@ public class AdminController extends BaseController { } } + @ApiOperation(value = "Get auto commit settings (getAutoCommitSettings)", + notes = "Get the auto commit settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/autoCommitSettings") + @ResponseBody + public AutoCommitSettings getAutoCommitSettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + return checkNotNull(autoCommitSettingsService.get(getTenantId())); + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Check auto commit settings exists (autoCommitSettingsExists)", + notes = "Check whether the auto commit settings exists. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/autoCommitSettings/exists") + @ResponseBody + public Boolean autoCommitSettingsExists() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + return autoCommitSettingsService.get(getTenantId()) != null; + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Creates or Updates the auto commit settings (saveAutoCommitSettings)", + notes = "Creates or Updates the auto commit settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @PostMapping("/autoCommitSettings") + public AutoCommitSettings saveAutoCommitSettings(@RequestBody AutoCommitSettings settings) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); + return autoCommitSettingsService.save(getTenantId(), settings); + } + + @ApiOperation(value = "Delete auto commit settings (deleteAutoCommitSettings)", + notes = "Deletes the auto commit settings." + + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/autoCommitSettings", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void deleteAutoCommitSettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.DELETE); + autoCommitSettingsService.delete(getTenantId()); + } catch (Exception e) { + throw handleException(e); + } + } + @ApiOperation(value = "Check for new Platform Releases (checkUpdates)", notes = "Check notifications about new platform releases. " + SYSTEM_AUTHORITY_PARAGRAPH) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java index bf7011867d..36aa6fd961 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; +import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; @@ -237,6 +238,18 @@ public class EntitiesVersionControlController extends BaseController { } } + @GetMapping("/info/{versionId}/{entityType}/{internalEntityUuid}") + public DeferredResult getEntityDataInfo(@PathVariable String versionId, + @PathVariable EntityType entityType, + @PathVariable UUID internalEntityUuid) throws ThingsboardException { + try { + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid); + return wrapFuture(versionControlService.getEntityDataInfo(getCurrentUser(), entityId, versionId)); + } catch (Exception e) { + throw handleException(e); + } + } + @GetMapping("/diff/{branch}/{entityType}/{internalEntityUuid}") public DeferredResult compareEntityDataToVersion(@PathVariable String branch, @PathVariable EntityType entityType, diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java index 2a7570e644..aec96b2e47 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java @@ -42,7 +42,11 @@ public class CustomerImportService extends BaseEntityImportService exportData, IdProvider idProvider) { - return customerService.saveCustomer(customer); + if (customer.isPublic()) { + return customerService.findOrCreatePublicCustomer(tenantId); + } else { + return customerService.saveCustomer(customer); + } } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index 7f9a9646ba..cca21929c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -44,7 +44,8 @@ import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; @@ -68,7 +69,9 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.sync.ie.EntitiesExportImportService; import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; +import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService; import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; +import org.thingsboard.server.service.sync.vc.repository.TbRepositorySettingsService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -92,7 +95,8 @@ import static com.google.common.util.concurrent.Futures.transformAsync; @Slf4j public class DefaultEntitiesVersionControlService implements EntitiesVersionControlService { - private final TbVersionControlSettingsService vcSettingsService; + private final TbRepositorySettingsService repositorySettingsService; + private final TbAutoCommitSettingsService autoCommitSettingsService; private final GitVersionControlQueueService gitServiceQueue; private final EntitiesExportImportService exportImportService; private final ExportableEntitiesService exportableEntitiesService; @@ -119,7 +123,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont @SuppressWarnings("UnstableApiUsage") @Override public ListenableFuture saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception { - var pendingCommit = gitServiceQueue.prepareCommit(user.getTenantId(), request); + var pendingCommit = gitServiceQueue.prepareCommit(user, request); return transformAsync(pendingCommit, commit -> { List> gitFutures = new ArrayList<>(); @@ -325,34 +329,42 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont EntityId externalId = ((ExportableEntity) entity).getExternalId(); if (externalId == null) externalId = entityId; - EntityExportData currentVersion = exportImportService.exportEntity(user, entityId, EntityExportSettings.builder() - .exportRelations(true) - .exportAttributes(true) - .build()); return transformAsync(gitServiceQueue.getEntity(user.getTenantId(), versionId, externalId), - otherVersion -> transform(gitServiceQueue.getContentsDiff(user.getTenantId(), - JacksonUtil.toPrettyString(currentVersion), - JacksonUtil.toPrettyString(otherVersion)), rawDiff -> { - return new EntityDataDiff(currentVersion, otherVersion, rawDiff); - }, MoreExecutors.directExecutor()), MoreExecutors.directExecutor()); + otherVersion -> { + EntityExportData currentVersion = exportImportService.exportEntity(user, entityId, EntityExportSettings.builder() + .exportRelations(otherVersion.getRelations() != null) + .exportAttributes(otherVersion.getAttributes() != null) + .build()); + return transform(gitServiceQueue.getContentsDiff(user.getTenantId(), + JacksonUtil.toPrettyString(currentVersion.sort()), + JacksonUtil.toPrettyString(otherVersion.sort())), + rawDiff -> new EntityDataDiff(currentVersion, otherVersion, rawDiff), MoreExecutors.directExecutor()); + }, MoreExecutors.directExecutor()); } + @Override + public ListenableFuture getEntityDataInfo(SecurityUser user, EntityId entityId, String versionId) { + return Futures.transform(gitServiceQueue.getEntity(user.getTenantId(), versionId, entityId), + entity -> new EntityDataInfo(entity.getRelations() != null, entity.getAttributes() != null), MoreExecutors.directExecutor()); + } + + @Override public ListenableFuture> listBranches(TenantId tenantId) throws Exception { return gitServiceQueue.listBranches(tenantId); } @Override - public EntitiesVersionControlSettings getVersionControlSettings(TenantId tenantId) { - return vcSettingsService.get(tenantId); + public RepositorySettings getVersionControlSettings(TenantId tenantId) { + return repositorySettingsService.get(tenantId); } @Override - public ListenableFuture saveVersionControlSettings(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings) { - var restoredSettings = this.vcSettingsService.restore(tenantId, versionControlSettings); + public ListenableFuture saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings) { + var restoredSettings = this.repositorySettingsService.restore(tenantId, versionControlSettings); try { var future = gitServiceQueue.initRepository(tenantId, restoredSettings); - return Futures.transform(future, f -> vcSettingsService.save(tenantId, restoredSettings), MoreExecutors.directExecutor()); + return Futures.transform(future, f -> repositorySettingsService.save(tenantId, restoredSettings), MoreExecutors.directExecutor()); } catch (Exception e) { log.debug("{} Failed to init repository: {}", tenantId, versionControlSettings, e); throw new RuntimeException("Failed to init repository!", e); @@ -361,7 +373,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont @Override public ListenableFuture deleteVersionControlSettings(TenantId tenantId) throws Exception { - if (vcSettingsService.delete(tenantId)) { + if (repositorySettingsService.delete(tenantId)) { return gitServiceQueue.clearRepository(tenantId); } else { return Futures.immediateFuture(null); @@ -369,8 +381,8 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont } @Override - public ListenableFuture checkVersionControlAccess(TenantId tenantId, EntitiesVersionControlSettings settings) throws ThingsboardException { - settings = this.vcSettingsService.restore(tenantId, settings); + public ListenableFuture checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws ThingsboardException { + settings = this.repositorySettingsService.restore(tenantId, settings); try { return gitServiceQueue.testRepository(tenantId, settings); } catch (Exception e) { @@ -381,22 +393,29 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont @Override public ListenableFuture autoCommit(SecurityUser user, EntityId entityId) throws Exception { - var settings = vcSettingsService.get(user.getTenantId()); + var repositorySettings = repositorySettingsService.get(user.getTenantId()); + if (repositorySettings == null) { + return Futures.immediateFuture(null); + } + var autoCommitSettings = autoCommitSettingsService.get(user.getTenantId()); + if (autoCommitSettings == null) { + return Futures.immediateFuture(null); + } var entityType = entityId.getEntityType(); - if (settings != null && settings.getAutoCommitSettings() != null && settings.getAutoCommitSettings().containsKey(entityType)) { - AutoVersionCreateConfig autoCommitConfig = settings.getAutoCommitSettings().get(entityType); - SingleEntityVersionCreateRequest vcr = new SingleEntityVersionCreateRequest(); - var autoCommitBranchName = autoCommitConfig.getBranch(); - if (StringUtils.isEmpty(autoCommitBranchName)) { - autoCommitBranchName = StringUtils.isNotEmpty(settings.getDefaultBranch()) ? settings.getDefaultBranch() : "auto-commits"; - } - vcr.setBranch(autoCommitBranchName); - vcr.setVersionName("auto-commit by " + user.getEmail() + " at " + Instant.ofEpochSecond(System.currentTimeMillis() / 1000)); - vcr.setEntityId(entityId); - vcr.setConfig(autoCommitConfig); - return saveEntitiesVersion(user, vcr); + AutoVersionCreateConfig autoCommitConfig = autoCommitSettings.get(entityType); + if (autoCommitConfig == null) { + return Futures.immediateFuture(null); + } + SingleEntityVersionCreateRequest vcr = new SingleEntityVersionCreateRequest(); + var autoCommitBranchName = autoCommitConfig.getBranch(); + if (StringUtils.isEmpty(autoCommitBranchName)) { + autoCommitBranchName = StringUtils.isNotEmpty(repositorySettings.getDefaultBranch()) ? repositorySettings.getDefaultBranch() : "auto-commits"; } - return Futures.immediateFuture(null); + vcr.setBranch(autoCommitBranchName); + vcr.setVersionName("auto-commit at " + Instant.ofEpochSecond(System.currentTimeMillis() / 1000)); + vcr.setEntityId(entityId); + vcr.setConfig(autoCommitConfig); + return saveEntitiesVersion(user, vcr); } private String getCauseMessage(Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java index 9251fe392b..c87789a096 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java @@ -27,13 +27,14 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.sync.ie.EntityExportData; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.EntityVersionsDiff; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; @@ -66,10 +67,7 @@ import org.thingsboard.server.service.sync.vc.data.PendingGitRequest; import org.thingsboard.server.service.sync.vc.data.VersionsDiffGitRequest; import org.thingsboard.server.service.sync.vc.data.VoidGitRequest; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -96,12 +94,12 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu } @Override - public ListenableFuture prepareCommit(TenantId tenantId, VersionCreateRequest request) { + public ListenableFuture prepareCommit(User user, VersionCreateRequest request) { SettableFuture future = SettableFuture.create(); - CommitGitRequest commit = new CommitGitRequest(tenantId, request); + CommitGitRequest commit = new CommitGitRequest(user.getTenantId(), request); registerAndSend(commit, builder -> builder.setCommitRequest( - buildCommitRequest(commit).setPrepareMsg(getCommitPrepareMsg(request)).build() + buildCommitRequest(commit).setPrepareMsg(getCommitPrepareMsg(user, request)).build() ).build(), wrap(future, commit)); return future; } @@ -111,7 +109,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu SettableFuture future = SettableFuture.create(); String path = getRelativePath(entityData.getEntityType(), entityData.getEntity().getId()); - String entityDataJson = JacksonUtil.toPrettyString(entityData); + String entityDataJson = JacksonUtil.toPrettyString(entityData.sort()); registerAndSend(commit, builder -> builder.setCommitRequest( buildCommitRequest(commit).setAddMsg( @@ -132,7 +130,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu buildCommitRequest(commit).setDeleteMsg( TransportProtos.DeleteMsg.newBuilder().setRelativePath(path).build() ).build() - ).build(), wrap(commit.getFuture(), null)); + ).build(), wrap(future, null)); return future; } @@ -154,7 +152,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu return listVersions(tenantId, applyPageLinkParameters( ListVersionsRequestMsg.newBuilder() - .setBranchName(branch), + .setBranchName(branch), pageLink ).build()); } @@ -164,8 +162,8 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu return listVersions(tenantId, applyPageLinkParameters( ListVersionsRequestMsg.newBuilder() - .setBranchName(branch) - .setEntityType(entityType.name()), + .setBranchName(branch) + .setEntityType(entityType.name()), pageLink ).build()); } @@ -175,10 +173,10 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu return listVersions(tenantId, applyPageLinkParameters( ListVersionsRequestMsg.newBuilder() - .setBranchName(branch) - .setEntityType(entityId.getEntityType().name()) - .setEntityIdMSB(entityId.getId().getMostSignificantBits()) - .setEntityIdLSB(entityId.getId().getLeastSignificantBits()), + .setBranchName(branch) + .setEntityType(entityId.getEntityType().name()) + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits()), pageLink ).build()); } @@ -271,7 +269,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu } private void registerAndSend(PendingGitRequest request, - Function enrichFunction, EntitiesVersionControlSettings settings, TbQueueCallback callback) { + Function enrichFunction, RepositorySettings settings, TbQueueCallback callback) { if (!request.getFuture().isDone()) { pendingRequestMap.putIfAbsent(request.getRequestId(), request); var requestBody = enrichFunction.apply(newRequestProto(request, settings)); @@ -307,7 +305,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu } @Override - public ListenableFuture initRepository(TenantId tenantId, EntitiesVersionControlSettings settings) { + public ListenableFuture initRepository(TenantId tenantId, RepositorySettings settings) { VoidGitRequest request = new VoidGitRequest(tenantId); registerAndSend(request, builder -> builder.setInitRepositoryRequest(GenericRepositoryRequestMsg.newBuilder().build()).build() @@ -317,7 +315,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu } @Override - public ListenableFuture testRepository(TenantId tenantId, EntitiesVersionControlSettings settings) { + public ListenableFuture testRepository(TenantId tenantId, RepositorySettings settings) { VoidGitRequest request = new VoidGitRequest(tenantId); registerAndSend(request, builder -> builder @@ -356,7 +354,9 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu } else if (vcResponseMsg.hasCommitResponse()) { var commitResponse = vcResponseMsg.getCommitResponse(); var commitResult = new VersionCreationResult(); - commitResult.setVersion(new EntityVersion(commitResponse.getTs(), commitResponse.getCommitId(), commitResponse.getName())); + if (commitResponse.getTs() > 0) { + commitResult.setVersion(new EntityVersion(commitResponse.getTs(), commitResponse.getCommitId(), commitResponse.getName(), commitResponse.getAuthor())); + } commitResult.setAdded(commitResponse.getAdded()); commitResult.setRemoved(commitResponse.getRemoved()); commitResult.setModified(commitResponse.getModified()); @@ -405,7 +405,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu } private EntityVersion getEntityVersion(TransportProtos.EntityVersionProto proto) { - return new EntityVersion(proto.getTs(), proto.getId(), proto.getName()); + return new EntityVersion(proto.getTs(), proto.getId(), proto.getName(), proto.getAuthor()); } private VersionedEntityInfo getVersionedEntityInfo(TransportProtos.VersionedEntityInfoProto proto) { @@ -453,11 +453,26 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu return path; } - private static PrepareMsg getCommitPrepareMsg(VersionCreateRequest request) { - return PrepareMsg.newBuilder().setCommitMsg(request.getVersionName()).setBranchName(request.getBranch()).build(); + private static PrepareMsg getCommitPrepareMsg(User user, VersionCreateRequest request) { + return PrepareMsg.newBuilder().setCommitMsg(request.getVersionName()) + .setBranchName(request.getBranch()).setAuthorName(getAuthorName(user)).setAuthorEmail(user.getEmail()).build(); + } + + private static String getAuthorName(User user) { + List parts = new ArrayList<>(); + if (StringUtils.isNotBlank(user.getFirstName())) { + parts.add(user.getFirstName()); + } + if (StringUtils.isNotBlank(user.getLastName())) { + parts.add(user.getLastName()); + } + if (parts.isEmpty()) { + parts.add(user.getName()); + } + return String.join(" ", parts); } - private ToVersionControlServiceMsg.Builder newRequestProto(PendingGitRequest request, EntitiesVersionControlSettings settings) { + private ToVersionControlServiceMsg.Builder newRequestProto(PendingGitRequest request, RepositorySettings settings) { var tenantId = request.getTenantId(); var requestId = request.getRequestId(); var builder = ToVersionControlServiceMsg.newBuilder() @@ -466,7 +481,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setRequestIdMSB(requestId.getMostSignificantBits()) .setRequestIdLSB(requestId.getLeastSignificantBits()); - EntitiesVersionControlSettings vcSettings = settings; + RepositorySettings vcSettings = settings; if (vcSettings == null && request.requiresSettings()) { vcSettings = entitiesVersionControlService.getVersionControlSettings(tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultTbVersionControlSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultTbVersionControlSettingsService.java deleted file mode 100644 index 1d1fff81a0..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultTbVersionControlSettingsService.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.sync.vc; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cache.TbTransactionalCache; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; -import org.thingsboard.server.common.data.sync.vc.VersionControlAuthMethod; -import org.thingsboard.server.dao.settings.AdminSettingsService; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Service -@TbCoreComponent -@RequiredArgsConstructor -public class DefaultTbVersionControlSettingsService implements TbVersionControlSettingsService { - - public static final String SETTINGS_KEY = "entitiesVersionControl"; - private final AdminSettingsService adminSettingsService; - private final TbTransactionalCache cache; - - @Override - public EntitiesVersionControlSettings restore(TenantId tenantId, EntitiesVersionControlSettings settings) { - EntitiesVersionControlSettings storedSettings = get(tenantId); - if (storedSettings != null) { - VersionControlAuthMethod authMethod = settings.getAuthMethod(); - if (VersionControlAuthMethod.USERNAME_PASSWORD.equals(authMethod) && settings.getPassword() == null) { - settings.setPassword(storedSettings.getPassword()); - } else if (VersionControlAuthMethod.PRIVATE_KEY.equals(authMethod) && settings.getPrivateKey() == null) { - settings.setPrivateKey(storedSettings.getPrivateKey()); - if (settings.getPrivateKeyPassword() == null) { - settings.setPrivateKeyPassword(storedSettings.getPrivateKeyPassword()); - } - } - } - return settings; - } - - @Override - public EntitiesVersionControlSettings get(TenantId tenantId) { - EntitiesVersionControlSettings settings = cache.getAndPutInTransaction(tenantId, () -> { - AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, SETTINGS_KEY); - if (adminSettings != null) { - try { - return JacksonUtil.convertValue(adminSettings.getJsonValue(), EntitiesVersionControlSettings.class); - } catch (Exception e) { - throw new RuntimeException("Failed to load version control settings!", e); - } - } - return null; - }, true); - if (settings != null) { - settings = new EntitiesVersionControlSettings(settings); - } - return settings; - } - - @Override - public EntitiesVersionControlSettings save(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings) { - AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, SETTINGS_KEY); - if (adminSettings == null) { - adminSettings = new AdminSettings(); - adminSettings.setKey(SETTINGS_KEY); - adminSettings.setTenantId(tenantId); - } - adminSettings.setJsonValue(JacksonUtil.valueToTree(versionControlSettings)); - AdminSettings savedAdminSettings = adminSettingsService.saveAdminSettings(tenantId, adminSettings); - EntitiesVersionControlSettings savedVersionControlSettings; - try { - savedVersionControlSettings = JacksonUtil.convertValue(savedAdminSettings.getJsonValue(), EntitiesVersionControlSettings.class); - } catch (Exception e) { - throw new RuntimeException("Failed to load version control settings!", e); - } - //API calls to adminSettingsService are not in transaction, so we can simply evict the cache. - cache.evict(tenantId); - return savedVersionControlSettings; - } - - @Override - public boolean delete(TenantId tenantId) { - boolean result = adminSettingsService.deleteAdminSettings(tenantId, SETTINGS_KEY); - cache.evict(tenantId); - return result; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java index 836a75e021..88c23616b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java @@ -16,15 +16,15 @@ package org.thingsboard.server.service.sync.vc; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; +import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; @@ -54,13 +54,15 @@ public interface EntitiesVersionControlService { ListenableFuture> listBranches(TenantId tenantId) throws Exception; - EntitiesVersionControlSettings getVersionControlSettings(TenantId tenantId); + RepositorySettings getVersionControlSettings(TenantId tenantId); - ListenableFuture saveVersionControlSettings(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings); + ListenableFuture saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings); ListenableFuture deleteVersionControlSettings(TenantId tenantId) throws Exception; - ListenableFuture checkVersionControlAccess(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception; + ListenableFuture checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws Exception; ListenableFuture autoCommit(SecurityUser user, EntityId entityId) throws Exception; + + ListenableFuture getEntityDataInfo(SecurityUser user, EntityId entityId, String versionId); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java index b7eb368055..a1aad04701 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java @@ -18,12 +18,13 @@ package org.thingsboard.server.service.sync.vc; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.sync.ie.EntityExportData; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; @@ -36,7 +37,7 @@ import java.util.List; public interface GitVersionControlQueueService { - ListenableFuture prepareCommit(TenantId tenantId, VersionCreateRequest request); + ListenableFuture prepareCommit(User user, VersionCreateRequest request); ListenableFuture addToCommit(CommitGitRequest commit, EntityExportData> entityData); @@ -64,9 +65,9 @@ public interface GitVersionControlQueueService { ListenableFuture getContentsDiff(TenantId tenantId, String rawEntityData1, String rawEntityData2); - ListenableFuture initRepository(TenantId tenantId, EntitiesVersionControlSettings settings); + ListenableFuture initRepository(TenantId tenantId, RepositorySettings settings); - ListenableFuture testRepository(TenantId tenantId, EntitiesVersionControlSettings settings); + ListenableFuture testRepository(TenantId tenantId, RepositorySettings settings); ListenableFuture clearRepository(TenantId tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java new file mode 100644 index 0000000000..e826c32c56 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java @@ -0,0 +1,80 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.settings.AdminSettingsService; + +import java.io.Serializable; + +public abstract class TbAbstractVersionControlSettingsService { + + private final String settingsKey; + private final AdminSettingsService adminSettingsService; + private final TbTransactionalCache cache; + private final Class clazz; + + public TbAbstractVersionControlSettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache cache, Class clazz, String settingsKey) { + this.adminSettingsService = adminSettingsService; + this.cache = cache; + this.clazz = clazz; + this.settingsKey = settingsKey; + } + + public T get(TenantId tenantId) { + return cache.getAndPutInTransaction(tenantId, () -> { + AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, settingsKey); + if (adminSettings != null) { + try { + return JacksonUtil.convertValue(adminSettings.getJsonValue(), clazz); + } catch (Exception e) { + throw new RuntimeException("Failed to load " + settingsKey + " settings!", e); + } + } + return null; + }, true); + } + + public T save(TenantId tenantId, T settings) { + AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, settingsKey); + if (adminSettings == null) { + adminSettings = new AdminSettings(); + adminSettings.setKey(settingsKey); + adminSettings.setTenantId(tenantId); + } + adminSettings.setJsonValue(JacksonUtil.valueToTree(settings)); + AdminSettings savedAdminSettings = adminSettingsService.saveAdminSettings(tenantId, adminSettings); + T savedSettings; + try { + savedSettings = JacksonUtil.convertValue(savedAdminSettings.getJsonValue(), clazz); + } catch (Exception e) { + throw new RuntimeException("Failed to load auto commit settings!", e); + } + //API calls to adminSettingsService are not in transaction, so we can simply evict the cache. + cache.evict(tenantId); + return savedSettings; + } + + public boolean delete(TenantId tenantId) { + boolean result = adminSettingsService.deleteAdminSettings(tenantId, settingsKey); + cache.evict(tenantId); + return result; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java similarity index 66% rename from application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsCaffeineCache.java rename to application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java index 8cab03ca29..47b9e5f5d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsCaffeineCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java @@ -13,24 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.sync.vc; +package org.thingsboard.server.service.sync.vc.autocommit; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CaffeineTbTransactionalCache; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) -@Service("VersionControlCache") -public class VersionControlSettingsCaffeineCache extends CaffeineTbTransactionalCache { +@Service("AutoCommitSettingsCache") +public class AutoCommitSettingsCaffeineCache extends CaffeineTbTransactionalCache { - public VersionControlSettingsCaffeineCache(CacheManager cacheManager) { - super(cacheManager, CacheConstants.VC_SETTINGS_CACHE); + public AutoCommitSettingsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.AUTO_COMMIT_SETTINGS_CACHE); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsRedisCache.java rename to application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java index 9c778d6e4f..f88b1cf6bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java @@ -13,29 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.sync.vc; +package org.thingsboard.server.service.sync.vc.autocommit; -import com.google.protobuf.InvalidProtocolBufferException; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.SerializationException; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") -@Service("VersionControlCache") -public class VersionControlSettingsRedisCache extends RedisTbTransactionalCache { +@Service("AutoCommitSettingsCache") +public class AutoCommitSettingsRedisCache extends RedisTbTransactionalCache { - public VersionControlSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.VC_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + public AutoCommitSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.AUTO_COMMIT_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java new file mode 100644 index 0000000000..b6e8d45ec2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.autocommit; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.TbAbstractVersionControlSettingsService; + +@Service +@TbCoreComponent +public class DefaultTbAutoCommitSettingsService extends TbAbstractVersionControlSettingsService implements TbAutoCommitSettingsService { + + public static final String SETTINGS_KEY = "autoCommitSettings"; + + public DefaultTbAutoCommitSettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache cache) { + super(adminSettingsService, cache, AutoCommitSettings.class, SETTINGS_KEY); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java new file mode 100644 index 0000000000..51978481e1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.autocommit; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +public interface TbAutoCommitSettingsService { + + AutoCommitSettings get(TenantId tenantId); + + AutoCommitSettings save(TenantId tenantId, AutoCommitSettings settings); + + boolean delete(TenantId tenantId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java new file mode 100644 index 0000000000..555490bd06 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.repository; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.TbAbstractVersionControlSettingsService; + +@Service +@TbCoreComponent +public class DefaultTbRepositorySettingsService extends TbAbstractVersionControlSettingsService implements TbRepositorySettingsService { + + public static final String SETTINGS_KEY = "entitiesVersionControl"; + + public DefaultTbRepositorySettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache cache) { + super(adminSettingsService, cache, RepositorySettings.class, SETTINGS_KEY); + } + + @Override + public RepositorySettings restore(TenantId tenantId, RepositorySettings settings) { + RepositorySettings storedSettings = get(tenantId); + if (storedSettings != null) { + RepositoryAuthMethod authMethod = settings.getAuthMethod(); + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(authMethod) && settings.getPassword() == null) { + settings.setPassword(storedSettings.getPassword()); + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(authMethod) && settings.getPrivateKey() == null) { + settings.setPrivateKey(storedSettings.getPrivateKey()); + if (settings.getPrivateKeyPassword() == null) { + settings.setPrivateKeyPassword(storedSettings.getPrivateKeyPassword()); + } + } + } + return settings; + } + + @Override + public RepositorySettings get(TenantId tenantId) { + RepositorySettings settings = super.get(tenantId); + if (settings != null) { + settings = new RepositorySettings(settings); + } + return settings; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java new file mode 100644 index 0000000000..60b7f50e12 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.repository; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("RepositorySettingsCache") +public class RepositorySettingsCaffeineCache extends CaffeineTbTransactionalCache { + + public RepositorySettingsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.REPOSITORY_SETTINGS_CACHE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java new file mode 100644 index 0000000000..3cccb6d24e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.repository; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("RepositorySettingsCache") +public class RepositorySettingsRedisCache extends RedisTbTransactionalCache { + + public RepositorySettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/TbVersionControlSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/sync/vc/TbVersionControlSettingsService.java rename to application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java index 178499c6c7..946d06c87c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/TbVersionControlSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.sync.vc; +package org.thingsboard.server.service.sync.vc.repository; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; -public interface TbVersionControlSettingsService { +public interface TbRepositorySettingsService { - EntitiesVersionControlSettings restore(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings); + RepositorySettings restore(TenantId tenantId, RepositorySettings versionControlSettings); - EntitiesVersionControlSettings get(TenantId tenantId); + RepositorySettings get(TenantId tenantId); - EntitiesVersionControlSettings save(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings); + RepositorySettings save(TenantId tenantId, RepositorySettings versionControlSettings); boolean delete(TenantId tenantId); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 31e5d7a76e..9663396d7e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -433,9 +433,12 @@ cache: edges: timeToLiveInMinutes: "${CACHE_SPECS_EDGES_TTL:1440}" maxSize: "${CACHE_SPECS_EDGES_MAX_SIZE:10000}" - vcSettings: - timeToLiveInMinutes: "${CACHE_SPECS_VC_SETTINGS_TTL:1440}" - maxSize: "${CACHE_SPECS_VC_SETTINGS_MAX_SIZE:10000}" + repositorySettings: + timeToLiveInMinutes: "${CACHE_SPECS_REPOSITORY_SETTINGS_TTL:1440}" + maxSize: "${CACHE_SPECS_REPOSITORY_SETTINGS_MAX_SIZE:10000}" + autoCommitSettings: + timeToLiveInMinutes: "${CACHE_SPECS_AUTO_COMMIT_SETTINGS_TTL:1440}" + maxSize: "${CACHE_SPECS_AUTO_COMMIT_SETTINGS_MAX_SIZE:10000}" redis: # standalone or cluster diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 969ee0261c..3079978bce 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -692,14 +692,17 @@ message CommitResponseMsg { int64 ts = 1; string commitId = 2; string name = 3; - int32 added = 4; - int32 modified = 5; - int32 removed = 6; + string author = 4; + int32 added = 5; + int32 modified = 6; + int32 removed = 7; } message PrepareMsg { string commitMsg = 1; string branchName = 2; + string authorName = 3; + string authorEmail = 4; } message AddMsg { @@ -733,6 +736,7 @@ message EntityVersionProto { int64 ts = 1; string id = 2; string name = 3; + string author = 4; } message ListVersionsResponseMsg { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index 85ea116f0a..94ca9db5a5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -31,5 +31,6 @@ public class CacheConstants { public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime"; public static final String OTA_PACKAGE_CACHE = "otaPackages"; public static final String OTA_PACKAGE_DATA_CACHE = "otaPackagesData"; - public static final String VC_SETTINGS_CACHE = "vcSettings"; + public static final String REPOSITORY_SETTINGS_CACHE = "repositorySettings"; + public static final String AUTO_COMMIT_SETTINGS_CACHE = "autoCommitSettings"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java index 542e675940..0fbdae756e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java @@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.sync.JsonTbEntity; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -41,6 +43,15 @@ import java.util.Map; @Data public class EntityExportData> { + public static final Comparator relationsComparator = Comparator + .comparing(EntityRelation::getFrom, Comparator.comparing(EntityId::getId)) + .thenComparing(EntityRelation::getTo, Comparator.comparing(EntityId::getId)) + .thenComparing(EntityRelation::getTypeGroup) + .thenComparing(EntityRelation::getType); + + public static final Comparator attrComparator = Comparator + .comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs); + @JsonTbEntity private E entity; private EntityType entityType; @@ -48,4 +59,14 @@ public class EntityExportData> { private List relations; private Map> attributes; + public EntityExportData sort() { + if (relations != null && !relations.isEmpty()) { + relations.sort(relationsComparator); + } + if (attributes != null && !attributes.isEmpty()) { + attributes.values().forEach(list -> list.sort(attrComparator)); + } + return this; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java new file mode 100644 index 0000000000..2120ec4c44 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.sync.vc.request.create.AutoVersionCreateConfig; + +import java.util.HashMap; + +public class AutoCommitSettings extends HashMap { + + private static final long serialVersionUID = -5757067601838792059L; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java new file mode 100644 index 0000000000..ab548b1cfc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EntityDataInfo { + boolean hasRelations; + boolean hasAttributes; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java index 50d3fef8a8..c90336d3d7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java @@ -26,4 +26,5 @@ public class EntityVersion { private long timestamp; private String id; private String name; + private String author; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionControlAuthMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java similarity index 94% rename from common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionControlAuthMethod.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java index 770a1e582c..3c0e5849a3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionControlAuthMethod.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.sync.vc; -public enum VersionControlAuthMethod { +public enum RepositoryAuthMethod { USERNAME_PASSWORD, PRIVATE_KEY } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntitiesVersionControlSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java similarity index 69% rename from common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntitiesVersionControlSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java index 7764274d22..50e23a1dba 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntitiesVersionControlSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java @@ -15,20 +15,17 @@ */ package org.thingsboard.server.common.data.sync.vc; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.sync.vc.request.create.AutoVersionCreateConfig; import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; @Data -public class EntitiesVersionControlSettings implements Serializable { +public class RepositorySettings implements Serializable { private static final long serialVersionUID = -3211552851889198721L; private String repositoryUri; - private VersionControlAuthMethod authMethod; + private RepositoryAuthMethod authMethod; private String username; private String password; private String privateKeyFileName; @@ -36,12 +33,10 @@ public class EntitiesVersionControlSettings implements Serializable { private String privateKeyPassword; private String defaultBranch; - private Map autoCommitSettings; - - public EntitiesVersionControlSettings() { + public RepositorySettings() { } - public EntitiesVersionControlSettings(EntitiesVersionControlSettings settings) { + public RepositorySettings(RepositorySettings settings) { this.repositoryUri = settings.getRepositoryUri(); this.authMethod = settings.getAuthMethod(); this.username = settings.getUsername(); @@ -50,6 +45,5 @@ public class EntitiesVersionControlSettings implements Serializable { this.privateKey = settings.getPrivateKey(); this.privateKeyPassword = settings.getPrivateKeyPassword(); this.defaultBranch = settings.getDefaultBranch(); - this.autoCommitSettings = settings.getAutoCommitSettings() != null ? new HashMap<>(settings.getAutoCommitSettings()) : new HashMap<>(); } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index b6e2b54479..d622801814 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -18,8 +18,10 @@ package org.thingsboard.common.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; @@ -31,8 +33,10 @@ import java.util.Arrays; public class JacksonUtil { public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - public static final ObjectMapper PRETTY_JSON_MAPPER = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT); + public static final ObjectMapper PRETTY_SORTED_JSON_MAPPER = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT) + .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .build(); public static T convertValue(Object fromValue, Class toValueType) { try { @@ -99,7 +103,7 @@ public class JacksonUtil { public static String toPrettyString(Object o) { try { - return PRETTY_JSON_MAPPER.writeValueAsString(o); + return PRETTY_SORTED_JSON_MAPPER.writeValueAsString(o); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java index cd112324f6..b28618372e 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -312,7 +312,7 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe .setTotalElements(data.getTotalElements()) .setHasNext(data.hasNext()) .addAllVersions(data.getData().stream().map( - v -> EntityVersionProto.newBuilder().setTs(v.getTimestamp()).setId(v.getId()).setName(v.getName()).build() + v -> EntityVersionProto.newBuilder().setTs(v.getTimestamp()).setId(v.getId()).setName(v.getName()).setAuthor(v.getAuthor()).build() ).collect(Collectors.toList()))) ); } @@ -397,7 +397,8 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe private void prepareCommit(VersionControlRequestCtx ctx, UUID txId, PrepareMsg prepareMsg) { var tenantId = ctx.getTenantId(); - var pendingCommit = new PendingCommit(tenantId, ctx.getNodeId(), txId, prepareMsg.getBranchName(), prepareMsg.getCommitMsg()); + var pendingCommit = new PendingCommit(tenantId, ctx.getNodeId(), txId, prepareMsg.getBranchName(), + prepareMsg.getCommitMsg(), prepareMsg.getAuthorName(), prepareMsg.getAuthorEmail()); PendingCommit old = pendingCommitMap.get(tenantId); if (old != null) { doAbortCurrentCommit(tenantId, old); @@ -456,13 +457,18 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe } private void reply(VersionControlRequestCtx ctx, VersionCreationResult result) { - reply(ctx, Optional.empty(), builder -> builder.setCommitResponse(CommitResponseMsg.newBuilder() - .setTs(result.getVersion().getTimestamp()) - .setCommitId(result.getVersion().getId()) - .setName(result.getVersion().getName()) - .setAdded(result.getAdded()) + var responseBuilder = CommitResponseMsg.newBuilder().setAdded(result.getAdded()) .setModified(result.getModified()) - .setRemoved(result.getRemoved()))); + .setRemoved(result.getRemoved()); + + if (result.getVersion() != null) { + responseBuilder.setTs(result.getVersion().getTimestamp()) + .setCommitId(result.getVersion().getId()) + .setName(result.getVersion().getName()) + .setAuthor(result.getVersion().getAuthor()); + } + + reply(ctx, Optional.empty(), builder -> builder.setCommitResponse(responseBuilder)); } private void reply(VersionControlRequestCtx ctx, Optional e) { @@ -495,8 +501,8 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe producer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); } - private EntitiesVersionControlSettings getEntitiesVersionControlSettings(ToVersionControlServiceMsg msg) { - Optional settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray()); + private RepositorySettings getEntitiesVersionControlSettings(ToVersionControlServiceMsg msg) { + Optional settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray()); if (settingsOpt.isPresent()) { return settingsOpt.get(); } else { diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java index a759ddbfa8..99dbebc3c9 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; @@ -117,10 +117,11 @@ public class DefaultGitRepositoryService implements GitRepositoryService { result.setModified(status.getModified().size()); result.setRemoved(status.getRemoved().size()); - GitRepository.Commit gitCommit = repository.commit(commit.getVersionName()); - repository.push(commit.getWorkingBranch(), commit.getBranch()); - - result.setVersion(toVersion(gitCommit)); + if (result.getAdded() > 0 || result.getModified() > 0 || result.getRemoved() > 0) { + GitRepository.Commit gitCommit = repository.commit(commit.getVersionName(), commit.getAuthorName(), commit.getAuthorEmail()); + repository.push(commit.getWorkingBranch(), commit.getBranch()); + result.setVersion(toVersion(gitCommit)); + } return result; } catch (GitAPIException gitAPIException) { //TODO: analyze and return meaningful exceptions that we can show to the client; @@ -216,13 +217,13 @@ public class DefaultGitRepositoryService implements GitRepositoryService { } @Override - public void testRepository(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception { + public void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception { Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); GitRepository.test(settings, repositoryDirectory.toFile()); } @Override - public void initRepository(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception { + public void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception { clearRepository(tenantId); log.debug("[{}] Init tenant repository started.", tenantId); Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); @@ -238,7 +239,7 @@ public class DefaultGitRepositoryService implements GitRepositoryService { } @Override - public EntitiesVersionControlSettings getRepositorySettings(TenantId tenantId) throws Exception { + public RepositorySettings getRepositorySettings(TenantId tenantId) throws Exception { var gitRepository = repositories.get(tenantId); return gitRepository != null ? gitRepository.getSettings() : null; } @@ -255,7 +256,15 @@ public class DefaultGitRepositoryService implements GitRepositoryService { } private EntityVersion toVersion(GitRepository.Commit commit) { - return new EntityVersion(commit.getTimestamp(), commit.getId(), commit.getMessage()); + return new EntityVersion(commit.getTimestamp(), commit.getId(), commit.getMessage(), this.getAuthor(commit)); + } + + private String getAuthor(GitRepository.Commit commit) { + String author = String.format("<%s>", commit.getAuthorEmail()); + if (StringUtils.isNotBlank(commit.getAuthorName())) { + author = String.format("%s %s", commit.getAuthorName(), author); + } + return author; } public static EntityId fromRelativePath(String path) { diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java index 5945067284..2b2802d003 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java @@ -58,8 +58,8 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; -import org.thingsboard.server.common.data.sync.vc.VersionControlAuthMethod; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -77,14 +77,14 @@ public class GitRepository { private final Git git; @Getter - private final EntitiesVersionControlSettings settings; + private final RepositorySettings settings; private final CredentialsProvider credentialsProvider; private final SshdSessionFactory sshSessionFactory; @Getter private final String directory; - private GitRepository(Git git, EntitiesVersionControlSettings settings, CredentialsProvider credentialsProvider, SshdSessionFactory sshSessionFactory, String directory) { + private GitRepository(Git git, RepositorySettings settings, CredentialsProvider credentialsProvider, SshdSessionFactory sshSessionFactory, String directory) { this.git = git; this.settings = settings; this.credentialsProvider = credentialsProvider; @@ -92,12 +92,12 @@ public class GitRepository { this.directory = directory; } - public static GitRepository clone(EntitiesVersionControlSettings settings, File directory) throws GitAPIException { + public static GitRepository clone(RepositorySettings settings, File directory) throws GitAPIException { CredentialsProvider credentialsProvider = null; SshdSessionFactory sshSessionFactory = null; - if (VersionControlAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); - } else if (VersionControlAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); } CloneCommand cloneCommand = Git.cloneRepository() @@ -109,24 +109,24 @@ public class GitRepository { return new GitRepository(git, settings, credentialsProvider, sshSessionFactory, directory.getAbsolutePath()); } - public static GitRepository open(File directory, EntitiesVersionControlSettings settings) throws IOException { + public static GitRepository open(File directory, RepositorySettings settings) throws IOException { Git git = Git.open(directory); CredentialsProvider credentialsProvider = null; SshdSessionFactory sshSessionFactory = null; - if (VersionControlAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); - } else if (VersionControlAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); } return new GitRepository(git, settings, credentialsProvider, sshSessionFactory, directory.getAbsolutePath()); } - public static void test(EntitiesVersionControlSettings settings, File directory) throws GitAPIException { + public static void test(RepositorySettings settings, File directory) throws GitAPIException { CredentialsProvider credentialsProvider = null; SshdSessionFactory sshSessionFactory = null; - if (VersionControlAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); - } else if (VersionControlAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); } LsRemoteCommand lsRemoteCommand = Git.lsRemoteRepository().setRemote(settings.getRepositoryUri()); @@ -255,8 +255,9 @@ public class GitRepository { return new Status(status.getAdded(), modified, status.getRemoved()); } - public Commit commit(String message) throws GitAPIException { + public Commit commit(String message, String authorName, String authorEmail) throws GitAPIException { RevCommit revCommit = execute(git.commit() + .setAuthor(authorName, authorEmail) .setMessage(message)); return toCommit(revCommit); } @@ -325,7 +326,8 @@ public class GitRepository { } private Commit toCommit(RevCommit revCommit) { - return new Commit(revCommit.getCommitTime() * 1000l, revCommit.getName(), revCommit.getFullMessage(), revCommit.getAuthorIdent().getName()); + return new Commit(revCommit.getCommitTime() * 1000l, revCommit.getName(), + revCommit.getFullMessage(), revCommit.getAuthorIdent().getName(), revCommit.getAuthorIdent().getEmailAddress()); } private RevCommit resolveCommit(String id) throws IOException { @@ -470,6 +472,7 @@ public class GitRepository { private final String id; private final String message; private final String authorName; + private final String authorEmail; } @Data diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java index ec69e0c80e..057fa9c1ab 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java @@ -19,7 +19,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.EntityVersion; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; @@ -39,11 +39,11 @@ public interface GitRepositoryService { List listEntitiesAtVersion(TenantId tenantId, String versionId, String path) throws Exception; - void testRepository(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception; + void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception; - void initRepository(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception; + void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception; - EntitiesVersionControlSettings getRepositorySettings(TenantId tenantId) throws Exception; + RepositorySettings getRepositorySettings(TenantId tenantId) throws Exception; void clearRepository(TenantId tenantId) throws IOException; diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java index b2eccd79db..ccd5fc685e 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java @@ -30,12 +30,18 @@ public class PendingCommit { private String branch; private String versionName; - public PendingCommit(TenantId tenantId, String nodeId, UUID txId, String branch, String versionName) { + private String authorName; + + private String authorEmail; + + public PendingCommit(TenantId tenantId, String nodeId, UUID txId, String branch, String versionName, String authorName, String authorEmail) { this.tenantId = tenantId; this.nodeId = nodeId; this.txId = txId; this.branch = branch; this.versionName = versionName; + this.authorName = authorName; + this.authorEmail = authorEmail; this.workingBranch = txId.toString(); } } diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java index 6541a2978a..26ab0d94c1 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.sync.vc; import lombok.Data; import lombok.RequiredArgsConstructor; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import java.util.UUID; @@ -29,9 +29,9 @@ public class VersionControlRequestCtx { private final String nodeId; private final UUID requestId; private final TenantId tenantId; - private final EntitiesVersionControlSettings settings; + private final RepositorySettings settings; - public VersionControlRequestCtx(ToVersionControlServiceMsg msg, EntitiesVersionControlSettings settings) { + public VersionControlRequestCtx(ToVersionControlServiceMsg msg, RepositorySettings settings) { this.nodeId = msg.getNodeId(); this.requestId = new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB()); this.tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts index 872607b822..a60720e61d 100644 --- a/ui-ngx/src/app/core/auth/auth.actions.ts +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -24,7 +24,7 @@ export enum AuthActionTypes { LOAD_USER = '[Auth] Load User', UPDATE_USER_DETAILS = '[Auth] Update User Details', UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id', - UPDATE_HAS_VERSION_CONTROL = '[Auth] Change Has Version Control' + UPDATE_HAS_REPOSITORY = '[Auth] Change Has Repository' } export class ActionAuthAuthenticated implements Action { @@ -55,11 +55,11 @@ export class ActionAuthUpdateLastPublicDashboardId implements Action { constructor(readonly payload: { lastPublicDashboardId: string }) {} } -export class ActionAuthUpdateHasVersionControl implements Action { - readonly type = AuthActionTypes.UPDATE_HAS_VERSION_CONTROL; +export class ActionAuthUpdateHasRepository implements Action { + readonly type = AuthActionTypes.UPDATE_HAS_REPOSITORY; - constructor(readonly payload: { hasVersionControl: boolean }) {} + constructor(readonly payload: { hasRepository: boolean }) {} } export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | - ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId | ActionAuthUpdateHasVersionControl; + ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId | ActionAuthUpdateHasRepository; diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index a69b783526..33b84945aa 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -20,7 +20,7 @@ export interface SysParamsState { userTokenAccessEnabled: boolean; allowedDashboardIds: string[]; edgesSupportEnabled: boolean; - hasVersionControl: boolean; + hasRepository: boolean; } export interface AuthPayload extends SysParamsState { diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 9d9404524b..cace62660e 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -24,7 +24,7 @@ const emptyUserAuthState: AuthPayload = { forceFullscreen: false, allowedDashboardIds: [], edgesSupportEnabled: false, - hasVersionControl: false + hasRepository: false }; export const initialState: AuthState = { @@ -55,7 +55,7 @@ export function authReducer( case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID: return { ...state, ...action.payload}; - case AuthActionTypes.UPDATE_HAS_VERSION_CONTROL: + case AuthActionTypes.UPDATE_HAS_REPOSITORY: return { ...state, ...action.payload}; default: diff --git a/ui-ngx/src/app/core/auth/auth.selectors.ts b/ui-ngx/src/app/core/auth/auth.selectors.ts index 7a099d5acd..4a63dbcbed 100644 --- a/ui-ngx/src/app/core/auth/auth.selectors.ts +++ b/ui-ngx/src/app/core/auth/auth.selectors.ts @@ -55,9 +55,9 @@ export const selectUserTokenAccessEnabled = createSelector( (state: AuthState) => state.userTokenAccessEnabled ); -export const selectHasVersionControl = createSelector( +export const selectHasRepository = createSelector( selectAuthState, - (state: AuthState) => state.hasVersionControl + (state: AuthState) => state.hasRepository ); export function getCurrentAuthState(store: Store): AuthState { diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 6fe809955f..74f6689553 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -437,9 +437,9 @@ export class AuthService { return this.http.get('/api/edges/enabled', defaultHttpOptions()); } - private loadHasVersionControl(authUser: AuthUser): Observable { + private loadHasRepository(authUser: AuthUser): Observable { if (authUser.authority === Authority.TENANT_ADMIN) { - return this.http.get('/api/admin/vcSettings/exists', defaultHttpOptions()); + return this.http.get('/api/admin/repositorySettings/exists', defaultHttpOptions()); } else { return of(false); } @@ -449,15 +449,15 @@ export class AuthService { const sources = [this.loadIsUserTokenAccessEnabled(authPayload.authUser), this.fetchAllowedDashboardIds(authPayload), this.loadIsEdgesSupportEnabled(), - this.loadHasVersionControl(authPayload.authUser), + this.loadHasRepository(authPayload.authUser), this.timeService.loadMaxDatapointsLimit()]; return forkJoin(sources) .pipe(map((data) => { const userTokenAccessEnabled: boolean = data[0] as boolean; const allowedDashboardIds: string[] = data[1] as string[]; const edgesSupportEnabled: boolean = data[2] as boolean; - const hasVersionControl: boolean = data[3] as boolean; - return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasVersionControl}; + const hasRepository: boolean = data[3] as boolean; + return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasRepository}; }, catchError((err) => { return of({}); }))); diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 7c12bd3a5d..9b8c3b76b9 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -15,19 +15,21 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { AdminSettings, - EntitiesVersionControlSettings, + RepositorySettings, MailServerSettings, SecuritySettings, TestSmsRequest, - UpdateMessage + UpdateMessage, AutoCommitSettings } from '@shared/models/settings.models'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { tap } from 'rxjs/operators'; +import { AuthUser } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; @Injectable({ providedIn: 'root' @@ -68,13 +70,13 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } - public getEntitiesVersionControlSettings(config?: RequestConfig): Observable { - return this.http.get(`/api/admin/vcSettings`, defaultHttpOptionsFromConfig(config)); + public getRepositorySettings(config?: RequestConfig): Observable { + return this.http.get(`/api/admin/repositorySettings`, defaultHttpOptionsFromConfig(config)); } - public saveEntitiesVersionControlSettings(versionControlSettings: EntitiesVersionControlSettings, - config?: RequestConfig): Observable { - return this.http.post('/api/admin/vcSettings', versionControlSettings, + public saveRepositorySettings(repositorySettings: RepositorySettings, + config?: RequestConfig): Observable { + return this.http.post('/api/admin/repositorySettings', repositorySettings, defaultHttpOptionsFromConfig(config)).pipe( tap(() => { this.entitiesVersionControlService.clearBranchList(); @@ -82,17 +84,34 @@ export class AdminService { ); } - public deleteEntitiesVersionControlSettings(config?: RequestConfig) { - return this.http.delete('/api/admin/vcSettings', defaultHttpOptionsFromConfig(config)).pipe( + public deleteRepositorySettings(config?: RequestConfig) { + return this.http.delete('/api/admin/repositorySettings', defaultHttpOptionsFromConfig(config)).pipe( tap(() => { this.entitiesVersionControlService.clearBranchList(); }) ); } - public checkVersionControlAccess(versionControlSettings: EntitiesVersionControlSettings, - config?: RequestConfig): Observable { - return this.http.post('/api/admin/vcSettings/checkAccess', versionControlSettings, defaultHttpOptionsFromConfig(config)); + public checkRepositoryAccess(repositorySettings: RepositorySettings, + config?: RequestConfig): Observable { + return this.http.post('/api/admin/repositorySettings/checkAccess', repositorySettings, defaultHttpOptionsFromConfig(config)); + } + + public getAutoCommitSettings(config?: RequestConfig): Observable { + return this.http.get(`/api/admin/autoCommitSettings`, defaultHttpOptionsFromConfig(config)); + } + + public autoCommitSettingsExists(config?: RequestConfig): Observable { + return this.http.get('/api/admin/autoCommitSettings/exists', defaultHttpOptionsFromConfig(config)); + } + + public saveAutoCommitSettings(autoCommitSettings: AutoCommitSettings, + config?: RequestConfig): Observable { + return this.http.post('/api/admin/autoCommitSettings', autoCommitSettings, defaultHttpOptionsFromConfig(config)); + } + + public deleteAutoCommitSettings(config?: RequestConfig) { + return this.http.delete('/api/admin/autoCommitSettings', defaultHttpOptionsFromConfig(config)); } public checkUpdates(config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/core/http/entities-version-control.service.ts b/ui-ngx/src/app/core/http/entities-version-control.service.ts index 7578696774..9f6f4f3403 100644 --- a/ui-ngx/src/app/core/http/entities-version-control.service.ts +++ b/ui-ngx/src/app/core/http/entities-version-control.service.ts @@ -17,7 +17,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; -import { combineLatest, Observable, of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { BranchInfo, EntityDataDiff, EntityVersion, @@ -29,10 +29,10 @@ import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { EntityId } from '@shared/models/id/entity-id'; import { EntityType } from '@shared/models/entity-type.models'; -import { createSelector, select, Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { selectHasVersionControl, selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors'; -import { catchError, combineAll, tap } from 'rxjs/operators'; +import { selectIsUserLoaded } from '@core/auth/auth.selectors'; +import { catchError, tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index ee81f007ee..af62bf7918 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -369,7 +369,7 @@ export class MenuService { name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '120px', + height: '160px', icon: 'settings', pages: [ { @@ -388,10 +388,17 @@ export class MenuService { }, { id: guid(), - name: 'admin.git-settings', + name: 'admin.repository-settings', type: 'link', - path: '/settings/vc', + path: '/settings/repository', icon: 'manage_history' + }, + { + id: guid(), + name: 'admin.auto-commit-settings', + type: 'link', + path: '/settings/auto-commit', + icon: 'settings_backup_restore' } ] } @@ -538,9 +545,14 @@ export class MenuService { path: '/settings/resources-library' }, { - name: 'admin.git-settings', + name: 'admin.repository-settings', icon: 'manage_history', - path: '/settings/vc', + path: '/settings/repository', + }, + { + name: 'admin.auto-commit-settings', + icon: 'settings_backup_restore', + path: '/settings/auto-commit' } ] } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index e9fc3f8c9a..153a8d9759 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -153,7 +153,7 @@ import { TenantProfileQueuesComponent } from '@home/components/profile/queue/ten import { QueueFormComponent } from '@home/components/queue/queue-form.component'; import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module'; import { WidgetSettingsComponent } from '@home/components/widget/widget-settings.component'; -import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component'; +import { RepositorySettingsComponent } from '@home/components/vc/repository-settings.component'; import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { EntityVersionsTableComponent } from '@home/components/vc/entity-versions-table.component'; import { EntityVersionCreateComponent } from '@home/components/vc/entity-version-create.component'; @@ -163,6 +163,8 @@ import { ComplexVersionCreateComponent } from '@home/components/vc/complex-versi import { EntityTypesVersionCreateComponent } from '@home/components/vc/entity-types-version-create.component'; import { EntityTypesVersionLoadComponent } from '@home/components/vc/entity-types-version-load.component'; import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version-load.component'; +import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component'; +import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-settings.component'; @NgModule({ declarations: @@ -287,7 +289,7 @@ import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version DisplayWidgetTypesPanelComponent, TenantProfileQueuesComponent, QueueFormComponent, - VersionControlSettingsComponent, + RepositorySettingsComponent, VersionControlComponent, EntityVersionsTableComponent, EntityVersionCreateComponent, @@ -296,7 +298,9 @@ import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version ComplexVersionCreateComponent, EntityTypesVersionCreateComponent, EntityTypesVersionLoadComponent, - ComplexVersionLoadComponent + ComplexVersionLoadComponent, + RemoveOtherEntitiesConfirmComponent, + AutoCommitSettingsComponent ], imports: [ CommonModule, @@ -415,7 +419,7 @@ import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version DisplayWidgetTypesPanelComponent, TenantProfileQueuesComponent, QueueFormComponent, - VersionControlSettingsComponent, + RepositorySettingsComponent, VersionControlComponent, EntityVersionsTableComponent, EntityVersionCreateComponent, @@ -424,7 +428,9 @@ import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version ComplexVersionCreateComponent, EntityTypesVersionCreateComponent, EntityTypesVersionLoadComponent, - ComplexVersionLoadComponent + ComplexVersionLoadComponent, + RemoveOtherEntitiesConfirmComponent, + AutoCommitSettingsComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html new file mode 100644 index 0000000000..3e668b54e6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html @@ -0,0 +1,125 @@ + +
+ + +
+ admin.auto-commit-settings + +
+
+
+ + +
+ +
+
+ admin.auto-commit-entities +
+
+
+ + +
+ +
+
+
+
+ + +
+
+ +
+ +
+
+ + +
+ + +
+
+
+ + {{ 'version-control.export-entity-relations' | translate }} + + + {{ 'version-control.export-entity-attributes' | translate }} + +
+
+
+
+
+
+
+
+ admin.no-auto-commit-entities-prompt +
+
+ + + +
+
+
+
+ + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss new file mode 100644 index 0000000000..ea8017b9f1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2022 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. + */ +:host { + mat-card.auto-commit-settings { + margin: 8px; + .mat-divider { + position: relative; + } + } + .fields-group { + padding: 0 16px 8px; + margin-bottom: 10px; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + + legend { + color: rgba(0, 0, 0, .7); + width: fit-content; + } + + legend + * { + display: block; + margin-top: 16px; + } + } + + .tb-control-list { + overflow-y: auto; + max-height: 600px; + } + + .tb-prompt { + margin: 30px 0; + } + + mat-expansion-panel.entity-type-config { + box-shadow: none; + border: 1px groove rgba(0, 0, 0, .25); + .mat-expansion-panel-header { + padding: 0 24px 0 8px; + height: 48px; + } + .entity-type-config-content { + padding: 0 8px 8px; + tb-branch-autocomplete { + min-width: 200px; + max-width: 200px; + display: block; + } + } + } +} + +:host ::ng-deep { + .mat-expansion-panel.entity-type-config { + .mat-expansion-panel-body { + padding: 0; + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts new file mode 100644 index 0000000000..8ac301338e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts @@ -0,0 +1,210 @@ +/// +/// Copyright © 2016-2022 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, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { AutoCommitSettings, AutoVersionCreateConfig } from '@shared/models/settings.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DialogService } from '@core/services/dialog.service'; +import { catchError, mergeMap } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { EntityTypeVersionCreateConfig, exportableEntityTypes } from '@shared/models/vc.models'; +import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +@Component({ + selector: 'tb-auto-commit-settings', + templateUrl: './auto-commit-settings.component.html', + styleUrls: ['./auto-commit-settings.component.scss', './../../pages/admin/settings-card.scss'] +}) +export class AutoCommitSettingsComponent extends PageComponent implements OnInit { + + autoCommitSettingsForm: FormGroup; + settings: AutoCommitSettings = null; + + constructor(protected store: Store, + private adminService: AdminService, + private dialogService: DialogService, + private sanitizer: DomSanitizer, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.autoCommitSettingsForm = this.fb.group({ + entityTypes: this.fb.array([], []) + }); + this.adminService.autoCommitSettingsExists().pipe( + catchError(() => of(false)), + mergeMap((hasAutoCommitSettings) => { + if (hasAutoCommitSettings) { + return this.adminService.getAutoCommitSettings({ignoreErrors: true}).pipe( + catchError(() => of(null)) + ); + } else { + return of(null); + } + }) + ).subscribe( + (settings) => { + this.settings = settings; + this.autoCommitSettingsForm.setControl('entityTypes', + this.prepareEntityTypesFormArray(settings), {emitEvent: false}); + }); + } + + entityTypesFormGroupArray(): FormGroup[] { + return (this.autoCommitSettingsForm.get('entityTypes') as FormArray).controls as FormGroup[]; + } + + entityTypesFormGroupExpanded(entityTypeControl: AbstractControl): boolean { + return !!(entityTypeControl as any).expanded; + } + + public trackByEntityType(index: number, entityTypeControl: AbstractControl): any { + return entityTypeControl; + } + + public removeEntityType(index: number) { + (this.autoCommitSettingsForm.get('entityTypes') as FormArray).removeAt(index); + this.autoCommitSettingsForm.markAsDirty(); + } + + public addEnabled(): boolean { + const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray; + return entityTypesArray.length < exportableEntityTypes.length; + } + + public addEntityType() { + const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray; + const config: AutoVersionCreateConfig = { + branch: null, + saveRelations: false, + saveAttributes: false + }; + const allowed = this.allowedEntityTypes(); + let entityType: EntityType = null; + if (allowed.length) { + entityType = allowed[0]; + } + const entityTypeControl = this.createEntityTypeControl(entityType, config); + (entityTypeControl as any).expanded = true; + entityTypesArray.push(entityTypeControl); + this.autoCommitSettingsForm.updateValueAndValidity(); + this.autoCommitSettingsForm.markAsDirty(); + } + + public removeAll() { + const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray; + entityTypesArray.clear(); + this.autoCommitSettingsForm.updateValueAndValidity(); + this.autoCommitSettingsForm.markAsDirty(); + } + + entityTypeText(entityTypeControl: AbstractControl): SafeHtml { + const entityType: EntityType = entityTypeControl.get('entityType').value; + const config: AutoVersionCreateConfig = entityTypeControl.get('config').value; + let message = entityType ? this.translate.instant(entityTypeTranslations.get(entityType).typePlural) : 'Undefined'; + let branchName; + if (config.branch) { + branchName = config.branch; + } else { + branchName = this.translate.instant('version-control.default'); + } + message += ` (${this.translate.instant('version-control.auto-commit-to-branch', {branch: branchName})})`; + return this.sanitizer.bypassSecurityTrustHtml(message); + } + + allowedEntityTypes(entityTypeControl?: AbstractControl): Array { + let res = [...exportableEntityTypes]; + const currentEntityType: EntityType = entityTypeControl?.get('entityType')?.value; + const value: [{entityType: string, config: EntityTypeVersionCreateConfig}] = + this.autoCommitSettingsForm.get('entityTypes').value || []; + const usedEntityTypes = value.map(val => val.entityType).filter(val => val); + res = res.filter(entityType => !usedEntityTypes.includes(entityType) || entityType === currentEntityType); + return res; + } + + save(): void { + const value: [{entityType: string, config: AutoVersionCreateConfig}] = + this.autoCommitSettingsForm.get('entityTypes').value || []; + const settings: AutoCommitSettings = {}; + if (value && value.length) { + value.forEach((val) => { + settings[val.entityType] = val.config; + }); + } + this.adminService.saveAutoCommitSettings(settings).subscribe( + (savedSettings) => { + this.settings = savedSettings; + this.autoCommitSettingsForm.setControl('entityTypes', + this.prepareEntityTypesFormArray(savedSettings), {emitEvent: false}); + this.autoCommitSettingsForm.markAsPristine(); + } + ); + } + + delete(formDirective: FormGroupDirective): void { + this.dialogService.confirm( + this.translate.instant('admin.delete-auto-commit-settings-title', ), + this.translate.instant('admin.delete-auto-commit-settings-text'), null, + this.translate.instant('action.delete') + ).subscribe((data) => { + if (data) { + this.adminService.deleteAutoCommitSettings().subscribe( + () => { + this.settings = null; + this.autoCommitSettingsForm.setControl('entityTypes', + this.prepareEntityTypesFormArray(this.settings), {emitEvent: false}); + this.autoCommitSettingsForm.markAsPristine(); + } + ); + } + }); + } + + private prepareEntityTypesFormArray(settings: AutoCommitSettings | null): FormArray { + const entityTypesControls: Array = []; + if (settings) { + for (const entityType of Object.keys(settings)) { + const config = settings[entityType]; + entityTypesControls.push(this.createEntityTypeControl(entityType as EntityType, config)); + } + } + return this.fb.array(entityTypesControls); + } + + private createEntityTypeControl(entityType: EntityType, config: AutoVersionCreateConfig): AbstractControl { + const entityTypeControl = this.fb.group( + { + entityType: [entityType, [Validators.required]], + config: this.fb.group({ + branch: [config.branch, []], + saveRelations: [config.saveRelations, []], + saveAttributes: [config.saveAttributes, []] + }) + } + ); + return entityTypeControl; + } + + +} diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html index 9d1192c0c9..d4da91028f 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html @@ -17,7 +17,7 @@ -->
- version-control.entity-types + version-control.entities-to-export
- + version-control.sync-strategy @@ -64,9 +64,14 @@ - - {{ 'version-control.export-entity-relations' | translate }} - +
+ + {{ 'version-control.export-entity-relations' | translate }} + + + {{ 'version-control.export-entity-attributes' | translate }} + +
version-control.no-entity-types + class="tb-prompt">version-control.no-entities-to-export-prompt
diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts index 9f998bc2af..2ff8c03227 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts @@ -62,7 +62,8 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni this.createVersionFormGroup = this.fb.group({ branch: [this.branch, [Validators.required]], versionName: [null, [Validators.required]], - saveRelations: [false, []] + saveRelations: [false, []], + saveAttributes: [false, []] }); } @@ -78,7 +79,8 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni branch: this.createVersionFormGroup.get('branch').value, versionName: this.createVersionFormGroup.get('versionName').value, config: { - saveRelations: this.createVersionFormGroup.get('saveRelations').value + saveRelations: this.createVersionFormGroup.get('saveRelations').value, + saveAttributes: this.createVersionFormGroup.get('saveAttributes').value, }, type: VersionCreateRequestType.SINGLE_ENTITY }; diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html index cb71696b4e..e84beb03dc 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html @@ -29,6 +29,9 @@ {{ 'version-control.load-entity-relations' | translate }} + + {{ 'version-control.load-entity-attributes' | translate }} + diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts index 2cc615c011..ea5443e036 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts @@ -57,7 +57,8 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn ngOnInit(): void { this.restoreFormGroup = this.fb.group({ - loadRelations: [false, []] + loadRelations: [false, []], + loadAttributes: [false, []] }); } @@ -73,7 +74,8 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn versionId: this.versionId, externalEntityId: this.externalEntityId, config: { - loadRelations: this.restoreFormGroup.get('loadRelations').value + loadRelations: this.restoreFormGroup.get('loadRelations').value, + loadAttributes: this.restoreFormGroup.get('loadAttributes').value }, type: VersionLoadRequestType.SINGLE_ENTITY }; diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html index b4541c7182..d2dbc119bf 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html @@ -101,11 +101,17 @@ - {{ 'version-control.version-name' | translate }} + {{ 'version-control.version-name' | translate }} {{ entityVersion.name }} + + {{ 'version-control.author' | translate }} + + {{ entityVersion.author }} + + diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts index be714e6f97..589800e143 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts @@ -62,7 +62,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni @Input() singleEntityMode = false; - displayedColumns = ['timestamp', 'id', 'name', 'actions']; + displayedColumns = ['timestamp', 'id', 'name', 'author', 'actions']; pageLink: PageLink; textSearchMode = false; dataSource: EntityVersionsDatasource; diff --git a/ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.html b/ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.html new file mode 100644 index 0000000000..14ae91e2f2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.html @@ -0,0 +1,41 @@ + +
+
+
+
+ + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.ts b/ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.ts new file mode 100644 index 0000000000..f569d96fac --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.ts @@ -0,0 +1,66 @@ +/// +/// Copyright © 2016-2022 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, Input, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +@Component({ + selector: 'tb-remove-other-entities-confirm', + templateUrl: './remove-other-entities-confirm.component.html', + styleUrls: [] +}) +export class RemoveOtherEntitiesConfirmComponent extends PageComponent implements OnInit { + + @Input() + onClose: (result: boolean | null) => void; + + confirmFormGroup: FormGroup; + + removeOtherEntitiesConfirmText: SafeHtml; + + removeOtherEntitiesVerificationText = 'remove other entities'; + + constructor(protected store: Store, + private translate: TranslateService, + private sanitizer: DomSanitizer, + private fb: FormBuilder) { + super(store); + this.removeOtherEntitiesConfirmText = this.sanitizer.bypassSecurityTrustHtml(this.translate.instant('version-control.remove-other-entities-confirm-text')); + } + + ngOnInit(): void { + this.confirmFormGroup = this.fb.group({ + verification: [null, []] + }); + } + + cancel(): void { + if (this.onClose) { + this.onClose(null); + } + } + + confirm(): void { + if (this.onClose) { + this.onClose(true); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html similarity index 78% rename from ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.html rename to ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html index b48162d06f..bdab6c061b 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html @@ -16,24 +16,24 @@ -->
- +
- admin.git-repository-settings + admin.repository-settings -
+
-
+
admin.repository-url - + admin.repository-url-required @@ -46,12 +46,12 @@ admin.auth-method - - {{versionControlAuthMethodTranslations.get(method) | translate}} + + {{repositoryAuthMethodTranslations.get(method) | translate}} -
+
common.username
-
+
+ (fileNameChanged)="repositorySettingsForm.get('privateKeyFileName').patchValue($event)"> @@ -96,10 +96,10 @@ -
diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.scss b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss similarity index 96% rename from ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.scss rename to ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss index d1d55faf19..77ae0ffd8f 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.scss +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss @@ -14,7 +14,7 @@ * limitations under the License. */ :host { - mat-card.vc-settings { + mat-card.repository-settings { margin: 8px; } .fields-group { diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts new file mode 100644 index 0000000000..c73cf0e632 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts @@ -0,0 +1,216 @@ +/// +/// Copyright © 2016-2022 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, Input, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { + RepositorySettings, + RepositoryAuthMethod, + repositoryAuthMethodTranslationMap +} from '@shared/models/settings.models'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { isNotEmptyStr } from '@core/utils'; +import { DialogService } from '@core/services/dialog.service'; +import { ActionAuthUpdateHasRepository } from '@core/auth/auth.actions'; +import { selectHasRepository } from '@core/auth/auth.selectors'; +import { catchError, mergeMap, take } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Component({ + selector: 'tb-repository-settings', + templateUrl: './repository-settings.component.html', + styleUrls: ['./repository-settings.component.scss', './../../pages/admin/settings-card.scss'] +}) +export class RepositorySettingsComponent extends PageComponent implements OnInit { + + @Input() + detailsMode = false; + + repositorySettingsForm: FormGroup; + settings: RepositorySettings = null; + + repositoryAuthMethod = RepositoryAuthMethod; + repositoryAuthMethods = Object.values(RepositoryAuthMethod); + repositoryAuthMethodTranslations = repositoryAuthMethodTranslationMap; + + showChangePassword = false; + changePassword = false; + + showChangePrivateKeyPassword = false; + changePrivateKeyPassword = false; + + constructor(protected store: Store, + private adminService: AdminService, + private dialogService: DialogService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.repositorySettingsForm = this.fb.group({ + repositoryUri: [null, [Validators.required]], + defaultBranch: ['main', []], + authMethod: [RepositoryAuthMethod.USERNAME_PASSWORD, [Validators.required]], + username: [null, []], + password: [null, []], + privateKeyFileName: [null, [Validators.required]], + privateKey: [null, []], + privateKeyPassword: [null, []] + }); + this.updateValidators(false); + this.repositorySettingsForm.get('authMethod').valueChanges.subscribe(() => { + this.updateValidators(true); + }); + this.repositorySettingsForm.get('privateKeyFileName').valueChanges.subscribe(() => { + this.updateValidators(false); + }); + this.store.pipe( + select(selectHasRepository), + take(1), + mergeMap((hasRepository) => { + if (hasRepository) { + return this.adminService.getRepositorySettings({ignoreErrors: true}).pipe( + catchError(() => of(null)) + ); + } else { + return of(null); + } + }) + ).subscribe( + (settings) => { + this.settings = settings; + if (this.settings != null) { + if (this.settings.authMethod === RepositoryAuthMethod.USERNAME_PASSWORD) { + this.showChangePassword = true; + } else { + this.showChangePrivateKeyPassword = true; + } + this.repositorySettingsForm.reset(this.settings); + this.updateValidators(false); + } + }); + } + + checkAccess(): void { + const settings: RepositorySettings = this.repositorySettingsForm.value; + this.adminService.checkRepositoryAccess(settings).subscribe(() => { + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('admin.check-repository-access-success'), + type: 'success' })); + }); + } + + save(): void { + const settings: RepositorySettings = this.repositorySettingsForm.value; + this.adminService.saveRepositorySettings(settings).subscribe( + (savedSettings) => { + this.settings = savedSettings; + if (this.settings.authMethod === RepositoryAuthMethod.USERNAME_PASSWORD) { + this.showChangePassword = true; + this.changePassword = false; + } else { + this.showChangePrivateKeyPassword = true; + this.changePrivateKeyPassword = false; + } + this.repositorySettingsForm.reset(this.settings); + this.updateValidators(false); + this.store.dispatch(new ActionAuthUpdateHasRepository({ hasRepository: true })); + } + ); + } + + delete(formDirective: FormGroupDirective): void { + this.dialogService.confirm( + this.translate.instant('admin.delete-repository-settings-title', ), + this.translate.instant('admin.delete-repository-settings-text'), null, + this.translate.instant('action.delete') + ).subscribe((data) => { + if (data) { + this.adminService.deleteRepositorySettings().subscribe( + () => { + this.settings = null; + this.showChangePassword = false; + this.changePassword = false; + this.showChangePrivateKeyPassword = false; + this.changePrivateKeyPassword = false; + formDirective.resetForm(); + this.repositorySettingsForm.reset({ defaultBranch: 'main', authMethod: RepositoryAuthMethod.USERNAME_PASSWORD }); + this.updateValidators(false); + this.store.dispatch(new ActionAuthUpdateHasRepository({ hasRepository: false })); + } + ); + } + }); + } + + changePasswordChanged() { + if (this.changePassword) { + this.repositorySettingsForm.get('password').patchValue(''); + this.repositorySettingsForm.get('password').markAsDirty(); + } + this.updateValidators(false); + } + + changePrivateKeyPasswordChanged() { + if (this.changePrivateKeyPassword) { + this.repositorySettingsForm.get('privateKeyPassword').patchValue(''); + this.repositorySettingsForm.get('privateKeyPassword').markAsDirty(); + } + this.updateValidators(false); + } + + updateValidators(emitEvent?: boolean): void { + const authMethod: RepositoryAuthMethod = this.repositorySettingsForm.get('authMethod').value; + const privateKeyFileName: string = this.repositorySettingsForm.get('privateKeyFileName').value; + if (authMethod === RepositoryAuthMethod.USERNAME_PASSWORD) { + this.repositorySettingsForm.get('username').enable({emitEvent}); + if (this.changePassword || !this.showChangePassword) { + this.repositorySettingsForm.get('password').enable({emitEvent}); + } else { + this.repositorySettingsForm.get('password').disable({emitEvent}); + } + this.repositorySettingsForm.get('privateKeyFileName').disable({emitEvent}); + this.repositorySettingsForm.get('privateKey').disable({emitEvent}); + this.repositorySettingsForm.get('privateKeyPassword').disable({emitEvent}); + } else { + this.repositorySettingsForm.get('username').disable({emitEvent}); + this.repositorySettingsForm.get('password').disable({emitEvent}); + this.repositorySettingsForm.get('privateKeyFileName').enable({emitEvent}); + this.repositorySettingsForm.get('privateKey').enable({emitEvent}); + if (this.changePrivateKeyPassword || !this.showChangePrivateKeyPassword) { + this.repositorySettingsForm.get('privateKeyPassword').enable({emitEvent}); + } else { + this.repositorySettingsForm.get('privateKeyPassword').disable({emitEvent}); + } + if (isNotEmptyStr(privateKeyFileName)) { + this.repositorySettingsForm.get('privateKey').clearValidators(); + } else { + this.repositorySettingsForm.get('privateKey').setValidators([Validators.required]); + } + } + this.repositorySettingsForm.get('username').updateValueAndValidity({emitEvent: false}); + this.repositorySettingsForm.get('password').updateValueAndValidity({emitEvent: false}); + this.repositorySettingsForm.get('privateKeyFileName').updateValueAndValidity({emitEvent: false}); + this.repositorySettingsForm.get('privateKey').updateValueAndValidity({emitEvent: false}); + this.repositorySettingsForm.get('privateKeyPassword').updateValueAndValidity({emitEvent: false}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.ts deleted file mode 100644 index 3462fbb728..0000000000 --- a/ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.ts +++ /dev/null @@ -1,218 +0,0 @@ -/// -/// Copyright © 2016-2022 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, Input, OnInit } from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; -import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; -import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; -import { select, Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { AdminService } from '@core/http/admin.service'; -import { - EntitiesVersionControlSettings, - VersionControlAuthMethod, - versionControlAuthMethodTranslationMap -} from '@shared/models/settings.models'; -import { ActionNotificationShow } from '@core/notification/notification.actions'; -import { TranslateService } from '@ngx-translate/core'; -import { isNotEmptyStr } from '@core/utils'; -import { DialogService } from '@core/services/dialog.service'; -import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; -import { ActionAuthUpdateHasVersionControl } from '@core/auth/auth.actions'; -import { selectHasVersionControl } from '@core/auth/auth.selectors'; -import { catchError, mergeMap, take } from 'rxjs/operators'; -import { of } from 'rxjs'; - -@Component({ - selector: 'tb-version-control-settings', - templateUrl: './version-control-settings.component.html', - styleUrls: ['./version-control-settings.component.scss', './../../pages/admin/settings-card.scss'] -}) -export class VersionControlSettingsComponent extends PageComponent implements OnInit { - - @Input() - detailsMode = false; - - versionControlSettingsForm: FormGroup; - settings: EntitiesVersionControlSettings = null; - - versionControlAuthMethod = VersionControlAuthMethod; - versionControlAuthMethods = Object.values(VersionControlAuthMethod); - versionControlAuthMethodTranslations = versionControlAuthMethodTranslationMap; - - showChangePassword = false; - changePassword = false; - - showChangePrivateKeyPassword = false; - changePrivateKeyPassword = false; - - constructor(protected store: Store, - private adminService: AdminService, - private dialogService: DialogService, - private translate: TranslateService, - public fb: FormBuilder) { - super(store); - } - - ngOnInit() { - this.versionControlSettingsForm = this.fb.group({ - repositoryUri: [null, [Validators.required]], - defaultBranch: ['main', []], - authMethod: [VersionControlAuthMethod.USERNAME_PASSWORD, [Validators.required]], - username: [null, []], - password: [null, []], - privateKeyFileName: [null, [Validators.required]], - privateKey: [null, []], - privateKeyPassword: [null, []] - }); - this.updateValidators(false); - this.versionControlSettingsForm.get('authMethod').valueChanges.subscribe(() => { - this.updateValidators(true); - }); - this.versionControlSettingsForm.get('privateKeyFileName').valueChanges.subscribe(() => { - this.updateValidators(false); - }); - this.store.pipe( - select(selectHasVersionControl), - take(1), - mergeMap((hasVersionControl) => { - if (hasVersionControl) { - return this.adminService.getEntitiesVersionControlSettings({ignoreErrors: true}).pipe( - catchError(() => of(null)) - ); - } else { - return of(null); - } - }) - ).subscribe( - (settings) => { - this.settings = settings; - if (this.settings != null) { - if (this.settings.authMethod === VersionControlAuthMethod.USERNAME_PASSWORD) { - this.showChangePassword = true; - } else { - this.showChangePrivateKeyPassword = true; - } - this.versionControlSettingsForm.reset(this.settings); - this.updateValidators(false); - } - }); - } - - checkAccess(): void { - const settings: EntitiesVersionControlSettings = this.versionControlSettingsForm.value; - this.adminService.checkVersionControlAccess(settings).subscribe(() => { - this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('admin.check-vc-access-success'), - type: 'success' })); - }); - } - - save(): void { - const settings: EntitiesVersionControlSettings = this.versionControlSettingsForm.value; - this.adminService.saveEntitiesVersionControlSettings(settings).subscribe( - (savedSettings) => { - this.settings = savedSettings; - if (this.settings.authMethod === VersionControlAuthMethod.USERNAME_PASSWORD) { - this.showChangePassword = true; - this.changePassword = false; - } else { - this.showChangePrivateKeyPassword = true; - this.changePrivateKeyPassword = false; - } - this.versionControlSettingsForm.reset(this.settings); - this.updateValidators(false); - this.store.dispatch(new ActionAuthUpdateHasVersionControl({ hasVersionControl: true })); - } - ); - } - - delete(formDirective: FormGroupDirective): void { - this.dialogService.confirm( - this.translate.instant('admin.delete-git-settings-title', ), - this.translate.instant('admin.delete-git-settings-text'), null, - this.translate.instant('action.delete') - ).subscribe((data) => { - if (data) { - this.adminService.deleteEntitiesVersionControlSettings().subscribe( - () => { - this.settings = null; - this.showChangePassword = false; - this.changePassword = false; - this.showChangePrivateKeyPassword = false; - this.changePrivateKeyPassword = false; - formDirective.resetForm(); - this.versionControlSettingsForm.reset({ defaultBranch: 'main', authMethod: VersionControlAuthMethod.USERNAME_PASSWORD }); - this.updateValidators(false); - this.store.dispatch(new ActionAuthUpdateHasVersionControl({ hasVersionControl: false })); - } - ); - } - }); - } - - changePasswordChanged() { - if (this.changePassword) { - this.versionControlSettingsForm.get('password').patchValue(''); - this.versionControlSettingsForm.get('password').markAsDirty(); - } - this.updateValidators(false); - } - - changePrivateKeyPasswordChanged() { - if (this.changePrivateKeyPassword) { - this.versionControlSettingsForm.get('privateKeyPassword').patchValue(''); - this.versionControlSettingsForm.get('privateKeyPassword').markAsDirty(); - } - this.updateValidators(false); - } - - updateValidators(emitEvent?: boolean): void { - const authMethod: VersionControlAuthMethod = this.versionControlSettingsForm.get('authMethod').value; - const privateKeyFileName: string = this.versionControlSettingsForm.get('privateKeyFileName').value; - if (authMethod === VersionControlAuthMethod.USERNAME_PASSWORD) { - this.versionControlSettingsForm.get('username').enable({emitEvent}); - if (this.changePassword || !this.showChangePassword) { - this.versionControlSettingsForm.get('password').enable({emitEvent}); - } else { - this.versionControlSettingsForm.get('password').disable({emitEvent}); - } - this.versionControlSettingsForm.get('privateKeyFileName').disable({emitEvent}); - this.versionControlSettingsForm.get('privateKey').disable({emitEvent}); - this.versionControlSettingsForm.get('privateKeyPassword').disable({emitEvent}); - } else { - this.versionControlSettingsForm.get('username').disable({emitEvent}); - this.versionControlSettingsForm.get('password').disable({emitEvent}); - this.versionControlSettingsForm.get('privateKeyFileName').enable({emitEvent}); - this.versionControlSettingsForm.get('privateKey').enable({emitEvent}); - if (this.changePrivateKeyPassword || !this.showChangePrivateKeyPassword) { - this.versionControlSettingsForm.get('privateKeyPassword').enable({emitEvent}); - } else { - this.versionControlSettingsForm.get('privateKeyPassword').disable({emitEvent}); - } - if (isNotEmptyStr(privateKeyFileName)) { - this.versionControlSettingsForm.get('privateKey').clearValidators(); - } else { - this.versionControlSettingsForm.get('privateKey').setValidators([Validators.required]); - } - } - this.versionControlSettingsForm.get('username').updateValueAndValidity({emitEvent: false}); - this.versionControlSettingsForm.get('password').updateValueAndValidity({emitEvent: false}); - this.versionControlSettingsForm.get('privateKeyFileName').updateValueAndValidity({emitEvent: false}); - this.versionControlSettingsForm.get('privateKey').updateValueAndValidity({emitEvent: false}); - this.versionControlSettingsForm.get('privateKeyPassword').updateValueAndValidity({emitEvent: false}); - } - -} diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html index e5e650ac85..3cf10ab168 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html @@ -15,9 +15,9 @@ limitations under the License. --> - - + + (); - hasVersionControl$ = this.store.pipe(select(selectHasVersionControl)); + hasRepository$ = this.store.pipe(select(selectHasRepository)); constructor(private store: Store) { @@ -61,7 +61,7 @@ export class VersionControlComponent implements OnInit, HasConfirmForm { } confirmForm(): FormGroup { - return this.versionControlSettingsComponent?.versionControlSettingsForm; + return this.repositorySettingsComponent?.repositorySettingsForm; } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 958e7b1c9b..a70cbba04d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -33,7 +33,8 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { QueuesTableConfigResolver } from '@home/pages/admin/queue/queues-table-config.resolver'; -import { VersionControlAdminSettingsComponent } from '@home/pages/admin/version-control-admin-settings.component'; +import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; +import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -225,17 +226,30 @@ const routes: Routes = [ ] }, { - path: 'vc', - component: VersionControlAdminSettingsComponent, + path: 'repository', + component: RepositoryAdminSettingsComponent, canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.TENANT_ADMIN], - title: 'admin.git-settings', + title: 'admin.repository-settings', breadcrumb: { - label: 'admin.git-settings', + label: 'admin.repository-settings', icon: 'manage_history' } } + }, + { + path: 'auto-commit', + component: AutoCommitAdminSettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.TENANT_ADMIN], + title: 'admin.auto-commit-settings', + breadcrumb: { + label: 'admin.auto-commit-settings', + icon: 'settings_backup_restore' + } + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 51612270df..1e9c45948f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -29,7 +29,8 @@ import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dial import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component'; import { QueueComponent} from '@home/pages/admin/queue/queue.component'; -import { VersionControlAdminSettingsComponent } from '@home/pages/admin/version-control-admin-settings.component'; +import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; +import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component'; @NgModule({ declarations: @@ -43,7 +44,8 @@ import { VersionControlAdminSettingsComponent } from '@home/pages/admin/version- HomeSettingsComponent, ResourcesLibraryComponent, QueueComponent, - VersionControlAdminSettingsComponent + RepositoryAdminSettingsComponent, + AutoCommitAdminSettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.html new file mode 100644 index 0000000000..63060f6a0a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.html @@ -0,0 +1,23 @@ + + + + + + diff --git a/ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.ts new file mode 100644 index 0000000000..6c5c110884 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.ts @@ -0,0 +1,51 @@ +/// +/// Copyright © 2016-2022 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, OnInit, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormGroup } from '@angular/forms'; +import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-settings.component'; +import { selectHasRepository } from '@core/auth/auth.selectors'; +import { RepositorySettingsComponent } from '@home/components/vc/repository-settings.component'; + +@Component({ + selector: 'tb-auto-commit-admin-settings', + templateUrl: './auto-commit-admin-settings.component.html', + styleUrls: [] +}) +export class AutoCommitAdminSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { + + @ViewChild('repositorySettingsComponent', {static: false}) repositorySettingsComponent: RepositorySettingsComponent; + @ViewChild('autoCommitSettingsComponent', {static: false}) autoCommitSettingsComponent: AutoCommitSettingsComponent; + + hasRepository$ = this.store.pipe(select(selectHasRepository)); + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + } + + confirmForm(): FormGroup { + return this.repositorySettingsComponent ? + this.repositorySettingsComponent?.repositorySettingsForm : + this.autoCommitSettingsComponent?.autoCommitSettingsForm; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.html similarity index 87% rename from ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.html rename to ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.html index 869067e1fb..7b408fc93a 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.html @@ -15,4 +15,4 @@ limitations under the License. --> - + diff --git a/ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.ts similarity index 68% rename from ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.ts rename to ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.ts index a203f64426..1ee0a7288a 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.ts @@ -20,16 +20,16 @@ import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormGroup } from '@angular/forms'; -import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component'; +import { RepositorySettingsComponent } from '@home/components/vc/repository-settings.component'; @Component({ - selector: 'tb-version-control-admin-settings', - templateUrl: './version-control-admin-settings.component.html', + selector: 'tb-repository-admin-settings', + templateUrl: './repository-admin-settings.component.html', styleUrls: [] }) -export class VersionControlAdminSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { +export class RepositoryAdminSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { - @ViewChild('versionControlSettingsComponent') versionControlSettingsComponent: VersionControlSettingsComponent; + @ViewChild('repositorySettingsComponent') repositorySettingsComponent: RepositorySettingsComponent; constructor(protected store: Store) { super(store); @@ -39,6 +39,6 @@ export class VersionControlAdminSettingsComponent extends PageComponent implemen } confirmForm(): FormGroup { - return this.versionControlSettingsComponent?.versionControlSettingsForm; + return this.repositorySettingsComponent?.repositorySettingsForm; } } diff --git a/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html b/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html index b5f7ceb02e..38ab9e377a 100644 --- a/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html @@ -15,9 +15,9 @@ limitations under the License. --> - + {{ 'version-control.branch' | translate }} - ([ - [VersionControlAuthMethod.USERNAME_PASSWORD, 'admin.auth-method-username-password'], - [VersionControlAuthMethod.PRIVATE_KEY, 'admin.auth-method-private-key'] +export const repositoryAuthMethodTranslationMap = new Map([ + [RepositoryAuthMethod.USERNAME_PASSWORD, 'admin.auth-method-username-password'], + [RepositoryAuthMethod.PRIVATE_KEY, 'admin.auth-method-private-key'] ]); -export interface EntitiesVersionControlSettings { +export interface RepositorySettings { repositoryUri: string; defaultBranch: string; - authMethod: VersionControlAuthMethod; + authMethod: RepositoryAuthMethod; username: string; password: string; privateKeyFileName: string; privateKey: string; privateKeyPassword: string; } + +export interface AutoVersionCreateConfig extends VersionCreateConfig { + branch: string; +} + +export type AutoCommitSettings = {[entityType: string]: AutoVersionCreateConfig}; diff --git a/ui-ngx/src/app/shared/models/vc.models.ts b/ui-ngx/src/app/shared/models/vc.models.ts index 6e66daa24d..cd1cba93ed 100644 --- a/ui-ngx/src/app/shared/models/vc.models.ts +++ b/ui-ngx/src/app/shared/models/vc.models.ts @@ -32,6 +32,7 @@ export const exportableEntityTypes: Array = [ export interface VersionCreateConfig { saveRelations: boolean; + saveAttributes: boolean; } export enum VersionCreateRequestType { @@ -81,6 +82,7 @@ export function createDefaultEntityTypesVersionCreate(): {[entityType: string]: res[entityType] = { syncStrategy: null, saveRelations: false, + saveAttributes: false, allEntities: true, entityIds: [] }; @@ -90,6 +92,7 @@ export function createDefaultEntityTypesVersionCreate(): {[entityType: string]: export interface VersionLoadConfig { loadRelations: boolean; + loadAttributes: boolean; } export enum VersionLoadRequestType { @@ -111,6 +114,7 @@ export interface SingleEntityVersionLoadRequest extends VersionLoadRequest { export interface EntityTypeVersionLoadConfig extends VersionLoadConfig { removeOtherEntities: boolean; + findExistingEntityByName: boolean; } export interface EntityTypeVersionLoadRequest extends VersionLoadRequest { @@ -123,7 +127,9 @@ export function createDefaultEntityTypesVersionLoad(): {[entityType: string]: En for (const entityType of exportableEntityTypes) { res[entityType] = { loadRelations: false, - removeOtherEntities: false + loadAttributes: false, + removeOtherEntities: false, + findExistingEntityByName: false }; } return res; @@ -138,6 +144,7 @@ export interface EntityVersion { timestamp: number; id: string; name: string; + author: string; } export interface VersionCreationResult { @@ -175,18 +182,5 @@ export interface EntityDataDiff { } export function entityExportDataToJsonString(data: EntityExportData): string { - if (!data.relations) { - data.relations = []; - } - const allKeys = new Set(); - JSON.stringify(data, (key, value) => (allKeys.add(key), value)); - return JSON.stringify(data, Array.from(allKeys).sort((key1, key2) => { - if (key1 === 'relations') { - return 1; - } else if (key2 === 'relations') { - return -1; - } else { - return 0; - } - }), 4); + return JSON.stringify(data, null, 4); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 90193b1499..0cca6c6a43 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -58,7 +58,8 @@ "next-with-label": "Next: {{label}}", "read-more": "Read more", "hide": "Hide", - "restore": "Restore" + "restore": "Restore", + "confirm": "Confirm" }, "aggregation": { "aggregation": "Aggregation", @@ -321,8 +322,7 @@ "queue-submit-strategy": "Submit strategy", "queue-processing-strategy": "Processing strategy", "queue-configuration": "Queue configuration", - "git-settings": "Git settings", - "git-repository-settings": "Git repository settings", + "repository-settings": "Repository settings", "repository-url": "Repository URL", "repository-url-required": "Repository URL is required.", "default-branch": "Default branch name", @@ -338,9 +338,14 @@ "enter-passphrase": "Enter passphrase", "change-passphrase": "Change passphrase", "check-access": "Check access", - "check-vc-access-success": "Git repository access successfully verified!", - "delete-git-settings-title": "Are you sure you want to delete git settings?", - "delete-git-settings-text": "Be careful, after the confirmation the git settings will be removed and git synchronization feature will be unavailable." + "check-repository-access-success": "Repository access successfully verified!", + "delete-repository-settings-title": "Are you sure you want to delete repository settings?", + "delete-repository-settings-text": "Be careful, after the confirmation the repository settings will be removed and version control feature will be unavailable.", + "auto-commit-settings": "Auto-commit settings", + "auto-commit-entities": "Auto-commit entities", + "no-auto-commit-entities-prompt": "No entities configured for auto-commit", + "delete-auto-commit-settings-title": "Are you sure you want to delete auto-commit settings?", + "delete-auto-commit-settings-text": "Be careful, after the confirmation the auto-commit settings will be removed and auto-commit will be disabled for all entities." }, "alarm": { "alarm": "Alarm", @@ -3123,7 +3128,9 @@ "create-entity-version": "Create entity version", "version-name": "Version name", "version-name-required": "Version name is required", + "author": "Author", "export-entity-relations": "Export entity relations", + "export-entity-attributes": "Export entity attributes", "entity-versions": "Entity versions", "versions": "Versions", "created-time": "Created time", @@ -3136,6 +3143,7 @@ "restore-version": "Restore version", "restore-entity-from-version": "Restore entity from version '{{versionName}}'", "load-entity-relations": "Load entity relations", + "load-entity-attributes": "Load entity attributes", "show-version-diff": "Show version diff", "diff-entity-with-version": "Diff with entity version '{{versionName}}'", "previous-difference": "Previous Difference", @@ -3146,20 +3154,26 @@ "default-sync-strategy": "Default sync strategy", "sync-strategy-merge": "Merge", "sync-strategy-overwrite": "Overwrite", - "entity-types": "Entity types", + "entities-to-export": "Entities to export", + "entities-to-restore": "Entities to restore", "sync-strategy": "Sync strategy", "all-entities": "All entities", - "no-entity-types": "No entity types configured", + "no-entities-to-export-prompt": "Please specify entities to export", + "no-entities-to-restore-prompt": "Please specify entities to restore", "add-entity-type": "Add entity type", "remove-all": "Remove all", "version-create-result": "{ added, plural, 0 {No entities} 1 {1 entity} other {# entities} } added.\n{ modified, plural, 0 {No entities} 1 {1 entity} other {# entities} } modified.\n{ removed, plural, 0 {No entities} 1 {1 entity} other {# entities} } removed.", "load-entities-relations": "Load entities relations", + "load-entities-attributes": "Load entities attributes", "remove-other-entities": "Remove other entities", + "find-existing-entity-by-name": "Find existing entity by name", "restore-entities-from-version": "Restore entities from version '{{versionName}}'", "no-entities-restored": "No entities restored", "created": "{{created}} created", "updated": "{{updated}} updated", - "deleted": "{{deleted}} deleted" + "deleted": "{{deleted}} deleted", + "remove-other-entities-confirm-text": "Be careful! This will permanently delete all current entities
not present in the version you want to restore.

Please type remove other entities to confirm.", + "auto-commit-to-branch": "auto-commit to {{ branch }} branch" }, "widget": { "widget-library": "Widgets Library",