diff --git a/.gitignore b/.gitignore index ef52f3e78..efd255a32 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ dmp-backend/web/src/main/resources/certificates/ dmp-backend/target/classes/ dmp-backend/core/target/maven-archiver/ dmp-backend/node_modules/.yarn-integrity +dmp-frontend/.nx/ diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java index ce33da9b4..fe54aaef6 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java @@ -1,11 +1,6 @@ package eu.eudat.authorization; public final class Permission { - /////// Should Remove after Refactor - - public static String AdminRole = "AdminRole"; - public static String AuthenticatedRole = "AuthenticatedRole"; - ///// public static String DeferredAffiliation = "DeferredAffiliation"; @@ -222,5 +217,6 @@ public final class Permission { public static String ViewMyDmpPage = "ViewMyDmpPage"; public static String ViewHomePage = "ViewHomePage"; public static String ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage"; + public static String ViewTenantConfigurationPage = "ViewTenantConfigurationPage"; } diff --git a/dmp-backend/core/src/main/java/eu/eudat/errorcode/ErrorThesaurusProperties.java b/dmp-backend/core/src/main/java/eu/eudat/errorcode/ErrorThesaurusProperties.java index f3e518eff..800f5f8ba 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/errorcode/ErrorThesaurusProperties.java +++ b/dmp-backend/core/src/main/java/eu/eudat/errorcode/ErrorThesaurusProperties.java @@ -184,4 +184,14 @@ public class ErrorThesaurusProperties { public void setTenantConfigurationTypeCanNotChange(ErrorDescription tenantConfigurationTypeCanNotChange) { this.tenantConfigurationTypeCanNotChange = tenantConfigurationTypeCanNotChange; } + + private ErrorDescription multipleTenantConfigurationTypeNotAllowed; + + public ErrorDescription getMultipleTenantConfigurationTypeNotAllowed() { + return multipleTenantConfigurationTypeNotAllowed; + } + + public void setMultipleTenantConfigurationTypeNotAllowed(ErrorDescription multipleTenantConfigurationTypeNotAllowed) { + this.multipleTenantConfigurationTypeNotAllowed = multipleTenantConfigurationTypeNotAllowed; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/tenantconfiguration/TenantConfigurationPersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/tenantconfiguration/TenantConfigurationPersist.java index 26c071c37..0477c872c 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/persist/tenantconfiguration/TenantConfigurationPersist.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/tenantconfiguration/TenantConfigurationPersist.java @@ -157,19 +157,19 @@ public class TenantConfigurationPersist { .must(() -> !this.isNull(item.getCssColors())) .failOn(TenantConfigurationPersist._cssColors).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationPersist._cssColors}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.CssColors.equals(item.getType())) + .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.DefaultUserLocale.equals(item.getType())) .must(() -> !this.isNull(item.getDefaultUserLocale())) .failOn(TenantConfigurationPersist._defaultUserLocale).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationPersist._defaultUserLocale}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.CssColors.equals(item.getType())) + .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.DepositPlugins.equals(item.getType())) .must(() -> !this.isNull(item.getDepositPlugins())) .failOn(TenantConfigurationPersist._depositPlugins).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationPersist._depositPlugins}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.CssColors.equals(item.getType())) + .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.FileTransformerPlugins.equals(item.getType())) .must(() -> !this.isNull(item.getFileTransformerPlugins())) .failOn(TenantConfigurationPersist._fileTransformerPlugins).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationPersist._fileTransformerPlugins}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.CssColors.equals(item.getType())) + .iff(() -> !this.isNull(item.getType()) && TenantConfigurationType.Logo.equals(item.getType())) .must(() -> !this.isNull(item.getLogo())) .failOn(TenantConfigurationPersist._logo).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantConfigurationPersist._logo}, LocaleContextHolder.getLocale())), diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/tenantconfiguration/TenantConfigurationServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/tenantconfiguration/TenantConfigurationServiceImpl.java index d5f0f5ad4..a7cc91476 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/tenantconfiguration/TenantConfigurationServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/tenantconfiguration/TenantConfigurationServiceImpl.java @@ -21,12 +21,14 @@ import eu.eudat.model.persist.deposit.DepositSourcePersist; import eu.eudat.model.persist.filetransformer.FileTransformerSourcePersist; import eu.eudat.model.persist.tenantconfiguration.*; import eu.eudat.model.tenantconfiguration.TenantConfiguration; +import eu.eudat.query.TenantConfigurationQuery; import eu.eudat.service.encryption.EncryptionService; import eu.eudat.service.storage.StorageFileService; import eu.eudat.service.tenant.TenantProperties; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.exception.MyNotFoundException; @@ -79,6 +81,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic private final TenantProperties tenantProperties; private final StorageFileService storageFileService; + private final QueryFactory queryFactory; @Autowired public TenantConfigurationServiceImpl( TenantEntityManager entityManager, @@ -87,7 +90,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic BuilderFactory builderFactory, ConventionService conventionService, ErrorThesaurusProperties errors, - MessageSource messageSource, JsonHandlingService jsonHandlingService, EncryptionService encryptionService, TenantProperties tenantProperties, StorageFileService storageFileService) { + MessageSource messageSource, JsonHandlingService jsonHandlingService, EncryptionService encryptionService, TenantProperties tenantProperties, StorageFileService storageFileService, QueryFactory queryFactory) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -99,6 +102,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic this.encryptionService = encryptionService; this.tenantProperties = tenantProperties; this.storageFileService = storageFileService; + this.queryFactory = queryFactory; } public TenantConfiguration persist(TenantConfigurationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { @@ -122,7 +126,11 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic data.setType(model.getType()); } - + TenantConfigurationQuery tenantConfigurationQuery = this.queryFactory.query(TenantConfigurationQuery.class).excludedIds(data.getId()).isActive(IsActive.Active).types(data.getType()); + if (data.getTenantId() == null) tenantConfigurationQuery.tenantIsSet(false); + else tenantConfigurationQuery.tenantIsSet(false).tenantIds(data.getTenantId()); + if (tenantConfigurationQuery.count() > 0)throw new MyValidationException(this.errors.getMultipleTenantConfigurationTypeNotAllowed().getCode(), this.errors.getMultipleTenantConfigurationTypeNotAllowed().getMessage()); + switch (data.getType()){ case CssColors -> data.setValue(this.jsonHandlingService.toJson(this.buildCssColorsTenantConfigurationEntity(model.getCssColors()))); case DefaultUserLocale -> data.setValue(this.jsonHandlingService.toJson(this.buildDefaultUserLocaleTenantConfigurationEntity(model.getDefaultUserLocale()))); diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/TenantConfigurationController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/TenantConfigurationController.java index 2c8b5236d..7685d7520 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/TenantConfigurationController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/TenantConfigurationController.java @@ -3,6 +3,9 @@ package eu.eudat.controllers; import com.fasterxml.jackson.core.JsonProcessingException; import eu.eudat.audit.AuditableAction; import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.enums.TenantConfigurationType; +import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.data.TenantConfigurationEntity; import eu.eudat.model.DescriptionTemplateType; import eu.eudat.model.builder.tenantconfiguration.TenantConfigurationBuilder; @@ -56,23 +59,25 @@ public class TenantConfigurationController { private final QueryFactory queryFactory; private final MessageSource messageSource; + private final TenantScope tenantScope; public TenantConfigurationController( - BuilderFactory builderFactory, - AuditService auditService, - TenantConfigurationService tenantConfigurationService, CensorFactory censorFactory, - QueryFactory queryFactory, - MessageSource messageSource) { + BuilderFactory builderFactory, + AuditService auditService, + TenantConfigurationService tenantConfigurationService, CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource, TenantScope tenantScope) { this.builderFactory = builderFactory; this.auditService = auditService; this.tenantConfigurationService = tenantConfigurationService; this.censorFactory = censorFactory; this.queryFactory = queryFactory; this.messageSource = messageSource; + this.tenantScope = tenantScope; } @PostMapping("query") - public QueryResult Query(@RequestBody TenantConfigurationLookup lookup) throws MyApplicationException, MyForbiddenException { + public QueryResult query(@RequestBody TenantConfigurationLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", TenantConfiguration.class.getSimpleName()); this.censorFactory.censor(TenantConfigurationCensor.class).censor(lookup.getProject(), null); @@ -89,7 +94,7 @@ public class TenantConfigurationController { } @GetMapping("{id}") - public TenantConfiguration Get(@PathVariable("id") UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + public TenantConfiguration get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("id", id).And("fields", fieldSet)); this.censorFactory.censor(TenantConfigurationCensor.class).censor(fieldSet, null); @@ -107,10 +112,30 @@ public class TenantConfigurationController { return model; } + @GetMapping("current-tenant/{type}") + public TenantConfiguration getCurrentTenantType(@PathVariable("type") Short type, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { + logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("type", type).And("fields", fieldSet)); + + this.censorFactory.censor(TenantConfigurationCensor.class).censor(fieldSet, null); + + TenantConfigurationQuery query = this.queryFactory.query(TenantConfigurationQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).isActive(IsActive.Active).types(TenantConfigurationType.of(type)); + if (this.tenantScope.isDefaultTenant()) query.tenantIsSet(false); + else query.tenantIsSet(false).tenantIds(this.tenantScope.getTenant()); + + TenantConfiguration model = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(fieldSet, query.firstAs(fieldSet)); + + this.auditService.track(AuditableAction.TenantConfiguration_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("type", type), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + + return model; + } + @PostMapping("persist") @Transactional @ValidationFilterAnnotation(validator = TenantConfigurationPersist.TenantConfigurationPersistValidator.ValidatorName, argumentName = "model") - public TenantConfiguration Persist(@RequestBody TenantConfigurationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + public TenantConfiguration persist(@RequestBody TenantConfigurationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { logger.debug(new MapLogEntry("persisting" + DescriptionTemplateType.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); TenantConfiguration persisted = this.tenantConfigurationService.persist(model, fieldSet); @@ -124,7 +149,7 @@ public class TenantConfigurationController { @DeleteMapping("{id}") @Transactional - public void Delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("id", id)); this.tenantConfigurationService.deleteAndSave(id); diff --git a/dmp-backend/web/src/main/resources/config/errors.yml b/dmp-backend/web/src/main/resources/config/errors.yml index 7cdda0db9..284314b61 100644 --- a/dmp-backend/web/src/main/resources/config/errors.yml +++ b/dmp-backend/web/src/main/resources/config/errors.yml @@ -62,4 +62,7 @@ error-thesaurus: tenant-configuration-type-can-not-change: code: 124 message: Tenant configuration type can not change + multiple-tenant-configuration-type-not-allowed: + code: 125 + message: Multiple Tenant Configuration Type Not Allowed diff --git a/dmp-backend/web/src/main/resources/config/permissions.yml b/dmp-backend/web/src/main/resources/config/permissions.yml index d2007ee8b..378438232 100644 --- a/dmp-backend/web/src/main/resources/config/permissions.yml +++ b/dmp-backend/web/src/main/resources/config/permissions.yml @@ -1,20 +1,6 @@ permissions: extendedClaims: [ ] policies: - ###### Should Remove after Refactor - AdminRole: - roles: - - Admin - clients: [ ] - allowAnonymous: false - allowAuthenticated: false - AuthenticatedRole: - roles: [] - clients: [ ] - allowAnonymous: false - allowAuthenticated: true - - ###### # Affiliation DeferredAffiliation: roles: @@ -1027,4 +1013,11 @@ permissions: roles: [ ] clients: [ ] allowAnonymous: false - allowAuthenticated: true \ No newline at end of file + allowAuthenticated: true + ViewTenantConfigurationPage: + roles: + - TenantAdmin + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false \ No newline at end of file diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index 48b9f1317..713f61220 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -274,6 +274,18 @@ const appRoutes: Routes = [ }) }, }, + { + path: 'tenant-configuration', + loadChildren: () => import('./ui/admin/tenant-configuration/tenant-configuration.module').then(m => m.TenantConfigurationModule), + data: { + authContext: { + permissions: [AppPermission.ViewTenantConfigurationPage] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.TENANT-CONFIGURATION' + }) + }, + }, { path: 'notifications', loadChildren: () => import('@notification-service/ui/admin/notification/notification.module').then(m => m.NotificationModule), diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts index cf591a0b5..93c1d3aac 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -1,75 +1,199 @@ export enum AppPermission { - //DescriptionTemplateType - BrowseDescriptionTemplateType = "BrowseDescriptionTemplateType", - EditDescriptionTemplateType = "EditDescriptionTemplateType", - DeleteDescriptionTemplateType = "DeleteDescriptionTemplateType", + ///// + DeferredAffiliation = "DeferredAffiliation", - //DmpBlueprint - BrowseDmpBlueprint = "BrowseDmpBlueprint", - EditDmpBlueprint = "EditDmpBlueprint", - DeleteDmpBlueprint = "DeleteDmpBlueprint", - - //Description - BrowseDescription = "BrowseDescription", - EditDescription = "EditDescription", - FinalizeDescription = "FinalizeDescription", - DeleteDescription= "DeleteDescription", - - //Dmp - BrowseDmp = "BrowseDmp", - EditDmp = "EditDmp", - NewDmp = "NewDmp", - DeleteDmp = "DeleteDmp", - DepositDmp = "DepositDmp", - CloneDmp = "CloneDmp", - CreateNewVersionDmp = "CreateNewVersionDmp", - ExportDmp = "ExportDmp", - FinalizeDmp = "FinalizeDmp", - AssignDmpUsers = "AssignDmpUsers", - InviteDmpUsers = "InviteDmpUsers", - - //DescriptionTemplateType - BrowseDescriptionTemplate = "BrowseDescriptionTemplate", - EditDescriptionTemplate = "EditDescriptionTemplate", - DeleteDescriptionTemplate = "DeleteDescriptionTemplate", + //Public + PublicBrowseDescription = "PublicBrowseDescription", + PublicBrowseDescriptionTemplate = "PublicBrowseDescriptionTemplate", + PublicBrowseDmp = "PublicBrowseDmp", + PublicBrowseDmpReference = "PublicBrowseDmpReference", + PublicBrowseDmpUser = "PublicBrowseDmpUser", + PublicBrowseReference = "PublicBrowseReference", + PublicBrowseUser = "PublicBrowseUser", + PublicBrowseDashboardStatistics = "PublicBrowseDashboardStatistics", + PublicSendContactSupport = "PublicSendContactSupport", + PublicBrowseReferenceType = "PublicBrowseReferenceType", + //Elastic + ManageElastic = "ManageElastic", + //Queue Events + ManageQueueEvents = "ManageQueueEvents", - //ReferenceType - BrowseReferenceType = "BrowseReferenceType", - EditReferenceType = "EditReferenceType", - DeleteReferenceType = "DeleteReferenceType", + //Deposit + BrowseDeposit = "BrowseDeposit", + EditDeposit = "BrowseDeposit", - //Tenant - BrowseTenant = "BrowseTenant", - EditTenant = "EditTenant", - DeleteTenant = "DeleteTenant", - - //User - BrowseUser = "BrowseUser", - EditUser = "EditUser", - DeleteUser = "DeleteUser", - ExportUsers = "ExportUsers", - - //Reference - BrowseReference = "BrowseReference", - EditReference = "EditReference", - DeleteReference = "DeleteReference", //Language BrowseLanguage = "BrowseLanguage", EditLanguage = "EditLanguage", DeleteLanguage = "DeleteLanguage", - //Notification Template + + //NotificationTemplate BrowseNotificationTemplate = "BrowseNotificationTemplate", EditNotificationTemplate = "EditNotificationTemplate", DeleteNotificationTemplate = "DeleteNotificationTemplate", - //Prefilling Source - BrowsePrefillingSource= "BrowsePrefillingSource", - EditPrefillingSource = "EditPrefillingSource", + //Language + BrowseStatistics = "BrowseStatistics", + BrowsePublicStatistics = "BrowsePublicStatistics", + + //DescriptionTemplate + BrowseDescriptionTemplate = "BrowseDescriptionTemplate", + EditDescriptionTemplate = "EditDescriptionTemplate", + DeleteDescriptionTemplate = "DeleteDescriptionTemplate", + CloneDescriptionTemplate = "CloneDescriptionTemplate", + CreateNewVersionDescriptionTemplate = "CreateNewVersionDescriptionTemplate", + ImportDescriptionTemplate = "ImportDescriptionTemplate", + ExportDescriptionTemplate = "ExportDescriptionTemplate", + + + + //User + BrowseUser = "BrowseUser", + EditUser = "EditUser", + DeleteUser = "DeleteUser", + ExportUsers = "ExportUsers", + BrowseDmpAssociatedUser = "BrowseDmpAssociatedUser", + + + //StorageFile + BrowseStorageFile = "BrowseStorageFile", + EditStorageFile = "EditStorageFile", + DeleteStorageFile = "DeleteStorageFile", + + //DescriptionTemplateType + BrowseDescriptionTemplateType = "BrowseDescriptionTemplateType", + EditDescriptionTemplateType = "EditDescriptionTemplateType", + DeleteDescriptionTemplateType = "DeleteDescriptionTemplateType", + + //Dmp + BrowseDmp = "BrowseDmp", + EditDmp = "EditDmp", + NewDmp = "NewDmp", + DepositDmp = "DepositDmp", + DeleteDmp = "DeleteDmp", + CloneDmp = "CloneDmp", + ExportDmp = "ExportDmp", + CreateNewVersionDmp = "CreateNewVersionDmp", + FinalizeDmp = "FinalizeDmp", + UndoFinalizeDmp = "UndoFinalizeDmp", + AssignDmpUsers = "AssignDmpUsers", + InviteDmpUsers = "InviteDmpUsers", + + //DmpBlueprint + BrowseDmpBlueprint = "BrowseDmpBlueprint", + EditDmpBlueprint = "EditDmpBlueprint", + DeleteDmpBlueprint = "DeleteDmpBlueprint", + CloneDmpBlueprint = "CloneDmpBlueprint", + CreateNewVersionDmpBlueprint = "CreateNewVersionDmpBlueprint", + ExportDmpBlueprint = "ExportDmpBlueprint", + ImportDmpBlueprint = "ImportDmpBlueprint", + + //DmpDescriptionTemplate + BrowseDmpDescriptionTemplate = "BrowseDmpDescriptionTemplate", + EditDmpDescriptionTemplate = "EditDmpDescriptionTemplate", + DeleteDmpDescriptionTemplate = "DeleteDmpDescriptionTemplate", + + //DmpUser + BrowseDmpUser = "BrowseDmpUser", + EditDmpUser = "EditDmpUser", + DeleteDmpUser = "DeleteDmpUser", + + //Description + BrowseDescription = "BrowseDescription", + EditDescription = "EditDescription", + FinalizeDescription = "FinalizeDescription", + DeleteDescription = "DeleteDescription", + CloneDescription = "CloneDescription", + + //DescriptionTag + BrowseDescriptionTag = "BrowseDescriptionTag", + EditDescriptionTag = "EditDescriptionTag", + DeleteDescriptionTag = "DeleteDescriptionTag", + + //DescriptionTemplateType + BrowseEntityDoi = "BrowseEntityDoi", + EditEntityDoi = "EditEntityDoi", + DeleteEntityDoi = "DeleteEntityDoi", + + //UserSettings + BrowseUserSettings = "BrowseUserSettings", + EditUserSettings = "EditUserSettings", + DeleteUserSettings = "DeleteUserSettings", + + + + //Reference + BrowseReference = "BrowseReference", + EditReference = "EditReference", + DeleteReference = "DeleteReference", + + //Tag + BrowseTag = "BrowseTag", + EditTag = "EditTag", + DeleteTag = "DeleteTag", + + //DmpReference + BrowseDmpReference = "BrowseDmpReference", + EditDmpReference = "EditDmpReference", + DeleteDmpReference = "DeleteDmpReference", + + //DescriptionReference + BrowseDescriptionReference = "BrowseDescriptionReference", + EditDescriptionReference = "EditDescriptionReference", + DeleteDescriptionReference = "DeleteDescriptionReference", + + //SupportiveMaterial + BrowseSupportiveMaterial = "BrowseSupportiveMaterial", + EditSupportiveMaterial= "EditSupportiveMaterial", + DeleteSupportiveMaterial = "DeleteSupportiveMaterial", + + //ReferenceType + BrowseReferenceType = "BrowseReferenceType", + EditReferenceType= "EditReferenceType", + DeleteReferenceType = "DeleteReferenceType", + + //Tenant + BrowseTenant = "BrowseTenant", + EditTenant= "EditTenant", + DeleteTenant = "DeleteTenant", + AllowNoTenant = "AllowNoTenant", + + //TenantConfiguration + BrowseTenantConfiguration = "BrowseTenantConfiguration", + EditTenantConfiguration = "EditTenantConfiguration", + DeleteTenantConfiguration = "DeleteTenantConfiguration", + + //TenantUser + BrowseTenantUser = "BrowseTenantUser", + EditTenantUser = "EditTenantUser", + DeleteTenantUser = "DeleteTenantUser", + + //Prefilling + BrowsePrefilling = "BrowsePrefilling", + + + //Lock + BrowseLock = "BrowseLock", + EditLock = "EditLock", + DeleteLock = "DeleteLock", + + //ContactSupport + SendContactSupport = "SendContactSupport", + + //ActionConfirmation + BrowseActionConfirmation = "BrowseActionConfirmation", + EditActionConfirmation = "EditActionConfirmation", + DeleteActionConfirmation = "DeleteActionConfirmation", + + //PrefillingSource + BrowsePrefillingSource = "BrowsePrefillingSource", + EditPrefillingSource= "EditPrefillingSource", DeletePrefillingSource = "DeletePrefillingSource", + // UI Pages ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage", ViewMaintenancePage = "ViewMaintenancePage", @@ -91,5 +215,6 @@ export enum AppPermission { ViewMyDmpPage = "ViewMyDmpPage", ViewHomePage = "ViewHomePage", ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage", + ViewTenantConfigurationPage = "ViewTenantConfigurationPage", } diff --git a/dmp-frontend/src/app/core/common/enum/respone-error-code.ts b/dmp-frontend/src/app/core/common/enum/respone-error-code.ts index 8fbd45b17..acbf1515a 100644 --- a/dmp-frontend/src/app/core/common/enum/respone-error-code.ts +++ b/dmp-frontend/src/app/core/common/enum/respone-error-code.ts @@ -13,5 +13,8 @@ export enum ResponseErrorCode { DmpBlueprintHasNoDescriptionTemplates = 120, DmpBlueprintNewVersionConflict = 121, DmpDescriptionTemplateCanNotRemove = 122, - TenantTampering = 123 -} \ No newline at end of file + TenantTampering = 123, + TenantConfigurationTypeCanNotChange = 124, + MultipleTenantConfigurationTypeNotAllowed = 125, + +} diff --git a/dmp-frontend/src/app/core/common/enum/tenant-configuration-type.ts b/dmp-frontend/src/app/core/common/enum/tenant-configuration-type.ts new file mode 100644 index 000000000..d2d0f70dc --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/tenant-configuration-type.ts @@ -0,0 +1,7 @@ +export enum TenantConfigurationType { + DepositPlugins = 0, + FileTransformerPlugins = 1, + DefaultUserLocale = 2, + Logo = 3, + CssColors = 4, +} diff --git a/dmp-frontend/src/app/core/core-service.module.ts b/dmp-frontend/src/app/core/core-service.module.ts index 63b4d6875..816ab0bf5 100644 --- a/dmp-frontend/src/app/core/core-service.module.ts +++ b/dmp-frontend/src/app/core/core-service.module.ts @@ -46,6 +46,7 @@ import { SemanticsService } from './services/semantic/semantics.service'; import { PrefillingSourceService } from './services/prefilling-source/prefilling-source.service'; import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service'; import { StorageFileService } from './services/storage-file/storage-file.service'; +import { TenantConfigurationService } from './services/tenant-configuration/tenant-configuration.service'; // // // This is shared module that provides all the services. Its imported only once on the AppModule. @@ -110,6 +111,7 @@ export class CoreServiceModule { SemanticsService, PrefillingSourceService, VisibilityRulesService, + TenantConfigurationService, StorageFileService ], }; diff --git a/dmp-frontend/src/app/core/model/tenant-configuaration/tenant-configuration.ts b/dmp-frontend/src/app/core/model/tenant-configuaration/tenant-configuration.ts new file mode 100644 index 000000000..28b70c76a --- /dev/null +++ b/dmp-frontend/src/app/core/model/tenant-configuaration/tenant-configuration.ts @@ -0,0 +1,111 @@ +import { TenantConfigurationType } from "@app/core/common/enum/tenant-configuration-type"; +import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; +import { StorageFile } from "../storage-file/storage-file"; +import { Guid } from "@common/types/guid"; + +export interface TenantConfiguration extends BaseEntity{ + type?: TenantConfigurationType; + cssColors?: CssColorsTenantConfiguration; + defaultUserLocale?: DefaultUserLocaleTenantConfiguration; + depositPlugins?: DepositTenantConfiguration; + fileTransformerPlugins?: FileTransformerTenantConfiguration; + logo?: LogoTenantConfiguration; +} + +export interface CssColorsTenantConfiguration{ + primaryColor: string; + primaryColor2: string; + primaryColor3: string; + secondaryColor: string; +} + +export interface DefaultUserLocaleTenantConfiguration{ + timezone: string; + language: string; + culture: string; +} + +export interface DepositTenantConfiguration{ + sources: DepositSource[]; +} + +export interface FileTransformerTenantConfiguration{ + sources: FileTransformerSource[]; +} + +export interface DepositSource{ + repositoryId: string; + url: string; + issuerUrl: string; + clientId: string; + clientSecret: string; + scope: string; + pdfTransformerId: string; + rdaTransformerId: string; +} + +export interface FileTransformerSource{ + transformerId: string; + url: string; + issuerUrl: string; + clientId: string; + clientSecret: string; + scope: string; +} +export interface LogoTenantConfiguration{ + storageFile: StorageFile; +} +//persist + +export interface TenantConfigurationPersist extends BaseEntityPersist{ + type: TenantConfigurationType; + cssColors?: CssColorsTenantConfigurationPersist; + defaultUserLocale?: DefaultUserLocaleTenantConfigurationPersist; + depositPlugins?: DepositTenantConfigurationPersist; + fileTransformerPlugins?: FileTransformerTenantConfigurationPersist; + logo?: LogoTenantConfigurationPersist; +} + +export interface CssColorsTenantConfigurationPersist{ + primaryColor: string; + primaryColor2: string; + primaryColor3: string; + secondaryColor: string; +} + +export interface DefaultUserLocaleTenantConfigurationPersist{ + timezone: string; + language: string; + culture: string; +} + +export interface DepositTenantConfigurationPersist{ + sources: DepositSourcePersist[]; +} + +export interface FileTransformerTenantConfigurationPersist{ + sources: FileTransformerSourcePersist[]; +} + +export interface DepositSourcePersist{ + repositoryId: string; + url: string; + issuerUrl: string; + clientId: string; + clientSecret: string; + scope: string; + pdfTransformerId: string; + rdaTransformerId: string; +} + +export interface FileTransformerSourcePersist{ + transformerId: string; + url: string; + issuerUrl: string; + clientId: string; + clientSecret: string; + scope: string; +} +export interface LogoTenantConfigurationPersist{ + storageFileId: Guid; +} diff --git a/dmp-frontend/src/app/core/model/tenant/tenant.ts b/dmp-frontend/src/app/core/model/tenant/tenant.ts index 8c522db49..6aa3f282a 100644 --- a/dmp-frontend/src/app/core/model/tenant/tenant.ts +++ b/dmp-frontend/src/app/core/model/tenant/tenant.ts @@ -4,33 +4,6 @@ export interface Tenant extends BaseEntity{ name?: string; code?: string; description?: string; - config?: TenantConfig; -} - -export interface TenantConfig{ - deposit: TenantDepositConfig; - fileTransformers: TenantFileTransformersConfig; -} - -export interface TenantDepositConfig{ - sources: TenantSource[]; -} - -export interface TenantFileTransformersConfig{ - sources: TenantSource[]; -} - -export interface TenantSource{ - url: string; - codes: string[]; - issuerUrl: string; - clientId: string; - clientSecret: string; - scope: string; -} - -export interface SourceCode{ - code: string; } //persist @@ -39,31 +12,5 @@ export interface TenantPersist extends BaseEntityPersist{ name: string; code: string; description: string; - config?: TenantConfigPersist; } -export interface TenantConfigPersist{ - deposit: TenantDepositConfigPersist; - fileTransformers: TenantFileTransformersConfigPersist; -} - -export interface TenantDepositConfigPersist{ - sources: TenantSourcePersist[]; -} - -export interface TenantFileTransformersConfigPersist{ - sources: TenantSourcePersist[]; -} - -export interface TenantSourcePersist{ - url: string; - codes: string[]; - issuerUrl: string; - clientId: string; - clientSecret: string; - scope: string; -} - -export interface SourceCodePersist{ - code: string; -} diff --git a/dmp-frontend/src/app/core/query/tenant-configuration.lookup.ts b/dmp-frontend/src/app/core/query/tenant-configuration.lookup.ts new file mode 100644 index 000000000..19f1454b7 --- /dev/null +++ b/dmp-frontend/src/app/core/query/tenant-configuration.lookup.ts @@ -0,0 +1,26 @@ +import { Lookup } from '@common/model/lookup'; +import { Guid } from '@common/types/guid'; +import { IsActive } from '../common/enum/is-active.enum'; +import { TenantConfigurationType } from '../common/enum/tenant-configuration-type'; + +export class TenantConfigurationLookup extends Lookup implements TenantConfigurationFilter { + ids: Guid[]; + excludedIds: Guid[]; + types: TenantConfigurationType[]; + isActive: IsActive[]; + tenantIds: Guid[]; + tenantIsSet: boolean; + + constructor() { + super(); + } +} + +export interface TenantConfigurationFilter { + ids: Guid[]; + excludedIds: Guid[]; + types: TenantConfigurationType[]; + tenantIds: Guid[]; + tenantIsSet: boolean; + isActive: IsActive[]; +} diff --git a/dmp-frontend/src/app/core/services/tenant-configuration/tenant-configuration.service.ts b/dmp-frontend/src/app/core/services/tenant-configuration/tenant-configuration.service.ts new file mode 100644 index 000000000..903fe9599 --- /dev/null +++ b/dmp-frontend/src/app/core/services/tenant-configuration/tenant-configuration.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { QueryResult } from '@common/model/query-result'; +import { FilterService } from '@common/modules/text-filter/filter-service'; +import { Guid } from '@common/types/guid'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ConfigurationService } from '../configuration/configuration.service'; +import { BaseHttpV2Service } from '../http/base-http-v2.service'; +import { TenantConfigurationLookup } from '@app/core/query/tenant-configuration.lookup'; +import { TenantConfiguration, TenantConfigurationPersist } from '@app/core/model/tenant-configuaration/tenant-configuration'; +import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type'; + +@Injectable() +export class TenantConfigurationService { + + constructor(private http: BaseHttpV2Service, private configurationService: ConfigurationService, private filterService: FilterService) { + } + + private get apiBase(): string { return `${this.configurationService.server}tenant-configuration`; } + + query(q: TenantConfigurationLookup): Observable> { + const url = `${this.apiBase}/query`; + return this.http.post>(url, q).pipe(catchError((error: any) => throwError(error))); + } + + getSingle(id: Guid, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/${id}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + getCurrentTenantType(type: TenantConfigurationType, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/current-tenant/${type}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + + persist(item: TenantConfigurationPersist): Observable { + const url = `${this.apiBase}/persist`; + + return this.http + .post(url, item).pipe( + catchError((error: any) => throwError(error))); + } + + delete(id: Guid): Observable { + const url = `${this.apiBase}/${id}`; + + return this.http + .delete(url).pipe( + catchError((error: any) => throwError(error))); + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.html b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.html new file mode 100644 index 000000000..6d37074a5 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.html @@ -0,0 +1,43 @@ +
+
+
+ + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.PRIMARY-COLOR' | translate}} + + {{formGroup.get('cssColors')?.get('primaryColor')?.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + + + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.PRIMARY-COLOR-2' | translate}} + + {{formGroup.get('cssColors')?.get('primaryColor2')?.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + + + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.PRIMARY-COLOR-3' | translate}} + + {{formGroup.get('cssColors')?.get('primaryColor3')?.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + + + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.SECONDARY-COLOR' | translate}} + + {{formGroup.get('cssColors')?.get('secondaryColor')?.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.scss b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.scss new file mode 100644 index 000000000..33cd57c15 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.scss @@ -0,0 +1,3 @@ +.css-colors { + +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.ts new file mode 100644 index 000000000..c747d22ec --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.component.ts @@ -0,0 +1,207 @@ + +import { Component, OnInit } from '@angular/core'; +import { UntypedFormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { MatomoService } from '@app/core/services/matomo/matomo-service'; +import { FormService } from '@common/forms/form-service'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { TranslateService } from '@ngx-translate/core'; +import { map, takeUntil } from 'rxjs/operators'; +import { TenantConfigurationEditorModel } from './css-colors-editor.model'; +import { TenantConfiguration, TenantConfigurationPersist } from '@app/core/model/tenant-configuaration/tenant-configuration'; +import { TenantConfigurationService } from '@app/core/services/tenant-configuration/tenant-configuration.service'; +import { CssColorsEditorService } from './css-colors-editor.service'; +import { CssColorsEditorResolver } from './css-colors-editor.resolver'; +import { BasePendingChangesComponent } from '@common/base/base-pending-changes.component'; +import { Observable } from 'rxjs'; +import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ResponseErrorCode } from '@app/core/common/enum/respone-error-code'; +import { LoggingService } from '@app/core/services/logging/logging-service'; + + +@Component({ + selector: 'app-tenant-configuration-css-colors-editor', + templateUrl: 'css-colors-editor.component.html', + styleUrls: ['./css-colors-editor.component.scss'], + providers: [CssColorsEditorService] +}) +export class CssColorsEditorComponent extends BasePendingChangesComponent implements OnInit { + + isNew = true; + formGroup: UntypedFormGroup = null; + + get editorModel(): TenantConfigurationEditorModel { return this._editorModel; } + set editorModel(value: TenantConfigurationEditorModel) { this._editorModel = value; } + private _editorModel: TenantConfigurationEditorModel; + + protected get canDelete(): boolean { + return !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteTenantConfiguration); + } + + protected get canSave(): boolean { + return this.hasPermission(this.authService.permissionEnum.EditTenantConfiguration); + } + + private hasPermission(permission: AppPermission): boolean { + return this.authService.hasPermission(permission); + } + + constructor( + protected dialog: MatDialog, + protected language: TranslateService, + protected formService: FormService, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected authService: AuthService, + private logger: LoggingService, + private tenantConfigurationService: TenantConfigurationService, + private cssColorsEditorService: CssColorsEditorService, + private matomoService: MatomoService + ) { + super(); + } + + canDeactivate(): boolean | Observable { + return this.formGroup ? !this.formGroup.dirty : true; + } + + ngOnInit(): void { + this.matomoService.trackPageView('Admin: TenantConfigurations'); + this.getItem((entity) => { + this.prepareForm(entity); + if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) { + this.formGroup.disable(); + } + }); + } + + getItem(successFunction: (item: TenantConfiguration) => void) { + this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.CssColors, CssColorsEditorResolver.lookupFields()) + .pipe(map(data => data as TenantConfiguration), takeUntil(this._destroyed)) + .subscribe( + data => successFunction(data), + error => this.onCallbackError(error) + ); + } + + onCallbackError(errorResponse: HttpErrorResponse) { + + console.log("Error:", errorResponse); + + const error: HttpError = this.httpErrorHandlingService.getError(errorResponse); + if (error.statusCode === 400) { + this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error); + if(errorResponse.error.code === ResponseErrorCode.TenantConfigurationTypeCanNotChange){ + this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error); + } + if(errorResponse.error.code === ResponseErrorCode.MultipleTenantConfigurationTypeNotAllowed){ + this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error); + } + this.formService.validateAllFormFields(this.formGroup); + } else { + this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning); + } + } + onCallbackSuccess(data?: any): void { + + console.log("Success:", data); + + this.uiNotificationService.snackBarNotification(this.isNew ? this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-CREATION') : this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success); + this.refreshData(); + } + + + prepareForm(data: TenantConfiguration) { + try { + this.editorModel = data ? new TenantConfigurationEditorModel().fromModel(data) : new TenantConfigurationEditorModel(); + + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse TenantConfiguration item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm() { + this.formGroup = this.editorModel.buildForm(null, !this.authService.hasPermission(AppPermission.EditTenantConfiguration)); + this.cssColorsEditorService.setValidationErrorModel(this.editorModel.validationErrorModel); + } + + refreshData(): void { + this.getItem((entity) => { + this.prepareForm(entity); + if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) { + this.formGroup.disable(); + } + }); + } + + persistEntity(onSuccess?: (response) => void): void { + const formData = this.formService.getValue(this.formGroup.value) as TenantConfigurationPersist; + + this.tenantConfigurationService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete), + error => this.onCallbackError(error) + ); + } + + formSubmit(): void { + this.clearErrorModel(); + this.formService.removeAllBackEndErrors(this.formGroup); + this.formService.touchAllFormFields(this.formGroup); + if (!this.isFormValid()) { + return; + } + + this.persistEntity(); + } + + public isFormValid() { + return this.formGroup.valid; + } + + public delete() { + const value = this.formGroup.value; + if (value.id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + maxWidth: '300px', + data: { + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.tenantConfigurationService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackDeleteSuccessConfig(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + onCallbackDeleteSuccessConfig(data?: any): void { + + console.log("Success Delete:", data); + + this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); + this.prepareForm(null) + } + + + clearErrorModel() { + this.editorModel.validationErrorModel.clear(); + this.formService.validateAllFormFields(this.formGroup); + } + + +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.model.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.model.ts new file mode 100644 index 000000000..039cba2e4 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.model.ts @@ -0,0 +1,116 @@ +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; +import { TenantConfigurationType } from "@app/core/common/enum/tenant-configuration-type"; +import { CssColorsTenantConfiguration, CssColorsTenantConfigurationPersist, TenantConfiguration, TenantConfigurationPersist } from "@app/core/model/tenant-configuaration/tenant-configuration"; +import { BaseEditorModel } from "@common/base/base-form-editor-model"; +import { BackendErrorValidator } from "@common/forms/validation/custom-validator"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; +import { Validation, ValidationContext } from "@common/forms/validation/validation-context"; + +export class TenantConfigurationEditorModel extends BaseEditorModel implements TenantConfigurationPersist { + type: TenantConfigurationType; + cssColors: CssColorsTenantConfigurationEditorModel = new CssColorsTenantConfigurationEditorModel(this.validationErrorModel); + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { super(); this.type = TenantConfigurationType.CssColors; } + + public fromModel(item: TenantConfiguration): TenantConfigurationEditorModel { + if (item) { + super.fromModel(item); + this.type = item.type; + if (item.cssColors) this.cssColors = new CssColorsTenantConfigurationEditorModel(this.validationErrorModel).fromModel(item.cssColors); + } else { + this.type = TenantConfigurationType.CssColors; + } + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], + type: [{ value: this.type, disabled: disabled }, context.getValidation('type').validators], + hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators], + cssColors: this.cssColors.buildForm({ + rootPath: `cssColors.`, + }), + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); + baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'type')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} + +export class CssColorsTenantConfigurationEditorModel implements CssColorsTenantConfigurationPersist { + primaryColor: string; + primaryColor2: string; + primaryColor3: string; + secondaryColor: string; + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor( + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() + ) { } + + public fromModel(item: CssColorsTenantConfiguration): CssColorsTenantConfigurationEditorModel { + if (item) { + this.primaryColor = item.primaryColor; + this.primaryColor2 = item.primaryColor2; + this.primaryColor3 = item.primaryColor3; + this.secondaryColor = item.secondaryColor; + } + return this; + } + + buildForm(params?: { + context?: ValidationContext, + disabled?: boolean, + rootPath?: string + }): UntypedFormGroup { + let { context = null, disabled = false, rootPath } = params ?? {} + if (context == null) { + context = CssColorsTenantConfigurationEditorModel.createValidationContext({ + validationErrorModel: this.validationErrorModel, + rootPath + }); + } + + const form: UntypedFormGroup = this.formBuilder.group({ + primaryColor: [{ value: this.primaryColor, disabled: disabled }, context.getValidation('primaryColor').validators], + primaryColor2: [{ value: this.primaryColor2, disabled: disabled }, context.getValidation('primaryColor2').validators], + primaryColor3: [{ value: this.primaryColor3, disabled: disabled }, context.getValidation('primaryColor3').validators], + secondaryColor: [{ value: this.secondaryColor, disabled: disabled }, context.getValidation('secondaryColor').validators], + }); + + return form; + } + + + static createValidationContext(params: { + rootPath?: string, + validationErrorModel: ValidationErrorModel + }): ValidationContext { + const { rootPath = '', validationErrorModel } = params; + + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'primaryColor', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}primaryColor`)] }); + baseValidationArray.push({ key: 'primaryColor2', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}primaryColor2`)] }); + baseValidationArray.push({ key: 'primaryColor3', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}primaryColor3`)] }); + baseValidationArray.push({ key: 'secondaryColor', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}secondaryColor`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.resolver.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.resolver.ts new file mode 100644 index 000000000..60da3eb84 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.resolver.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type'; +import { CssColorsTenantConfiguration, TenantConfiguration } from '@app/core/model/tenant-configuaration/tenant-configuration'; +import { TenantConfigurationService } from '@app/core/services/tenant-configuration/tenant-configuration.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { BaseEditorResolver } from '@common/base/base-editor.resolver'; +import { takeUntil, tap } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; + +@Injectable() +export class CssColorsEditorResolver extends BaseEditorResolver { + + constructor(private tenantConfigurationService: TenantConfigurationService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + ...BaseEditorResolver.lookupFields(), + nameof(x => x.id), + nameof(x => x.type), + nameof(x => x.cssColors), + + [nameof(x => x.cssColors), nameof(x => x.primaryColor)].join('.'), + [nameof(x => x.cssColors), nameof(x => x.primaryColor2)].join('.'), + [nameof(x => x.cssColors), nameof(x => x.primaryColor3)].join('.'), + [nameof(x => x.cssColors), nameof(x => x.secondaryColor)].join('.'), + + + nameof(x => x.createdAt), + nameof(x => x.updatedAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve() { + + const fields = [ + ...CssColorsEditorResolver.lookupFields() + ]; + + return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.CssColors, fields).pipe(takeUntil(this._destroyed)); + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.service.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.service.ts new file mode 100644 index 000000000..3ba1ddfdc --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/css-colors/css-colors-editor.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from "@angular/core"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; + +@Injectable() +export class CssColorsEditorService { + private validationErrorModel: ValidationErrorModel; + + public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void { + this.validationErrorModel = validationErrorModel; + } + + public getValidationErrorModel(): ValidationErrorModel { + return this.validationErrorModel; + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.html b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.html new file mode 100644 index 000000000..1e9fa8a8f --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.html @@ -0,0 +1,49 @@ +
+
+
+ + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.TIMEZONE' | translate}} + + + {{ timezone | timezoneInfoDisplay }} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + {{formGroup.get('defaultUserLocale')?.get('timezone')?.getError('backendError').message}} + + + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.CULTURE' | translate}} + + + {{ culture.displayName }} - {{ culture.nativeName }} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + {{formGroup.get('defaultUserLocale')?.get('culture')?.getError('backendError').message}} + + + {{'TENANT-CONFIGURATION-EDITOR.FIELDS.LANGUAGE' | translate}} + + + {{ "GENERAL.LANGUAGES."+ language | translate }} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + {{formGroup.get('defaultUserLocale')?.get('language')?.getError('backendError').message}} + +
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.scss b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.scss new file mode 100644 index 000000000..fb054da9a --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.scss @@ -0,0 +1,2 @@ +.default-user-locale { +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.ts new file mode 100644 index 000000000..e6b859e2a --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.component.ts @@ -0,0 +1,229 @@ + +import { Component, OnInit } from '@angular/core'; +import { UntypedFormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +// import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { MatomoService } from '@app/core/services/matomo/matomo-service'; +import { FormService } from '@common/forms/form-service'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { TranslateService } from '@ngx-translate/core'; +import { map, takeUntil } from 'rxjs/operators'; +import { TenantConfigurationEditorModel } from './default-user-locale-editor.model'; +import { TenantConfiguration, TenantConfigurationPersist } from '@app/core/model/tenant-configuaration/tenant-configuration'; +import { TenantConfigurationService } from '@app/core/services/tenant-configuration/tenant-configuration.service'; +import { DefaultUserLocaleEditorService } from './default-user-locale-editor.service'; +import { DefaultUserLocaleEditorResolver } from './default-user-locale-editor.resolver'; +import { BasePendingChangesComponent } from '@common/base/base-pending-changes.component'; +import { Observable, of } from 'rxjs'; +import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ResponseErrorCode } from '@app/core/common/enum/respone-error-code'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { CultureInfo } from '@app/core/model/culture-info'; +import * as moment from 'moment'; +import { CultureService } from '@app/core/services/culture/culture-service'; +import { LanguageService } from '@app/core/services/language/language.service'; + + +@Component({ + selector: 'app-tenant-configuration-default-user-locale-editor', + templateUrl: 'default-user-locale-editor.component.html', + styleUrls: ['./default-user-locale-editor.component.scss'], + providers: [DefaultUserLocaleEditorService] +}) +export class DefaultUserLocaleEditorComponent extends BasePendingChangesComponent implements OnInit { + + isNew = true; + formGroup: UntypedFormGroup = null; + + get editorModel(): TenantConfigurationEditorModel { return this._editorModel; } + set editorModel(value: TenantConfigurationEditorModel) { this._editorModel = value; } + private _editorModel: TenantConfigurationEditorModel; + + timezones: any[]; + cultures: CultureInfo[]; + languages = []; + protected get canDelete(): boolean { + return !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteTenantConfiguration); + } + + protected get canSave(): boolean { + return this.hasPermission(this.authService.permissionEnum.EditTenantConfiguration); + } + + private hasPermission(permission: AppPermission): boolean { + return this.authService.hasPermission(permission); + } + + constructor( + protected dialog: MatDialog, + protected language: TranslateService, + protected formService: FormService, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected authService: AuthService, + private logger: LoggingService, + private cultureService: CultureService, + private tenantConfigurationService: TenantConfigurationService, + private languageService: LanguageService, + private defaultUserLocaleEditorService: DefaultUserLocaleEditorService, + private matomoService: MatomoService + ) { + super(); + this.languages = this.languageService.getAvailableLanguagesCodes().sort((x, y) => x.localeCompare(y)); + this.cultures = this.cultureService.getCultureValues().sort((x, y) => x.displayName.localeCompare(y.displayName)); + this.timezones = moment.tz.names().sort((x, y) => x.localeCompare(y)); + } + + canDeactivate(): boolean | Observable { + return this.formGroup ? !this.formGroup.dirty : true; + } + + ngOnInit(): void { + this.matomoService.trackPageView('Admin: TenantConfigurations'); + this.getItem((entity) => { + this.prepareForm(entity); + if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) { + this.formGroup.disable(); + } + }); + } + + getItem(successFunction: (item: TenantConfiguration) => void) { + this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.DefaultUserLocale, DefaultUserLocaleEditorResolver.lookupFields()) + .pipe(map(data => data as TenantConfiguration), takeUntil(this._destroyed)) + .subscribe( + data => successFunction(data), + error => this.onCallbackError(error) + ); + } + + onCallbackError(errorResponse: HttpErrorResponse) { + + console.log("Error:", errorResponse); + + const error: HttpError = this.httpErrorHandlingService.getError(errorResponse); + if (error.statusCode === 400) { + this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error); + if(errorResponse.error.code === ResponseErrorCode.TenantConfigurationTypeCanNotChange){ + this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error); + } + if(errorResponse.error.code === ResponseErrorCode.MultipleTenantConfigurationTypeNotAllowed){ + this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error); + } + this.formService.validateAllFormFields(this.formGroup); + } else { + this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning); + } + } + onCallbackSuccess(data?: any): void { + + console.log("Success:", data); + + this.uiNotificationService.snackBarNotification(this.isNew ? this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-CREATION') : this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success); + this.refreshData(); + } + + + prepareForm(data: TenantConfiguration) { + try { + this.editorModel = data ? new TenantConfigurationEditorModel().fromModel(data) : new TenantConfigurationEditorModel(); + + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse TenantConfiguration item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm() { + this.formGroup = this.editorModel.buildForm(null, !this.authService.hasPermission(AppPermission.EditTenantConfiguration)); + this.defaultUserLocaleEditorService.setValidationErrorModel(this.editorModel.validationErrorModel); + } + + refreshData(): void { + this.getItem((entity) => { + this.prepareForm(entity); + if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) { + this.formGroup.disable(); + } + }); + } + + persistEntity(onSuccess?: (response) => void): void { + const formData = this.formService.getValue(this.formGroup.value) as TenantConfigurationPersist; + + this.tenantConfigurationService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete), + error => this.onCallbackError(error) + ); + } + + formSubmit(): void { + this.clearErrorModel(); + this.formService.removeAllBackEndErrors(this.formGroup); + this.formService.touchAllFormFields(this.formGroup); + if (!this.isFormValid()) { + return; + } + + this.persistEntity(); + } + + public isFormValid() { + return this.formGroup.valid; + } + + public delete() { + const value = this.formGroup.value; + if (value.id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + maxWidth: '300px', + data: { + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.RESET-TO-DEFAULT'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.tenantConfigurationService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackDeleteSuccessConfig(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + onCallbackDeleteSuccessConfig(data?: any): void { + + console.log("Success Delete:", data); + + this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-RESET'), SnackBarNotificationLevel.Success); + this.prepareForm(null) + } + + + clearErrorModel() { + this.editorModel.validationErrorModel.clear(); + this.formService.validateAllFormFields(this.formGroup); + } + + displayCultureFn(culture?: CultureInfo): string | undefined { + + if (culture == null + || culture.displayName == null + || culture.nativeName == null) + return undefined; + + return culture.displayName + '-' + culture.nativeName; + } + +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.model.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.model.ts new file mode 100644 index 000000000..770be2ebf --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.model.ts @@ -0,0 +1,112 @@ +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; +import { TenantConfigurationType } from "@app/core/common/enum/tenant-configuration-type"; +import { DefaultUserLocaleTenantConfiguration, DefaultUserLocaleTenantConfigurationPersist, TenantConfiguration, TenantConfigurationPersist } from "@app/core/model/tenant-configuaration/tenant-configuration"; +import { BaseEditorModel } from "@common/base/base-form-editor-model"; +import { BackendErrorValidator } from "@common/forms/validation/custom-validator"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; +import { Validation, ValidationContext } from "@common/forms/validation/validation-context"; + +export class TenantConfigurationEditorModel extends BaseEditorModel implements TenantConfigurationPersist { + type: TenantConfigurationType; + defaultUserLocale: DefaultUserLocaleTenantConfigurationEditorModel = new DefaultUserLocaleTenantConfigurationEditorModel(this.validationErrorModel); + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { super(); this.type = TenantConfigurationType.DefaultUserLocale; } + + public fromModel(item: TenantConfiguration): TenantConfigurationEditorModel { + if (item) { + super.fromModel(item); + this.type = item.type; + if (item.defaultUserLocale) this.defaultUserLocale = new DefaultUserLocaleTenantConfigurationEditorModel(this.validationErrorModel).fromModel(item.defaultUserLocale); + } else { + this.type = TenantConfigurationType.DefaultUserLocale; + } + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], + type: [{ value: this.type, disabled: disabled }, context.getValidation('type').validators], + hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators], + defaultUserLocale: this.defaultUserLocale.buildForm({ + rootPath: `defaultUserLocale.`, + }), + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); + baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'type')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} + +export class DefaultUserLocaleTenantConfigurationEditorModel implements DefaultUserLocaleTenantConfigurationPersist { + timezone: string; + language: string; + culture: string; + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor( + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() + ) { } + + public fromModel(item: DefaultUserLocaleTenantConfiguration): DefaultUserLocaleTenantConfigurationEditorModel { + if (item) { + this.timezone = item.timezone; + this.language = item.language; + this.culture = item.culture; + } + return this; + } + + buildForm(params?: { + context?: ValidationContext, + disabled?: boolean, + rootPath?: string + }): UntypedFormGroup { + let { context = null, disabled = false, rootPath } = params ?? {} + if (context == null) { + context = DefaultUserLocaleTenantConfigurationEditorModel.createValidationContext({ + validationErrorModel: this.validationErrorModel, + rootPath + }); + } + + const form: UntypedFormGroup = this.formBuilder.group({ + timezone: [{ value: this.timezone, disabled: disabled }, context.getValidation('timezone').validators], + language: [{ value: this.language, disabled: disabled }, context.getValidation('language').validators], + culture: [{ value: this.culture, disabled: disabled }, context.getValidation('culture').validators], + }); + + return form; + } + + + static createValidationContext(params: { + rootPath?: string, + validationErrorModel: ValidationErrorModel + }): ValidationContext { + const { rootPath = '', validationErrorModel } = params; + + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'timezone', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}timezone`)] }); + baseValidationArray.push({ key: 'language', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}language`)] }); + baseValidationArray.push({ key: 'culture', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}culture`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.resolver.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.resolver.ts new file mode 100644 index 000000000..d4db63ff6 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.resolver.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type'; +import { DefaultUserLocaleTenantConfiguration, TenantConfiguration } from '@app/core/model/tenant-configuaration/tenant-configuration'; +import { TenantConfigurationService } from '@app/core/services/tenant-configuration/tenant-configuration.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { BaseEditorResolver } from '@common/base/base-editor.resolver'; +import { takeUntil, tap } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; + +@Injectable() +export class DefaultUserLocaleEditorResolver extends BaseEditorResolver { + + constructor(private tenantConfigurationService: TenantConfigurationService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + ...BaseEditorResolver.lookupFields(), + nameof(x => x.id), + nameof(x => x.type), + nameof(x => x.defaultUserLocale), + + [nameof(x => x.defaultUserLocale), nameof(x => x.culture)].join('.'), + [nameof(x => x.defaultUserLocale), nameof(x => x.language)].join('.'), + [nameof(x => x.defaultUserLocale), nameof(x => x.timezone)].join('.'), + + + nameof(x => x.createdAt), + nameof(x => x.updatedAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve() { + + const fields = [ + ...DefaultUserLocaleEditorResolver.lookupFields() + ]; + + return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.DefaultUserLocale, fields).pipe(takeUntil(this._destroyed)); + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.service.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.service.ts new file mode 100644 index 000000000..b33a22061 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/default-user-locale/default-user-locale-editor.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from "@angular/core"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; + +@Injectable() +export class DefaultUserLocaleEditorService { + private validationErrorModel: ValidationErrorModel; + + public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void { + this.validationErrorModel = validationErrorModel; + } + + public getValidationErrorModel(): ValidationErrorModel { + return this.validationErrorModel; + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html new file mode 100644 index 000000000..1217fb937 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html @@ -0,0 +1,43 @@ +
+
+
+
+
+

{{'TENANT-CONFIGURATION-EDITOR.TITLE' | translate}}

+ +
+
+ + + + {{'TENANT-CONFIGURATION-EDITOR.TITLE' | translate}} + + +
+ + + + {{'TENANT-CONFIGURATION-EDITOR.DEFAULT-USER-LOCALE.TITLE' | translate}} + {{'TENANT-CONFIGURATION-EDITOR.DEFAULT-USER-LOCALE.HINT' | translate}} + + + + + + + + {{'TENANT-CONFIGURATION-EDITOR.CSS-COLORS.TITLE' | translate}} + {{'TENANT-CONFIGURATION-EDITOR.CSS-COLORS.HINT' | translate}} + + + + + + + +
+
+
+
+
+
diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.scss b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.scss new file mode 100644 index 000000000..552facec6 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.scss @@ -0,0 +1,16 @@ +.tenant-configuration-component { + padding-top: 1em; + + .configuration-container { + margin-top: 2em; + } + + .nav-list-width { + width: 15em; + } + + .headers-align .mat-expansion-panel-header-title, + .headers-align .mat-expansion-panel-header-description { + flex-basis: 0; + } +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.ts new file mode 100644 index 000000000..18fa71318 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.ts @@ -0,0 +1,22 @@ + +import { Component } from '@angular/core'; +// import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item'; +import { BaseComponent } from '@common/base/base.component'; + + +@Component({ + selector: 'app-tenant-configuration-editor-component', + templateUrl: 'tenant-configuration-editor.component.html', + styleUrls: ['./tenant-configuration-editor.component.scss'], +}) +export class TenantConfigurationEditorComponent extends BaseComponent { + + constructor( + ) { + super(); + } + + ngOnInit() { + } + +} diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts new file mode 100644 index 000000000..9bcddb6a6 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts @@ -0,0 +1,41 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { NgModule } from "@angular/core"; +import { AutoCompleteModule } from "@app/library/auto-complete/auto-complete.module"; +import { CommonFormattingModule } from '@common/formatting/common-formatting.module'; +import { CommonFormsModule } from '@common/forms/common-forms.module'; +import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module'; +import { HybridListingModule } from "@common/modules/hybrid-listing/hybrid-listing.module"; +import { TextFilterModule } from "@common/modules/text-filter/text-filter.module"; +import { UserSettingsModule } from "@common/modules/user-settings/user-settings.module"; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { NgxDropzoneModule } from "ngx-dropzone"; +import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module'; +import { TenantConfigurationRoutingModule } from './tenant-configuration.routing'; +import { TenantConfigurationEditorComponent } from './editor/tenant-configuration-editor.component'; +import { CssColorsEditorComponent } from './editor/css-colors/css-colors-editor.component'; +import { DefaultUserLocaleEditorComponent } from './editor/default-user-locale/default-user-locale-editor.component'; +import { FormattingModule } from '@app/core/formatting.module'; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule, + ConfirmationDialogModule, + TenantConfigurationRoutingModule, + NgxDropzoneModule, + DragDropModule, + FormattingModule, + AutoCompleteModule, + HybridListingModule, + TextFilterModule, + UserSettingsModule, + CommonFormattingModule, + RichTextEditorModule + ], + declarations: [ + TenantConfigurationEditorComponent, + CssColorsEditorComponent, + DefaultUserLocaleEditorComponent + ] +}) +export class TenantConfigurationModule { } diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.routing.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.routing.ts new file mode 100644 index 000000000..15c747b24 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.routing.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from '@app/core/auth-guard.service'; +import { TenantConfigurationEditorComponent } from './editor/tenant-configuration-editor.component'; + +const routes: Routes = [ + { + path: '', + component: TenantConfigurationEditorComponent, + canActivate: [AuthGuard] + }, + + { path: '**', loadChildren: () => import('@common/modules/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule) }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [] +}) +export class TenantConfigurationRoutingModule { } diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.html b/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.html deleted file mode 100644 index c1cdb2410..000000000 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.html +++ /dev/null @@ -1,83 +0,0 @@ -

- {{label}} - -

-
-
-
-
- {{'TENANT-EDITOR.FIELDS.SOURCE' | translate}} {{sourceIndex + 1}} -
-
- -
-
-
-
- - {{'TENANT-EDITOR.FIELDS.URL' | translate}} - - {{source.get('url').getError('backendError').message}} - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'TENANT-EDITOR.FIELDS.ISSUER-URL' | translate}} - - {{source.get('issuerUrl').getError('backendError').message}} - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'TENANT-EDITOR.FIELDS.CLIENT-ID' | translate}} - - {{source.get('clientId').getError('backendError').message}} - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'TENANT-EDITOR.FIELDS.CLIENT-SECRET' | translate}} - - {{source.get('clientSecret').getError('backendError').message}} - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'TENANT-EDITOR.FIELDS.SCOPE' | translate}} - - {{source.get('scope').getError('backendError').message}} - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
- - {{'TENANT-EDITOR.FIELDS.CODES' | translate}} - - - {{code}} - - - - - {{source.get('codes').getError('backendError').message}} - {{'GENERAL.VALIDATION.REQUIRED' | translate}} - -
-
-
-
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.scss b/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.scss deleted file mode 100644 index 186cc7dc6..000000000 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.action-btn { - border-radius: 30px; - background-color: var(--secondary-color); - border: 1px solid transparent; - padding-left: 2em; - padding-right: 2em; - box-shadow: 0px 3px 6px #1E202029; - - transition-property: background-color, color; - transition-duration: 200ms; - transition-delay: 50ms; - transition-timing-function: ease-in-out; - &:disabled{ - background-color: #CBCBCB; - color: #FFF; - border: 0px; - } -} \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.ts b/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.ts deleted file mode 100644 index c5ce43c93..000000000 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/source/tenant-source.component.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { UntypedFormArray } from '@angular/forms'; -import { MatChipEditedEvent, MatChipInputEvent } from '@angular/material/chips'; -import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; -import { BaseComponent } from '@common/base/base.component'; -import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; -import { TenantDepositConfigEditorModel, TenantEditorModel, TenantSourceEditorModel } from '../tenant-editor.model'; - -@Component({ - selector: 'app-tenant-source-component', - templateUrl: 'tenant-source.component.html', - styleUrls: ['./tenant-source.component.scss'] -}) -export class TenantSourceComponent extends BaseComponent implements OnInit { - - @Input() form; - @Input() validationErrorModel: ValidationErrorModel = null; - @Input() validationRootPath: string = null; - @Input() codes: Map; - @Input() label: string = null; - - constructor( - public enumUtils: EnumUtils, - ) { super(); } - - ngOnInit() { - } - - // - // source - // - addSource(): void { - const formArray = this.form.get('sources') as UntypedFormArray; - this.codes.set(formArray.length, []); - const source: TenantSourceEditorModel = new TenantSourceEditorModel(this.validationErrorModel); - formArray.push(source.buildForm({ rootPath: this.validationRootPath + 'sources[' + formArray.length + '].' })); - } - - removeSource(sourceIndex: number): void { - this.codes.delete((this.form.get('sources') as UntypedFormArray).length); - (this.form.get('sources') as UntypedFormArray).removeAt(sourceIndex); - - // Reapply validators - TenantDepositConfigEditorModel.reapplySourcesFieldsValidators( - { - formArray: this.form.get('sources') as UntypedFormArray, - validationErrorModel: this.validationErrorModel, - rootPath: this.validationRootPath - } - ) - this.form.get('sources').markAsDirty(); - } - - // source codes - - addCode(event: MatChipInputEvent, key: number): void { - const value = (event.value || '').trim(); - - if (value){ - const values = this.codes.get(key); - values.push(value); - this.codes.set(key, values); - } - event.chipInput!.clear(); - } - - removeCode(code: string, key: number): void { - const values = this.codes.get(key); - if (values){ - const index = values.indexOf(code); - - if (index >= 0) { - values.splice(index, 1); - this.codes.set(key, values); - } - } - } - - editCode(code: string, event: MatChipEditedEvent, key: number) { - const values = this.codes.get(key); - if (values){ - const value = event.value.trim(); - - // Remove code if it no longer has a value - if (!value) { - this.removeCode(code, key); - return; - } - - const index = values.indexOf(code); - if (index >= 0) { - values[index] = value; - this.codes.set(key, values); - } - } - } - - -} diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.html b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.html index c3fc0a1d9..d1c6cccc6 100644 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.html +++ b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.html @@ -57,25 +57,6 @@ - -
- - -
-
- - -
diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.ts b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.ts index 8af96b8c1..9d1496a7c 100644 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.ts +++ b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.component.ts @@ -108,10 +108,6 @@ export class TenantEditorComponent extends BaseEditor try { this.editorModel = data ? new TenantEditorModel().fromModel(data) : new TenantEditorModel(); - if(data && data.config){ - if(data.config.deposit && data.config.deposit.sources) data.config.deposit.sources.forEach((source, index) => this.depositCodes.set(index, source.codes)); - if(data.config.fileTransformers && data.config.fileTransformers.sources) data.config.fileTransformers.sources.forEach((source, index) => this.fileTransformersCodes.set(index, source.codes)); - } this.isDeleted = data ? data.isActive === IsActive.Inactive : false; this.buildForm(); } catch (error) { diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts index 16b9a4cbd..9472b4749 100644 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts +++ b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts @@ -1,5 +1,5 @@ -import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; -import { Tenant, TenantConfig, TenantConfigPersist, TenantDepositConfig, TenantDepositConfigPersist, TenantFileTransformersConfig, TenantFileTransformersConfigPersist, TenantPersist, TenantSource, TenantSourcePersist } from "@app/core/model/tenant/tenant"; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; +import { Tenant, TenantPersist } from "@app/core/model/tenant/tenant"; import { BaseEditorModel } from "@common/base/base-form-editor-model"; import { BackendErrorValidator } from "@common/forms/validation/custom-validator"; import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; @@ -9,7 +9,6 @@ export class TenantEditorModel extends BaseEditorModel implements TenantPersist name: string; code: string; description: string; - config: TenantConfigEditorModel = new TenantConfigEditorModel(); permissions: string[]; public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); @@ -23,7 +22,6 @@ export class TenantEditorModel extends BaseEditorModel implements TenantPersist this.name = item.name; this.code = item.code; this.description = item.description; - if (item.config) this.config = new TenantConfigEditorModel(this.validationErrorModel).fromModel(item.config); } return this; } @@ -36,9 +34,6 @@ export class TenantEditorModel extends BaseEditorModel implements TenantPersist name: [{ value: this.name, disabled: disabled }, context.getValidation('name').validators], code: [{ value: this.code, disabled: disabled }, context.getValidation('code').validators], description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], - config: this.config.buildForm({ - rootPath: `config.`, - }), hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators] }); } @@ -55,301 +50,4 @@ export class TenantEditorModel extends BaseEditorModel implements TenantPersist baseContext.validation = baseValidationArray; return baseContext; } - - static reApplyDepositSourcesValidators(params: { - formGroup: UntypedFormGroup, - validationErrorModel: ValidationErrorModel, - }): void { - - const { formGroup, validationErrorModel } = params; - const control = formGroup?.get('config').get('deposit'); - TenantDepositConfigEditorModel.reapplySourcesFieldsValidators({ - formArray: control.get('sources') as UntypedFormArray, - rootPath: `config.deposit.`, - validationErrorModel: validationErrorModel - }); - } - - static reApplyFileTransformerSourcesValidators(params: { - formGroup: UntypedFormGroup, - validationErrorModel: ValidationErrorModel, - }): void { - - const { formGroup, validationErrorModel } = params; - const control = formGroup?.get('config').get('fileTransformers'); - TenantDepositConfigEditorModel.reapplySourcesFieldsValidators({ - formArray: control.get('sources') as UntypedFormArray, - rootPath: `config.fileTransformers.`, - validationErrorModel: validationErrorModel - }); - } -} - -export class TenantConfigEditorModel implements TenantConfigPersist { - deposit: TenantDepositConfigEditorModel = new TenantDepositConfigEditorModel(); - fileTransformers: TenantFileTransformersConfigEditorModel = new TenantFileTransformersConfigEditorModel(); - - protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); - - constructor( - public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() - ) { } - - public fromModel(item: TenantConfig): TenantConfigEditorModel { - if (item) { - if (item.deposit) this.deposit = new TenantDepositConfigEditorModel(this.validationErrorModel).fromModel(item.deposit); - if (item.fileTransformers) this.fileTransformers = new TenantFileTransformersConfigEditorModel(this.validationErrorModel).fromModel(item.fileTransformers); - } - return this; - } - - buildForm(params?: { - context?: ValidationContext, - disabled?: boolean, - rootPath?: string - }): UntypedFormGroup { - let { context = null, disabled = false, rootPath } = params ?? {} - if (context == null) { - context = TenantConfigEditorModel.createValidationContext({ - validationErrorModel: this.validationErrorModel, - rootPath - }); - } - - return this.formBuilder.group({ - deposit: this.deposit.buildForm({ - rootPath: `${rootPath}deposit.` - }), - fileTransformers: this.fileTransformers.buildForm({ - rootPath: `${rootPath}fileTransformers.` - }), - }); - } - - static createValidationContext(params: { - rootPath?: string, - validationErrorModel: ValidationErrorModel - }): ValidationContext { - const { rootPath = '', validationErrorModel } = params; - - const baseContext: ValidationContext = new ValidationContext(); - const baseValidationArray: Validation[] = new Array(); - - baseContext.validation = baseValidationArray; - return baseContext; - } -} - -export class TenantDepositConfigEditorModel implements TenantDepositConfigPersist { - sources: TenantSourceEditorModel[]= []; - - protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); - - constructor( - public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() - ) { } - - public fromModel(item: TenantDepositConfig): TenantDepositConfigEditorModel { - if (item) { - if (item.sources) { item.sources.map(x => this.sources.push(new TenantSourceEditorModel(this.validationErrorModel).fromModel(x))); } - } - return this; - } - - buildForm(params?: { - context?: ValidationContext, - disabled?: boolean, - rootPath?: string - }): UntypedFormGroup { - let { context = null, disabled = false, rootPath } = params ?? {} - if (context == null) { - context = TenantDepositConfigEditorModel.createValidationContext({ - validationErrorModel: this.validationErrorModel, - rootPath - }); - } - - return this.formBuilder.group({ - sources: this.formBuilder.array( - (this.sources ?? []).map( - (item, index) => item.buildForm({ - rootPath: `${rootPath}sources[${index}].` - }) - ), context.getValidation('sources') - ), - }); - } - - static createValidationContext(params: { - rootPath?: string, - validationErrorModel: ValidationErrorModel - }): ValidationContext { - const { rootPath = '', validationErrorModel } = params; - - const baseContext: ValidationContext = new ValidationContext(); - const baseValidationArray: Validation[] = new Array(); - baseValidationArray.push({ key: 'sources', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sources`)] }); - - baseContext.validation = baseValidationArray; - return baseContext; - } - - static reapplySourcesFieldsValidators(params: { - formArray: UntypedFormArray, - validationErrorModel: ValidationErrorModel, - rootPath: string - }): void { - const { validationErrorModel, rootPath, formArray } = params; - formArray?.controls?.forEach( - (control, index) => TenantSourceEditorModel.reapplyValidators({ - formGroup: control as UntypedFormGroup, - rootPath: `${rootPath}sources[${index}].`, - validationErrorModel: validationErrorModel - }) - ); - } -} - -export class TenantFileTransformersConfigEditorModel implements TenantFileTransformersConfigPersist { - sources: TenantSourceEditorModel[]= []; - - protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); - - constructor( - public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() - ) { } - - public fromModel(item: TenantFileTransformersConfig): TenantFileTransformersConfigEditorModel { - if (item) { - if (item.sources) { item.sources.map(x => this.sources.push(new TenantSourceEditorModel(this.validationErrorModel).fromModel(x))); } - } - return this; - } - - buildForm(params?: { - context?: ValidationContext, - disabled?: boolean, - rootPath?: string - }): UntypedFormGroup { - let { context = null, disabled = false, rootPath } = params ?? {} - if (context == null) { - context = TenantFileTransformersConfigEditorModel.createValidationContext({ - validationErrorModel: this.validationErrorModel, - rootPath - }); - } - - return this.formBuilder.group({ - sources: this.formBuilder.array( - (this.sources ?? []).map( - (item, index) => item.buildForm({ - rootPath: `${rootPath}sources[${index}].` - }) - ), context.getValidation('sources') - ), - }); - } - - static createValidationContext(params: { - rootPath?: string, - validationErrorModel: ValidationErrorModel - }): ValidationContext { - const { rootPath = '', validationErrorModel } = params; - - const baseContext: ValidationContext = new ValidationContext(); - const baseValidationArray: Validation[] = new Array(); - baseValidationArray.push({ key: 'sources', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sources`)] }); - - baseContext.validation = baseValidationArray; - return baseContext; - } -} - -export class TenantSourceEditorModel implements TenantSourcePersist { - url: string; - codes: string[]= []; - issuerUrl: string; - clientId: string; - clientSecret: string; - scope: string; - - protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); - - constructor( - public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() - ) { } - - public fromModel(item: TenantSource): TenantSourceEditorModel { - if (item) { - this.url = item.url; - //if (item.codes) { item.codes.map(x => this.codes.push(new SourceCodeEditorModel().fromModel(x))); } - this.codes = item.codes; - this.issuerUrl = item.issuerUrl; - this.clientId = item.clientId; - this.clientSecret = item.clientSecret; - this.scope = item.scope; - } - return this; - } - - buildForm(params?: { - context?: ValidationContext, - disabled?: boolean, - rootPath?: string - }): UntypedFormGroup { - let { context = null, disabled = false, rootPath } = params ?? {} - if (context == null) { - context = TenantSourceEditorModel.createValidationContext({ - validationErrorModel: this.validationErrorModel, - rootPath - }); - } - - return this.formBuilder.group({ - url: [{ value: this.url, disabled: disabled }, context.getValidation('url').validators], - issuerUrl: [{ value: this.issuerUrl, disabled: disabled }, context.getValidation('issuerUrl').validators], - clientId: [{ value: this.clientId, disabled: disabled }, context.getValidation('clientId').validators], - clientSecret: [{ value: this.clientSecret, disabled: disabled }, context.getValidation('clientSecret').validators], - scope: [{ value: this.scope, disabled: disabled }, context.getValidation('scope').validators], - codes: [{ value: this.codes, disabled: disabled }, context.getValidation('codes').validators], - }); - } - - static createValidationContext(params: { - rootPath?: string, - validationErrorModel: ValidationErrorModel - }): ValidationContext { - const { rootPath = '', validationErrorModel } = params; - - const baseContext: ValidationContext = new ValidationContext(); - const baseValidationArray: Validation[] = new Array(); - baseValidationArray.push({ key: 'url', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}url`)] }); - baseValidationArray.push({ key: 'codes', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}codes`)] }); - baseValidationArray.push({ key: 'issuerUrl', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}issuerUrl`)] }); - baseValidationArray.push({ key: 'clientId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}clientId`)] }); - baseValidationArray.push({ key: 'clientSecret', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}clientSecret`)] }); - baseValidationArray.push({ key: 'scope', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}scope`)] }); - - baseContext.validation = baseValidationArray; - return baseContext; - } - - static reapplyValidators(params: { - formGroup: UntypedFormGroup, - validationErrorModel: ValidationErrorModel, - rootPath: string - }): void { - - const { formGroup, rootPath, validationErrorModel } = params; - const context = TenantSourceEditorModel.createValidationContext({ - rootPath, - validationErrorModel - }); - - ['url', 'codes', 'issuerUrl', 'clientId', 'clientSecret', 'scope'].forEach(keyField => { - const control = formGroup?.get(keyField); - control?.clearValidators(); - control?.addValidators(context.getValidation(keyField).validators); - }) - } } diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.resolver.ts b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.resolver.ts index b2fff3ecc..34265d276 100644 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.resolver.ts +++ b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.resolver.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { SourceCode, Tenant, TenantConfig, TenantDepositConfig, TenantSource } from '@app/core/model/tenant/tenant'; +import { Tenant } from '@app/core/model/tenant/tenant'; import { TenantService } from '@app/core/services/tenant/tenant.service'; import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; import { BaseEditorResolver } from '@common/base/base-editor.resolver'; @@ -23,20 +23,6 @@ export class TenantEditorResolver extends BaseEditorResolver { nameof(x => x.code), nameof(x => x.description), - [nameof(x => x.config), nameof(x => x.deposit), nameof(x => x.sources), nameof(x => x.url)].join('.'), - [nameof(x => x.config), nameof(x => x.deposit), nameof(x => x.sources), nameof(x => x.issuerUrl)].join('.'), - [nameof(x => x.config), nameof(x => x.deposit), nameof(x => x.sources), nameof(x => x.clientId)].join('.'), - [nameof(x => x.config), nameof(x => x.deposit), nameof(x => x.sources), nameof(x => x.clientSecret)].join('.'), - [nameof(x => x.config), nameof(x => x.deposit), nameof(x => x.sources), nameof(x => x.scope)].join('.'), - [nameof(x => x.config), nameof(x => x.deposit), nameof(x => x.sources), nameof(x => x.codes)].join('.'), - - [nameof(x => x.config), nameof(x => x.fileTransformers), nameof(x => x.sources), nameof(x => x.url)].join('.'), - [nameof(x => x.config), nameof(x => x.fileTransformers), nameof(x => x.sources), nameof(x => x.issuerUrl)].join('.'), - [nameof(x => x.config), nameof(x => x.fileTransformers), nameof(x => x.sources), nameof(x => x.clientId)].join('.'), - [nameof(x => x.config), nameof(x => x.fileTransformers), nameof(x => x.sources), nameof(x => x.clientSecret)].join('.'), - [nameof(x => x.config), nameof(x => x.fileTransformers), nameof(x => x.sources), nameof(x => x.scope)].join('.'), - [nameof(x => x.config), nameof(x => x.fileTransformers), nameof(x => x.sources), nameof(x => x.codes)].join('.'), - nameof(x => x.createdAt), nameof(x => x.updatedAt), diff --git a/dmp-frontend/src/app/ui/admin/tenant/tenant.module.ts b/dmp-frontend/src/app/ui/admin/tenant/tenant.module.ts index 766f819f0..79e6dceb0 100644 --- a/dmp-frontend/src/app/ui/admin/tenant/tenant.module.ts +++ b/dmp-frontend/src/app/ui/admin/tenant/tenant.module.ts @@ -14,7 +14,6 @@ import { TenantEditorComponent } from './editor/tenant-editor.component'; import { TenantListingComponent } from './listing/tenant-listing.component'; import { TenantListingFiltersComponent } from "./listing/filters/tenant-listing-filters.component"; import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module'; -import { TenantSourceComponent } from './editor/source/tenant-source.component'; @NgModule({ imports: [ @@ -34,8 +33,7 @@ import { TenantSourceComponent } from './editor/source/tenant-source.component'; declarations: [ TenantEditorComponent, TenantListingComponent, - TenantListingFiltersComponent, - TenantSourceComponent + TenantListingFiltersComponent ] }) export class TenantModule { } diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts index caf1732eb..86362a1c4 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts @@ -116,6 +116,7 @@ export class SidebarComponent implements OnInit { if (this.authentication.hasPermission(AppPermission.ViewReferenceTypePage)) this.adminItems.routes.push({ path: '/reference-type', title: 'SIDE-BAR.REFERENCE-TYPES', icon: 'add_link' }); if (this.authentication.hasPermission(AppPermission.ViewPrefillingSourcePage)) this.adminItems.routes.push({ path: '/prefilling-sources', title: 'SIDE-BAR.PREFILLING-SOURCES', icon: 'quick_reference_all' }); if (this.authentication.hasPermission(AppPermission.ViewTenantPage)) this.adminItems.routes.push({ path: '/tenants', title: 'SIDE-BAR.TENANTS', icon: 'tenancy' }); + if (this.authentication.hasPermission(AppPermission.ViewTenantConfigurationPage)) this.adminItems.routes.push({ path: '/tenant-configuration', title: 'SIDE-BAR.TENANT-CONFIGURATION', icon: 'settings' }); if (this.authentication.hasPermission(AppPermission.ViewUserPage)) this.adminItems.routes.push({ path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' }); if (this.authentication.hasPermission(AppPermission.ViewLanguagePage)) this.adminItems.routes.push({ path: '/languages', title: 'SIDE-BAR.LANGUAGES', icon: 'language' }); if (this.authentication.hasPermission(AppPermission.ViewSupportiveMaterialPage)) this.adminItems.routes.push({ path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'help_center' });