#2 Amélioration générales des configurations

This commit is contained in:
FyloZ 2021-08-09 20:52:36 -04:00
parent 28cf2d04cd
commit 1a5c09cb41
Signed by: william
GPG Key ID: 835378AE9AF4AE97
26 changed files with 499 additions and 461 deletions

View File

@ -3,8 +3,7 @@ import {Routes, RouterModule} from '@angular/router'
import {CatalogComponent} from './pages/catalog/catalog.component'
import {AdministrationComponent} from './pages/administration/administration.component'
import {MiscComponent} from './pages/others/misc.component'
import {CreConfigEditor} from './modules/configuration/config'
import {CreConfigEditor} from './modules/configuration/config-editor'
const routes: Routes = [{
path: 'color',

View File

@ -1,4 +0,0 @@
<div *ngIf="configuration" [attr.title]="tooltip?.content" class="d-flex flex-row justify-content-between align-items-center">
<cre-checkbox-input [label]="label.content" [control]="config.control"></cre-checkbox-input>
<mat-hint>{{lastUpdated}}</mat-hint>
</div>

View File

@ -0,0 +1,6 @@
<cre-config-container [configuration]="config" [tooltip]="tooltip">
<div class="d-flex flex-row justify-content-between align-items-center">
<cre-checkbox-input [label]="label" [control]="control"></cre-checkbox-input>
<mat-hint>{{inputHint}}</mat-hint>
</div>
</cre-config-container>

View File

@ -0,0 +1,6 @@
<div
class="cre-config"
[class.cre-readonly-config]="readOnly"
[attr.title]="tooltip">
<ng-content></ng-content>
</div>

View File

@ -0,0 +1,12 @@
<cre-config-container [configuration]="config" [tooltip]="tooltip">
<cre-input
class="w-100"
type="text"
[label]="label"
[hint]="inputHint"
[control]="control"
[icon]="inputIcon"
[iconTitle]="inputIconTitle"
iconColor="warning">
</cre-input>
</cre-config-container>

View File

@ -0,0 +1,120 @@
<form *ngIf="form" [formGroup]="form">
<cre-action-bar>
<cre-action-group>
<cre-primary-button routerLink="/color">Retour</cre-primary-button>
</cre-action-group>
<cre-action-group>
<cre-accent-button type="submit" (click)="onSubmit()">Enregistrer</cre-accent-button>
</cre-action-group>
</cre-action-bar>
<div class="d-flex flex-column" style="gap: 1.5rem">
<cre-config-section *ngIf="!emergencyMode" label="Apparence">
<cre-config-list>
<cre-image-config
label="Logo"
tooltip="Affiché dans la bannière de l'application web. Il peut être nécessaire de forcer le
rafraîchissement du cache du navigateur pour que ce changement prenne effet (généralement avec les touches
'Ctrl+F5')."
[configControl]="getConfigControl(keys.INSTANCE_LOGO_PATH)" previewWidth="170px"
(invalidFormat)="invalidFormatConfirmBox.show()">
</cre-image-config>
<cre-image-config
label="Icône"
tooltip="Affiché dans l'onglet de la page dans le navigateur. Il peut être nécessaire de forcer le
rafraîchissement du cache du navigateur pour que ce changement prenne effet (généralement avec les touches
'Ctrl+F5')."
[configControl]="getConfigControl(keys.INSTANCE_ICON_PATH)" previewWidth="32px"
(invalidFormat)="invalidFormatConfirmBox.show()">
</cre-image-config>
</cre-config-list>
</cre-config-section>
<cre-config-section *ngIf="!emergencyMode" label="Données">
<cre-config-list class="pt-2">
<cre-period-config
label="Période d'expiration de l'approbation de l'échantillon des recettes"
[configControl]="getConfigControl(keys.RECIPE_APPROBATION_EXPIRATION)">
</cre-period-config>
<cre-period-config
label="Période d'expiration des kits de retouches complets"
tooltip="Les kits de retouche complétés expirent après la période configurée. Les kits de retouche expirés seront
supprimés automatiquement."
[configControl]="getConfigControl(keys.TOUCH_UP_KIT_EXPIRATION)">
</cre-period-config>
<cre-bool-config
label="Activer le cache des PDFs générés"
tooltip="Cette option permet de stocker les PDFs générés sur le disque, ce qui permet d'accélérer
l'accès aux PDFs si la lecture des fichiers cachés sur le disque est plus rapide que la génération d'un
nouveau PDF."
[configControl]="getConfigControl(keys.TOUCH_UP_KIT_CACHE_PDF)">
</cre-bool-config>
</cre-config-list>
</cre-config-section>
<cre-config-section label="Système">
<cre-config-list>
<cre-text-config
*ngIf="!emergencyMode"
label="URL de l'instance"
tooltip="Utilisé pour générer l'URL de certaines ressources, comme les images et les fiches signalitiques."
[configControl]="getConfigControl(keys.INSTANCE_URL)">
</cre-text-config>
<cre-text-config
label="URL de la base de données"
[configControl]="getConfigControl(keys.DATABASE_URL)">
</cre-text-config>
<cre-text-config
label="Utilisateur de la base de données"
[configControl]="getConfigControl(keys.DATABASE_USER)">
</cre-text-config>
<cre-secure-config
label="Mot de passe de la base de données"
buttonLabel="Modifier le mot de passe de la base de données"
[configControl]="getConfigControl(keys.DATABASE_PASSWORD)">
</cre-secure-config>
<cre-text-config
label="Version de la base de données"
[configControl]="getConfigControl(keys.DATABASE_VERSION)">
</cre-text-config>
<cre-text-config
label="Version de Color Recipes Explorer"
[configControl]="getConfigControl(keys.BACKEND_BUILD_VERSION)">
</cre-text-config>
<cre-date-config
label="Date de compilation de Color Recipes Explorer"
[configControl]="getConfigControl(keys.BACKEND_BUILD_TIME)">
</cre-date-config>
<cre-text-config
label="Version de Java"
[configControl]="getConfigControl(keys.JAVA_VERSION)">
</cre-text-config>
<cre-text-config
label="Système d'exploitation"
[configControl]="getConfigControl(keys.OPERATING_SYSTEM)">
</cre-text-config>
</cre-config-list>
<cre-config-actions>
<cre-warn-button (click)="restartConfirmBox.show()">Redémarrer le serveur</cre-warn-button>
</cre-config-actions>
</cre-config-section>
</div>
</form>
<cre-confirm-box #invalidFormatConfirmBox message="Le format du fichier choisi n'est pas valide"></cre-confirm-box>
<cre-confirm-box #restartConfirmBox
message="Voulez-vous vraiment redémarrer le serveur? Les changements nécessitant un redémarrage seront appliqués."
(confirm)="restart()"></cre-confirm-box>
<cre-confirm-box #restartingConfirmBox message="Le serveur est en cours de redémarrage" (cancel)="reload()"
(confirm)="reload()"></cre-confirm-box>

View File

@ -0,0 +1,94 @@
import {Component, ViewChild} from '@angular/core'
import {ErrorHandlingComponent} from '../shared/components/subscribing.component'
import {ConfirmBoxComponent} from '../shared/components/confirm-box/confirm-box.component'
import {buildFormControl, Config, ConfigControl} from '../shared/model/config.model'
import {FormBuilder, FormControl, FormGroup} from '@angular/forms'
import {ConfigService} from '../shared/service/config.service'
import {ErrorService} from '../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
@Component({
selector: 'cre-config-editor',
templateUrl: 'config-editor.html'
})
export class CreConfigEditor extends ErrorHandlingComponent {
@ViewChild('restartingConfirmBox', {static: true}) restartConfirmBox: ConfirmBoxComponent
keys = {
INSTANCE_NAME: Config.INSTANCE_NAME,
INSTANCE_LOGO_PATH: Config.INSTANCE_LOGO_PATH,
INSTANCE_ICON_PATH: Config.INSTANCE_ICON_PATH,
INSTANCE_URL: Config.INSTANCE_URL,
DATABASE_URL: Config.DATABASE_URL,
DATABASE_USER: Config.DATABASE_USER,
DATABASE_PASSWORD: Config.DATABASE_PASSWORD,
DATABASE_VERSION: Config.DATABASE_VERSION,
RECIPE_APPROBATION_EXPIRATION: Config.RECIPE_APPROBATION_EXPIRATION,
TOUCH_UP_KIT_CACHE_PDF: Config.TOUCH_UP_KIT_CACHE_PDF,
TOUCH_UP_KIT_EXPIRATION: Config.TOUCH_UP_KIT_EXPIRATION,
BACKEND_BUILD_VERSION: Config.BACKEND_BUILD_VERSION,
BACKEND_BUILD_TIME: Config.BACKEND_BUILD_TIME,
JAVA_VERSION: Config.JAVA_VERSION,
OPERATING_SYSTEM: Config.OPERATING_SYSTEM
}
configs = new Map<string, Config>()
form: FormGroup | null
constructor(
private configService: ConfigService,
formBuilder: FormBuilder,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router
) {
super(errorService, activatedRoute, router)
this.fetchConfigurations(formBuilder)
}
getConfigControl(key: string): ConfigControl {
return {
config: this.configs.get(key),
control: this.form.controls[key] as FormControl
}
}
onSubmit() {
this.subscribe(
this.configService.setFromForm(this.form),
() => this.reload()
)
}
restart() {
this.subscribe(
this.configService.restart(),
() => this.restartConfirmBox.show()
)
}
reload() {
window.location.reload()
}
get emergencyMode(): boolean {
return this.configs.get(Config.EMERGENCY_MODE).content === 'true';
}
private fetchConfigurations(formBuilder: FormBuilder) {
this.subscribe(
this.configService.all,
configurations => this.buildForm(formBuilder, configurations)
)
}
private buildForm(formBuilder: FormBuilder, configurations: Config[]) {
const group = {}
configurations.forEach(config => {
group[config.key] = buildFormControl(config)
this.configs.set(config.key, config)
})
this.form = formBuilder.group(group)
}
}

View File

@ -0,0 +1,28 @@
<div class="cre-image-config-label">
<p>
{{label}}
</p>
</div>
<cre-config-container
[configuration]="config"
[tooltip]="tooltip">
<div class="d-flex flex-row justify-content-between align-items-center">
<cre-file-input
class="w-100"
accept="image/png,image/jpeg,image/x-icon,image/svg+xml"
[control]="control"
(selection)="updateImage($event)"
(invalidFormat)="invalidFormat.emit()">
</cre-file-input>
<div class="image-wrapper d-flex flex-column justify-content-end">
<div>
<img
[src]="updatedImage ? updatedImage : configuredImageUrl"
[attr.width]="previewWidth ? previewWidth : null"
class="mat-elevation-z3"/>
</div>
<mat-hint>{{lastUpdated}}</mat-hint>
</div>
</div>
</cre-config-container>

View File

@ -0,0 +1,7 @@
<cre-config-container [configuration]="config" [tooltip]="tooltip">
<cre-period-input
[control]="control"
[label]="label"
[hint]="inputHint">
</cre-period-input>
</cre-config-container>

View File

@ -1,8 +1,6 @@
<mat-card class="w-50 x-centered">
<mat-card-header>
<mat-card-title>
<ng-content select="cre-config-label"></ng-content>
</mat-card-title>
<mat-card-title>{{label}}</mat-card-title>
</mat-card-header>
<mat-card-content [class.no-action]="!hasActions">
<ng-content select="cre-config-list"></ng-content>

View File

@ -0,0 +1,24 @@
<cre-config-container [configuration]="config" [tooltip]="tooltip">
<cre-primary-button
class="w-100 mb-3"
(click)="onOpen()">
{{buttonLabel}}
</cre-primary-button>
<cre-prompt-dialog
[title]="label"
(cancel)="onCancel()">
<cre-dialog-body>
<cre-input
class="w-100"
type="password"
label="Nouvelle valeur"
[hint]="inputHint"
[control]="control"
[icon]="inputIcon"
[iconTitle]="inputIconTitle"
iconColor="warning">
</cre-input>
</cre-dialog-body>
</cre-prompt-dialog>
</cre-config-container>

View File

@ -0,0 +1,11 @@
<cre-config-container [tooltip]="tooltip">
<cre-input
class="w-100"
[control]="control"
[label]="label"
[hint]="inputHint"
[icon]="inputIcon"
[iconTitle]="inputIconTitle"
iconColor="warning">
</cre-input>
</cre-config-container>

View File

@ -1,12 +0,0 @@
<div *ngIf="configuration" [attr.title]="tooltip?.content">
<cre-input [class.has-hint]="configuration.editable"
class="w-100"
[type]="config.key === 'database.password' ? 'password' : 'text'"
[label]="label.content"
[hint]="configuration.editable ? lastUpdated : null"
[control]="config.control"
[icon]="configuration.requireRestart ? 'alert' : null"
[iconTitle]="configuration.requireRestart ? 'Requiert un redémarrage' : null"
iconColor="warning">
</cre-input>
</div>

View File

@ -1,33 +1,35 @@
import {NgModule} from '@angular/core'
import {
CreConfig,
CreConfigLabel,
CreConfigEditor,
CreConfigSection,
CreImageConfig,
CreConfigList,
CreBoolConfig,
CreConfigActions,
CreConfigTooltip, CrePeriodConfig, CreBoolConfig, CreDateConfig, CreSecureConfig
CreConfigContainer,
CreConfigList,
CreConfigSection,
CreDateConfig,
CreImageConfig,
CrePeriodConfig,
CreSecureConfig,
CreTextConfig
} from './config'
import {SharedModule} from '../shared/shared.module'
import {CreInputsModule} from '../shared/components/inputs/inputs.module'
import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module'
import {CreButtonsModule} from '../shared/components/buttons/buttons.module'
import {CreConfigEditor} from './config-editor'
@NgModule({
declarations: [
CreConfigLabel,
CreConfigTooltip,
CreConfigEditor,
CreConfig,
CreImageConfig,
CreConfigSection,
CreConfigList,
CreConfigActions,
CreConfigSection,
CreConfigContainer,
CreTextConfig,
CreImageConfig,
CreBoolConfig,
CrePeriodConfig,
CreDateConfig,
CreSecureConfig
CreSecureConfig,
CreConfigEditor
],
imports: [
SharedModule,
@ -36,4 +38,5 @@ import {CreButtonsModule} from '../shared/components/buttons/buttons.module'
CreButtonsModule
]
})
export class ConfigModule { }
export class ConfigModule {
}

View File

@ -1,10 +1,10 @@
mat-hint
font-size: .8em
cre-config
cre-config-container
display: block
cre-input.has-hint
.cre-config:not(.cre-editable-config)
margin-bottom: 1em
mat-hint
@ -50,6 +50,3 @@ cre-image-config
mat-hint
margin-top: .2em
//cre-secure-config button
//

View File

@ -1,58 +1,13 @@
import {
AfterViewInit,
Component,
ContentChild,
Directive,
ElementRef,
EventEmitter,
Input,
Output,
ViewChild,
ViewEncapsulation
} from '@angular/core'
import {AfterViewInit, Component, ContentChild, Directive, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from '@angular/core'
import {ConfigService} from '../shared/service/config.service'
import {Config} from '../shared/model/config.model'
import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component'
import {Config, ConfigControl} from '../shared/model/config.model'
import {SubscribingComponent} from '../shared/components/subscribing.component'
import {ErrorService} from '../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {formatDate, formatDateTime, getFileUrl, readFile} from '../shared/utils/utils'
import {FormControl, Validators} from '@angular/forms'
import {ConfirmBoxComponent} from '../shared/components/confirm-box/confirm-box.component'
import {MatDialog} from '@angular/material/dialog'
import {AbstractControl} from '@angular/forms'
import {CrePromptDialog} from '../shared/components/dialogs/dialogs'
@Directive({
selector: 'cre-config-label'
})
export class CreConfigLabel implements AfterViewInit {
content: string
constructor(
private element: ElementRef
) {
}
ngAfterViewInit(): void {
this.content = this.element.nativeElement.innerHTML
}
}
@Directive({
selector: 'cre-config-tooltip'
})
export class CreConfigTooltip implements AfterViewInit {
content: string
constructor(
private element: ElementRef
) {
}
ngAfterViewInit(): void {
this.content = this.element.nativeElement.innerHTML
}
}
@Directive({
selector: 'cre-config-list'
})
@ -71,6 +26,8 @@ export class CreConfigActions {
templateUrl: 'config-section.html'
})
export class CreConfigSection {
@Input() label: string
@ContentChild(CreConfigActions) actions: CreConfigActions
get hasActions(): boolean {
@ -79,17 +36,25 @@ export class CreConfigSection {
}
@Component({
selector: 'cre-config',
templateUrl: 'config.html',
styleUrls: ['config.sass']
selector: 'cre-config-container',
templateUrl: 'config-container.html',
styleUrls: ['config.sass'],
encapsulation: ViewEncapsulation.None
})
export class CreConfig extends SubscribingComponent {
@Input() config: { key: string, control: FormControl }
export class CreConfigContainer {
@Input() configuration?: Config
@Input() tooltip: string
@ContentChild(CreConfigLabel, {static: true}) label: CreConfigLabel
@ContentChild(CreConfigTooltip, {static: true}) tooltip: CreConfigTooltip
get readOnly(): boolean {
return !this.configuration?.editable ?? true
}
}
configuration: Config | null
@Directive()
abstract class _CreConfigBase extends SubscribingComponent {
@Input() configControl: ConfigControl
@Input() label: string
@Input() tooltip?: string
constructor(
private configService: ConfigService,
@ -102,92 +67,99 @@ export class CreConfig extends SubscribingComponent {
ngOnInit() {
super.ngOnInit()
this.subscribe(
this.configService.get(this.config.key),
config => this.setConfig(config)
)
}
protected setConfig(config: Config) {
this.configuration = config
this.config.control.setValue(config.content)
if (!config.editable) {
this.config.control.disable()
}
get config(): Config {
return this.configControl.config
}
get control(): AbstractControl {
return this.configControl.control
}
get lastUpdated(): string {
return 'Dernière mise à jour: ' + formatDateTime(this.configuration.lastUpdated)
return 'Dernière mise à jour: ' + formatDateTime(this.config.lastUpdated)
}
get inputHint(): string {
return this.config?.editable ? this.lastUpdated : null
}
}
@Directive()
abstract class _CreTextConfigBase extends _CreConfigBase {
private static readonly REQUIRE_RESTART_ICON = 'alert'
private static readonly REQUIRE_RESTART_ICON_TITLE = 'Requiert un redémarrage'
get inputIcon(): string {
return this.config?.requireRestart ? _CreTextConfigBase.REQUIRE_RESTART_ICON : null
}
get inputIconTitle(): string {
return this.config?.requireRestart ? _CreTextConfigBase.REQUIRE_RESTART_ICON_TITLE : null
}
}
@Component({
selector: 'cre-text-config',
templateUrl: 'config-text.html',
styleUrls: ['config.sass']
})
export class CreTextConfig extends _CreTextConfigBase {
}
@Component({
selector: 'cre-image-config',
templateUrl: 'image.html',
templateUrl: 'config-image.html',
styleUrls: ['config.sass'],
encapsulation: ViewEncapsulation.None
})
export class CreImageConfig extends CreConfig {
export class CreImageConfig extends _CreConfigBase {
@Input() previewWidth: string | null
@Output() invalidFormat = new EventEmitter<void>()
updatedImage: any | null
constructor(
configService: ConfigService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router
) {
super(configService, errorService, activatedRoute, router)
}
updateImage(file: File): any {
readFile(file, (content) => this.updatedImage = content)
}
get configuredImageUrl(): string {
return getFileUrl(this.configuration.content)
return getFileUrl(this.config.content)
}
}
@Component({
selector: 'cre-bool-config',
templateUrl: 'bool.html'
templateUrl: 'config-bool.html'
})
export class CreBoolConfig extends CreConfig {
protected setConfig(config: Config) {
super.setConfig(config)
this.config.control.setValue(config.content === 'true')
}
export class CreBoolConfig extends _CreConfigBase {
}
@Component({
selector: 'cre-period-config',
templateUrl: 'period.html'
templateUrl: 'config-period.html'
})
export class CrePeriodConfig extends CreConfig {
export class CrePeriodConfig extends _CreConfigBase {
}
@Component({
selector: 'cre-date-config',
templateUrl: 'date.html'
templateUrl: 'config-date.html'
})
export class CreDateConfig extends CreConfig {
protected setConfig(config: Config) {
super.setConfig(config)
this.config.control.setValue(formatDate(config.content))
export class CreDateConfig extends _CreTextConfigBase implements AfterViewInit {
ngAfterViewInit(): void {
this.control.setValue(formatDate(this.config.content))
}
}
@Component({
selector: 'cre-secure-config',
templateUrl: 'secure.html'
templateUrl: 'config-secure.html'
})
export class CreSecureConfig extends CreConfig {
export class CreSecureConfig extends _CreTextConfigBase {
@ViewChild(CrePromptDialog) dialog: CrePromptDialog
@Input() buttonLabel: string
@ -200,91 +172,15 @@ export class CreSecureConfig extends CreConfig {
activatedRoute: ActivatedRoute,
router: Router
) {
super(configService, errorService, activatedRoute, router);
}
protected setConfig(config: Config) {
super.setConfig(config)
super(configService, errorService, activatedRoute, router)
}
onOpen() {
this.initialValue = this.config.control.value
this.initialValue = this.control.value
this.dialog.show()
}
onCancel() {
this.config.control.setValue(this.initialValue)
}
}
@Component({
selector: 'cre-config-editor',
templateUrl: 'editor.html'
})
export class CreConfigEditor extends ErrorHandlingComponent {
@ViewChild('restartingConfirmBox', {static: true}) restartConfirmBox: ConfirmBoxComponent
keys = {
INSTANCE_NAME: Config.INSTANCE_NAME,
INSTANCE_LOGO_PATH: Config.INSTANCE_LOGO_PATH,
INSTANCE_ICON_PATH: Config.INSTANCE_ICON_PATH,
INSTANCE_URL: Config.INSTANCE_URL,
DATABASE_URL: Config.DATABASE_URL,
DATABASE_USER: Config.DATABASE_USER,
DATABASE_PASSWORD: Config.DATABASE_PASSWORD,
DATABASE_VERSION: Config.DATABASE_VERSION,
RECIPE_APPROBATION_EXPIRATION: Config.RECIPE_APPROBATION_EXPIRATION,
TOUCH_UP_KIT_CACHE_PDF: Config.TOUCH_UP_KIT_CACHE_PDF,
TOUCH_UP_KIT_EXPIRATION: Config.TOUCH_UP_KIT_EXPIRATION,
BACKEND_BUILD_VERSION: Config.BACKEND_BUILD_VERSION,
BACKEND_BUILD_TIME: Config.BACKEND_BUILD_TIME,
JAVA_VERSION: Config.JAVA_VERSION,
OPERATING_SYSTEM: Config.OPERATING_SYSTEM
}
controls = new Map<string, FormControl>()
emergencyMode: string | null
constructor(
private configService: ConfigService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router
) {
super(errorService, activatedRoute, router)
for (let key in this.keys) {
this.controls.set(this.keys[key], new FormControl(null, Validators.required))
}
}
ngOnInit() {
this.subscribe(
this.configService.get(Config.EMERGENCY_MODE),
config => {
this.emergencyMode = config.content
}
)
}
getConfig(key: string) {
return {key, control: this.controls.get(key)}
}
save() {
this.subscribe(
this.configService.set(this.controls),
() => this.reload()
)
}
restart() {
this.subscribe(
this.configService.restart(),
() => this.restartConfirmBox.show()
)
}
reload() {
window.location.reload()
this.control.setValue(this.initialValue)
}
}

View File

@ -1,12 +0,0 @@
<div *ngIf="configuration" [attr.title]="tooltip?.content">
<cre-input [class.has-hint]="configuration.editable"
class="w-100"
type="text"
[label]="label.content"
[hint]="configuration.editable ? lastUpdated : null"
[control]="config.control"
[icon]="configuration.requireRestart ? 'alert' : null"
[iconTitle]="configuration.requireRestart ? 'Requiert un redémarrage' : null"
iconColor="warning">
</cre-input>
</div>

View File

@ -1,128 +0,0 @@
<cre-action-bar>
<cre-action-group>
<cre-primary-button routerLink="/color">Retour</cre-primary-button>
</cre-action-group>
<cre-action-group>
<cre-accent-button (click)="save()">Enregistrer</cre-accent-button>
</cre-action-group>
</cre-action-bar>
<div *ngIf="emergencyMode" class="d-flex flex-column" style="gap: 1.5rem">
<cre-config-section *ngIf="emergencyMode === 'false'">
<cre-config-label>Apparence</cre-config-label>
<cre-config-list>
<!-- <cre-config [config]="getConfig(keys.INSTANCE_NAME)">-->
<!-- <cre-config-label>Nom de l'instance</cre-config-label>-->
<!-- <cre-config-tooltip>-->
<!-- Affiché dans la barre de titre du navigateur ou en survolant l'onglet de la page dans le navigateur.-->
<!-- </cre-config-tooltip>-->
<!-- </cre-config>-->
<cre-image-config [config]="getConfig(keys.INSTANCE_LOGO_PATH)" previewWidth="170px"
(invalidFormat)="invalidFormatConfirmBox.show()">
<cre-config-label>Logo</cre-config-label>
<cre-config-tooltip>
Affiché dans la bannière de l'application web. Il peut être nécessaire de forcer le
rafraîchissement du cache du navigateur pour que ce changement prenne effet (généralement avec les touches
'Ctrl+F5').
</cre-config-tooltip>
</cre-image-config>
<cre-image-config [config]="getConfig(keys.INSTANCE_ICON_PATH)" previewWidth="32px"
(invalidFormat)="invalidFormatConfirmBox.show()">
<cre-config-label>Icône</cre-config-label>
<cre-config-tooltip>
Affiché dans l'onglet de la page dans le navigateur. Il peut être nécessaire de forcer le
rafraîchissement du cache du navigateur pour que ce changement prenne effet (généralement avec les touches
'Ctrl+F5').
</cre-config-tooltip>
</cre-image-config>
</cre-config-list>
</cre-config-section>
<cre-config-section>
<cre-config-label>Données</cre-config-label>
<cre-config-list class="pt-2">
<cre-period-config [config]="getConfig(keys.RECIPE_APPROBATION_EXPIRATION)">
<cre-config-label>Période d'expiration de l'approbation de l'échantillon des recettes</cre-config-label>
</cre-period-config>
<cre-period-config [config]="getConfig(keys.TOUCH_UP_KIT_EXPIRATION)">
<cre-config-label>Période d'expiration des kits de retouches complets</cre-config-label>
<cre-config-tooltip>
Les kits de retouche complétés expirent après la période configurée. Les kits de retouche expirés seront
supprimés automatiquement.
</cre-config-tooltip>
</cre-period-config>
<cre-bool-config [config]="getConfig(keys.TOUCH_UP_KIT_CACHE_PDF)">
<cre-config-label>Activer le cache des PDFs générés</cre-config-label>
<cre-config-tooltip>
Cette option permet de stocker les PDFs générés sur le disque, ce qui permet d'accélérer
l'accès aux PDFs si la lecture des fichiers cachés sur le disque est plus rapide que la génération d'un
nouveau PDF.
</cre-config-tooltip>
</cre-bool-config>
</cre-config-list>
</cre-config-section>
<cre-config-section>
<cre-config-label>Système</cre-config-label>
<cre-config-list>
<cre-config [config]="getConfig(keys.INSTANCE_URL)">
<cre-config-label>URL de l'instance</cre-config-label>
<cre-config-tooltip>
Utilisé pour générer l'URL de certaines ressources, comme les images et les fiches signalitiques.
</cre-config-tooltip>
</cre-config>
<cre-config [config]="getConfig(keys.DATABASE_URL)">
<cre-config-label>URL de la base de données</cre-config-label>
</cre-config>
<cre-config [config]="getConfig(keys.DATABASE_USER)">
<cre-config-label>Utilisateur de la base de données</cre-config-label>
</cre-config>
<!-- <cre-config [config]="getConfig(keys.DATABASE_PASSWORD)">-->
<!-- <cre-config-label>Mot de passe de la base de données</cre-config-label>-->
<!-- </cre-config>-->
<cre-secure-config
[config]="getConfig(keys.DATABASE_PASSWORD)"
buttonLabel="Modifier le mot de passe de la base de données">
<cre-config-label>Mot de passe de la base de données</cre-config-label>
</cre-secure-config>
<cre-config [config]="getConfig(keys.DATABASE_VERSION)">
<cre-config-label>Version de la base de données</cre-config-label>
</cre-config>
<cre-config [config]="getConfig(keys.BACKEND_BUILD_VERSION)">
<cre-config-label>Version de Color Recipes Explorer</cre-config-label>
</cre-config>
<cre-date-config [config]="getConfig(keys.BACKEND_BUILD_TIME)">
<cre-config-label>Date de compilation de Color Recipes Explorer</cre-config-label>
</cre-date-config>
<cre-config [config]="getConfig(keys.JAVA_VERSION)">
<cre-config-label>Version de Java</cre-config-label>
</cre-config>
<cre-config [config]="getConfig(keys.OPERATING_SYSTEM)">
<cre-config-label>Système d'exploitation</cre-config-label>
</cre-config>
</cre-config-list>
<cre-config-actions>
<cre-warn-button (click)="restartConfirmBox.show()">Redémarrer le serveur</cre-warn-button>
</cre-config-actions>
</cre-config-section>
</div>
<cre-confirm-box #invalidFormatConfirmBox message="Le format du fichier choisi n'est pas valide"></cre-confirm-box>
<cre-confirm-box #restartConfirmBox
message="Voulez-vous vraiment redémarrer le serveur? Les changements nécessitant un redémarrage seront appliqués."
(confirm)="restart()"></cre-confirm-box>
<cre-confirm-box #restartingConfirmBox message="Le serveur est en cours de redémarrage" (cancel)="reload()"
(confirm)="reload()"></cre-confirm-box>

View File

@ -1,26 +0,0 @@
<div class="cre-image-config-label">
<p>
<ng-content select="cre-config-label"></ng-content>
</p>
</div>
<div *ngIf="configuration"
class="d-flex flex-row justify-content-between align-items-center"
[attr.title]="tooltip?.content">
<cre-file-input
class="w-100"
accept="image/png,image/jpeg,image/x-icon,image/svg+xml"
[control]="config.control"
(selection)="updateImage($event)"
(invalidFormat)="invalidFormat.emit()">
</cre-file-input>
<div class="image-wrapper d-flex flex-column justify-content-end">
<div>
<img
[src]="updatedImage ? updatedImage : configuredImageUrl"
[attr.width]="previewWidth ? previewWidth : null"
class="mat-elevation-z3"/>
</div>
<mat-hint>{{lastUpdated}}</mat-hint>
</div>
</div>

View File

@ -1,7 +0,0 @@
<div *ngIf="configuration" [attr.title]="tooltip?.content">
<cre-period-input
[control]="config.control"
[label]="label.content"
[hint]="lastUpdated">
</cre-period-input>
</div>

View File

@ -1,25 +0,0 @@
<div *ngIf="configuration" [attr.title]="tooltip?.content">
<cre-primary-button
class="w-100 mb-3"
(click)="onOpen()">
{{buttonLabel}}
</cre-primary-button>
<cre-prompt-dialog
[title]="label.content"
(cancel)="onCancel()">
<cre-dialog-body>
<cre-input
[class.has-hint]="configuration.editable"
class="w-100"
type="password"
label="Nouvelle valeur"
[hint]="configuration.editable ? lastUpdated : null"
[control]="config.control"
[icon]="configuration.requireRestart ? 'alert' : null"
[iconTitle]="configuration.requireRestart ? 'Requiert un redémarrage' : null"
iconColor="warning">
</cre-input>
</cre-dialog-body>
</cre-prompt-dialog>
</div>

View File

@ -4,7 +4,7 @@ import {ThemePalette} from '@angular/material/core'
@Component({
selector: 'cre-button',
template: `
<button mat-raised-button [color]="color" [disabled]="disabled">
<button mat-raised-button [type]="type" [color]="color" [disabled]="disabled">
<ng-content></ng-content>
</button>
`,
@ -13,44 +13,48 @@ import {ThemePalette} from '@angular/material/core'
})
export class CreButtonComponent {
@Input() color: ThemePalette
@Input() type = 'button'
@Input() disabled = false
}
@Component({
selector: 'cre-primary-button',
template: `
<cre-button color="primary" [disabled]="disabled">
<cre-button color="primary" [type]="type" [disabled]="disabled">
<ng-content></ng-content>
</cre-button>
`,
styleUrls: ['buttons.sass']
})
export class CrePrimaryButtonComponent {
@Input() type = 'button'
@Input() disabled = false
}
@Component({
selector: 'cre-accent-button',
template: `
<cre-button color="accent" [disabled]="disabled">
<cre-button color="accent" [type]="type" [disabled]="disabled">
<ng-content></ng-content>
</cre-button>
`,
styleUrls: ['buttons.sass']
})
export class CreAccentButtonComponent {
@Input() type = 'button'
@Input() disabled = false
}
@Component({
selector: 'cre-warn-button',
template: `
<cre-button color="warn" [disabled]="disabled">
<cre-button color="warn" [type]="type" [disabled]="disabled">
<ng-content></ng-content>
</cre-button>
`,
styleUrls: ['buttons.sass']
})
export class CreWarnButtonComponent {
@Input() type = 'button'
@Input() disabled = false
}

View File

@ -1,25 +1,21 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<input
*ngIf="!control"
matInput
[type]="type"
[step]="step"
[placeholder]="placeholder"
[(ngModel)]="value"
[required]="required"
[autocomplete]="autocomplete ? 'on' : 'off'"
[disabled]="disabled"
(change)="valueChange.emit(value)"/>
<input
*ngIf="control"
matInput
[type]="type"
[step]="step"
[placeholder]="placeholder"
[required]="required"
[formControl]="control"
[autocomplete]="autocomplete ? 'on' : 'off'"/>
<ng-container *ngIf="!control">
<input
#input
matInput
[disabled]="disabled"
[(ngModel)]="value"
(change)="valueChange.emit(value)"/>
</ng-container>
<ng-container *ngIf="control">
<input
#input
matInput
[formControl]="control">
</ng-container>
<mat-icon
matSuffix
[svgIcon]="icon"

View File

@ -1,6 +1,8 @@
import {
AfterViewInit,
Component,
ContentChild,
Directive,
ElementRef,
EventEmitter,
Input,
@ -17,7 +19,14 @@ import {Observable, Subject} from 'rxjs'
import {map, takeUntil} from 'rxjs/operators'
import {MatChipInputEvent} from '@angular/material/chips'
import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'
import {MatOptionSelectionChange} from '@angular/material/core'
@Directive()
abstract class _CreInputBase {
@Input() control: AbstractControl | null
@Input() label: string
@Input() value
@Input() disabled = false
}
@Component({
selector: 'cre-input',
@ -25,24 +34,30 @@ import {MatOptionSelectionChange} from '@angular/material/core'
encapsulation: ViewEncapsulation.None,
styleUrls: ['input.sass']
})
export class CreInputComponent {
@Input() control: FormControl | null
export class CreInputComponent extends _CreInputBase implements AfterViewInit {
@Input() type = 'text'
@Input() label: string
@Input() icon: string
@Input() iconTitle: string | null
@Input() required = true
@Input() autocomplete = true
@Input() placeholder: string | null
@Input() step = 1
@Input() value
@Input() iconColor: string = 'black'
@Input() disabled = false
@Input() hint: string | null
@Output() valueChange = new EventEmitter<any>()
@ViewChild('input') input: any
@ContentChild(TemplateRef) errors: TemplateRef<any>
ngAfterViewInit() {
const element = this.input.nativeElement
element.type = this.type
element.step = this.step.toString()
element.placeholder = this.placeholder
element.required = this.required
element.autocomplete = this.autocomplete ? 'on' : 'off'
}
}
@Component({
@ -51,7 +66,7 @@ export class CreInputComponent {
encapsulation: ViewEncapsulation.None
})
export class CreAutocompleteInputComponent {
@Input() control: FormControl | null
@Input() control: AbstractControl | null
@Input() label: string
@Input() icon: string
@Input() required = true
@ -69,7 +84,7 @@ export class CreAutocompleteInputComponent {
encapsulation: ViewEncapsulation.None
})
export class CreChipInputComponent implements OnInit {
@Input() control: FormControl
@Input() control: AbstractControl
@Input() label: string
@Input() icon: string
@Input() required = true
@ -118,7 +133,7 @@ export class CreChipInputComponent implements OnInit {
encapsulation: ViewEncapsulation.None
})
export class CreComboBoxComponent {
@Input() control: FormControl
@Input() control: AbstractControl
@Input() label: string
@Input() icon: string
@Input() required = true
@ -184,12 +199,16 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O
selector: 'cre-checkbox-input',
templateUrl: 'checkbox.html'
})
export class CreCheckboxInputComponent {
export class CreCheckboxInputComponent implements OnInit {
@Input() label: string
@Input() control: FormControl
@Input() control: AbstractControl
@Input() checked: boolean
@Output() checkedChange = new EventEmitter<boolean>()
ngOnInit(): void {
this.control?.setValue(this.control.value === 'true')
}
}
@Component({
@ -200,7 +219,7 @@ export class CreFileInputComponent implements OnInit {
@Input() label: string
@Input() icon: string
@Input() accept = ''
@Input() control: FormControl | null
@Input() control: AbstractControl | null
@Output() selection = new EventEmitter<File>()
@Output() invalidFormat = new EventEmitter<void>()
@ -234,7 +253,7 @@ export class CreFileInputComponent implements OnInit {
encapsulation: ViewEncapsulation.None
})
export class CrePeriodInputComponent implements OnInit {
@Input() control: FormControl
@Input() control: AbstractControl
@Input() label: string
@Input() hint: string | null
@ -264,9 +283,12 @@ export class CrePeriodInputComponent implements OnInit {
}
private setValuesFromPeriod(period: string) {
if (!period) {
return
}
const periodTypeChar = period.slice(-1)
period = period.slice(1, -1)
this.selectControl.setValue(periodTypeChar)
this.inputControl.setValue(period)
}

View File

@ -1,4 +1,4 @@
import {Form, FormControl} from '@angular/forms'
import {AbstractControl, Form, FormControl, Validators} from '@angular/forms'
import {filterMap} from '../utils/map.utils'
export class Config {
@ -24,6 +24,10 @@ export class Config {
Config.INSTANCE_ICON_PATH
]
static readonly PASSWORD_CONFIG_KEYS = [
Config.DATABASE_PASSWORD
]
public key: string
public requireRestart: boolean
public editable: boolean
@ -36,7 +40,20 @@ export class ConfigKeyContent {
public content: string
}
export function filterConfigKeyControlMap(map: Map<string, FormControl>): Map<string, FormControl> {
export class ConfigControl {
public config: Config
public control: AbstractControl
}
export function buildFormControl(config: Config): AbstractControl {
return new FormControl({value: config.content, disabled: !config.editable}, Validators.required)
}
export function configKeyIsPassword(key: string): boolean {
return Config.PASSWORD_CONFIG_KEYS.indexOf(key) >= 0
}
export function filterConfigKeyControlMap(map: Map<string, AbstractControl>): Map<string, AbstractControl> {
return filterMap(map, (key, control) => {
return control.dirty &&
Config.IMAGE_CONFIG_KEYS.indexOf(key) < 0 && // Filter image configs because they are sent to a different endpoint
@ -45,17 +62,17 @@ export function filterConfigKeyControlMap(map: Map<string, FormControl>): Map<st
})
}
export function filterImageConfigKeyControlMap(map: Map<string, FormControl>): Map<string, FormControl> {
export function filterImageConfigKeyControlMap(map: Map<string, AbstractControl>): Map<string, AbstractControl> {
return filterMap(map, (key, control) => {
return Config.IMAGE_CONFIG_KEYS.indexOf(key) >= 0 && control.dirty
})
}
export function mapToConfigKeyContent(key: string, control: FormControl): ConfigKeyContent {
export function mapToConfigKeyContent(key: string, control: AbstractControl): ConfigKeyContent {
return {key, content: control.value}
}
export function mapToConfigKeyContentArray(map: Map<string, FormControl>): ConfigKeyContent[] {
export function mapToConfigKeyContentArray(map: Map<string, AbstractControl>): ConfigKeyContent[] {
const array: ConfigKeyContent[] = []
map.forEach((control, key) => {
array.push(mapToConfigKeyContent(key, control))

View File

@ -2,7 +2,7 @@ import {Injectable} from '@angular/core'
import {Config, filterConfigKeyControlMap, filterImageConfigKeyControlMap, mapToConfigKeyContentArray} from '../model/config.model'
import {Observable} from 'rxjs'
import {ApiService} from './api.service'
import {FormControl} from '@angular/forms'
import {AbstractControl, FormGroup} from '@angular/forms'
import {transformMap} from '../utils/map.utils'
@Injectable({
@ -14,11 +14,23 @@ export class ConfigService {
) {
}
get all(): Observable<Config[]> {
return this.api.get<Config[]>('/config')
}
get(key: string): Observable<Config> {
return this.api.get<Config>(`/config/${key}`)
}
set(configs: Map<string, FormControl>): Observable<void> {
setFromForm(form: FormGroup): Observable<void> {
const map = new Map<string, AbstractControl>()
for (let key in form.controls) {
map.set(key, form.controls[key])
}
return this.set(map);
}
set(configs: Map<string, AbstractControl>): Observable<void> {
const body = mapToConfigKeyContentArray(filterConfigKeyControlMap(configs))
const imageConfigs = filterImageConfigKeyControlMap(configs)
@ -38,7 +50,7 @@ export class ConfigService {
return this.api.post<void>('/config/restart')
}
private setImages(configs: Map<string, FormControl>) {
private setImages(configs: Map<string, AbstractControl>) {
const subscriptions = this.getImageConfigsSubscriptions(configs)
while (subscriptions.length > 0) {
const subscription = subscriptions.pop().subscribe({
@ -47,7 +59,7 @@ export class ConfigService {
}
}
private getImageConfigsSubscriptions(configs: Map<string, FormControl>): Observable<void>[] {
private getImageConfigsSubscriptions(configs: Map<string, AbstractControl>): Observable<void>[] {
return transformMap(configs, (key, control) => {
return this.setImage(key, control.value)
})