Ajout des configurations

This commit is contained in:
FyloZ 2021-05-28 19:22:13 -04:00
parent 6152e09316
commit 54aca43371
29 changed files with 731 additions and 27 deletions

View File

@ -3,6 +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'
const routes: Routes = [{
@ -38,6 +39,10 @@ const routes: Routes = [{
}, {
path: 'group',
loadChildren: () => import('./modules/groups/group.module').then(m => m.GroupModule)
}, {
path: 'config',
loadChildren: () => import('./modules/configuration/config.module').then(m => m.ConfigModule),
component: CreConfigEditor
}, {
path: '',
pathMatch: 'full',

View File

@ -4,6 +4,9 @@ import {AppState} from './modules/shared/app-state'
import {SubscribingComponent} from './modules/shared/components/subscribing.component'
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorService} from './modules/shared/service/error.service'
import {ConfigService} from './modules/shared/service/config.service'
import {Config} from './modules/shared/model/config.model'
import {environment} from '../environments/environment'
@Component({
selector: 'cre-root',
@ -13,9 +16,11 @@ import {ErrorService} from './modules/shared/service/error.service'
export class AppComponent extends SubscribingComponent {
isOnline: boolean
isServerOnline = true
favIcon: HTMLLinkElement = document.querySelector('#favicon')
constructor(
@Inject(PLATFORM_ID) private platformId: object,
private configService: ConfigService,
private appState: AppState,
errorService: ErrorService,
router: Router,
@ -32,6 +37,8 @@ export class AppComponent extends SubscribingComponent {
this.appState.serverOnline$,
online => this.isServerOnline = online
)
this.favIcon.href = environment.apiUrl + "/file?path=images%2Ficon"
}
reload() {

View File

@ -9,7 +9,7 @@
</mat-list-item>
</mat-list>
<p *ngIf="!selectedGroupId" class="empty-text text-center">Aucun groupe n'est sélectionné</p>
<p *ngIf="selectedGroupId && steps.length === 0" class="empty-text text-center">Il n'y a aucune étape définie pour ce groupe</p>
<p *ngIf="!selectedGroupId" class="light-text text-center">Aucun groupe n'est sélectionné</p>
<p *ngIf="selectedGroupId && steps.length === 0" class="light-text text-center">Il n'y a aucune étape définie pour ce groupe</p>
</mat-card-content>
</mat-card>

View File

@ -12,7 +12,7 @@
</mat-select>
</mat-form-field>
<p *ngIf="!selectedGroupId" class="empty-text text-center">Aucun groupe n'est sélectionné</p>
<p *ngIf="!selectedGroupId" class="light-text text-center">Aucun groupe n'est sélectionné</p>
<ng-container
*ngIf="selectedGroupId"

View File

@ -7,6 +7,8 @@ import {getRecipeLuma, Recipe, recipeApprobationExpired} from '../../../shared/m
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {ConfigService} from '../../../shared/service/config.service'
import {Config} from '../../../shared/model/config.model'
@Component({
selector: 'cre-list',
@ -23,6 +25,7 @@ export class ListComponent extends ErrorHandlingComponent {
constructor(
private recipeService: RecipeService,
private accountService: AccountService,
private configService: ConfigService,
private cdRef: ChangeDetectorRef,
private appState: AppState,
errorService: ErrorService,
@ -37,8 +40,17 @@ export class ListComponent extends ErrorHandlingComponent {
this.appState.title = "Explorateur"
this.subscribe(
this.recipeService.allSortedByCompany,
recipes => this.recipes = recipes
this.configService.get(Config.EMERGENCY_MODE),
config => {
if (config.content === "false") {
this.subscribe(
this.recipeService.allSortedByCompany,
recipes => this.recipes = recipes
)
} else {
this.urlUtils.navigateTo("/admin/config")
}
}
)
}

View File

@ -0,0 +1,4 @@
<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>Dernière mise à jour: {{lastUpdated}}</mat-hint>
</div>

View File

@ -0,0 +1,3 @@
<div class="d-flex flex-row justify-content-between">
<ng-content></ng-content>
</div>

View File

@ -0,0 +1,13 @@
<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-header>
<mat-card-content [class.no-action]="!hasActions">
<ng-content select="cre-config-list"></ng-content>
</mat-card-content>
<mat-card-actions *ngIf="hasActions">
<ng-content select="cre-config-actions"></ng-content>
</mat-card-actions>
</mat-card>

View File

@ -0,0 +1,12 @@
<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 ? 'Dernière mise à jour: ' + 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

@ -0,0 +1,35 @@
import {NgModule} from '@angular/core'
import {
CreConfig,
CreConfigLabel,
CreConfigEditor,
CreConfigSection,
CreImageConfig,
CreConfigList,
CreConfigActions,
CreConfigTooltip
} 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'
@NgModule({
declarations: [
CreConfigLabel,
CreConfigTooltip,
CreConfigEditor,
CreConfig,
CreImageConfig,
CreConfigSection,
CreConfigList,
CreConfigActions
],
imports: [
SharedModule,
CreInputsModule,
CreActionBarModule,
CreButtonsModule
]
})
export class ConfigModule { }

View File

@ -0,0 +1,52 @@
mat-hint
font-size: .8em
cre-config
display: block
cre-input.has-hint
margin-bottom: 1em
mat-hint
font-size: 1em
cre-image-config
display: block
border: 1px solid rgba(0, 0, 0, 0.42)
border-radius: 4px
padding: 8px
position: relative
margin-bottom: 1.34375em
transition: border-color 300ms
&:hover
border: 2px solid black
padding: 7px
.cre-image-config-label
top: -10px
left: 3px
.cre-image-config-label
position: absolute
top: -9px
left: 4px
margin-bottom: 0
background-color: white
padding: 0 5px
color: rgba(0, 0, 0, 0.52)
font-size: .8em
.image-wrapper
width: 200px
text-align: right
hr
margin: 0 0 .5em
background-color: rgba(0, 0, 0, 0.42)
img
border: 2px solid black
mat-hint
margin-top: .2em

View File

@ -0,0 +1,228 @@
import {
AfterViewInit,
Component,
ContentChild,
Directive,
ElementRef,
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 {ErrorService} from '../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {formatDateTime, readFile} from '../shared/utils/utils'
import {FormControl, Validators} from '@angular/forms'
import {ConfirmBoxComponent} from '../shared/components/confirm-box/confirm-box.component'
@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'
})
export class CreConfigList {
}
@Component({
selector: 'cre-config-actions',
templateUrl: 'config-actions.html'
})
export class CreConfigActions {
}
@Component({
selector: 'cre-config-section',
templateUrl: 'config-section.html'
})
export class CreConfigSection {
@ContentChild(CreConfigActions) actions: CreConfigActions
get hasActions(): boolean {
return this.actions !== undefined
}
}
@Component({
selector: 'cre-config',
templateUrl: 'config.html',
styleUrls: ['config.sass']
})
export class CreConfig extends SubscribingComponent {
@Input() config: { key: string, control: FormControl }
@ContentChild(CreConfigLabel, {static: true}) label: CreConfigLabel
@ContentChild(CreConfigTooltip, {static: true}) tooltip: CreConfigTooltip
configuration: Config | null
constructor(
private configService: ConfigService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router
) {
super(errorService, activatedRoute, router)
}
ngOnInit() {
super.ngOnInit()
this.subscribe(
this.configService.get(this.config.key),
config => this.setConfig(config)
)
}
setConfig(config: Config) {
this.configuration = config
this.config.control.setValue(config.content)
if (!config.editable) {
this.config.control.disable()
}
}
get lastUpdated(): string {
return formatDateTime(this.configuration.lastUpdated)
}
}
@Component({
selector: 'cre-image-config',
templateUrl: 'image-config.html',
styleUrls: ['config.sass'],
encapsulation: ViewEncapsulation.None
})
export class CreImageConfig extends CreConfig {
@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)
}
}
@Component({
selector: 'cre-bool-config',
templateUrl: 'bool-config.html'
})
export class CreBoolConfig extends CreConfig {
setConfig(config: Config) {
super.setConfig(config);
this.config.control.setValue(config.content === "true")
}
}
@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,
TOUCH_UP_KIT_CACHE_PDF: Config.TOUCH_UP_KIT_CACHE_PDF,
APP_VERSION: Config.APP_VERSION,
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[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[key]}
}
save() {
this.subscribe(
this.configService.set(this.controls),
() => this.reload()
)
}
restart() {
this.subscribe(
this.configService.restart(),
() => this.restartConfirmBox.show()
)
}
reload() {
window.location.reload()
}
}

View File

@ -0,0 +1,105 @@
<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>Kits de retouche</cre-config-label>
<cre-config-list class="pt-2">
<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-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.APP_VERSION)">
<cre-config-label>Version de Color Recipes Explorer</cre-config-label>
</cre-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

@ -0,0 +1,26 @@
<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 : configuration.content"
[attr.width]="previewWidth ? previewWidth : null"
class="mat-elevation-z3"/>
</div>
<mat-hint>Dernière mise à jour:<br/>{{lastUpdated}}</mat-hint>
</div>
</div>

View File

@ -19,7 +19,8 @@
<cre-user-menu></cre-user-menu>
<img
class="flex-grow-0"
src="assets/logo.png"
[src]="logoUrl"
alt="Color Recipes Explorer"
title="Color Recipes Explorer"/>
title="Color Recipes Explorer"
height="70px"/>
</header>

View File

@ -5,6 +5,8 @@ import {Permission} from '../../model/user'
import {AccountService} from '../../../accounts/services/account.service'
import {SubscribingComponent} from '../subscribing.component'
import {ErrorService} from '../../service/error.service'
import {ConfigService} from '../../service/config.service'
import {environment} from '../../../../../environments/environment'
@Component({
selector: 'cre-header',
@ -22,6 +24,7 @@ export class HeaderComponent extends SubscribingComponent {
constructor(
private accountService: AccountService,
private configService: ConfigService,
private appState: AppState,
errorService: ErrorService,
router: Router,
@ -62,6 +65,10 @@ export class HeaderComponent extends SubscribingComponent {
super.ngOnDestroy()
}
get logoUrl(): string {
return environment.apiUrl + "/file?path=images%2Flogo"
}
set activeLink(link: string) {
this._activeLink = link
this.router.navigate([link])

View File

@ -0,0 +1,3 @@
<mat-checkbox [formControl]="control">
{{label}}
</mat-checkbox>

View File

@ -0,0 +1,10 @@
<div class="d-flex flex-row">
<cre-accent-button (click)="fileInput.click()">Choisir un fichier</cre-accent-button>
<div class="ml-3">
<p *ngIf="selectedFileName">{{selectedFileName}}</p>
<p *ngIf="!selectedFileName" class="light-text">Aucun fichier sélectionné</p>
</div>
</div>
<input #fileInput type="file" hidden [accept]="accept" (change)="onFileSelected($event)">

View File

@ -9,6 +9,7 @@
[(ngModel)]="value"
[required]="required"
[autocomplete]="autocomplete ? 'on' : 'off'"
[disabled]="disabled"
(change)="valueChange.emit(value)"/>
<input
*ngIf="control"
@ -19,7 +20,13 @@
[required]="required"
[formControl]="control"
[autocomplete]="autocomplete ? 'on' : 'off'"/>
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
<mat-icon
matSuffix
[svgIcon]="icon"
[attr.title]="iconTitle ? iconTitle : null"
[class]="'color-' + iconColor">
</mat-icon>
<mat-hint *ngIf="hint">{{hint}}</mat-hint>
<mat-error *ngIf="control && control.invalid">
<span *ngIf="control.errors.required">Ce champ est requis</span>
<ng-container *ngIf="errors" [ngTemplateOutlet]="errors"

View File

@ -0,0 +1,14 @@
cre-input
display: block
width: inherit
mat-form-field
width: inherit
cre-file-input
p
line-height: 36px
margin-bottom: 0
cre-checkbox-input mat-checkbox
margin-top: .5rem

View File

@ -1,9 +1,9 @@
import {NgModule} from '@angular/core'
import {
CreAutocompleteInputComponent,
CreAutocompleteInputComponent, CreCheckboxInputComponent,
CreChipComboBoxComponent,
CreChipInputComponent,
CreComboBoxComponent,
CreComboBoxComponent, CreFileInputComponent,
CreInputComponent
} from './inputs'
import {MatInputModule} from '@angular/material/input'
@ -14,6 +14,9 @@ import {MatCardModule} from '@angular/material/card'
import {MatListModule} from '@angular/material/list'
import {MatAutocompleteModule} from '@angular/material/autocomplete'
import {MatChipsModule} from '@angular/material/chips'
import {CreButtonsModule} from '../buttons/buttons.module'
import {CreBoolConfig} from '../../../configuration/config'
import {MatCheckboxModule} from '@angular/material/checkbox'
@NgModule({
declarations: [
@ -21,25 +24,32 @@ import {MatChipsModule} from '@angular/material/chips'
CreChipInputComponent,
CreAutocompleteInputComponent,
CreComboBoxComponent,
CreChipComboBoxComponent
CreChipComboBoxComponent,
CreFileInputComponent,
CreBoolConfig,
CreCheckboxInputComponent
],
imports: [
MatInputModule,
MatAutocompleteModule,
MatIconModule,
ReactiveFormsModule,
CommonModule,
MatCardModule,
MatListModule,
MatChipsModule,
FormsModule,
CreButtonsModule,
MatCheckboxModule,
],
imports: [
MatInputModule,
MatAutocompleteModule,
MatIconModule,
ReactiveFormsModule,
CommonModule,
MatCardModule,
MatListModule,
MatChipsModule,
FormsModule,
],
exports: [
CreInputComponent,
CreComboBoxComponent,
CreChipComboBoxComponent,
CreChipInputComponent,
CreAutocompleteInputComponent
CreAutocompleteInputComponent,
CreFileInputComponent,
CreBoolConfig
]
})
export class CreInputsModule {

View File

@ -17,22 +17,28 @@ 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 {MatFormFieldAppearance} from '@angular/material/form-field'
@Component({
selector: 'cre-input',
templateUrl: 'input.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
styleUrls: ['input.sass']
})
export class CreInputComponent {
@Input() control: FormControl | null
@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>()
@ -174,6 +180,50 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O
}
}
@Component({
selector: 'cre-checkbox-input',
templateUrl: 'checkbox.html'
})
export class CreCheckboxInputComponent {
@Input() label: string
@Input() control: FormControl
}
@Component({
selector: 'cre-file-input',
templateUrl: 'file-input.html'
})
export class CreFileInputComponent implements OnInit {
@Input() label: string
@Input() icon: string
@Input() accept = ''
@Input() control: FormControl | null
@Output() selection = new EventEmitter<File>()
@Output() invalidFormat = new EventEmitter<void>()
private acceptedMediaTypes: string[]
ngOnInit(): void {
this.acceptedMediaTypes = this.accept.split(',')
}
selectedFile: File | null
selectedFileName: string
onFileSelected(event: any) {
this.selectedFile = event.target.files[0]
if (this.acceptedMediaTypes.indexOf(this.selectedFile.type) >= 0) {
this.selectedFileName = this.selectedFile.name
this.control?.setValue(this.selectedFile)
this.control?.markAsDirty()
this.selection.emit(this.selectedFile)
} else {
this.invalidFormat.emit()
}
}
}
export class ComboBoxEntry {
constructor(
public key: any,

View File

@ -0,0 +1,24 @@
export class Config {
static readonly INSTANCE_NAME = 'instance.name'
static readonly INSTANCE_LOGO_PATH = 'instance.logo.path'
static readonly INSTANCE_ICON_PATH = 'instance.icon.path'
static readonly INSTANCE_URL = 'instance.url'
static readonly DATABASE_URL = 'database.url'
static readonly DATABASE_USER = 'database.user'
static readonly DATABASE_PASSWORD = 'database.password'
static readonly DATABASE_VERSION = 'database.version.supported'
static readonly TOUCH_UP_KIT_CACHE_PDF = 'touchupkit.pdf.cache'
static readonly EMERGENCY_MODE = 'env.emergency'
static readonly APP_VERSION = 'env.version'
static readonly JAVA_VERSION = 'env.java.version'
static readonly OPERATING_SYSTEM = 'env.os'
constructor(
public key: string,
public content: string,
public lastUpdated: string,
public requireRestart: boolean,
public editable: boolean
) {
}
}

View File

@ -0,0 +1,61 @@
import {Injectable} from '@angular/core'
import {Config} from '../model/config.model'
import {Observable} from 'rxjs'
import {ApiService} from './api.service'
import {FormControl} from '@angular/forms'
const imageConfigsKeys = [
Config.INSTANCE_LOGO_PATH,
Config.INSTANCE_ICON_PATH
]
@Injectable({
providedIn: 'root'
})
export class ConfigService {
constructor(
private api: ApiService
) {
}
get(key: string): Observable<Config> {
return this.api.get<Config>(`/config/${key}`)
}
set(configs: Map<string, FormControl>): Observable<void> {
const body = []
for (let key in configs) {
const control = configs[key]
if (control.dirty && key.indexOf('path') < 0) {
body.push({key, content: control.value})
}
}
const subscriptions = []
imageConfigsKeys.forEach(key => {
if (configs[key].dirty) {
subscriptions.push(this.setImage(key, configs[key].value))
}
})
while (subscriptions.length > 0) {
const subscription = subscriptions.pop().subscribe({
next: () => subscription.unsubscribe()
})
}
return this.api.put<void>('/config', body)
}
setImage(key: string, image: File): Observable<void> {
const body = new FormData()
body.append('key', key)
body.append('image', image)
return this.api.put<void>('/config/image', body)
}
restart(): Observable<void> {
return this.api.post<void>('/config/restart')
}
}

View File

@ -1,5 +1,5 @@
/** Returns [value] if it is not null or [or]. */
import {DateTimeFormatter, LocalDate} from 'js-joda'
import {DateTimeFormatter, LocalDate, LocalDateTime} from 'js-joda'
import {TouchUpKit} from '../model/touch-up-kit.model'
import {environment} from '../../../../environments/environment'
@ -32,13 +32,27 @@ export function openRawUrl(url: string) {
const dateFormatter = DateTimeFormatter
.ofPattern('dd-MM-yyyy')
const dateTimeFormatter = DateTimeFormatter
.ofPattern('dd-MM-yyyy HH:mm:ss')
export function formatDate(date: string): string {
return LocalDate.parse(date).format(dateFormatter)
}
export function formatDateTime(dateTime: string): string {
return LocalDateTime.parse(dateTime).format(dateTimeFormatter)
}
export function reduceDashes(arr: string[]): string {
return arr.reduce((acc, cur) => {
return `${acc} - ${cur}`
})
}
export function readFile(file: File, consumer: (any) => void) {
const reader = new FileReader()
reader.onload = (e: any) => {
consumer(e.target.result)
}
reader.readAsDataURL(file)
}

View File

@ -12,5 +12,6 @@ export class AdministrationComponent extends SubMenuComponent {
links: NavLink[] = [
{route: '/admin/user', title: 'Utilisateurs', permission: Permission.VIEW_USERS},
{route: '/admin/group', title: 'Groupes', permission: Permission.VIEW_USERS},
{route: '/admin/config', title: 'Configuration', permission: Permission.ADMIN}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -5,7 +5,7 @@
<title>Color Recipes Explorer</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="assets/favicon.png">
<link id="favicon" rel="icon" type="image/x-icon" href="assets/favicon.png">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>

View File

@ -170,7 +170,7 @@ div.empty
.alert p
margin-bottom: 0
.empty-text
.light-text
color: rgba(0, 0, 0, 0.54)
.dark-background