Merge branch 'features' into 'master'
Ajout des configurations See merge request color-recipes-explorer/frontend!25
This commit is contained in:
commit
1b7fbe335a
|
@ -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',
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
<div class="d-flex flex-row justify-content-between">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 { }
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<mat-checkbox [formControl]="control">
|
||||
{{label}}
|
||||
</mat-checkbox>
|
|
@ -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)">
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -170,7 +170,7 @@ div.empty
|
|||
.alert p
|
||||
margin-bottom: 0
|
||||
|
||||
.empty-text
|
||||
.light-text
|
||||
color: rgba(0, 0, 0, 0.54)
|
||||
|
||||
.dark-background
|
||||
|
|
Loading…
Reference in New Issue