From 28cf2d04cd4a65aa76fe953dcdbb240888a64099 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sat, 7 Aug 2021 22:05:46 -0400 Subject: [PATCH 1/4] =?UTF-8?q?#2=20Ajout=20du=20support=20pour=20les=20co?= =?UTF-8?q?nfiguration=20s=C3=A9curis=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 8 +- src/_variables.scss | 20 +++++ .../mix-table/mix-table.component.sass | 2 +- .../modules/configuration/config.module.ts | 5 +- src/app/modules/configuration/config.sass | 3 + src/app/modules/configuration/config.ts | 49 ++++++++++-- src/app/modules/configuration/editor.html | 28 ++++--- src/app/modules/configuration/secure.html | 25 +++++++ .../shared/components/buttons/buttons.sass | 6 ++ .../shared/components/buttons/buttons.ts | 15 ++-- .../components/dialogs/dialogs.module.ts | 21 ++++++ .../shared/components/dialogs/dialogs.scss | 26 +++++++ .../shared/components/dialogs/dialogs.ts | 74 +++++++++++++++++++ .../shared/components/dialogs/prompt.html | 10 +++ .../info-banner/info-banner.component.sass | 2 +- .../shared/components/tables/table.sass | 2 +- .../user-info/user-menu.component.sass | 2 +- src/app/modules/shared/model/config.model.ts | 53 +++++++++++-- .../modules/shared/service/config.service.ts | 46 +++++------- src/app/modules/shared/shared.module.ts | 4 +- src/app/modules/shared/utils/map.utils.ts | 17 +++++ .../touch-up-kit/components/finish.sass | 2 +- src/custom-theme.scss | 4 - src/styles.sass | 4 +- 24 files changed, 353 insertions(+), 75 deletions(-) create mode 100644 src/_variables.scss create mode 100644 src/app/modules/configuration/secure.html create mode 100644 src/app/modules/shared/components/buttons/buttons.sass create mode 100644 src/app/modules/shared/components/dialogs/dialogs.module.ts create mode 100644 src/app/modules/shared/components/dialogs/dialogs.scss create mode 100644 src/app/modules/shared/components/dialogs/dialogs.ts create mode 100644 src/app/modules/shared/components/dialogs/prompt.html create mode 100644 src/app/modules/shared/utils/map.utils.ts diff --git a/docker-compose.yml b/docker-compose.yml index 135ab08..11391cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,18 +8,18 @@ services: MYSQL_ROOT_PASSWORD: "pass" MYSQL_DATABASE: "cre" ports: - - 3306:3306 + - "3306:3306" backend: - image: fyloz.dev:5443/color-recipes-explorer/backend:master + image: registry.fyloz.dev:5443/colorrecipesexplorer/backend:latest environment: spring_profiles_active: "mysql,debug" cre_database_url: "mysql://database:3306/cre" cre_database_username: "root" cre_database_password: "pass" - CRE_ENABLE_DB_UPDATE: 0 + CRE_ENABLE_DB_UPDATE: 1 server_port: 9090 ports: - - 9090:9090 + - "9090:9090" volumes: - cre_data:/usr/bin/cre/data - cre_config:/usr/bin/cre/config diff --git a/src/_variables.scss b/src/_variables.scss new file mode 100644 index 0000000..4ff7528 --- /dev/null +++ b/src/_variables.scss @@ -0,0 +1,20 @@ +@import "assets/sass/modules/fonts"; +@import "custom-theme"; +@import "~material-design-icons/iconfont/material-icons.css"; + +// Spacing +$spacer: 1rem; +$spacers: ( + 1: $spacer * 0.5, + 2: $spacer * 0.75, + 3: $spacer, + 4: $spacer * 1.5, + 5: $spacer * 2 +); + +// Colors +$color-primary: map-get($theme-primary, 500); +$text-color-primary: white; + +$color-accent: map-get($theme-accent, 500); +$color-warn: map-get($theme-error, 500); diff --git a/src/app/modules/colors/components/mix-table/mix-table.component.sass b/src/app/modules/colors/components/mix-table/mix-table.component.sass index 927de1b..1f8c7e4 100644 --- a/src/app/modules/colors/components/mix-table/mix-table.component.sass +++ b/src/app/modules/colors/components/mix-table/mix-table.component.sass @@ -1,4 +1,4 @@ -@import '../../../../../custom-theme' +@import "~src/variables" mat-expansion-panel width: 48rem diff --git a/src/app/modules/configuration/config.module.ts b/src/app/modules/configuration/config.module.ts index 8127ec2..6d16826 100644 --- a/src/app/modules/configuration/config.module.ts +++ b/src/app/modules/configuration/config.module.ts @@ -7,7 +7,7 @@ import { CreImageConfig, CreConfigList, CreConfigActions, - CreConfigTooltip, CrePeriodConfig, CreBoolConfig, CreDateConfig + CreConfigTooltip, CrePeriodConfig, CreBoolConfig, CreDateConfig, CreSecureConfig } from './config' import {SharedModule} from '../shared/shared.module' import {CreInputsModule} from '../shared/components/inputs/inputs.module' @@ -26,7 +26,8 @@ import {CreButtonsModule} from '../shared/components/buttons/buttons.module' CreConfigActions, CreBoolConfig, CrePeriodConfig, - CreDateConfig + CreDateConfig, + CreSecureConfig ], imports: [ SharedModule, diff --git a/src/app/modules/configuration/config.sass b/src/app/modules/configuration/config.sass index 6812169..062d34e 100644 --- a/src/app/modules/configuration/config.sass +++ b/src/app/modules/configuration/config.sass @@ -50,3 +50,6 @@ cre-image-config mat-hint margin-top: .2em + +//cre-secure-config button +// diff --git a/src/app/modules/configuration/config.ts b/src/app/modules/configuration/config.ts index a40e7de..9ba1d5b 100644 --- a/src/app/modules/configuration/config.ts +++ b/src/app/modules/configuration/config.ts @@ -18,7 +18,8 @@ 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 {environment} from '../../../environments/environment' +import {MatDialog} from '@angular/material/dialog' +import {CrePromptDialog} from '../shared/components/dialogs/dialogs' @Directive({ selector: 'cre-config-label' @@ -108,7 +109,7 @@ export class CreConfig extends SubscribingComponent { ) } - setConfig(config: Config) { + protected setConfig(config: Config) { this.configuration = config this.config.control.setValue(config.content) if (!config.editable) { @@ -158,7 +159,7 @@ export class CreImageConfig extends CreConfig { templateUrl: 'bool.html' }) export class CreBoolConfig extends CreConfig { - setConfig(config: Config) { + protected setConfig(config: Config) { super.setConfig(config) this.config.control.setValue(config.content === 'true') } @@ -176,12 +177,46 @@ export class CrePeriodConfig extends CreConfig { templateUrl: 'date.html' }) export class CreDateConfig extends CreConfig { - setConfig(config: Config) { - super.setConfig(config); + protected setConfig(config: Config) { + super.setConfig(config) this.config.control.setValue(formatDate(config.content)) } } +@Component({ + selector: 'cre-secure-config', + templateUrl: 'secure.html' +}) +export class CreSecureConfig extends CreConfig { + @ViewChild(CrePromptDialog) dialog: CrePromptDialog + + @Input() buttonLabel: string + + private initialValue: string | null + + constructor( + configService: ConfigService, + errorService: ErrorService, + activatedRoute: ActivatedRoute, + router: Router + ) { + super(configService, errorService, activatedRoute, router); + } + + protected setConfig(config: Config) { + super.setConfig(config) + } + + onOpen() { + this.initialValue = this.config.control.value + this.dialog.show() + } + + onCancel() { + this.config.control.setValue(this.initialValue) + } +} + @Component({ selector: 'cre-config-editor', templateUrl: 'editor.html' @@ -218,7 +253,7 @@ export class CreConfigEditor extends ErrorHandlingComponent { super(errorService, activatedRoute, router) for (let key in this.keys) { - this.controls[this.keys[key]] = new FormControl(null, Validators.required) + this.controls.set(this.keys[key], new FormControl(null, Validators.required)) } } @@ -232,7 +267,7 @@ export class CreConfigEditor extends ErrorHandlingComponent { } getConfig(key: string) { - return {key, control: this.controls[key]} + return {key, control: this.controls.get(key)} } save() { diff --git a/src/app/modules/configuration/editor.html b/src/app/modules/configuration/editor.html index 66cd181..ec78574 100644 --- a/src/app/modules/configuration/editor.html +++ b/src/app/modules/configuration/editor.html @@ -11,12 +11,12 @@ Apparence - - - - - - + + + + + + @@ -50,7 +50,8 @@ Période d'expiration des kits de retouches complets - 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. + 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. @@ -83,9 +84,15 @@ Utilisateur de la base de données - + + + + + Mot de passe de la base de données - + Version de la base de données @@ -114,7 +121,8 @@ - diff --git a/src/app/modules/configuration/secure.html b/src/app/modules/configuration/secure.html new file mode 100644 index 0000000..1c1fcae --- /dev/null +++ b/src/app/modules/configuration/secure.html @@ -0,0 +1,25 @@ +
+ + {{buttonLabel}} + + + + + + + + +
diff --git a/src/app/modules/shared/components/buttons/buttons.sass b/src/app/modules/shared/components/buttons/buttons.sass new file mode 100644 index 0000000..3c36340 --- /dev/null +++ b/src/app/modules/shared/components/buttons/buttons.sass @@ -0,0 +1,6 @@ +cre-button, cre-primary-button, cre-accent-button, cre-warn-button + display: inline-block + width: inherit + + button + width: 100% diff --git a/src/app/modules/shared/components/buttons/buttons.ts b/src/app/modules/shared/components/buttons/buttons.ts index 7b3679e..a388f90 100644 --- a/src/app/modules/shared/components/buttons/buttons.ts +++ b/src/app/modules/shared/components/buttons/buttons.ts @@ -1,4 +1,4 @@ -import {Component, Input} from '@angular/core' +import {Component, Input, ViewEncapsulation} from '@angular/core' import {ThemePalette} from '@angular/material/core' @Component({ @@ -7,7 +7,9 @@ import {ThemePalette} from '@angular/material/core' - ` + `, + styleUrls: ['buttons.sass'], + encapsulation: ViewEncapsulation.None }) export class CreButtonComponent { @Input() color: ThemePalette @@ -20,7 +22,8 @@ export class CreButtonComponent { - ` + `, + styleUrls: ['buttons.sass'] }) export class CrePrimaryButtonComponent { @Input() disabled = false @@ -32,7 +35,8 @@ export class CrePrimaryButtonComponent { - ` + `, + styleUrls: ['buttons.sass'] }) export class CreAccentButtonComponent { @Input() disabled = false @@ -44,7 +48,8 @@ export class CreAccentButtonComponent { - ` + `, + styleUrls: ['buttons.sass'] }) export class CreWarnButtonComponent { @Input() disabled = false diff --git a/src/app/modules/shared/components/dialogs/dialogs.module.ts b/src/app/modules/shared/components/dialogs/dialogs.module.ts new file mode 100644 index 0000000..ac98cdc --- /dev/null +++ b/src/app/modules/shared/components/dialogs/dialogs.module.ts @@ -0,0 +1,21 @@ +import {NgModule} from '@angular/core' +import {CreDialogBody, CrePromptDialog} from './dialogs' +import {MatDialogModule} from '@angular/material/dialog' +import {CreButtonsModule} from '../buttons/buttons.module' + +@NgModule({ + declarations: [ + CrePromptDialog, + CreDialogBody + ], + exports: [ + CrePromptDialog, + CreDialogBody + ], + imports: [ + MatDialogModule, + CreButtonsModule + ] +}) +export class CreDialogsModule { +} diff --git a/src/app/modules/shared/components/dialogs/dialogs.scss b/src/app/modules/shared/components/dialogs/dialogs.scss new file mode 100644 index 0000000..3653e6c --- /dev/null +++ b/src/app/modules/shared/components/dialogs/dialogs.scss @@ -0,0 +1,26 @@ +@import "~src/variables"; + +.cre-dialog-panel { + min-width: 20rem; + + mat-dialog-container { + padding: 0; + + .mat-dialog-title, .mat-dialog-content, .mat-dialog-actions { + margin: 0; + padding: $spacer; + } + + .mat-dialog-title { + background-color: $color-primary; + color: $text-color-primary; + } + + .mat-dialog-actions { + min-height: auto; + justify-content: end; + gap: map-get($spacers, 1); + padding-top: 0; + } + } +} diff --git a/src/app/modules/shared/components/dialogs/dialogs.ts b/src/app/modules/shared/components/dialogs/dialogs.ts new file mode 100644 index 0000000..a06f602 --- /dev/null +++ b/src/app/modules/shared/components/dialogs/dialogs.ts @@ -0,0 +1,74 @@ +import {Component, Directive, EventEmitter, Input, Output, TemplateRef, ViewChild, ViewEncapsulation} from '@angular/core' +import {MatDialog, MatDialogRef} from '@angular/material/dialog' + +@Directive({ + selector: 'cre-dialog-body' +}) +export class CreDialogBody { +} + +@Directive() +abstract class CreDialog { + @ViewChild(TemplateRef) dialogTemplate: TemplateRef + + @Output() cancel = new EventEmitter(); + @Output() continue = new EventEmitter(); + + private dialogRef: MatDialogRef> | null + + constructor( + protected dialog: MatDialog + ) { + } + + protected abstract get data(): D + + show() { + this.open() + } + + onCancel() { + this.close() + this.cancel.emit(); + } + + onContinue() { + this.close() + this.continue.emit(); + } + + private open() { + const config = { + panelClass: 'cre-dialog-panel', + data: this.data + } + this.dialogRef = this.dialog.open(this.dialogTemplate, config) + } + + private close() { + this.dialogRef.close() + } +} + +@Component({ + selector: 'cre-prompt-dialog', + templateUrl: 'prompt.html', + styleUrls: ['dialogs.scss'], + encapsulation: ViewEncapsulation.None +}) +export class CrePromptDialog extends CreDialog { + @Input() title: string + + protected get data(): CrePromptDialogData { + return { + title: this.title + } + } +} + +abstract class CreDialogData { + title: string +} + +class CrePromptDialogData extends CreDialogData { +} diff --git a/src/app/modules/shared/components/dialogs/prompt.html b/src/app/modules/shared/components/dialogs/prompt.html new file mode 100644 index 0000000..0d14939 --- /dev/null +++ b/src/app/modules/shared/components/dialogs/prompt.html @@ -0,0 +1,10 @@ + +

{{data.title}}

+
+ +
+
+ Annuler + Continuer +
+
diff --git a/src/app/modules/shared/components/info-banner/info-banner.component.sass b/src/app/modules/shared/components/info-banner/info-banner.component.sass index 558af2c..31c5568 100644 --- a/src/app/modules/shared/components/info-banner/info-banner.component.sass +++ b/src/app/modules/shared/components/info-banner/info-banner.component.sass @@ -1,4 +1,4 @@ -@import "~src/custom-theme" +@import "~src/variables" .info-banner-wrapper background-color: $color-primary diff --git a/src/app/modules/shared/components/tables/table.sass b/src/app/modules/shared/components/tables/table.sass index 4458b6e..9cbb67d 100644 --- a/src/app/modules/shared/components/tables/table.sass +++ b/src/app/modules/shared/components/tables/table.sass @@ -1,4 +1,4 @@ -@import "~src/custom-theme" +@import "../../../../../custom-theme" cre-table display: block diff --git a/src/app/modules/shared/components/user-info/user-menu.component.sass b/src/app/modules/shared/components/user-info/user-menu.component.sass index d3ffcd7..329544c 100644 --- a/src/app/modules/shared/components/user-info/user-menu.component.sass +++ b/src/app/modules/shared/components/user-info/user-menu.component.sass @@ -1,4 +1,4 @@ -@import "../../../../../custom-theme" +@import "~src/variables" p, labeled-icon margin: 0 diff --git a/src/app/modules/shared/model/config.model.ts b/src/app/modules/shared/model/config.model.ts index 642a736..80a559a 100644 --- a/src/app/modules/shared/model/config.model.ts +++ b/src/app/modules/shared/model/config.model.ts @@ -1,3 +1,6 @@ +import {Form, FormControl} from '@angular/forms' +import {filterMap} from '../utils/map.utils' + export class Config { static readonly INSTANCE_NAME = 'instance.name' static readonly INSTANCE_LOGO_PATH = 'instance.logo.path' @@ -16,12 +19,46 @@ export class Config { 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 - ) { - } + static readonly IMAGE_CONFIG_KEYS = [ + Config.INSTANCE_LOGO_PATH, + Config.INSTANCE_ICON_PATH + ] + + public key: string + public requireRestart: boolean + public editable: boolean + public content?: string + public lastUpdated?: string +} + +export class ConfigKeyContent { + public key: string + public content: string +} + +export function filterConfigKeyControlMap(map: Map): Map { + 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 + control.value !== undefined && + control.value !== null + }) +} + +export function filterImageConfigKeyControlMap(map: Map): Map { + return filterMap(map, (key, control) => { + return Config.IMAGE_CONFIG_KEYS.indexOf(key) >= 0 && control.dirty + }) +} + +export function mapToConfigKeyContent(key: string, control: FormControl): ConfigKeyContent { + return {key, content: control.value} +} + +export function mapToConfigKeyContentArray(map: Map): ConfigKeyContent[] { + const array: ConfigKeyContent[] = [] + map.forEach((control, key) => { + array.push(mapToConfigKeyContent(key, control)) + }) + return array } diff --git a/src/app/modules/shared/service/config.service.ts b/src/app/modules/shared/service/config.service.ts index 880b697..7ebe376 100644 --- a/src/app/modules/shared/service/config.service.ts +++ b/src/app/modules/shared/service/config.service.ts @@ -1,13 +1,9 @@ import {Injectable} from '@angular/core' -import {Config} from '../model/config.model' +import {Config, filterConfigKeyControlMap, filterImageConfigKeyControlMap, mapToConfigKeyContentArray} 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 -] +import {transformMap} from '../utils/map.utils' @Injectable({ providedIn: 'root' @@ -23,27 +19,10 @@ export class ConfigService { } set(configs: Map): Observable { - 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() - }) - } + const body = mapToConfigKeyContentArray(filterConfigKeyControlMap(configs)) + const imageConfigs = filterImageConfigKeyControlMap(configs) + this.setImages(imageConfigs) return this.api.put('/config', body) } @@ -58,4 +37,19 @@ export class ConfigService { restart(): Observable { return this.api.post('/config/restart') } + + private setImages(configs: Map) { + const subscriptions = this.getImageConfigsSubscriptions(configs) + while (subscriptions.length > 0) { + const subscription = subscriptions.pop().subscribe({ + next: () => subscription.unsubscribe() + }) + } + } + + private getImageConfigsSubscriptions(configs: Map): Observable[] { + return transformMap(configs, (key, control) => { + return this.setImage(key, control.value) + }) + } } diff --git a/src/app/modules/shared/shared.module.ts b/src/app/modules/shared/shared.module.ts index 1d7d62e..05aaf24 100644 --- a/src/app/modules/shared/shared.module.ts +++ b/src/app/modules/shared/shared.module.ts @@ -36,6 +36,7 @@ import {InfoBannerModule} from './components/info-banner/info-banner.module' import {CreFormsModule} from './components/forms/forms.module' import {VarDirective} from './directives/var.directive' import {CreColorPreview} from './components/color-preview/color-preview' +import {CreDialogsModule} from './components/dialogs/dialogs.module' @NgModule({ declarations: [VarDirective, HeaderComponent, UserMenuComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent, LoadingWheelComponent, CreColorPreview], @@ -71,7 +72,8 @@ import {CreColorPreview} from './components/color-preview/color-preview' InfoBannerModule, CreFormsModule, VarDirective, - CreColorPreview + CreColorPreview, + CreDialogsModule ], imports: [ MatTabsModule, diff --git a/src/app/modules/shared/utils/map.utils.ts b/src/app/modules/shared/utils/map.utils.ts new file mode 100644 index 0000000..ba99aec --- /dev/null +++ b/src/app/modules/shared/utils/map.utils.ts @@ -0,0 +1,17 @@ +export function filterMap(map: Map, predicate: (key: K, value: V) => boolean): Map { + const filteredMap = new Map() + map.forEach((value, key) => { + if (predicate(key, value)) { + filteredMap.set(key, value) + } + }) + return filteredMap +} + +export function transformMap(map: Map, transform: (key: K, value: V) => T): T[] { + const transformedArray = [] + map.forEach((value, key) => { + transformedArray.push(transform(key, value)) + }) + return transformedArray +} diff --git a/src/app/modules/touch-up-kit/components/finish.sass b/src/app/modules/touch-up-kit/components/finish.sass index 899e060..30c9991 100644 --- a/src/app/modules/touch-up-kit/components/finish.sass +++ b/src/app/modules/touch-up-kit/components/finish.sass @@ -1,4 +1,4 @@ -@import '~src/custom-theme' +@import '../../../../custom-theme' .touchupkit-finish-container display: inline-block diff --git a/src/custom-theme.scss b/src/custom-theme.scss index 5f9a53b..5f766ef 100644 --- a/src/custom-theme.scss +++ b/src/custom-theme.scss @@ -122,10 +122,6 @@ $color-recipes-explorer-frontend-theme: mat-light-theme($theme-primary, $theme-a // that you are using. @include angular-material-theme($color-recipes-explorer-frontend-theme); -$color-primary: map-get($theme-primary, 500); -$color-accent: map-get($theme-accent, 500); -$color-warn: map-get($theme-error, 500); - html, body { height: 100%; diff --git a/src/styles.sass b/src/styles.sass index e026f75..c096222 100644 --- a/src/styles.sass +++ b/src/styles.sass @@ -1,6 +1,4 @@ -@import 'assets/sass/modules/_fonts.sass' -@import "custom-theme" -@import "~material-design-icons/iconfont/material-icons.css" +@import "variables" mat-card padding: 0 !important From 1a5c09cb4166eaf67b4995ef8ab0b6f30c9589bf Mon Sep 17 00:00:00 2001 From: FyloZ Date: Mon, 9 Aug 2021 20:52:36 -0400 Subject: [PATCH 2/4] =?UTF-8?q?#2=20Am=C3=A9lioration=20g=C3=A9n=C3=A9rale?= =?UTF-8?q?s=20des=20configurations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/app-routing.module.ts | 3 +- src/app/modules/configuration/bool.html | 4 - .../modules/configuration/config-bool.html | 6 + .../configuration/config-container.html | 6 + .../modules/configuration/config-date.html | 12 + .../modules/configuration/config-editor.html | 120 +++++++++ .../modules/configuration/config-editor.ts | 94 +++++++ .../modules/configuration/config-image.html | 28 ++ .../modules/configuration/config-period.html | 7 + .../modules/configuration/config-section.html | 4 +- .../modules/configuration/config-secure.html | 24 ++ .../modules/configuration/config-text.html | 11 + src/app/modules/configuration/config.html | 12 - .../modules/configuration/config.module.ts | 33 +-- src/app/modules/configuration/config.sass | 7 +- src/app/modules/configuration/config.ts | 246 +++++------------- src/app/modules/configuration/date.html | 12 - src/app/modules/configuration/editor.html | 128 --------- src/app/modules/configuration/image.html | 26 -- src/app/modules/configuration/period.html | 7 - src/app/modules/configuration/secure.html | 25 -- .../shared/components/buttons/buttons.ts | 12 +- .../shared/components/inputs/input.html | 36 ++- .../shared/components/inputs/inputs.ts | 50 +++- src/app/modules/shared/model/config.model.ts | 27 +- .../modules/shared/service/config.service.ts | 20 +- 26 files changed, 499 insertions(+), 461 deletions(-) delete mode 100644 src/app/modules/configuration/bool.html create mode 100644 src/app/modules/configuration/config-bool.html create mode 100644 src/app/modules/configuration/config-container.html create mode 100644 src/app/modules/configuration/config-date.html create mode 100644 src/app/modules/configuration/config-editor.html create mode 100644 src/app/modules/configuration/config-editor.ts create mode 100644 src/app/modules/configuration/config-image.html create mode 100644 src/app/modules/configuration/config-period.html create mode 100644 src/app/modules/configuration/config-secure.html create mode 100644 src/app/modules/configuration/config-text.html delete mode 100644 src/app/modules/configuration/config.html delete mode 100644 src/app/modules/configuration/date.html delete mode 100644 src/app/modules/configuration/editor.html delete mode 100644 src/app/modules/configuration/image.html delete mode 100644 src/app/modules/configuration/period.html delete mode 100644 src/app/modules/configuration/secure.html diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 24a2458..a0ef43d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -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', diff --git a/src/app/modules/configuration/bool.html b/src/app/modules/configuration/bool.html deleted file mode 100644 index 48709db..0000000 --- a/src/app/modules/configuration/bool.html +++ /dev/null @@ -1,4 +0,0 @@ -
- - {{lastUpdated}} -
diff --git a/src/app/modules/configuration/config-bool.html b/src/app/modules/configuration/config-bool.html new file mode 100644 index 0000000..0f7a298 --- /dev/null +++ b/src/app/modules/configuration/config-bool.html @@ -0,0 +1,6 @@ + +
+ + {{inputHint}} +
+
diff --git a/src/app/modules/configuration/config-container.html b/src/app/modules/configuration/config-container.html new file mode 100644 index 0000000..8c928f0 --- /dev/null +++ b/src/app/modules/configuration/config-container.html @@ -0,0 +1,6 @@ +
+ +
diff --git a/src/app/modules/configuration/config-date.html b/src/app/modules/configuration/config-date.html new file mode 100644 index 0000000..2f443e3 --- /dev/null +++ b/src/app/modules/configuration/config-date.html @@ -0,0 +1,12 @@ + + + + diff --git a/src/app/modules/configuration/config-editor.html b/src/app/modules/configuration/config-editor.html new file mode 100644 index 0000000..11982bc --- /dev/null +++ b/src/app/modules/configuration/config-editor.html @@ -0,0 +1,120 @@ +
+ + + Retour + + + Enregistrer + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Redémarrer le serveur + + +
+
+ + + + diff --git a/src/app/modules/configuration/config-editor.ts b/src/app/modules/configuration/config-editor.ts new file mode 100644 index 0000000..b6d7a67 --- /dev/null +++ b/src/app/modules/configuration/config-editor.ts @@ -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() + 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) + } +} diff --git a/src/app/modules/configuration/config-image.html b/src/app/modules/configuration/config-image.html new file mode 100644 index 0000000..084aad1 --- /dev/null +++ b/src/app/modules/configuration/config-image.html @@ -0,0 +1,28 @@ +
+

+ {{label}} +

+
+ + +
+ + +
+
+ +
+ {{lastUpdated}} +
+
+
diff --git a/src/app/modules/configuration/config-period.html b/src/app/modules/configuration/config-period.html new file mode 100644 index 0000000..90d8af9 --- /dev/null +++ b/src/app/modules/configuration/config-period.html @@ -0,0 +1,7 @@ + + + + diff --git a/src/app/modules/configuration/config-section.html b/src/app/modules/configuration/config-section.html index 08c2b94..8d02e34 100644 --- a/src/app/modules/configuration/config-section.html +++ b/src/app/modules/configuration/config-section.html @@ -1,8 +1,6 @@ - - - + {{label}} diff --git a/src/app/modules/configuration/config-secure.html b/src/app/modules/configuration/config-secure.html new file mode 100644 index 0000000..aaea0b0 --- /dev/null +++ b/src/app/modules/configuration/config-secure.html @@ -0,0 +1,24 @@ + + + {{buttonLabel}} + + + + + + + + + diff --git a/src/app/modules/configuration/config-text.html b/src/app/modules/configuration/config-text.html new file mode 100644 index 0000000..1878b43 --- /dev/null +++ b/src/app/modules/configuration/config-text.html @@ -0,0 +1,11 @@ + + + + diff --git a/src/app/modules/configuration/config.html b/src/app/modules/configuration/config.html deleted file mode 100644 index 8f23ca1..0000000 --- a/src/app/modules/configuration/config.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - -
diff --git a/src/app/modules/configuration/config.module.ts b/src/app/modules/configuration/config.module.ts index 6d16826..d3d6bba 100644 --- a/src/app/modules/configuration/config.module.ts +++ b/src/app/modules/configuration/config.module.ts @@ -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 { +} diff --git a/src/app/modules/configuration/config.sass b/src/app/modules/configuration/config.sass index 062d34e..0e8f839 100644 --- a/src/app/modules/configuration/config.sass +++ b/src/app/modules/configuration/config.sass @@ -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 -// diff --git a/src/app/modules/configuration/config.ts b/src/app/modules/configuration/config.ts index 9ba1d5b..ff20401 100644 --- a/src/app/modules/configuration/config.ts +++ b/src/app/modules/configuration/config.ts @@ -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() 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() - 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) } } diff --git a/src/app/modules/configuration/date.html b/src/app/modules/configuration/date.html deleted file mode 100644 index 83e8af4..0000000 --- a/src/app/modules/configuration/date.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - -
diff --git a/src/app/modules/configuration/editor.html b/src/app/modules/configuration/editor.html deleted file mode 100644 index ec78574..0000000 --- a/src/app/modules/configuration/editor.html +++ /dev/null @@ -1,128 +0,0 @@ - - - Retour - - - Enregistrer - - - -
- - Apparence - - - - - - - - - - Logo - - 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'). - - - - - Icône - - 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'). - - - - - - - Données - - - Période d'expiration de l'approbation de l'échantillon des recettes - - - - Période d'expiration des kits de retouches complets - - 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. - - - - - Activer le cache des PDFs générés - - 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. - - - - - - - Système - - - URL de l'instance - - Utilisé pour générer l'URL de certaines ressources, comme les images et les fiches signalitiques. - - - - - URL de la base de données - - - - Utilisateur de la base de données - - - - - - - - Mot de passe de la base de données - - - - Version de la base de données - - - - Version de Color Recipes Explorer - - - - Date de compilation de Color Recipes Explorer - - - - Version de Java - - - - Système d'exploitation - - - - Redémarrer le serveur - - -
- - - - diff --git a/src/app/modules/configuration/image.html b/src/app/modules/configuration/image.html deleted file mode 100644 index 21459eb..0000000 --- a/src/app/modules/configuration/image.html +++ /dev/null @@ -1,26 +0,0 @@ -
-

- -

-
- -
- - -
-
- -
- {{lastUpdated}} -
-
diff --git a/src/app/modules/configuration/period.html b/src/app/modules/configuration/period.html deleted file mode 100644 index 722ec27..0000000 --- a/src/app/modules/configuration/period.html +++ /dev/null @@ -1,7 +0,0 @@ -
- - -
diff --git a/src/app/modules/configuration/secure.html b/src/app/modules/configuration/secure.html deleted file mode 100644 index 1c1fcae..0000000 --- a/src/app/modules/configuration/secure.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - {{buttonLabel}} - - - - - - - - -
diff --git a/src/app/modules/shared/components/buttons/buttons.ts b/src/app/modules/shared/components/buttons/buttons.ts index a388f90..80ab652 100644 --- a/src/app/modules/shared/components/buttons/buttons.ts +++ b/src/app/modules/shared/components/buttons/buttons.ts @@ -4,7 +4,7 @@ import {ThemePalette} from '@angular/material/core' @Component({ selector: 'cre-button', template: ` - `, @@ -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: ` - + `, styleUrls: ['buttons.sass'] }) export class CrePrimaryButtonComponent { + @Input() type = 'button' @Input() disabled = false } @Component({ selector: 'cre-accent-button', template: ` - + `, styleUrls: ['buttons.sass'] }) export class CreAccentButtonComponent { + @Input() type = 'button' @Input() disabled = false } @Component({ selector: 'cre-warn-button', template: ` - + `, styleUrls: ['buttons.sass'] }) export class CreWarnButtonComponent { + @Input() type = 'button' @Input() disabled = false } diff --git a/src/app/modules/shared/components/inputs/input.html b/src/app/modules/shared/components/inputs/input.html index d123b75..2bf813e 100644 --- a/src/app/modules/shared/components/inputs/input.html +++ b/src/app/modules/shared/components/inputs/input.html @@ -1,25 +1,21 @@ {{label}} - - + + + + + + + + () + @ViewChild('input') input: any @ContentChild(TemplateRef) errors: TemplateRef + + 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() + + 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() @Output() invalidFormat = new EventEmitter() @@ -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) } diff --git a/src/app/modules/shared/model/config.model.ts b/src/app/modules/shared/model/config.model.ts index 80a559a..edbd0f8 100644 --- a/src/app/modules/shared/model/config.model.ts +++ b/src/app/modules/shared/model/config.model.ts @@ -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): Map { +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): Map { 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): Map): Map { +export function filterImageConfigKeyControlMap(map: Map): Map { 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): ConfigKeyContent[] { +export function mapToConfigKeyContentArray(map: Map): ConfigKeyContent[] { const array: ConfigKeyContent[] = [] map.forEach((control, key) => { array.push(mapToConfigKeyContent(key, control)) diff --git a/src/app/modules/shared/service/config.service.ts b/src/app/modules/shared/service/config.service.ts index 7ebe376..dcfd95d 100644 --- a/src/app/modules/shared/service/config.service.ts +++ b/src/app/modules/shared/service/config.service.ts @@ -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 { + return this.api.get('/config') + } + get(key: string): Observable { return this.api.get(`/config/${key}`) } - set(configs: Map): Observable { + setFromForm(form: FormGroup): Observable { + const map = new Map() + for (let key in form.controls) { + map.set(key, form.controls[key]) + } + return this.set(map); + } + + set(configs: Map): Observable { const body = mapToConfigKeyContentArray(filterConfigKeyControlMap(configs)) const imageConfigs = filterImageConfigKeyControlMap(configs) @@ -38,7 +50,7 @@ export class ConfigService { return this.api.post('/config/restart') } - private setImages(configs: Map) { + private setImages(configs: Map) { 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): Observable[] { + private getImageConfigsSubscriptions(configs: Map): Observable[] { return transformMap(configs, (key, control) => { return this.setImage(key, control.value) }) From 7bed9865366bdf49fbe7ed1c6d7ca262f412f39d Mon Sep 17 00:00:00 2001 From: FyloZ Date: Mon, 9 Aug 2021 21:33:15 -0400 Subject: [PATCH 3/4] =?UTF-8?q?#2=20Am=C3=A9lioration=20g=C3=A9n=C3=A9rale?= =?UTF-8?q?s=20des=20configurations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/modules/configuration/config-editor.html | 2 +- src/app/modules/configuration/config-editor.ts | 4 ++++ src/app/modules/shared/model/config.model.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/modules/configuration/config-editor.html b/src/app/modules/configuration/config-editor.html index 11982bc..9bb90b6 100644 --- a/src/app/modules/configuration/config-editor.html +++ b/src/app/modules/configuration/config-editor.html @@ -4,7 +4,7 @@ Retour - Enregistrer + Enregistrer diff --git a/src/app/modules/configuration/config-editor.ts b/src/app/modules/configuration/config-editor.ts index b6d7a67..bde9502 100644 --- a/src/app/modules/configuration/config-editor.ts +++ b/src/app/modules/configuration/config-editor.ts @@ -47,6 +47,10 @@ export class CreConfigEditor extends ErrorHandlingComponent { this.fetchConfigurations(formBuilder) } + ngOnInit() { + super.ngOnInit() + } + getConfigControl(key: string): ConfigControl { return { config: this.configs.get(key), diff --git a/src/app/modules/shared/model/config.model.ts b/src/app/modules/shared/model/config.model.ts index edbd0f8..02e4923 100644 --- a/src/app/modules/shared/model/config.model.ts +++ b/src/app/modules/shared/model/config.model.ts @@ -46,7 +46,7 @@ export class ConfigControl { } export function buildFormControl(config: Config): AbstractControl { - return new FormControl({value: config.content, disabled: !config.editable}, Validators.required) + return new FormControl({value: config.content, disabled: !config.editable}, !configKeyIsPassword(config.key) ? Validators.required : null) } export function configKeyIsPassword(key: string): boolean { From 9a651b21c25c908e27e3215a920b9115b23d1949 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Mon, 9 Aug 2021 22:05:19 -0400 Subject: [PATCH 4/4] #2 Add CI/CD --- .dockerignore | 6 ++++ .drone.yml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 72 ---------------------------------------------- Dockerfile | 34 ++++++++++++++-------- ng.Dockerfile | 17 ----------- nginx.conf | 2 +- 6 files changed, 107 insertions(+), 101 deletions(-) create mode 100644 .dockerignore create mode 100644 .drone.yml delete mode 100644 .gitlab-ci.yml delete mode 100644 ng.Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..642937a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +**/node_modules +.gitignore +.dockerignore +Dockerfile +docker-compose.yml +package-lock.json diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..d97b6e1 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,77 @@ +--- +global-variables: + release: &release ${DRONE_BRANCH##/**} + environment: &environment + CRE_REGISTRY_IMAGE: registry.fyloz.dev:5443/colorrecipesexplorer/frontend + CRE_PORT: 9102 + CRE_RELEASE: *release + alpine-image: &alpine-image alpine:latest + docker-registry-repo: &docker-registry-repo registry.fyloz.dev:5443/colorrecipesexplorer/frontend + +kind: pipeline +name: default +type: docker + +steps: + - name: set-docker-tags-latest + image: *alpine-image + environment: + <<: *environment + commands: + - echo -n "latest" > .tags + when: + branch: develop + + - name: set-docker-tags-release + image: *alpine-image + environment: + <<: *environment + commands: + - echo -n "latest-release,$CRE_RELEASE" > .tags + when: + branch: release/** + + - name: containerize-dev + image: plugins/docker + environment: + <<: *environment + settings: + repo: *docker-registry-repo + when: + branch: + - develop + - release/** + + - name: deploy + image: alpine:latest + environment: + <<: *environment + CRE_REGISTRY_IMAGE: *docker-registry-repo + DEPLOY_SERVER: + from_secret: deploy_server + DEPLOY_SERVER_USERNAME: + from_secret: deploy_server_username + DEPLOY_SERVER_SSH_PORT: + from_secret: deploy_server_ssh_port + DEPLOY_SERVER_SSH_KEY: + from_secret: deploy_server_ssh_key + DEPLOY_CONTAINER_NAME: cre_frontend-${DRONE_BRANCH} + commands: + - apk update + - apk add --no-cache openssh-client + - mkdir -p ~/.ssh + - echo "$DEPLOY_SERVER_SSH_KEY" | tr -d '\r' > ~/.ssh/id_rsa + - chmod 700 ~/.ssh/id_rsa + - eval $(ssh-agent -s) + - ssh-add ~/.ssh/id_rsa + - ssh-keyscan -p $DEPLOY_SERVER_SSH_PORT -H $DEPLOY_SERVER >> ~/.ssh/known_hosts + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker stop $DEPLOY_CONTAINER_NAME || true && docker rm $DEPLOY_CONTAINER_NAME || true" + - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker pull $CRE_REGISTRY_IMAGE:$CRE_RELEASE" + - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker run -d -p $CRE_PORT:80 --name=$DEPLOY_CONTAINER_NAME $CRE_REGISTRY_IMAGE:$CRE_RELEASE" + when: + branch: release/** +trigger: + branch: + - develop + - master diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 74cbfb6..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,72 +0,0 @@ -variables: - CI_REGISTRY_IMAGE_NG: "$CI_REGISTRY_IMAGE:latest-ng" - CI_REGISTRY_IMAGE_FRONTEND: "$CI_REGISTRY_IMAGE:latest" - -before_script: - - docker info - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - -stages: - - build - - package - - deploy - -.only-master: - only: - - master - -build: - stage: build - extends: .only-master - script: - - docker pull $CI_REGISTRY_IMAGE_NG || true - - docker build --cache-from $CI_REGISTRY_IMAGE_NG -f ng.Dockerfile -t $CI_REGISTRY_IMAGE_NG . - - docker push $CI_REGISTRY_IMAGE_NG - -package: - stage: package - needs: ['build'] - extends: .only-master - variables: - PACKAGE_CONTAINER_NAME: "cre_frontend_package" - ARTIFACT_NAME: "ColorRecipesExplorer-frontend-$CI_PIPELINE_IID" - script: - - apk update - - apk add --no-cache zip - - mkdir dist - - docker run --name $PACKAGE_CONTAINER_NAME $CI_REGISTRY_IMAGE_NG ng build --configuration=$ANGULAR_CONFIGURATION --output-hashing=none --stats-json --source-map=false - - docker cp $PACKAGE_CONTAINER_NAME:/usr/src/cre/dist/color-recipes-explorer-frontend/ dist/ - - zip -r $ARTIFACT_NAME.zip dist/ - - docker build -t $CI_REGISTRY_IMAGE_FRONTEND --build-arg ARTIFACT_NAME=$ARTIFACT_NAME . - - docker push $CI_REGISTRY_IMAGE_FRONTEND - after_script: - - docker stop $PACKAGE_CONTAINER_NAME || true - - docker rm $PACKAGE_CONTAINER_NAME || true - artifacts: - paths: - - $ARTIFACT_NAME.zip - expire_in: 1 week - -deploy: - stage: deploy - image: alpine:latest - needs: ['package'] - extends: .only-master - variables: - DEPLOYED_CONTAINER_NAME: "cre_frontend" - before_script: - - apk update - - apk add --no-cache openssh-client - - mkdir -p ~/.ssh - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa - - chmod 700 ~/.ssh/id_rsa - - eval $(ssh-agent -s) - - ssh-add ~/.ssh/id_rsa - - ssh-keyscan -p $DEPLOYMENT_SERVER_SSH_PORT -H $DEPLOYMENT_SERVER >> ~/.ssh/known_hosts - - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - script: - - ssh -p $DEPLOYMENT_SERVER_SSH_PORT $DEPLOYMENT_SERVER_USERNAME@$DEPLOYMENT_SERVER "docker stop $DEPLOYED_CONTAINER_NAME || true && docker rm $DEPLOYED_CONTAINER_NAME || true" - - ssh -p $DEPLOYMENT_SERVER_SSH_PORT $DEPLOYMENT_SERVER_USERNAME@$DEPLOYMENT_SERVER "docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY && docker pull $CI_REGISTRY_IMAGE_FRONTEND" - - ssh -p $DEPLOYMENT_SERVER_SSH_PORT $DEPLOYMENT_SERVER_USERNAME@$DEPLOYMENT_SERVER "docker run -d -p $PORT:80 --name=$DEPLOYED_CONTAINER_NAME $CI_REGISTRY_IMAGE_FRONTEND" - - diff --git a/Dockerfile b/Dockerfile index 464c4fc..1325a16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,29 @@ -FROM nginx:mainline-alpine - -WORKDIR /usr/bin/cre/ - -ARG ARTIFACT_NAME=ColorRecipesExplorer-ng -COPY $ARTIFACT_NAME.zip . -COPY nginx.conf /etc/nginx/nginx.conf +FROM alpine:latest AS build +WORKDIR /usr/src/ RUN apk update -RUN apk add --no-cache zip +RUN apk add --no-cache nodejs npm -RUN unzip $ARTIFACT_NAME.zip -RUN rm $ARTIFACT_NAME.zip +RUN npm install -g typescript@4.0.7 && \ + npm install -g @angular/cli@11.2.9 || true --fo -EXPOSE 80 +ENV NG_CLI_ANALYTICS=ci + +COPY . . + +ARG ANGULAR_CONFIGURATION=production + +RUN npm install --force +RUN ng build --configuration=$ANGULAR_CONFIGURATION --stats-json --source-map=false + +FROM nginx:mainline-alpine + +WORKDIR /usr/bin/ + +COPY nginx.conf /etc/nginx/nginx.conf +COPY --from=build /usr/src/dist/color-recipes-explorer-frontend/ . + +ARG CRE_PORT=80 +EXPOSE $CRE_PORT CMD ["nginx", "-g", "daemon off;"] diff --git a/ng.Dockerfile b/ng.Dockerfile deleted file mode 100644 index 92c9ee8..0000000 --- a/ng.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM alpine:latest -WORKDIR /usr/src/cre/ - -RUN apk update -RUN apk add --no-cache nodejs -RUN apk add --no-cache npm - -RUN npm install -g typescript@4.0.7 -RUN npm install -g @angular/cli@11.2.9 || true - -ENV NG_CLI_ANALYTICS=ci - -COPY package.json . - -RUN npm install --force - -COPY . . diff --git a/nginx.conf b/nginx.conf index 1f73fad..dea68be 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,7 +5,7 @@ events { worker_connections 1024; } http { server { listen 80; - root /usr/bin/cre/dist/color-recipes-explorer-frontend; + root /usr/bin/; include /etc/nginx/mime.types; location / {