From c5e80f2d849d0456f602325dd46912af9b271182 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 1 Jan 2023 16:01:51 +0100 Subject: [PATCH] Cleaning up config and performance improvement #569 --- src/common/config/private/PrivateConfig.ts | 5 +- src/common/config/private/WebConfig.ts | 10 +- src/frontend/app/app.module.ts | 10 +- .../app/ui/admin/admin.component.html | 57 ---- src/frontend/app/ui/admin/admin.component.ts | 7 +- .../_abstract/abstract.settings.component.css | 28 -- .../_abstract/abstract.settings.component.ts | 261 ----------------- .../_abstract/abstract.settings.service.ts | 24 -- .../app/ui/settings/settings.service.ts | 14 +- .../ISettingsComponent.ts | 0 .../settings-entry.component.html | 48 ++- .../settings-entry.component.ts | 78 +++-- .../settings-entry.settings.component.css | 0 .../settings/template/template.component.css | 28 ++ .../settings/template/template.component.html | 4 +- .../settings/template/template.component.ts | 277 ++++++++++++++++-- .../button/job-button.settings.component.css | 0 .../button/job-button.settings.component.html | 0 .../button/job-button.settings.component.ts | 0 .../job-progress.settings.component.css | 0 .../job-progress.settings.component.html | 0 .../job-progress.settings.component.ts | 0 22 files changed, 379 insertions(+), 472 deletions(-) delete mode 100644 src/frontend/app/ui/settings/_abstract/abstract.settings.component.css delete mode 100644 src/frontend/app/ui/settings/_abstract/abstract.settings.component.ts delete mode 100644 src/frontend/app/ui/settings/_abstract/abstract.settings.service.ts rename src/frontend/app/ui/settings/{_abstract => template}/ISettingsComponent.ts (100%) rename src/frontend/app/ui/settings/{_abstract => template}/settings-entry/settings-entry.component.html (89%) rename src/frontend/app/ui/settings/{_abstract => template}/settings-entry/settings-entry.component.ts (83%) rename src/frontend/app/ui/settings/{_abstract => template}/settings-entry/settings-entry.settings.component.css (100%) rename src/frontend/app/ui/settings/{jobs => workflow}/button/job-button.settings.component.css (100%) rename src/frontend/app/ui/settings/{jobs => workflow}/button/job-button.settings.component.html (100%) rename src/frontend/app/ui/settings/{jobs => workflow}/button/job-button.settings.component.ts (100%) rename src/frontend/app/ui/settings/{jobs => workflow}/progress/job-progress.settings.component.css (100%) rename src/frontend/app/ui/settings/{jobs => workflow}/progress/job-progress.settings.component.html (100%) rename src/frontend/app/ui/settings/{jobs => workflow}/progress/job-progress.settings.component.ts (100%) diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index c46114bc..84ae1306 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -910,7 +910,10 @@ export class ServerMediaConfig extends ClientMediaConfig { export class ServerServiceConfig extends ClientServiceConfig { @ConfigProperty({ arrayType: 'string', - tags: {secret: true} + tags: { + secret: true, + name: 'sessionSecret' + } }) sessionSecret: string[] = []; diff --git a/src/common/config/private/WebConfig.ts b/src/common/config/private/WebConfig.ts index 313c56a2..bbd6e481 100644 --- a/src/common/config/private/WebConfig.ts +++ b/src/common/config/private/WebConfig.ts @@ -4,8 +4,8 @@ import {ServerConfig} from './PrivateConfig'; import {WebConfigClass} from 'typeconfig/web'; import {ConfigState} from 'typeconfig/common'; import {WebConfigClassBuilder} from '../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder'; -import {IWebConfigClass} from '../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass'; -import { TAGS } from '../public/ClientConfig'; +import {IWebConfigClassPrivate} from '../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass'; +import {TAGS} from '../public/ClientConfig'; @WebConfigClass({softReadonly: true}) @@ -13,9 +13,9 @@ export class WebConfig extends ServerConfig { @ConfigState() State: any; - clone(): IWebConfigClass & WebConfig { - const wcg = WebConfigClassBuilder.attachInterface(new WebConfig()); - wcg.load(WebConfigClassBuilder.attachInterface(this).toJSON()); + clone(): IWebConfigClassPrivate & WebConfig { + const wcg = WebConfigClassBuilder.attachPrivateInterface(new WebConfig()); + wcg.load(WebConfigClassBuilder.attachPrivateInterface(this).toJSON()); return wcg; } } diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index f360193d..e620021d 100644 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -73,7 +73,6 @@ import {ScheduledJobsService} from './ui/settings/scheduled-jobs.service'; import {BackendtextService} from './model/backendtext.service'; import {ErrorInterceptor} from './model/network/helper/error.interceptor'; import {CSRFInterceptor} from './model/network/helper/csrf.interceptor'; -import {SettingsEntryComponent} from './ui/settings/_abstract/settings-entry/settings-entry.component'; import {GallerySearchQueryEntryComponent} from './ui/gallery/search/query-enrty/query-entry.search.gallery.component'; import {StringifySearchQuery} from './pipes/StringifySearchQuery'; import {AutoCompleteService} from './ui/gallery/search/autocomplete.service'; @@ -99,11 +98,11 @@ import {GalleryFilterComponent} from './ui/gallery/filter/filter.gallery.compone import {GallerySortingService} from './ui/gallery/navigator/sorting.service'; import {FilterService} from './ui/gallery/filter/filter.service'; import {TemplateComponent} from './ui/settings/template/template.component'; -import {AbstractSettingsService} from './ui/settings/_abstract/abstract.settings.service'; import {WorkflowComponent} from './ui/settings/workflow/workflow.component'; -import {JobProgressComponent} from './ui/settings/jobs/progress/job-progress.settings.component'; -import {JobButtonComponent} from './ui/settings/jobs/button/job-button.settings.component'; import {GalleryStatisticComponent} from './ui/settings/gallery-statistic/gallery-statistic.component'; +import { JobButtonComponent } from './ui/settings/workflow/button/job-button.settings.component'; +import { JobProgressComponent } from './ui/settings/workflow/progress/job-progress.settings.component'; +import {SettingsEntryComponent} from './ui/settings/template/settings-entry/settings-entry.component'; @Injectable() export class MyHammerConfig extends HammerGestureConfig { @@ -271,8 +270,7 @@ Marker.prototype.options.icon = iconDefault; VersionService, ScheduledJobsService, BackendtextService, - CookieService, - AbstractSettingsService + CookieService ], bootstrap: [AppComponent], }) diff --git a/src/frontend/app/ui/admin/admin.component.html b/src/frontend/app/ui/admin/admin.component.html index 88db46dc..f880ae9a 100644 --- a/src/frontend/app/ui/admin/admin.component.html +++ b/src/frontend/app/ui/admin/admin.component.html @@ -89,7 +89,6 @@ *ngFor="let cp of configPaths" #setting #tmpl - icon="list" [ConfigPath]="cp" [hidden]="!tmpl.HasAvailableSettings"> - diff --git a/src/frontend/app/ui/admin/admin.component.ts b/src/frontend/app/ui/admin/admin.component.ts index 5ae50750..42f1685e 100644 --- a/src/frontend/app/ui/admin/admin.component.ts +++ b/src/frontend/app/ui/admin/admin.component.ts @@ -4,12 +4,13 @@ import {UserRoles} from '../../../../common/entities/UserDTO'; import {NotificationService} from '../../model/notification.service'; import {NotificationType} from '../../../../common/entities/NotificationDTO'; import {NavigationService} from '../../model/navigation.service'; -import {ISettingsComponent} from '../settings/_abstract/ISettingsComponent'; import {PageHelper} from '../../model/page.helper'; import {SettingsService} from '../settings/settings.service'; import {ConfigPriority} from '../../../../common/config/public/ClientConfig'; import {Utils} from '../../../../common/Utils'; import {WebConfig} from '../../../../common/config/private/WebConfig'; +import {ISettingsComponent} from '../settings/template/ISettingsComponent'; +import {WebConfigClassBuilder} from '../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder'; @Component({ selector: 'app-admin', @@ -32,7 +33,9 @@ export class AdminComponent implements OnInit, AfterViewInit { public settingsService: SettingsService, ) { this.configPriorities = Utils.enumToArray(ConfigPriority); - this.configPaths = Object.keys((new WebConfig()).State); + const wc = WebConfigClassBuilder.attachPrivateInterface(new WebConfig()); + this.configPaths = Object.keys(wc.State) + .filter(s => !wc.__state[s].volatile); } ngAfterViewInit(): void { diff --git a/src/frontend/app/ui/settings/_abstract/abstract.settings.component.css b/src/frontend/app/ui/settings/_abstract/abstract.settings.component.css deleted file mode 100644 index 1b89fb47..00000000 --- a/src/frontend/app/ui/settings/_abstract/abstract.settings.component.css +++ /dev/null @@ -1,28 +0,0 @@ -.title { - margin-left: -5px; -} - -.btn { - margin-left: 10px; -} - - -.switch-wrapper { - display: inline-block; - text-align: right; - padding: 0; - float: right; - margin-top: -4px; - margin-bottom: -4px; - -} - -.changed-settings input { - border-color: var(--bs-primary); - border-width: 1.5px; -} - -.changed-settings label { - color: var(--bs-primary); - font-weight: bold; -} diff --git a/src/frontend/app/ui/settings/_abstract/abstract.settings.component.ts b/src/frontend/app/ui/settings/_abstract/abstract.settings.component.ts deleted file mode 100644 index 7b7ebbfe..00000000 --- a/src/frontend/app/ui/settings/_abstract/abstract.settings.component.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Directive, Input, OnDestroy, OnInit, ViewChild,} from '@angular/core'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {UserRoles} from '../../../../../common/entities/UserDTO'; -import {Utils} from '../../../../../common/Utils'; -import {ErrorDTO} from '../../../../../common/entities/Error'; -import {NotificationService} from '../../../model/notification.service'; -import {NavigationService} from '../../../model/navigation.service'; -import {AbstractSettingsService} from './abstract.settings.service'; -import {Subscription} from 'rxjs'; -import {ISettingsComponent} from './ISettingsComponent'; -import {WebConfig} from '../../../../../common/config/private/WebConfig'; -import {FormControl} from '@angular/forms'; -import {ConfigPriority, TAGS} from '../../../../../common/config/public/ClientConfig'; -import {SettingsService} from '../settings.service'; -import {IWebConfigClassPrivate} from '../../../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass'; -import {WebConfigClassBuilder} from '../../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder'; - -interface ConfigState { - value: T; - original: T; - default: T; - readonly: boolean; - tags: TAGS; - onChange: () => unknown; - isEnumType: boolean; - isConfigType: boolean; - isConfigArrayType: boolean; - toJSON: () => T; -} - -export interface RecursiveState extends ConfigState { - shouldHide: any; - volatile: any; - tags: any; - isConfigType: any; - isConfigArrayType: any; - onChange: any; - isEnumType: any; - value: any; - original: any; - default: any; - readonly: any; - toJSON: any; - - [key: string]: RecursiveState; -} - - -@Directive() -export abstract class SettingsComponentDirective< - T extends RecursiveState> implements OnInit, OnDestroy, ISettingsComponent { - public icon: string; - @Input() ConfigPath: string; - - @ViewChild('settingsForm', {static: true}) - form: FormControl; - - - public inProgress = false; - public error: string = null; - public changed = false; - public states: RecursiveState = {} as RecursiveState; - protected name: string; - - private subscription: Subscription = null; - private settingsSubscription: Subscription = null; - protected sliceFN?: (s: WebConfig) => T; - - protected constructor( - protected authService: AuthenticationService, - private navigation: NavigationService, - public settingsService: AbstractSettingsService, - protected notification: NotificationService, - public globalSettingsService: SettingsService, - sliceFN?: (s: IWebConfigClassPrivate & WebConfig) => T - ) { - this.setSliceFN(sliceFN); - } - - setSliceFN(sliceFN?: (s: IWebConfigClassPrivate & WebConfig) => T) { - if (sliceFN) { - this.sliceFN = sliceFN; - this.settingsSubscription = this.settingsService.Settings.subscribe( - this.onNewSettings - ); - this.onNewSettings(this.settingsService.settingsService.settings.value); - } - } - - get Name(): string { - return this.changed ? this.name + '*' : this.name; - } - - get Changed(): boolean { - return this.changed; - } - - get HasAvailableSettings(): boolean { - return !this.states?.shouldHide || !this.states?.shouldHide(); - } - - onNewSettings = (s: WebConfig) => { - this.states = this.sliceFN(s.clone()) as RecursiveState; - const instrument = (st: RecursiveState, parent: RecursiveState) => { - const shouldHide = (state: RecursiveState) => { - return () => { - if (state.volatile) { - return true; - } - - if (state.tags && - ((state.tags.relevant && !state.tags.relevant(parent.value)) - || state.tags.secret)) { - return true; - } - - // if all sub elements are hidden, hide the parent too. - if (state.isConfigType) { - if (state.value.__state && - Object.keys(state.value.__state).findIndex(k => !st.value.__state[k].shouldHide()) === -1) { - return true; - } - } - - - if (state.isConfigArrayType) { - for (let i = 0; i < state.value?.length; ++i) { - if (state.value[i].__state && - Object.keys(state.value[i].__state).findIndex(k => !(st.value[i].__state[k].shouldHide && st.value[i].__state[k].shouldHide())) === -1) { - return true; - } - } - return false; - } - return ( - (state.tags?.priority > this.globalSettingsService.configPriority || - (this.globalSettingsService.configPriority === ConfigPriority.basic && - state.tags?.dockerSensitive && this.globalSettingsService.settings.value.Environment.isDocker)) && //if this value should not change in Docker, lets hide it - Utils.equalsFilter(state.value, state.default, - ['__propPath', '__created', '__prototype', '__rootConfig']) && - Utils.equalsFilter(state.original, state.default, - ['__propPath', '__created', '__prototype', '__rootConfig'])); - }; - }; - - st.shouldHide = shouldHide(st); - st.onChange = this.onOptionChange; - st.rootConfig = parent?.value; - if (typeof st.value !== 'undefined') { - st.original = Utils.clone(st.value); - } - if (st.isConfigType) { - for (const k of Object.keys(st.value.__state)) { - instrument(st.value.__state[k], st); - } - } - if (st.isConfigArrayType) { - for (let i = 0; i < st.value?.length; ++i) { - for (const k of Object.keys(st.value[i].__state)) { - instrument(st.value[i].__state[k], st); - } - } - } - }; - instrument(this.states, null); - this.icon = this.states.tags?.uiIcon; - }; - - onOptionChange = () => { - setTimeout(() => { - const settingsSame = (state: RecursiveState): boolean => { - if (typeof state === 'undefined') { - return true; - } - if (typeof state.original === 'object') { - return Utils.equalsFilter(state.value, state.original, - ['__propPath', '__created', '__prototype', '__rootConfig', '__state']); - } - if (typeof state.original !== 'undefined') { - return state.value === state.original; - } - - const keys = Object.keys(state); - - for (const key of keys) { - if (settingsSame(state[key]) === false) { - return false; - } - } - - return true; - }; - - this.changed = !settingsSame(this.states); - }, 0); - }; - - ngOnInit(): void { - if ( - !this.authService.isAuthenticated() || - this.authService.user.value.role < UserRoles.Admin - ) { - this.navigation.toLogin(); - return; - } - this.getSettings(); - - // TODO: fix after this issue is fixed: https://github.com/angular/angular/issues/24818 - this.subscription = this.form.valueChanges.subscribe(() => { - this.onOptionChange(); - }); - } - - ngOnDestroy(): void { - if (this.subscription != null) { - this.subscription.unsubscribe(); - } - if (this.settingsSubscription != null) { - this.settingsSubscription.unsubscribe(); - } - } - - public reset(): void { - this.getSettings(); - } - - stateToSettings(): T { - return WebConfigClassBuilder.attachInterface(this.states.value).toJSON(); - } - - public async save(): Promise { - this.inProgress = true; - this.error = ''; - try { - await this.settingsService.updateSettings(this.stateToSettings(), this.ConfigPath); - await this.getSettings(); - this.notification.success( - this.Name + ' ' + $localize`settings saved`, - $localize`Success` - ); - this.inProgress = false; - return true; - } catch (err) { - console.error(err); - if (err.message) { - this.error = (err as ErrorDTO).message; - } - } - - this.inProgress = false; - return false; - } - - private async getSettings(): Promise { - await this.settingsService.getSettings(); - this.changed = false; - } -} - - - diff --git a/src/frontend/app/ui/settings/_abstract/abstract.settings.service.ts b/src/frontend/app/ui/settings/_abstract/abstract.settings.service.ts deleted file mode 100644 index adb6a7bb..00000000 --- a/src/frontend/app/ui/settings/_abstract/abstract.settings.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {BehaviorSubject} from 'rxjs'; -import {SettingsService} from '../settings.service'; -import {WebConfig} from '../../../../../common/config/private/WebConfig'; -import {NetworkService} from '../../../model/network/network.service'; -import {Injectable} from '@angular/core'; - -@Injectable() -export class AbstractSettingsService { - constructor(public settingsService: SettingsService, - private networkService: NetworkService) { - } - - get Settings(): BehaviorSubject { - return this.settingsService.settings; - } - - public getSettings(): Promise { - return this.settingsService.getSettings(); - } - - public updateSettings(settings: Record, settingsPath: string): Promise { - return this.networkService.putJson('/settings', {settings, settingsPath}); - } -} diff --git a/src/frontend/app/ui/settings/settings.service.ts b/src/frontend/app/ui/settings/settings.service.ts index 62a94239..1af0feb9 100644 --- a/src/frontend/app/ui/settings/settings.service.ts +++ b/src/frontend/app/ui/settings/settings.service.ts @@ -4,17 +4,18 @@ import {NetworkService} from '../../model/network/network.service'; import {WebConfig} from '../../../../common/config/private/WebConfig'; import {WebConfigClassBuilder} from 'typeconfig/src/decorators/builders/WebConfigClassBuilder'; -import {ConfigPriority} from '../../../../common/config/public/ClientConfig'; +import {ConfigPriority, TAGS} from '../../../../common/config/public/ClientConfig'; import {CookieNames} from '../../../../common/CookieNames'; import {CookieService} from 'ngx-cookie-service'; import {DefaultsJobs, JobDTO} from '../../../../common/entities/job/JobDTO'; import {StatisticDTO} from '../../../../common/entities/settings/StatisticDTO'; import {ScheduledJobsService} from './scheduled-jobs.service'; +import {IWebConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass'; @Injectable() export class SettingsService { public configPriority = ConfigPriority.basic; - public settings: BehaviorSubject; + public settings: BehaviorSubject & WebConfig>; private fetchingSettings = false; public availableJobs: BehaviorSubject; public statistic: BehaviorSubject; @@ -24,7 +25,7 @@ export class SettingsService { private cookieService: CookieService) { this.statistic = new BehaviorSubject(null); this.availableJobs = new BehaviorSubject([]); - this.settings = new BehaviorSubject(new WebConfig()); + this.settings = new BehaviorSubject & WebConfig>(WebConfigClassBuilder.attachPrivateInterface(new WebConfig())); this.getSettings().catch(console.error); if (this.cookieService.check(CookieNames.configPriority)) { @@ -59,7 +60,7 @@ export class SettingsService { } this.fetchingSettings = true; try { - const wcg = WebConfigClassBuilder.attachInterface(new WebConfig()); + const wcg = WebConfigClassBuilder.attachPrivateInterface(new WebConfig()); wcg.load( await this.networkService.getJson>('/settings') ); @@ -70,6 +71,11 @@ export class SettingsService { this.fetchingSettings = false; } + + public updateSettings(settings: Record, settingsPath: string): Promise { + return this.networkService.putJson('/settings', {settings, settingsPath}); + } + configPriorityChanged(): void { // save it for some years this.cookieService.set( diff --git a/src/frontend/app/ui/settings/_abstract/ISettingsComponent.ts b/src/frontend/app/ui/settings/template/ISettingsComponent.ts similarity index 100% rename from src/frontend/app/ui/settings/_abstract/ISettingsComponent.ts rename to src/frontend/app/ui/settings/template/ISettingsComponent.ts diff --git a/src/frontend/app/ui/settings/_abstract/settings-entry/settings-entry.component.html b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html similarity index 89% rename from src/frontend/app/ui/settings/_abstract/settings-entry/settings-entry.component.html rename to src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html index 166fc7e3..91333888 100644 --- a/src/frontend/app/ui/settings/_abstract/settings-entry/settings-entry.component.html +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html @@ -12,9 +12,9 @@
-
+
-
+