Browse Source

Merge remote-tracking branch 'origin/feature/entities-version-control' into vc-improvements

pull/6616/head
Viacheslav Klimov 4 years ago
parent
commit
4c8b3bd7cb
  1. 111
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  2. 13
      application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
  3. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java
  4. 87
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  5. 67
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  6. 102
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultTbVersionControlSettingsService.java
  7. 12
      application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java
  8. 9
      application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java
  9. 80
      application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java
  10. 14
      application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java
  11. 17
      application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java
  12. 36
      application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java
  13. 30
      application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java
  14. 63
      application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java
  15. 34
      application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java
  16. 36
      application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java
  17. 12
      application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java
  18. 9
      application/src/main/resources/thingsboard.yml
  19. 10
      common/cluster-api/src/main/proto/queue.proto
  20. 3
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  21. 21
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java
  22. 27
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java
  23. 28
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java
  24. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java
  26. 16
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java
  27. 10
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  28. 28
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java
  29. 27
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java
  30. 33
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  31. 8
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java
  32. 8
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java
  33. 6
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java
  34. 10
      ui-ngx/src/app/core/auth/auth.actions.ts
  35. 2
      ui-ngx/src/app/core/auth/auth.models.ts
  36. 4
      ui-ngx/src/app/core/auth/auth.reducer.ts
  37. 4
      ui-ngx/src/app/core/auth/auth.selectors.ts
  38. 10
      ui-ngx/src/app/core/auth/auth.service.ts
  39. 47
      ui-ngx/src/app/core/http/admin.service.ts
  40. 8
      ui-ngx/src/app/core/http/entities-version-control.service.ts
  41. 22
      ui-ngx/src/app/core/services/menu.service.ts
  42. 16
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  43. 125
      ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html
  44. 74
      ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss
  45. 210
      ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts
  46. 17
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html
  47. 4
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts
  48. 32
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html
  49. 44
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts
  50. 3
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.html
  51. 6
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts
  52. 3
      ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html
  53. 6
      ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts
  54. 8
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html
  55. 2
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts
  56. 41
      ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.html
  57. 66
      ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.ts
  58. 26
      ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html
  59. 2
      ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss
  60. 216
      ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts
  61. 218
      ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.ts
  62. 6
      ui-ngx/src/app/modules/home/components/vc/version-control.component.html
  63. 10
      ui-ngx/src/app/modules/home/components/vc/version-control.component.ts
  64. 24
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  65. 6
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  66. 23
      ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.html
  67. 51
      ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.ts
  68. 2
      ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.html
  69. 12
      ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.ts
  70. 4
      ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html
  71. 3
      ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts
  72. 3
      ui-ngx/src/app/shared/models/constants.ts
  73. 19
      ui-ngx/src/app/shared/models/settings.models.ts
  74. 24
      ui-ngx/src/app/shared/models/vc.models.ts
  75. 32
      ui-ngx/src/assets/locale/locale.constant-en_US.json

111
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<EntitiesVersionControlSettings> saveVersionControlSettings(@RequestBody EntitiesVersionControlSettings settings) throws ThingsboardException {
@PostMapping("/repositorySettings")
public DeferredResult<RepositorySettings> saveRepositorySettings(@RequestBody RepositorySettings settings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
ListenableFuture<EntitiesVersionControlSettings> future = versionControlService.saveVersionControlSettings(getTenantId(), settings);
ListenableFuture<RepositorySettings> 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<Void> deleteVersionControlSettings() throws ThingsboardException {
public DeferredResult<Void> 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<Void> 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<Void> 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)

13
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<EntityDataInfo> 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<EntityDataDiff> compareEntityDataToVersion(@PathVariable String branch,
@PathVariable EntityType entityType,

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java

@ -42,7 +42,11 @@ public class CustomerImportService extends BaseEntityImportService<CustomerId, C
@Override
protected Customer prepareAndSave(TenantId tenantId, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider) {
return customerService.saveCustomer(customer);
if (customer.isPublic()) {
return customerService.findOrCreatePublicCustomer(tenantId);
} else {
return customerService.saveCustomer(customer);
}
}
@Override

87
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<VersionCreationResult> 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<ListenableFuture<Void>> gitFutures = new ArrayList<>();
@ -325,34 +329,42 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
EntityId externalId = ((ExportableEntity<EntityId>) 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<EntityDataInfo> 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<List<String>> 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<EntitiesVersionControlSettings> saveVersionControlSettings(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings) {
var restoredSettings = this.vcSettingsService.restore(tenantId, versionControlSettings);
public ListenableFuture<RepositorySettings> 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<Void> 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<Void> checkVersionControlAccess(TenantId tenantId, EntitiesVersionControlSettings settings) throws ThingsboardException {
settings = this.vcSettingsService.restore(tenantId, settings);
public ListenableFuture<Void> 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<VersionCreationResult> 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) {

67
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<CommitGitRequest> prepareCommit(TenantId tenantId, VersionCreateRequest request) {
public ListenableFuture<CommitGitRequest> prepareCommit(User user, VersionCreateRequest request) {
SettableFuture<CommitGitRequest> 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<Void> 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 <T> void registerAndSend(PendingGitRequest<T> request,
Function<ToVersionControlServiceMsg.Builder, ToVersionControlServiceMsg> enrichFunction, EntitiesVersionControlSettings settings, TbQueueCallback callback) {
Function<ToVersionControlServiceMsg.Builder, ToVersionControlServiceMsg> 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<Void> initRepository(TenantId tenantId, EntitiesVersionControlSettings settings) {
public ListenableFuture<Void> 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<Void> testRepository(TenantId tenantId, EntitiesVersionControlSettings settings) {
public ListenableFuture<Void> 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<String> 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);
}

102
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultTbVersionControlSettingsService.java

@ -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<TenantId, EntitiesVersionControlSettings> 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;
}
}

12
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<List<String>> listBranches(TenantId tenantId) throws Exception;
EntitiesVersionControlSettings getVersionControlSettings(TenantId tenantId);
RepositorySettings getVersionControlSettings(TenantId tenantId);
ListenableFuture<EntitiesVersionControlSettings> saveVersionControlSettings(TenantId tenantId, EntitiesVersionControlSettings versionControlSettings);
ListenableFuture<RepositorySettings> saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings);
ListenableFuture<Void> deleteVersionControlSettings(TenantId tenantId) throws Exception;
ListenableFuture<Void> checkVersionControlAccess(TenantId tenantId, EntitiesVersionControlSettings settings) throws Exception;
ListenableFuture<Void> checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws Exception;
ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityId entityId) throws Exception;
ListenableFuture<EntityDataInfo> getEntityDataInfo(SecurityUser user, EntityId entityId, String versionId);
}

9
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<CommitGitRequest> prepareCommit(TenantId tenantId, VersionCreateRequest request);
ListenableFuture<CommitGitRequest> prepareCommit(User user, VersionCreateRequest request);
ListenableFuture<Void> addToCommit(CommitGitRequest commit, EntityExportData<ExportableEntity<EntityId>> entityData);
@ -64,9 +65,9 @@ public interface GitVersionControlQueueService {
ListenableFuture<String> getContentsDiff(TenantId tenantId, String rawEntityData1, String rawEntityData2);
ListenableFuture<Void> initRepository(TenantId tenantId, EntitiesVersionControlSettings settings);
ListenableFuture<Void> initRepository(TenantId tenantId, RepositorySettings settings);
ListenableFuture<Void> testRepository(TenantId tenantId, EntitiesVersionControlSettings settings);
ListenableFuture<Void> testRepository(TenantId tenantId, RepositorySettings settings);
ListenableFuture<Void> clearRepository(TenantId tenantId);

80
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<T extends Serializable> {
private final String settingsKey;
private final AdminSettingsService adminSettingsService;
private final TbTransactionalCache<TenantId, T> cache;
private final Class<T> clazz;
public TbAbstractVersionControlSettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache<TenantId, T> cache, Class<T> 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;
}
}

14
application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsCaffeineCache.java → 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<TenantId, EntitiesVersionControlSettings> {
@Service("AutoCommitSettingsCache")
public class AutoCommitSettingsCaffeineCache extends CaffeineTbTransactionalCache<TenantId, AutoCommitSettings> {
public VersionControlSettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.VC_SETTINGS_CACHE);
public AutoCommitSettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.AUTO_COMMIT_SETTINGS_CACHE);
}
}

17
application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlSettingsRedisCache.java → 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<TenantId, EntitiesVersionControlSettings> {
@Service("AutoCommitSettingsCache")
public class AutoCommitSettingsRedisCache extends RedisTbTransactionalCache<TenantId, AutoCommitSettings> {
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<>());
}
}

36
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<AutoCommitSettings> implements TbAutoCommitSettingsService {
public static final String SETTINGS_KEY = "autoCommitSettings";
public DefaultTbAutoCommitSettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache<TenantId, AutoCommitSettings> cache) {
super(adminSettingsService, cache, AutoCommitSettings.class, SETTINGS_KEY);
}
}

30
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);
}

63
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<RepositorySettings> implements TbRepositorySettingsService {
public static final String SETTINGS_KEY = "entitiesVersionControl";
public DefaultTbRepositorySettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache<TenantId, RepositorySettings> 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;
}
}

34
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<TenantId, RepositorySettings> {
public RepositorySettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.REPOSITORY_SETTINGS_CACHE);
}
}

36
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<TenantId, RepositorySettings> {
public RepositorySettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

12
application/src/main/java/org/thingsboard/server/service/sync/vc/TbVersionControlSettingsService.java → 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);

9
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

10
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 {

3
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";
}

21
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<E extends ExportableEntity<? extends EntityId>> {
public static final Comparator<EntityRelation> 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<AttributeExportData> attrComparator = Comparator
.comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs);
@JsonTbEntity
private E entity;
private EntityType entityType;
@ -48,4 +59,14 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
private List<EntityRelation> relations;
private Map<String, List<AttributeExportData>> attributes;
public EntityExportData<E> sort() {
if (relations != null && !relations.isEmpty()) {
relations.sort(relationsComparator);
}
if (attributes != null && !attributes.isEmpty()) {
attributes.values().forEach(list -> list.sort(attrComparator));
}
return this;
}
}

27
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<EntityType, AutoVersionCreateConfig> {
private static final long serialVersionUID = -5757067601838792059L;
}

28
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;
}

1
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;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionControlAuthMethod.java → 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
}

16
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntitiesVersionControlSettings.java → 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<EntityType, AutoVersionCreateConfig> 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<>();
}
}

10
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> T convertValue(Object fromValue, Class<T> 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);
}

28
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<Exception> 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<EntitiesVersionControlSettings> settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray());
private RepositorySettings getEntitiesVersionControlSettings(ToVersionControlServiceMsg msg) {
Optional<RepositorySettings> settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray());
if (settingsOpt.isPresent()) {
return settingsOpt.get();
} else {

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

33
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

8
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<VersionedEntityInfo> 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;

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

6
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()));

10
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;

2
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 {

4
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:

4
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<AppState>): AuthState {

10
ui-ngx/src/app/core/auth/auth.service.ts

@ -437,9 +437,9 @@ export class AuthService {
return this.http.get<boolean>('/api/edges/enabled', defaultHttpOptions());
}
private loadHasVersionControl(authUser: AuthUser): Observable<boolean> {
private loadHasRepository(authUser: AuthUser): Observable<boolean> {
if (authUser.authority === Authority.TENANT_ADMIN) {
return this.http.get<boolean>('/api/admin/vcSettings/exists', defaultHttpOptions());
return this.http.get<boolean>('/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({});
})));

47
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<EntitiesVersionControlSettings> {
return this.http.get<EntitiesVersionControlSettings>(`/api/admin/vcSettings`, defaultHttpOptionsFromConfig(config));
public getRepositorySettings(config?: RequestConfig): Observable<RepositorySettings> {
return this.http.get<RepositorySettings>(`/api/admin/repositorySettings`, defaultHttpOptionsFromConfig(config));
}
public saveEntitiesVersionControlSettings(versionControlSettings: EntitiesVersionControlSettings,
config?: RequestConfig): Observable<EntitiesVersionControlSettings> {
return this.http.post<EntitiesVersionControlSettings>('/api/admin/vcSettings', versionControlSettings,
public saveRepositorySettings(repositorySettings: RepositorySettings,
config?: RequestConfig): Observable<RepositorySettings> {
return this.http.post<RepositorySettings>('/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<void> {
return this.http.post<void>('/api/admin/vcSettings/checkAccess', versionControlSettings, defaultHttpOptionsFromConfig(config));
public checkRepositoryAccess(repositorySettings: RepositorySettings,
config?: RequestConfig): Observable<void> {
return this.http.post<void>('/api/admin/repositorySettings/checkAccess', repositorySettings, defaultHttpOptionsFromConfig(config));
}
public getAutoCommitSettings(config?: RequestConfig): Observable<AutoCommitSettings> {
return this.http.get<AutoCommitSettings>(`/api/admin/autoCommitSettings`, defaultHttpOptionsFromConfig(config));
}
public autoCommitSettingsExists(config?: RequestConfig): Observable<boolean> {
return this.http.get<boolean>('/api/admin/autoCommitSettings/exists', defaultHttpOptionsFromConfig(config));
}
public saveAutoCommitSettings(autoCommitSettings: AutoCommitSettings,
config?: RequestConfig): Observable<AutoCommitSettings> {
return this.http.post<AutoCommitSettings>('/api/admin/autoCommitSettings', autoCommitSettings, defaultHttpOptionsFromConfig(config));
}
public deleteAutoCommitSettings(config?: RequestConfig) {
return this.http.delete('/api/admin/autoCommitSettings', defaultHttpOptionsFromConfig(config));
}
public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {

8
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'

22
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'
}
]
}

16
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,

125
ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html

@ -0,0 +1,125 @@
<!--
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.
-->
<div>
<mat-card class="auto-commit-settings settings-card">
<mat-card-title>
<div fxLayout="row">
<span class="mat-headline" translate>admin.auto-commit-settings</span>
<span fxFlex></span>
<div tb-help="autoCommitSettings"></div>
</div>
</mat-card-title>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px;">
<form [formGroup]="autoCommitSettingsForm" #formDirective="ngForm" (ngSubmit)="save()">
<fieldset class="fields-group" [disabled]="isLoading$ | async">
<legend class="group-title" translate>admin.auto-commit-entities</legend>
<div fxLayout="column">
<div class="tb-control-list">
<div *ngFor="let entityTypeFormGroup of entityTypesFormGroupArray(); trackBy: trackByEntityType;
let $index = index; last as isLast;"
fxLayout="row" fxLayoutAlign="start center" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<mat-expansion-panel class="entity-type-config" fxFlex [formGroup]="entityTypeFormGroup" [expanded]="entityTypesFormGroupExpanded(entityTypeFormGroup)">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
<div [innerHTML]="entityTypeText(entityTypeFormGroup)"></div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeEntityType($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="entity-type-config-content" fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<div fxLayout="column" fxLayout.gt-lg="row" fxLayoutGap.gt-lg="16px">
<div fxLayout="row" fxLayoutGap="16px">
<tb-entity-type-select
showLabel
formControlName="entityType"
required
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="allowedEntityTypes(entityTypeFormGroup)">
</tb-entity-type-select>
<div formGroupName="config">
<tb-branch-autocomplete
emptyPlaceholder="{{ 'version-control.default' | translate }}"
[selectDefaultBranch]="false"
formControlName="branch">
</tb-branch-autocomplete>
</div>
</div>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px" formGroupName="config">
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes">
{{ 'version-control.export-entity-attributes' | translate }}
</mat-checkbox>
</div>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</div>
<div *ngIf="!entityTypesFormGroupArray().length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">admin.no-auto-commit-entities-prompt</span>
</div>
<div style="padding-top: 16px;" fxLayout="row">
<button mat-raised-button color="primary"
type="button"
[disabled]="!addEnabled()"
(click)="addEntityType()">
<span translate>version-control.add-entity-type</span>
</button>
<span fxFlex></span>
<button mat-raised-button color="primary"
type="button"
[disabled]="!entityTypesFormGroupArray().length"
(click)="removeAll()">
<span translate>version-control.remove-all</span>
</button>
</div>
</div>
</fieldset>
<div fxLayout="row" fxLayoutAlign="end center" fxLayout.xs="column" fxLayoutAlign.xs="end" fxLayoutGap="16px">
<button mat-raised-button color="warn" type="button" [fxShow]="settings !== null"
[disabled]="(isLoading$ | async)" (click)="delete(formDirective)">
{{'action.delete' | translate}}
</button>
<span fxFlex></span>
<button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || autoCommitSettingsForm.invalid || !autoCommitSettingsForm.dirty"
type="submit">{{'action.save' | translate}}
</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>

74
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;
}
}
}

210
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<AppState>,
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 += ` (<small>${this.translate.instant('version-control.auto-commit-to-branch', {branch: branchName})}</small>)`;
return this.sanitizer.bypassSecurityTrustHtml(message);
}
allowedEntityTypes(entityTypeControl?: AbstractControl): Array<EntityType> {
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<AbstractControl> = [];
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;
}
}

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

@ -17,7 +17,7 @@
-->
<section class="entity-types-version-create" [formGroup]="entityTypesVersionCreateFormGroup" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>version-control.entity-types</legend>
<legend class="group-title" translate>version-control.entities-to-export</legend>
<div fxLayout="column">
<div class="tb-control-list">
<div *ngFor="let entityTypeFormGroup of entityTypesFormGroupArray(); trackBy: trackByEntityType;
@ -53,7 +53,7 @@
[allowedEntityTypes]="allowedEntityTypes(entityTypeFormGroup)">
</tb-entity-type-select>
<div fxFlex fxLayout="row" fxLayoutGap="16px" fxLayoutAlign="start center" formGroupName="config">
<mat-form-field class="mat-block">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>version-control.sync-strategy</mat-label>
<mat-select formControlName="syncStrategy">
<mat-option [value]="'default'">
@ -64,9 +64,14 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<div fxFlex fxLayout="column" fxLayoutGap="8px">
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes">
{{ 'version-control.export-entity-attributes' | translate }}
</mat-checkbox>
</div>
</div>
</div>
<div fxLayout="row" fxLayoutGap="16px" fxLayoutAlign="start center"
@ -89,7 +94,7 @@
</div>
<div *ngIf="!entityTypesFormGroupArray().length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">version-control.no-entity-types</span>
class="tb-prompt">version-control.no-entities-to-export-prompt</span>
</div>
<div style="padding-top: 16px;" fxLayout="row">
<button mat-raised-button color="primary"

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

@ -80,7 +80,7 @@ export class EntityTypesVersionCreateComponent extends PageComponent implements
ngOnInit(): void {
this.entityTypesVersionCreateFormGroup = this.fb.group({
entityTypes: [this.fb.array([]), []]
entityTypes: this.fb.array([], [])
});
this.entityTypesVersionCreateFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -135,6 +135,7 @@ export class EntityTypesVersionCreateComponent extends PageComponent implements
config: this.fb.group({
syncStrategy: [config.syncStrategy === null ? 'default' : config.syncStrategy, []],
saveRelations: [config.saveRelations, []],
saveAttributes: [config.saveAttributes, []],
allEntities: [config.allEntities, []],
entityIds: [config.entityIds, [Validators.required]]
})
@ -183,6 +184,7 @@ export class EntityTypesVersionCreateComponent extends PageComponent implements
const config: EntityTypeVersionCreateConfig = {
syncStrategy: null,
saveRelations: false,
saveAttributes: false,
allEntities: true,
entityIds: []
};

32
ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html

@ -17,7 +17,7 @@
-->
<section class="entity-types-version-load" [formGroup]="entityTypesVersionLoadFormGroup" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>version-control.entity-types</legend>
<legend class="group-title" translate>version-control.entities-to-restore</legend>
<div fxLayout="column">
<div class="tb-control-list">
<div *ngFor="let entityTypeFormGroup of entityTypesFormGroupArray(); trackBy: trackByEntityType;
@ -44,7 +44,7 @@
<ng-template matExpansionPanelContent>
<div class="entity-type-config-content" fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<div fxLayout="row" fxLayoutGap="16px">
<div fxLayout="row" fxLayoutGap="32px">
<tb-entity-type-select
showLabel
formControlName="entityType"
@ -52,13 +52,25 @@
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="allowedEntityTypes(entityTypeFormGroup)">
</tb-entity-type-select>
<div fxFlex fxLayout="row" fxLayoutGap="16px" fxLayoutAlign="start center" formGroupName="config">
<mat-checkbox formControlName="removeOtherEntities">
{{ 'version-control.remove-other-entities' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="loadRelations">
{{ 'version-control.load-entities-relations' | translate }}
</mat-checkbox>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px" formGroupName="config">
<div fxFlex fxLayout="column" fxLayoutGap="8px">
<mat-checkbox #removeOtherEntitiesCheckbox
formControlName="removeOtherEntities"
(click)="onRemoveOtherEntities(removeOtherEntitiesCheckbox, entityTypeFormGroup, $event)">
{{ 'version-control.remove-other-entities' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="findExistingEntityByName">
{{ 'version-control.find-existing-entity-by-name' | translate }}
</mat-checkbox>
</div>
<div fxFlex fxLayout="column" fxLayoutGap="8px">
<mat-checkbox formControlName="loadRelations">
{{ 'version-control.load-entities-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="loadAttributes">
{{ 'version-control.load-entities-attributes' | translate }}
</mat-checkbox>
</div>
</div>
</div>
</div>
@ -68,7 +80,7 @@
</div>
<div *ngIf="!entityTypesFormGroupArray().length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">version-control.no-entity-types</span>
class="tb-prompt">version-control.no-entities-to-restore-prompt</span>
</div>
<div style="padding-top: 16px;" fxLayout="row">
<button mat-raised-button color="primary"

44
ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
@ -28,11 +28,15 @@ import {
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { EntityTypeVersionLoadConfig, exportableEntityTypes } from '@shared/models/vc.models';
import { EntityTypeVersionLoadConfig, exportableEntityTypes, VersionCreationResult } from '@shared/models/vc.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { MatCheckbox } from '@angular/material/checkbox/checkbox';
import { TbPopoverService } from '@shared/components/popover.service';
import { EntityVersionCreateComponent } from '@home/components/vc/entity-version-create.component';
import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component';
@Component({
selector: 'tb-entity-types-version-load',
@ -64,13 +68,16 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private fb: FormBuilder) {
super(store);
}
ngOnInit(): void {
this.entityTypesVersionLoadFormGroup = this.fb.group({
entityTypes: [this.fb.array([]), []]
entityTypes: this.fb.array([], [])
});
this.entityTypesVersionLoadFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -124,7 +131,9 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On
entityType: [entityType, [Validators.required]],
config: this.fb.group({
loadRelations: [config.loadRelations, []],
removeOtherEntities: [config.removeOtherEntities, []]
loadAttributes: [config.loadAttributes, []],
removeOtherEntities: [config.removeOtherEntities, []],
findExistingEntityByName: [config.findExistingEntityByName, []]
})
}
);
@ -156,7 +165,9 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On
const entityTypesArray = this.entityTypesVersionLoadFormGroup.get('entityTypes') as FormArray;
const config: EntityTypeVersionLoadConfig = {
loadRelations: false,
removeOtherEntities: false
loadAttributes: false,
removeOtherEntities: false,
findExistingEntityByName: false
};
const allowed = this.allowedEntityTypes();
let entityType: EntityType = null;
@ -194,6 +205,29 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On
return res;
}
onRemoveOtherEntities(removeOtherEntitiesCheckbox: MatCheckbox, entityTypeControl: AbstractControl, $event: Event) {
const removeOtherEntities: boolean = entityTypeControl.get('config.removeOtherEntities').value;
if (!removeOtherEntities) {
$event.preventDefault();
$event.stopPropagation();
const trigger = $('.mat-checkbox-frame', removeOtherEntitiesCheckbox._elementRef.nativeElement)[0];
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const removeOtherEntitiesConfirmPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, RemoveOtherEntitiesConfirmComponent, 'bottom', true, null,
{
onClose: (result: boolean | null) => {
removeOtherEntitiesConfirmPopover.hide();
if (result) {
entityTypeControl.get('config').get('removeOtherEntities').patchValue(true, {emitEvent: true});
}
}
}, {}, {}, {}, false);
}
}
}
private updateModel() {
const value: [{entityType: string, config: EntityTypeVersionLoadConfig}] =
this.entityTypesVersionLoadFormGroup.get('entityTypes').value || [];

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

@ -41,6 +41,9 @@
<mat-checkbox formControlName="saveRelations" style="margin-bottom: 16px;">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes" style="margin-bottom: 16px;">
{{ 'version-control.export-entity-attributes' | translate }}
</mat-checkbox>
</div>
</fieldset>
</form>

6
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
};

3
ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html

@ -29,6 +29,9 @@
<mat-checkbox formControlName="loadRelations" style="margin-bottom: 16px;">
{{ 'version-control.load-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="loadAttributes" style="margin-bottom: 16px;">
{{ 'version-control.load-entity-attributes' | translate }}
</mat-checkbox>
</div>
</fieldset>
</form>

6
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
};

8
ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html

@ -101,11 +101,17 @@
</mat-cell>
</ng-container>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef style="width: 100%"> {{ 'version-control.version-name' | translate }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef style="width: 50%"> {{ 'version-control.version-name' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let entityVersion">
{{ entityVersion.name }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="author">
<mat-header-cell *matHeaderCellDef style="width: 50%"> {{ 'version-control.author' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let entityVersion">
{{ entityVersion.author }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef [ngStyle]="singleEntityMode ? {minWidth: '80px', maxWidth: '80px', width: '80px'} :
{minWidth: '40px', maxWidth: '40px', width: '40px'}">

2
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;

41
ui-ngx/src/app/modules/home/components/vc/remove-other-entities-confirm.component.html

@ -0,0 +1,41 @@
<!--
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.
-->
<section style="min-width: 300px;">
<form [formGroup]="confirmFormGroup" style="padding-top: 16px;">
<fieldset [disabled]="(isLoading$ | async)">
<div style="user-select: none;" [innerHTML]="removeOtherEntitiesConfirmText"></div>
<mat-form-field class="mat-block">
<input matInput formControlName="verification"/>
</mat-form-field>
</fieldset>
</form>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="button"
(click)="confirm()"
[disabled]="(isLoading$ | async) || confirmFormGroup.invalid || confirmFormGroup.get('verification').value !== removeOtherEntitiesVerificationText">
{{ 'action.confirm' | translate }}
</button>
</div>
</section>

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

26
ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.html → ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html

@ -16,24 +16,24 @@
-->
<div>
<mat-card class="vc-settings" [ngClass]="{'settings-card': !detailsMode}">
<mat-card class="repository-settings" [ngClass]="{'settings-card': !detailsMode}">
<mat-card-title>
<div fxLayout="row">
<span class="mat-headline" translate>admin.git-repository-settings</span>
<span class="mat-headline" translate>admin.repository-settings</span>
<span fxFlex></span>
<div tb-help="versionControlSettings"></div>
<div tb-help="repositorySettings"></div>
</div>
</mat-card-title>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px;">
<form [formGroup]="versionControlSettingsForm" #formDirective="ngForm" (ngSubmit)="save()">
<form [formGroup]="repositorySettingsForm" #formDirective="ngForm" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<mat-form-field class="mat-block">
<mat-label translate>admin.repository-url</mat-label>
<input matInput required formControlName="repositoryUri">
<mat-error translate *ngIf="versionControlSettingsForm.get('repositoryUri').hasError('required')">
<mat-error translate *ngIf="repositorySettingsForm.get('repositoryUri').hasError('required')">
admin.repository-url-required
</mat-error>
</mat-form-field>
@ -46,12 +46,12 @@
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.auth-method</mat-label>
<mat-select required formControlName="authMethod">
<mat-option *ngFor="let method of versionControlAuthMethods" [value]="method">
{{versionControlAuthMethodTranslations.get(method) | translate}}
<mat-option *ngFor="let method of repositoryAuthMethods" [value]="method">
{{repositoryAuthMethodTranslations.get(method) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<section [fxShow]="versionControlSettingsForm.get('authMethod').value === versionControlAuthMethod.USERNAME_PASSWORD" fxLayout="column">
<section [fxShow]="repositorySettingsForm.get('authMethod').value === repositoryAuthMethod.USERNAME_PASSWORD" fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.username</mat-label>
<input matInput formControlName="username" placeholder="{{ 'common.enter-username' | translate }}"
@ -68,14 +68,14 @@
<tb-toggle-password matSuffix></tb-toggle-password>
</mat-form-field>
</section>
<section [fxShow]="versionControlSettingsForm.get('authMethod').value === versionControlAuthMethod.PRIVATE_KEY" fxLayout="column">
<section [fxShow]="repositorySettingsForm.get('authMethod').value === repositoryAuthMethod.PRIVATE_KEY" fxLayout="column">
<tb-file-input style="margin-bottom: 16px;"
[existingFileName]="versionControlSettingsForm.get('privateKeyFileName').value"
[existingFileName]="repositorySettingsForm.get('privateKeyFileName').value"
required
formControlName="privateKey"
dropLabel="{{ 'admin.drop-private-key-file-or' | translate }}"
[label]="'admin.private-key' | translate"
(fileNameChanged)="versionControlSettingsForm.get('privateKeyFileName').patchValue($event)">
(fileNameChanged)="repositorySettingsForm.get('privateKeyFileName').patchValue($event)">
</tb-file-input>
<mat-checkbox *ngIf="showChangePrivateKeyPassword" (change)="changePrivateKeyPasswordChanged()"
[(ngModel)]="changePrivateKeyPassword" [ngModelOptions]="{ standalone: true }" style="padding-bottom: 16px;">
@ -96,10 +96,10 @@
</button>
<span fxFlex></span>
<button mat-raised-button type="button"
[disabled]="(isLoading$ | async) || versionControlSettingsForm.invalid" (click)="checkAccess()">
[disabled]="(isLoading$ | async) || repositorySettingsForm.invalid" (click)="checkAccess()">
{{'admin.check-access' | translate}}
</button>
<button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || versionControlSettingsForm.invalid || !versionControlSettingsForm.dirty"
<button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || repositorySettingsForm.invalid || !repositorySettingsForm.dirty"
type="submit">{{'action.save' | translate}}
</button>
</div>

2
ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.scss → 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 {

216
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<AppState>,
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});
}
}

218
ui-ngx/src/app/modules/home/components/vc/version-control-settings.component.ts

@ -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<AppState>,
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});
}
}

6
ui-ngx/src/app/modules/home/components/vc/version-control.component.html

@ -15,9 +15,9 @@
limitations under the License.
-->
<tb-version-control-settings #versionControlSettingsComponent [detailsMode]="detailsMode"
*ngIf="!(hasVersionControl$ | async); else versionsTable">
</tb-version-control-settings>
<tb-repository-settings #repositorySettingsComponent [detailsMode]="detailsMode"
*ngIf="!(hasRepository$ | async); else versionsTable">
</tb-repository-settings>
<ng-template #versionsTable>
<tb-entity-versions-table [singleEntityMode]="singleEntityMode"
[active]="active"

10
ui-ngx/src/app/modules/home/components/vc/version-control.component.ts

@ -17,9 +17,9 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { selectHasVersionControl } from '@core/auth/auth.selectors';
import { selectHasRepository } from '@core/auth/auth.selectors';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component';
import { RepositorySettingsComponent } from '@home/components/vc/repository-settings.component';
import { FormGroup } from '@angular/forms';
import { EntityId } from '@shared/models/id/entity-id';
@ -30,7 +30,7 @@ import { EntityId } from '@shared/models/id/entity-id';
})
export class VersionControlComponent implements OnInit, HasConfirmForm {
@ViewChild('versionControlSettingsComponent', {static: false}) versionControlSettingsComponent: VersionControlSettingsComponent;
@ViewChild('repositorySettingsComponent', {static: false}) repositorySettingsComponent: RepositorySettingsComponent;
@Input()
detailsMode = false;
@ -50,7 +50,7 @@ export class VersionControlComponent implements OnInit, HasConfirmForm {
@Output()
versionRestored = new EventEmitter<void>();
hasVersionControl$ = this.store.pipe(select(selectHasVersionControl));
hasRepository$ = this.store.pipe(select(selectHasRepository));
constructor(private store: Store<AppState>) {
@ -61,7 +61,7 @@ export class VersionControlComponent implements OnInit, HasConfirmForm {
}
confirmForm(): FormGroup {
return this.versionControlSettingsComponent?.versionControlSettingsForm;
return this.repositorySettingsComponent?.repositorySettingsForm;
}
}

24
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<string> {
@ -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'
}
}
}
]
}

6
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,

23
ui-ngx/src/app/modules/home/pages/admin/auto-commit-admin-settings.component.html

@ -0,0 +1,23 @@
<!--
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.
-->
<tb-repository-settings #repositorySettingsComponent
*ngIf="!(hasRepository$ | async); else autoCommitSettings">
</tb-repository-settings>
<ng-template #autoCommitSettings>
<tb-auto-commit-settings #autoCommitSettingsComponent></tb-auto-commit-settings>
</ng-template>

51
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<AppState>) {
super(store);
}
ngOnInit() {
}
confirmForm(): FormGroup {
return this.repositorySettingsComponent ?
this.repositorySettingsComponent?.repositorySettingsForm :
this.autoCommitSettingsComponent?.autoCommitSettingsForm;
}
}

2
ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.html → ui-ngx/src/app/modules/home/pages/admin/repository-admin-settings.component.html

@ -15,4 +15,4 @@
limitations under the License.
-->
<tb-version-control-settings #versionControlSettingsComponent></tb-version-control-settings>
<tb-repository-settings #repositorySettingsComponent></tb-repository-settings>

12
ui-ngx/src/app/modules/home/pages/admin/version-control-admin-settings.component.ts → 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<AppState>) {
super(store);
@ -39,6 +39,6 @@ export class VersionControlAdminSettingsComponent extends PageComponent implemen
}
confirmForm(): FormGroup {
return this.versionControlSettingsComponent?.versionControlSettingsForm;
return this.repositorySettingsComponent?.repositorySettingsForm;
}
}

4
ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html

@ -15,9 +15,9 @@
limitations under the License.
-->
<mat-form-field [formGroup]="branchFormGroup" class="mat-block" [floatLabel]="selectionMode ? 'always' : 'auto'">
<mat-form-field [formGroup]="branchFormGroup" class="mat-block" [floatLabel]="(selectionMode || emptyPlaceholder) ? 'always' : 'auto'">
<mat-label>{{ 'version-control.branch' | translate }}</mat-label>
<input matInput type="text" placeholder="{{(loading ? 'common.loading' : 'version-control.select-branch') | translate}}"
<input matInput type="text" placeholder="{{emptyPlaceholder || ((loading ? 'common.loading' : 'version-control.select-branch') | translate)}}"
#branchInput
formControlName="branch"
(keydown.enter)="branchInput.blur(); autoCompleteTrigger.closePanel();"

3
ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts

@ -86,6 +86,9 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
@Input()
selectionMode = false;
@Input()
emptyPlaceholder: string;
@ViewChild('branchAutocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('branchInput', { read: MatAutocompleteTrigger, static: true }) autoCompleteTrigger: MatAutocompleteTrigger;
@ViewChild('branchInput', {static: true}) branchInput: ElementRef;

3
ui-ngx/src/app/shared/models/constants.ts

@ -135,7 +135,8 @@ export const HelpLinks = {
ruleNodePushToCloud: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-cloud',
ruleNodePushToEdge: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-edge',
queue: helpBaseUrl + '/docs/user-guide/queue',
versionControlSettings: helpBaseUrl + '/docs/user-guide/ui/version-control-settings'
repositorySettings: helpBaseUrl + '/docs/user-guide/ui/repository-settings',
autoCommitSettings: helpBaseUrl + '/docs/user-guide/ui/auto-commit-settings',
}
};

19
ui-ngx/src/app/shared/models/settings.models.ts

@ -16,6 +16,7 @@
import { ValidatorFn } from '@angular/forms';
import { isNotEmptyStr, isNumber } from '@core/utils';
import { VersionCreateConfig } from '@shared/models/vc.models';
export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
@ -397,23 +398,29 @@ export function createSmsProviderConfiguration(type: SmsProviderType): SmsProvid
return smsProviderConfiguration;
}
export enum VersionControlAuthMethod {
export enum RepositoryAuthMethod {
USERNAME_PASSWORD = 'USERNAME_PASSWORD',
PRIVATE_KEY = 'PRIVATE_KEY'
}
export const versionControlAuthMethodTranslationMap = new Map<VersionControlAuthMethod, string>([
[VersionControlAuthMethod.USERNAME_PASSWORD, 'admin.auth-method-username-password'],
[VersionControlAuthMethod.PRIVATE_KEY, 'admin.auth-method-private-key']
export const repositoryAuthMethodTranslationMap = new Map<RepositoryAuthMethod, string>([
[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};

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

@ -32,6 +32,7 @@ export const exportableEntityTypes: Array<EntityType> = [
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<any>): string {
if (!data.relations) {
data.relations = [];
}
const allKeys = new Set<string>();
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);
}

32
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 <b>delete</b> <b>all</b> current entities<br/>not present in the version you want to restore.<br/><br/>Please type <b>remove other entities</b> to confirm.",
"auto-commit-to-branch": "auto-commit to <b>{{ branch }}</b> branch"
},
"widget": {
"widget-library": "Widgets Library",

Loading…
Cancel
Save