tenant configuration changes

This commit is contained in:
Efstratios Giannopoulos 2024-04-22 18:36:35 +03:00
parent 73701b206c
commit db90097c16
25 changed files with 1789 additions and 11 deletions

View File

@ -57,11 +57,11 @@ public class DepositTenantConfigurationPersist {
this.spec() this.spec()
.must(() -> !this.isListNullOrEmpty(item.getSources())) .must(() -> !this.isListNullOrEmpty(item.getSources()))
.failOn(DepositTenantConfigurationPersist._sources).failWith(messageSource.getMessage("Validation_Required", new Object[]{DepositTenantConfigurationPersist._sources}, LocaleContextHolder.getLocale())), .failOn(DepositTenantConfigurationPersist._sources).failWith(messageSource.getMessage("Validation_Required", new Object[]{DepositTenantConfigurationPersist._sources}, LocaleContextHolder.getLocale())),
this.refSpec() this.navSpec()
.iff(() -> !this.isListNullOrEmpty(item.getSources())) .iff(() -> !this.isListNullOrEmpty(item.getSources()))
.on(DepositTenantConfigurationPersist._sources) .on(DepositTenantConfigurationPersist._sources)
.over(item.getSources()) .over(item.getSources())
.using(() -> this.validatorFactory.validator(DepositSourcePersist.DepositSourcePersistValidator.class)) .using((itm) -> this.validatorFactory.validator(DepositSourcePersist.DepositSourcePersistValidator.class))
); );
} }
} }

View File

@ -56,11 +56,11 @@ public class FileTransformerTenantConfigurationPersist {
this.spec() this.spec()
.must(() -> !this.isListNullOrEmpty(item.getSources())) .must(() -> !this.isListNullOrEmpty(item.getSources()))
.failOn(FileTransformerTenantConfigurationPersist._sources).failWith(messageSource.getMessage("Validation_Required", new Object[]{FileTransformerTenantConfigurationPersist._sources}, LocaleContextHolder.getLocale())), .failOn(FileTransformerTenantConfigurationPersist._sources).failWith(messageSource.getMessage("Validation_Required", new Object[]{FileTransformerTenantConfigurationPersist._sources}, LocaleContextHolder.getLocale())),
this.refSpec() this.navSpec()
.iff(() -> !this.isListNullOrEmpty(item.getSources())) .iff(() -> !this.isListNullOrEmpty(item.getSources()))
.on(FileTransformerTenantConfigurationPersist._sources) .on(FileTransformerTenantConfigurationPersist._sources)
.over(item.getSources()) .over(item.getSources())
.using(() -> this.validatorFactory.validator(FileTransformerSourcePersist.FileTransformerSourcePersistValidator.class)) .using((itm) -> this.validatorFactory.validator(FileTransformerSourcePersist.FileTransformerSourcePersistValidator.class))
); );
} }
} }

View File

@ -11,6 +11,7 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -48,7 +49,7 @@ public class LogoTenantConfigurationPersist {
protected List<Specification> specifications(LogoTenantConfigurationPersist item) { protected List<Specification> specifications(LogoTenantConfigurationPersist item) {
return Arrays.asList( return Arrays.asList(
this.spec() this.spec()
.must(() -> !this.isValidGuid(item.getStorageFileId())) .must(() -> this.isValidGuid(item.getStorageFileId()))
.failOn(LogoTenantConfigurationPersist._storageFileId).failWith(messageSource.getMessage("Validation_Required", new Object[]{LogoTenantConfigurationPersist._storageFileId}, LocaleContextHolder.getLocale())) .failOn(LogoTenantConfigurationPersist._storageFileId).failWith(messageSource.getMessage("Validation_Required", new Object[]{LogoTenantConfigurationPersist._storageFileId}, LocaleContextHolder.getLocale()))
); );
} }

View File

@ -172,7 +172,7 @@ export class CssColorsEditorComponent extends BasePendingChangesComponent implem
const dialogRef = this.dialog.open(ConfirmationDialogComponent, { const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px', maxWidth: '300px',
data: { data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.RESET-TO-DEFAULT'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
} }
@ -193,7 +193,7 @@ export class CssColorsEditorComponent extends BasePendingChangesComponent implem
console.log("Success Delete:", data); console.log("Success Delete:", data);
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-RESET'), SnackBarNotificationLevel.Success);
this.prepareForm(null) this.prepareForm(null)
} }

View File

@ -0,0 +1,103 @@
<div *ngIf="formGroup" class="container-fluid deposit">
<div class="row">
<div class="col-12">
<h3>
{{label}}
<button mat-button class="action-btn" type="button" (click)="addSource()" [disabled]="formGroup.disabled">{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.ADD-SOURCE' | translate}}</button>
</h3>
<div *ngFor="let source of formGroup.get('depositPlugins').get('sources').controls; let sourceIndex=index;" class="row mb-3">
<div class="col-12">
<div class="row mb-3 d-flex align-items-center">
<div class="col-auto d-flex">
<mat-card-title>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.DEPOSIT-PLUGINS' | translate}} {{sourceIndex + 1}}</mat-card-title>
</div>
<div class="col-auto d-flex">
<button mat-icon-button class="action-list-icon" matTooltip="{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.REMOVE-SOURCE' | translate}}" (click)="removeSource(sourceIndex)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<div class="row" >
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.REPOSITORY-ID' | translate}}</mat-label>
<input matInput type="text" name="repositoryId" [formControl]="source.get('repositoryId')" required>
<mat-error *ngIf="source.get('repositoryId').hasError('backendError')">{{source.get('repositoryId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('repositoryId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.URL' | translate}}</mat-label>
<input matInput type="text" name="url" [formControl]="source.get('url')" required>
<mat-error *ngIf="source.get('url').hasError('backendError')">{{source.get('url').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('url').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.ISSUER-URL' | translate}}</mat-label>
<input matInput type="text" name="issuerUrl" [formControl]="source.get('issuerUrl')" required>
<mat-error *ngIf="source.get('issuerUrl').hasError('backendError')">{{source.get('issuerUrl').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('issuerUrl').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.CLIENT-ID' | translate}}</mat-label>
<input matInput type="text" name="clientId" [formControl]="source.get('clientId')" required>
<mat-error *ngIf="source.get('clientId').hasError('backendError')">{{source.get('clientId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('clientId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.CLIENT-SECRET' | translate}}</mat-label>
<input matInput type="text" name="clientSecret" [formControl]="source.get('clientSecret')" required>
<mat-error *ngIf="source.get('clientSecret').hasError('backendError')">{{source.get('clientSecret').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('clientSecret').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.SCOPE' | translate}}</mat-label>
<input matInput type="text" name="scope" [formControl]="source.get('scope')" required>
<mat-error *ngIf="source.get('scope').hasError('backendError')">{{source.get('scope').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('scope').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.PDF-TRANSFORMER-ID' | translate}}</mat-label>
<input matInput type="text" name="pdfTransformerId" [formControl]="source.get('pdfTransformerId')" required>
<mat-error *ngIf="source.get('pdfTransformerId').hasError('backendError')">{{source.get('pdfTransformerId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('pdfTransformerId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.RDA-TRANSFORMER-ID' | translate}}</mat-label>
<input matInput type="text" name="rdaTransformerId" [formControl]="source.get('rdaTransformerId')" required>
<mat-error *ngIf="source.get('rdaTransformerId').hasError('backendError')">{{source.get('rdaTransformerId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('rdaTransformerId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="row actions-row">
<div class="col"></div>
<div class="col-auto" *ngIf="editorModel.id"><button mat-raised-button color="primary" (click)="delete()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.RESET-TO-DEFAULT' | translate}}
</button>
</div>
<div class="col-auto"><button mat-raised-button color="primary" (click)="formSubmit()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.SAVE' | translate}}
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
.deposit {
.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;
}
}
}

View File

@ -0,0 +1,227 @@
import { Component, OnInit } from '@angular/core';
import { UntypedFormArray, 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 { DepositSourceEditorModel, DepositTenantConfigurationEditorModel, TenantConfigurationEditorModel } from './deposit-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 { DepositEditorService } from './deposit-editor.service';
import { DepositEditorResolver } from './deposit-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-deposit-editor',
templateUrl: 'deposit-editor.component.html',
styleUrls: ['./deposit-editor.component.scss'],
providers: [DepositEditorService]
})
export class DepositEditorComponent 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 depositEditorService: DepositEditorService,
private matomoService: MatomoService
) {
super();
}
canDeactivate(): boolean | Observable<boolean> {
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.DepositPlugins, DepositEditorResolver.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.depositEditorService.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);
}
addSource(): void {
const formArray = this.formGroup.get('depositPlugins').get('sources') as UntypedFormArray;
const source: DepositSourceEditorModel = new DepositSourceEditorModel(this.editorModel.validationErrorModel);
formArray.push(source.buildForm({ rootPath: 'depositPlugins.sources[' + formArray.length + '].' }));
}
removeSource(sourceIndex: number): void {
(this.formGroup.get('depositPlugins').get('sources') as UntypedFormArray).removeAt(sourceIndex);
// Reapply validators
DepositTenantConfigurationEditorModel.reapplySourcesFieldsValidators(
{
formArray: this.formGroup.get('depositPlugins').get('sources') as UntypedFormArray,
validationErrorModel: this.editorModel.validationErrorModel,
rootPath: 'depositPlugins.'
}
)
this.formGroup.get('depositPlugins').get('sources').markAsDirty();
}
}

View File

@ -0,0 +1,235 @@
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { TenantConfigurationType } from "@app/core/common/enum/tenant-configuration-type";
import { DepositSource, DepositSourcePersist, DepositTenantConfiguration, DepositTenantConfigurationPersist, 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;
depositPlugins: DepositTenantConfigurationEditorModel = new DepositTenantConfigurationEditorModel(this.validationErrorModel);
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor() { super(); this.type = TenantConfigurationType.DepositPlugins; }
public fromModel(item: TenantConfiguration): TenantConfigurationEditorModel {
if (item) {
super.fromModel(item);
this.type = item.type;
if (item.depositPlugins) this.depositPlugins = new DepositTenantConfigurationEditorModel(this.validationErrorModel).fromModel(item.depositPlugins);
} else {
this.type = TenantConfigurationType.DepositPlugins;
}
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],
depositPlugins: this.depositPlugins.buildForm({
rootPath: `depositPlugins.`,
}),
});
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
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;
}
static reApplyDepositSourcesValidators(params: {
formGroup: UntypedFormGroup,
validationErrorModel: ValidationErrorModel,
}): void {
const { formGroup, validationErrorModel } = params;
const control = formGroup?.get('config').get('deposit');
DepositTenantConfigurationEditorModel.reapplySourcesFieldsValidators({
formArray: control.get('sources') as UntypedFormArray,
rootPath: `depositPlugins.`,
validationErrorModel: validationErrorModel
});
}
}
export class DepositTenantConfigurationEditorModel implements DepositTenantConfigurationPersist {
sources: DepositSourceEditorModel[] = [];
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: DepositTenantConfiguration): DepositTenantConfigurationEditorModel {
if (item) {
if (item.sources) { item.sources.map(x => this.sources.push(new DepositSourceEditorModel(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 = DepositTenantConfigurationEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
const form: UntypedFormGroup = this.formBuilder.group({
sources: this.formBuilder.array(
(this.sources ?? []).map(
(item, index) => item.buildForm({
rootPath: `${rootPath}sources[${index}].`
})
), context.getValidation('sources')
),
});
return form;
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
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) => DepositSourceEditorModel.reapplyValidators({
formGroup: control as UntypedFormGroup,
rootPath: `${rootPath}sources[${index}].`,
validationErrorModel: validationErrorModel
})
);
}
}
export class DepositSourceEditorModel implements DepositSourcePersist {
repositoryId: string;
url: string;
issuerUrl: string;
clientId: string;
clientSecret: string;
scope: string;
pdfTransformerId: string;
rdaTransformerId: string;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: DepositSource): DepositSourceEditorModel {
if (item) {
this.repositoryId = item.repositoryId;
this.url = item.url;
this.issuerUrl = item.issuerUrl;
this.clientId = item.clientId;
this.clientSecret = item.clientSecret;
this.scope = item.scope;
this.pdfTransformerId = item.pdfTransformerId;
this.rdaTransformerId = item.rdaTransformerId;
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DepositSourceEditorModel.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],
repositoryId: [{ value: this.repositoryId, disabled: disabled }, context.getValidation('repositoryId').validators],
pdfTransformerId: [{ value: this.pdfTransformerId, disabled: disabled }, context.getValidation('pdfTransformerId').validators],
rdaTransformerId: [{ value: this.rdaTransformerId, disabled: disabled }, context.getValidation('rdaTransformerId').validators],
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'url', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}url`)] });
baseValidationArray.push({ key: 'repositoryId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}repositoryId`)] });
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`)] });
baseValidationArray.push({ key: 'pdfTransformerId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}pdfTransformerId`)] });
baseValidationArray.push({ key: 'rdaTransformerId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}rdaTransformerId`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
static reapplyValidators(params: {
formGroup: UntypedFormGroup,
validationErrorModel: ValidationErrorModel,
rootPath: string
}): void {
const { formGroup, rootPath, validationErrorModel } = params;
const context = DepositSourceEditorModel.createValidationContext({
rootPath,
validationErrorModel
});
['url', 'repositoryId', 'issuerUrl', 'clientId', 'clientSecret', 'scope', 'pdfTransformerId', 'pdfTransformerId'].forEach(keyField => {
const control = formGroup?.get(keyField);
control?.clearValidators();
control?.addValidators(context.getValidation(keyField).validators);
})
}
}

View File

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type';
import { DepositSource, DepositTenantConfiguration, 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 DepositEditorResolver extends BaseEditorResolver {
constructor(private tenantConfigurationService: TenantConfigurationService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<TenantConfiguration>(x => x.id),
nameof<TenantConfiguration>(x => x.type),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.clientId)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.clientSecret)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.issuerUrl)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.repositoryId)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.scope)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.url)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.pdfTransformerId)].join('.'),
[nameof<TenantConfiguration>(x => x.depositPlugins), nameof<DepositTenantConfiguration>(x => x.sources), nameof<DepositSource>(x => x.rdaTransformerId)].join('.'),
nameof<TenantConfiguration>(x => x.createdAt),
nameof<TenantConfiguration>(x => x.updatedAt),
nameof<TenantConfiguration>(x => x.hash),
nameof<TenantConfiguration>(x => x.isActive)
]
}
resolve() {
const fields = [
...DepositEditorResolver.lookupFields()
];
return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.DepositPlugins, fields).pipe(takeUntil(this._destroyed));
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
@Injectable()
export class DepositEditorService {
private validationErrorModel: ValidationErrorModel;
public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void {
this.validationErrorModel = validationErrorModel;
}
public getValidationErrorModel(): ValidationErrorModel {
return this.validationErrorModel;
}
}

View File

@ -0,0 +1,87 @@
<div *ngIf="formGroup" class="container-fluid file-transformer">
<div class="row">
<div class="col-12">
<h3>
{{label}}
<button mat-button class="action-btn" type="button" (click)="addSource()" [disabled]="formGroup.disabled">{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.ADD-SOURCE' | translate}}</button>
</h3>
<div *ngFor="let source of formGroup.get('fileTransformerPlugins').get('sources').controls; let sourceIndex=index;" class="row mb-3">
<div class="col-12">
<div class="row mb-3 d-flex align-items-center">
<div class="col-auto d-flex">
<mat-card-title>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.FILE-TRANSFORMER-PLUGINS' | translate}} {{sourceIndex + 1}}</mat-card-title>
</div>
<div class="col-auto d-flex">
<button mat-icon-button class="action-list-icon" matTooltip="{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.REMOVE-SOURCE' | translate}}" (click)="removeSource(sourceIndex)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<div class="row" >
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.TRANSFORMER-ID' | translate}}</mat-label>
<input matInput type="text" name="transformerId" [formControl]="source.get('transformerId')" required>
<mat-error *ngIf="source.get('transformerId').hasError('backendError')">{{source.get('transformerId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('transformerId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.URL' | translate}}</mat-label>
<input matInput type="text" name="url" [formControl]="source.get('url')" required>
<mat-error *ngIf="source.get('url').hasError('backendError')">{{source.get('url').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('url').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.ISSUER-URL' | translate}}</mat-label>
<input matInput type="text" name="issuerUrl" [formControl]="source.get('issuerUrl')" required>
<mat-error *ngIf="source.get('issuerUrl').hasError('backendError')">{{source.get('issuerUrl').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('issuerUrl').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.CLIENT-ID' | translate}}</mat-label>
<input matInput type="text" name="clientId" [formControl]="source.get('clientId')" required>
<mat-error *ngIf="source.get('clientId').hasError('backendError')">{{source.get('clientId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('clientId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.CLIENT-SECRET' | translate}}</mat-label>
<input matInput type="text" name="clientSecret" [formControl]="source.get('clientSecret')" required>
<mat-error *ngIf="source.get('clientSecret').hasError('backendError')">{{source.get('clientSecret').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('clientSecret').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-6">
<mat-form-field class="w-100">
<mat-label>{{'TENANT-CONFIGURATION-EDITOR.FIELDS.SCOPE' | translate}}</mat-label>
<input matInput type="text" name="scope" [formControl]="source.get('scope')" required>
<mat-error *ngIf="source.get('scope').hasError('backendError')">{{source.get('scope').getError('backendError').message}}</mat-error>
<mat-error *ngIf="source.get('scope').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="row actions-row">
<div class="col"></div>
<div class="col-auto" *ngIf="editorModel.id"><button mat-raised-button color="primary" (click)="delete()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.RESET-TO-DEFAULT' | translate}}
</button>
</div>
<div class="col-auto"><button mat-raised-button color="primary" (click)="formSubmit()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.SAVE' | translate}}
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
.file-transformer {
.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;
}
}
}

View File

@ -0,0 +1,227 @@
import { Component, OnInit } from '@angular/core';
import { UntypedFormArray, 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 { FileTransformerSourceEditorModel, FileTransformerTenantConfigurationEditorModel, TenantConfigurationEditorModel } from './file-transformer-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 { FileTransformerEditorService } from './file-transformer-editor.service';
import { FileTransformerEditorResolver } from './file-transformer-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-file-transformer-editor',
templateUrl: 'file-transformer-editor.component.html',
styleUrls: ['./file-transformer-editor.component.scss'],
providers: [FileTransformerEditorService]
})
export class FileTransformerEditorComponent 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 fileTransformerEditorService: FileTransformerEditorService,
private matomoService: MatomoService
) {
super();
}
canDeactivate(): boolean | Observable<boolean> {
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.FileTransformerPlugins, FileTransformerEditorResolver.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.fileTransformerEditorService.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);
}
addSource(): void {
const formArray = this.formGroup.get('fileTransformerPlugins').get('sources') as UntypedFormArray;
const source: FileTransformerSourceEditorModel = new FileTransformerSourceEditorModel(this.editorModel.validationErrorModel);
formArray.push(source.buildForm({ rootPath: 'fileTransformerPlugins.sources[' + formArray.length + '].' }));
}
removeSource(sourceIndex: number): void {
(this.formGroup.get('fileTransformerPlugins').get('sources') as UntypedFormArray).removeAt(sourceIndex);
// Reapply validators
FileTransformerTenantConfigurationEditorModel.reapplySourcesFieldsValidators(
{
formArray: this.formGroup.get('fileTransformerPlugins').get('sources') as UntypedFormArray,
validationErrorModel: this.editorModel.validationErrorModel,
rootPath: 'fileTransformerPlugins.'
}
)
this.formGroup.get('fileTransformerPlugins').get('sources').markAsDirty();
}
}

View File

@ -0,0 +1,229 @@
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { TenantConfigurationType } from "@app/core/common/enum/tenant-configuration-type";
import { FileTransformerSource, FileTransformerSourcePersist, FileTransformerTenantConfiguration, FileTransformerTenantConfigurationPersist, 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;
fileTransformerPlugins: FileTransformerTenantConfigurationEditorModel = new FileTransformerTenantConfigurationEditorModel(this.validationErrorModel);
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor() { super(); this.type = TenantConfigurationType.FileTransformerPlugins; }
public fromModel(item: TenantConfiguration): TenantConfigurationEditorModel {
if (item) {
super.fromModel(item);
this.type = item.type;
if (item.fileTransformerPlugins) this.fileTransformerPlugins = new FileTransformerTenantConfigurationEditorModel(this.validationErrorModel).fromModel(item.fileTransformerPlugins);
} else {
this.type = TenantConfigurationType.FileTransformerPlugins;
}
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],
fileTransformerPlugins: this.fileTransformerPlugins.buildForm({
rootPath: `fileTransformerPlugins.`,
}),
});
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
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;
}
static reApplyFileTransformerSourcesValidators(params: {
formGroup: UntypedFormGroup,
validationErrorModel: ValidationErrorModel,
}): void {
const { formGroup, validationErrorModel } = params;
const control = formGroup?.get('config').get('fileTransformer');
FileTransformerTenantConfigurationEditorModel.reapplySourcesFieldsValidators({
formArray: control.get('sources') as UntypedFormArray,
rootPath: `fileTransformerPlugins.`,
validationErrorModel: validationErrorModel
});
}
}
export class FileTransformerTenantConfigurationEditorModel implements FileTransformerTenantConfigurationPersist {
sources: FileTransformerSourceEditorModel[] = [];
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: FileTransformerTenantConfiguration): FileTransformerTenantConfigurationEditorModel {
if (item) {
if (item.sources) { item.sources.map(x => this.sources.push(new FileTransformerSourceEditorModel(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 = FileTransformerTenantConfigurationEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
const form: UntypedFormGroup = this.formBuilder.group({
sources: this.formBuilder.array(
(this.sources ?? []).map(
(item, index) => item.buildForm({
rootPath: `${rootPath}sources[${index}].`
})
), context.getValidation('sources')
),
});
return form;
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
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) => FileTransformerSourceEditorModel.reapplyValidators({
formGroup: control as UntypedFormGroup,
rootPath: `${rootPath}sources[${index}].`,
validationErrorModel: validationErrorModel
})
);
}
}
export class FileTransformerSourceEditorModel implements FileTransformerSourcePersist {
transformerId: string;
url: string;
issuerUrl: string;
clientId: string;
clientSecret: string;
scope: string;
pdfTransformerId: string;
rdaTransformerId: string;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: FileTransformerSource): FileTransformerSourceEditorModel {
if (item) {
this.transformerId = item.transformerId;
this.url = item.url;
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 = FileTransformerSourceEditorModel.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],
transformerId: [{ value: this.transformerId, disabled: disabled }, context.getValidation('transformerId').validators],
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'url', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}url`)] });
baseValidationArray.push({ key: 'transformerId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}transformerId`)] });
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 = FileTransformerSourceEditorModel.createValidationContext({
rootPath,
validationErrorModel
});
['url', 'transformerId', 'issuerUrl', 'clientId', 'clientSecret', 'scope'].forEach(keyField => {
const control = formGroup?.get(keyField);
control?.clearValidators();
control?.addValidators(context.getValidation(keyField).validators);
})
}
}

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type';
import { FileTransformerSource, FileTransformerTenantConfiguration, 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 FileTransformerEditorResolver extends BaseEditorResolver {
constructor(private tenantConfigurationService: TenantConfigurationService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<TenantConfiguration>(x => x.id),
nameof<TenantConfiguration>(x => x.type),
[nameof<TenantConfiguration>(x => x.fileTransformerPlugins), nameof<FileTransformerTenantConfiguration>(x => x.sources), nameof<FileTransformerSource>(x => x.clientId)].join('.'),
[nameof<TenantConfiguration>(x => x.fileTransformerPlugins), nameof<FileTransformerTenantConfiguration>(x => x.sources), nameof<FileTransformerSource>(x => x.clientSecret)].join('.'),
[nameof<TenantConfiguration>(x => x.fileTransformerPlugins), nameof<FileTransformerTenantConfiguration>(x => x.sources), nameof<FileTransformerSource>(x => x.issuerUrl)].join('.'),
[nameof<TenantConfiguration>(x => x.fileTransformerPlugins), nameof<FileTransformerTenantConfiguration>(x => x.sources), nameof<FileTransformerSource>(x => x.transformerId)].join('.'),
[nameof<TenantConfiguration>(x => x.fileTransformerPlugins), nameof<FileTransformerTenantConfiguration>(x => x.sources), nameof<FileTransformerSource>(x => x.scope)].join('.'),
[nameof<TenantConfiguration>(x => x.fileTransformerPlugins), nameof<FileTransformerTenantConfiguration>(x => x.sources), nameof<FileTransformerSource>(x => x.url)].join('.'),
nameof<TenantConfiguration>(x => x.createdAt),
nameof<TenantConfiguration>(x => x.updatedAt),
nameof<TenantConfiguration>(x => x.hash),
nameof<TenantConfiguration>(x => x.isActive)
]
}
resolve() {
const fields = [
...FileTransformerEditorResolver.lookupFields()
];
return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.FileTransformerPlugins, fields).pipe(takeUntil(this._destroyed));
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
@Injectable()
export class FileTransformerEditorService {
private validationErrorModel: ValidationErrorModel;
public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void {
this.validationErrorModel = validationErrorModel;
}
public getValidationErrorModel(): ValidationErrorModel {
return this.validationErrorModel;
}
}

View File

@ -0,0 +1,35 @@
<div *ngIf="formGroup" class="container-fluid logo">
<div class="row">
<div class="col-12">
<ngx-dropzone #drop class="drop-file col-12" (change)="fileChangeEvent($event, true)" [multiple]="false" [accept]="'image/*'" [disabled]="formGroup.get('logo')?.get('storageFileId').disabled">
<ngx-dropzone-preview *ngIf="formGroup.get('logo')?.get('storageFileId')?.value" class="file-preview" [removable]="true" (removed)="onRemove()" (click)="download(formGroup.get('logo')?.get('storageFileId').value)">
<ngx-dropzone-label class="file-label">{{ fileNameDisplay }}</ngx-dropzone-label>
</ngx-dropzone-preview>
</ngx-dropzone>
<div class="col-12 d-flex justify-content-center attach-btn">
<button *ngIf="!formGroup.get('logo')?.get('storageFileId')?.value" mat-button (click)="drop.showFileSelector()" type="button" class="attach-file-btn" [disabled]="formGroup.get('logo')?.get('storageFileId')?.disabled">
<mat-icon class="mr-2">upload</mat-icon>
<mat-label>{{ "TENANT-CONFIGURATION-EDITOR.ACTIONS.UPLOAD" | translate }}</mat-label>
</button>
<button *ngIf="formGroup.get('logo')?.get('storageFileId')?.value" mat-button (click)="download(formGroup.get('logo')?.get('storageFileId')?.value)" type="button" class="attach-file-btn">
<mat-icon class="mr-2">download</mat-icon>
<mat-label>{{ "TENANT-CONFIGURATION-EDITOR.ACTIONS.DOWNLOAD" | translate }}</mat-label>
</button>
</div>
</div>
<div class="col-12">
<div class="row actions-row">
<div class="col"></div>
<div class="col-auto" *ngIf="editorModel.id"><button mat-raised-button color="primary" (click)="delete()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.RESET-TO-DEFAULT' | translate}}
</button>
</div>
<div class="col-auto"><button mat-raised-button color="primary" (click)="formSubmit()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.SAVE' | translate}}
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,267 @@
import { ChangeDetectorRef, 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 './logo-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 { LogoEditorService } from './logo-editor.service';
import { LogoEditorResolver } from './logo-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';
import { StorageFileService } from '@app/core/services/storage-file/storage-file.service';
import { Guid } from '@common/types/guid';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import * as FileSaver from 'file-saver';
@Component({
selector: 'app-tenant-configuration-logo-editor',
templateUrl: 'logo-editor.component.html',
styleUrls: ['./logo-editor.component.scss'],
providers: [LogoEditorService]
})
export class LogoEditorComponent extends BasePendingChangesComponent implements OnInit {
isNew = true;
formGroup: UntypedFormGroup = null;
fileNameDisplay: string = null;
filesToUpload: FileList;
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,
private cdr: ChangeDetectorRef,
protected formService: FormService,
protected uiNotificationService: UiNotificationService,
protected httpErrorHandlingService: HttpErrorHandlingService,
protected authService: AuthService,
private logger: LoggingService,
private tenantConfigurationService: TenantConfigurationService,
private logoEditorService: LogoEditorService,
private matomoService: MatomoService,
private fileUtils: FileUtils,
private storageFileService: StorageFileService
) {
super();
}
canDeactivate(): boolean | Observable<boolean> {
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.Logo, LogoEditorResolver.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.fileNameDisplay = data?.logo?.storageFile?.fullName;
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.logoEditorService.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);
}
fileChangeEvent(fileInput: any, dropped: boolean = false) {
if (dropped) {
this.filesToUpload = fileInput.addedFiles;
} else {
this.filesToUpload = fileInput.target.files;
}
this.upload();
}
public upload() {
this.storageFileService.uploadTempFiles(this.filesToUpload[0])
.pipe(takeUntil(this._destroyed)).subscribe((response) => {
this.formGroup.get('logo')?.get('storageFileId').patchValue(response[0].id.toString());
this.fileNameDisplay = response[0].name;
this.cdr.detectChanges();
}, error => {
this.onCallbackError(error.error);
})
}
download(fileId: Guid): void {
if (fileId) {
this.storageFileService.download(fileId).pipe(takeUntil(this._destroyed))
.subscribe(response => {
const blob = new Blob([response.body]);
const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
FileSaver.saveAs(blob, filename);
});
}
}
onRemove(makeFilesNull: boolean = true) {
this.makeFilesNull()
this.cdr.detectChanges();
}
makeFilesNull() {
this.filesToUpload = null;
this.formGroup.get('logo')?.get('storageFileId').patchValue(null);
this.formGroup.updateValueAndValidity();
this.fileNameDisplay = null;
}
}

View File

@ -0,0 +1,105 @@
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { TenantConfigurationType } from "@app/core/common/enum/tenant-configuration-type";
import { LogoTenantConfiguration, LogoTenantConfigurationPersist, 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";
import { Guid } from "@common/types/guid";
export class TenantConfigurationEditorModel extends BaseEditorModel implements TenantConfigurationPersist {
type: TenantConfigurationType;
logo: LogoTenantConfigurationEditorModel = new LogoTenantConfigurationEditorModel(this.validationErrorModel);
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor() { super(); this.type = TenantConfigurationType.Logo; }
public fromModel(item: TenantConfiguration): TenantConfigurationEditorModel {
if (item) {
super.fromModel(item);
this.type = item.type;
if (item.logo) this.logo = new LogoTenantConfigurationEditorModel(this.validationErrorModel).fromModel(item.logo);
} else {
this.type = TenantConfigurationType.Logo;
}
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],
logo: this.logo.buildForm({
rootPath: `logo.`,
}),
});
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
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 LogoTenantConfigurationEditorModel implements LogoTenantConfigurationPersist {
storageFileId: Guid;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: LogoTenantConfiguration): LogoTenantConfigurationEditorModel {
if (item) {
if (item.storageFile) this.storageFileId = item.storageFile.id;
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = LogoTenantConfigurationEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
const form: UntypedFormGroup = this.formBuilder.group({
storageFileId: [{ value: this.storageFileId, disabled: disabled }, context.getValidation('storageFileId').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<Validation>();
baseValidationArray.push({ key: 'storageFileId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}storageFileId`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { TenantConfigurationType } from '@app/core/common/enum/tenant-configuration-type';
import { StorageFile } from '@app/core/model/storage-file/storage-file';
import { LogoTenantConfiguration, 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 LogoEditorResolver extends BaseEditorResolver {
constructor(private tenantConfigurationService: TenantConfigurationService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<TenantConfiguration>(x => x.id),
nameof<TenantConfiguration>(x => x.type),
nameof<TenantConfiguration>(x => x.cssColors),
[nameof<TenantConfiguration>(x => x.logo), nameof<LogoTenantConfiguration>(x => x.storageFile), nameof<StorageFile>(x => x.id)].join('.'),
[nameof<TenantConfiguration>(x => x.logo), nameof<LogoTenantConfiguration>(x => x.storageFile), nameof<StorageFile>(x => x.name)].join('.'),
[nameof<TenantConfiguration>(x => x.logo), nameof<LogoTenantConfiguration>(x => x.storageFile), nameof<StorageFile>(x => x.extension)].join('.'),
[nameof<TenantConfiguration>(x => x.logo), nameof<LogoTenantConfiguration>(x => x.storageFile), nameof<StorageFile>(x => x.fullName)].join('.'),
nameof<TenantConfiguration>(x => x.createdAt),
nameof<TenantConfiguration>(x => x.updatedAt),
nameof<TenantConfiguration>(x => x.hash),
nameof<TenantConfiguration>(x => x.isActive)
]
}
resolve() {
const fields = [
...LogoEditorResolver.lookupFields()
];
return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.Logo, fields).pipe(takeUntil(this._destroyed));
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
@Injectable()
export class LogoEditorService {
private validationErrorModel: ValidationErrorModel;
public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void {
this.validationErrorModel = validationErrorModel;
}
public getValidationErrorModel(): ValidationErrorModel {
return this.validationErrorModel;
}
}

View File

@ -33,6 +33,33 @@
<app-tenant-configuration-css-colors-editor></app-tenant-configuration-css-colors-editor> <app-tenant-configuration-css-colors-editor></app-tenant-configuration-css-colors-editor>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>{{'TENANT-CONFIGURATION-EDITOR.DEPOSIT-PLUGINS.TITLE' | translate}}</mat-panel-title>
<mat-panel-description>{{'TENANT-CONFIGURATION-EDITOR.DEPOSIT-PLUGINS.HINT' | translate}}</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<app-tenant-configuration-deposit-editor></app-tenant-configuration-deposit-editor>
</ng-template>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>{{'TENANT-CONFIGURATION-EDITOR.FILE-TRANSFORMER-PLUGINS.TITLE' | translate}}</mat-panel-title>
<mat-panel-description>{{'TENANT-CONFIGURATION-EDITOR.FILE-TRANSFORMER-PLUGINS.HINT' | translate}}</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<app-tenant-configuration-file-transformer-editor></app-tenant-configuration-file-transformer-editor>
</ng-template>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>{{'TENANT-CONFIGURATION-EDITOR.LOGO.TITLE' | translate}}</mat-panel-title>
<mat-panel-description>{{'TENANT-CONFIGURATION-EDITOR.LOGO.HINT' | translate}}</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<app-tenant-configuration-logo-editor></app-tenant-configuration-logo-editor>
</ng-template>
</mat-expansion-panel>
</mat-accordion> </mat-accordion>
</div> </div>

View File

@ -15,6 +15,9 @@ import { TenantConfigurationEditorComponent } from './editor/tenant-configuratio
import { CssColorsEditorComponent } from './editor/css-colors/css-colors-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 { DefaultUserLocaleEditorComponent } from './editor/default-user-locale/default-user-locale-editor.component';
import { FormattingModule } from '@app/core/formatting.module'; import { FormattingModule } from '@app/core/formatting.module';
import { DepositEditorComponent } from './editor/deposit/deposit-editor.component';
import { FileTransformerEditorComponent } from './editor/file-transformer/file-transformer-editor.component';
import { LogoEditorComponent } from './editor/logo/logo-editor.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -35,7 +38,10 @@ import { FormattingModule } from '@app/core/formatting.module';
declarations: [ declarations: [
TenantConfigurationEditorComponent, TenantConfigurationEditorComponent,
CssColorsEditorComponent, CssColorsEditorComponent,
DefaultUserLocaleEditorComponent DefaultUserLocaleEditorComponent,
DepositEditorComponent,
FileTransformerEditorComponent,
LogoEditorComponent
] ]
}) })
export class TenantConfigurationModule { } export class TenantConfigurationModule { }

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormArray, UntypedFormGroup } from '@angular/forms'; import { UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { TenantService } from '@app/core/services/tenant/tenant.service'; import { TenantService } from '@app/core/services/tenant/tenant.service';
@ -16,7 +16,6 @@ import { LoggingService } from '@app/core/services/logging/logging-service';
import { MatomoService } from '@app/core/services/matomo/matomo-service'; import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service'; import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { BaseEditor } from '@common/base/base-editor'; import { BaseEditor } from '@common/base/base-editor';
import { FormService } from '@common/forms/form-service'; import { FormService } from '@common/forms/form-service';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
@ -188,5 +187,4 @@ export class TenantEditorComponent extends BaseEditor<TenantEditorModel, Tenant>
this.formService.validateAllFormFields(this.formGroup); this.formService.validateAllFormFields(this.formGroup);
} }
} }