mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-23 01:27:14 +02:00
Cleaning up config and performance improvement #569
This commit is contained in:
parent
c191549270
commit
c5e80f2d84
@ -910,7 +910,10 @@ export class ServerMediaConfig extends ClientMediaConfig {
|
|||||||
export class ServerServiceConfig extends ClientServiceConfig {
|
export class ServerServiceConfig extends ClientServiceConfig {
|
||||||
@ConfigProperty({
|
@ConfigProperty({
|
||||||
arrayType: 'string',
|
arrayType: 'string',
|
||||||
tags: {secret: true}
|
tags: {
|
||||||
|
secret: true,
|
||||||
|
name: 'sessionSecret'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
sessionSecret: string[] = [];
|
sessionSecret: string[] = [];
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import {ServerConfig} from './PrivateConfig';
|
|||||||
import {WebConfigClass} from 'typeconfig/web';
|
import {WebConfigClass} from 'typeconfig/web';
|
||||||
import {ConfigState} from 'typeconfig/common';
|
import {ConfigState} from 'typeconfig/common';
|
||||||
import {WebConfigClassBuilder} from '../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder';
|
import {WebConfigClassBuilder} from '../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder';
|
||||||
import {IWebConfigClass} from '../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass';
|
import {IWebConfigClassPrivate} from '../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass';
|
||||||
import { TAGS } from '../public/ClientConfig';
|
import {TAGS} from '../public/ClientConfig';
|
||||||
|
|
||||||
|
|
||||||
@WebConfigClass({softReadonly: true})
|
@WebConfigClass({softReadonly: true})
|
||||||
@ -13,9 +13,9 @@ export class WebConfig extends ServerConfig {
|
|||||||
@ConfigState()
|
@ConfigState()
|
||||||
State: any;
|
State: any;
|
||||||
|
|
||||||
clone(): IWebConfigClass<TAGS> & WebConfig {
|
clone(): IWebConfigClassPrivate<TAGS> & WebConfig {
|
||||||
const wcg = WebConfigClassBuilder.attachInterface(new WebConfig());
|
const wcg = WebConfigClassBuilder.attachPrivateInterface(new WebConfig());
|
||||||
wcg.load(WebConfigClassBuilder.attachInterface(this).toJSON());
|
wcg.load(WebConfigClassBuilder.attachPrivateInterface(this).toJSON());
|
||||||
return wcg;
|
return wcg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,6 @@ import {ScheduledJobsService} from './ui/settings/scheduled-jobs.service';
|
|||||||
import {BackendtextService} from './model/backendtext.service';
|
import {BackendtextService} from './model/backendtext.service';
|
||||||
import {ErrorInterceptor} from './model/network/helper/error.interceptor';
|
import {ErrorInterceptor} from './model/network/helper/error.interceptor';
|
||||||
import {CSRFInterceptor} from './model/network/helper/csrf.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 {GallerySearchQueryEntryComponent} from './ui/gallery/search/query-enrty/query-entry.search.gallery.component';
|
||||||
import {StringifySearchQuery} from './pipes/StringifySearchQuery';
|
import {StringifySearchQuery} from './pipes/StringifySearchQuery';
|
||||||
import {AutoCompleteService} from './ui/gallery/search/autocomplete.service';
|
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 {GallerySortingService} from './ui/gallery/navigator/sorting.service';
|
||||||
import {FilterService} from './ui/gallery/filter/filter.service';
|
import {FilterService} from './ui/gallery/filter/filter.service';
|
||||||
import {TemplateComponent} from './ui/settings/template/template.component';
|
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 {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 {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()
|
@Injectable()
|
||||||
export class MyHammerConfig extends HammerGestureConfig {
|
export class MyHammerConfig extends HammerGestureConfig {
|
||||||
@ -271,8 +270,7 @@ Marker.prototype.options.icon = iconDefault;
|
|||||||
VersionService,
|
VersionService,
|
||||||
ScheduledJobsService,
|
ScheduledJobsService,
|
||||||
BackendtextService,
|
BackendtextService,
|
||||||
CookieService,
|
CookieService
|
||||||
AbstractSettingsService
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
@ -89,7 +89,6 @@
|
|||||||
*ngFor="let cp of configPaths"
|
*ngFor="let cp of configPaths"
|
||||||
#setting
|
#setting
|
||||||
#tmpl
|
#tmpl
|
||||||
icon="list"
|
|
||||||
[ConfigPath]="cp"
|
[ConfigPath]="cp"
|
||||||
[hidden]="!tmpl.HasAvailableSettings">
|
[hidden]="!tmpl.HasAvailableSettings">
|
||||||
<ng-container
|
<ng-container
|
||||||
@ -99,62 +98,6 @@
|
|||||||
<app-settings-gallery-statistic></app-settings-gallery-statistic>
|
<app-settings-gallery-statistic></app-settings-gallery-statistic>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</app-settings-template>
|
</app-settings-template>
|
||||||
<!-- <app-settings-template #setting #server
|
|
||||||
icon="list"
|
|
||||||
[ConfigPath]="'Server'"
|
|
||||||
[hidden]="!server.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #users
|
|
||||||
icon="person"
|
|
||||||
[ConfigPath]="'Users'"
|
|
||||||
[hidden]="!users.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #db
|
|
||||||
icon="list"
|
|
||||||
[ConfigPath]="'Database'"
|
|
||||||
[hidden]="!db.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #media
|
|
||||||
icon="camera-slr"
|
|
||||||
[ConfigPath]="'Media'"
|
|
||||||
[hidden]="!media.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #gallery
|
|
||||||
icon="browser"
|
|
||||||
[ConfigPath]="'Gallery'"
|
|
||||||
[hidden]="!gallery.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #preview
|
|
||||||
icon="image"
|
|
||||||
[ConfigPath]="'Preview'"
|
|
||||||
[hidden]="!preview.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #metafile
|
|
||||||
icon="file"
|
|
||||||
[ConfigPath]="'MetaFile'"
|
|
||||||
[hidden]="!metafile.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #map
|
|
||||||
icon="map-marker"
|
|
||||||
[ConfigPath]="'Map'"
|
|
||||||
[hidden]="!map.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #sharing
|
|
||||||
icon="share"
|
|
||||||
[ConfigPath]="'Sharing'"
|
|
||||||
[hidden]="!sharing.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #faces
|
|
||||||
icon="people"
|
|
||||||
[ConfigPath]="'Faces'"
|
|
||||||
[hidden]="!faces.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #album
|
|
||||||
icon="grid-two-up"
|
|
||||||
[ConfigPath]="'Album'"
|
|
||||||
[hidden]="!album.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #randomPhoto
|
|
||||||
icon="random"
|
|
||||||
[ConfigPath]="'RandomPhoto'"
|
|
||||||
[hidden]="!randomPhoto.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #duplicates
|
|
||||||
icon="layers"
|
|
||||||
[ConfigPath]="'Duplicates'"
|
|
||||||
[hidden]="!duplicates.HasAvailableSettings"></app-settings-template>
|
|
||||||
<app-settings-template #setting #indexing
|
|
||||||
icon="pie-chart"
|
|
||||||
[ConfigPath]="'Indexing'"
|
|
||||||
[hidden]="!indexing.HasAvailableSettings"></app-settings-template>-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,12 +4,13 @@ import {UserRoles} from '../../../../common/entities/UserDTO';
|
|||||||
import {NotificationService} from '../../model/notification.service';
|
import {NotificationService} from '../../model/notification.service';
|
||||||
import {NotificationType} from '../../../../common/entities/NotificationDTO';
|
import {NotificationType} from '../../../../common/entities/NotificationDTO';
|
||||||
import {NavigationService} from '../../model/navigation.service';
|
import {NavigationService} from '../../model/navigation.service';
|
||||||
import {ISettingsComponent} from '../settings/_abstract/ISettingsComponent';
|
|
||||||
import {PageHelper} from '../../model/page.helper';
|
import {PageHelper} from '../../model/page.helper';
|
||||||
import {SettingsService} from '../settings/settings.service';
|
import {SettingsService} from '../settings/settings.service';
|
||||||
import {ConfigPriority} from '../../../../common/config/public/ClientConfig';
|
import {ConfigPriority} from '../../../../common/config/public/ClientConfig';
|
||||||
import {Utils} from '../../../../common/Utils';
|
import {Utils} from '../../../../common/Utils';
|
||||||
import {WebConfig} from '../../../../common/config/private/WebConfig';
|
import {WebConfig} from '../../../../common/config/private/WebConfig';
|
||||||
|
import {ISettingsComponent} from '../settings/template/ISettingsComponent';
|
||||||
|
import {WebConfigClassBuilder} from '../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin',
|
selector: 'app-admin',
|
||||||
@ -32,7 +33,9 @@ export class AdminComponent implements OnInit, AfterViewInit {
|
|||||||
public settingsService: SettingsService,
|
public settingsService: SettingsService,
|
||||||
) {
|
) {
|
||||||
this.configPriorities = Utils.enumToArray(ConfigPriority);
|
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 {
|
ngAfterViewInit(): void {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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<T = unknown> {
|
|
||||||
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<TAGS> & WebConfig) => T
|
|
||||||
) {
|
|
||||||
this.setSliceFN(sliceFN);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSliceFN(sliceFN?: (s: IWebConfigClassPrivate<TAGS> & 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<boolean> {
|
|
||||||
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<void> {
|
|
||||||
await this.settingsService.getSettings();
|
|
||||||
this.changed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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<WebConfig> {
|
|
||||||
return this.settingsService.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSettings(): Promise<void> {
|
|
||||||
return this.settingsService.getSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateSettings(settings: Record<string, any>, settingsPath: string): Promise<void> {
|
|
||||||
return this.networkService.putJson('/settings', {settings, settingsPath});
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,17 +4,18 @@ import {NetworkService} from '../../model/network/network.service';
|
|||||||
|
|
||||||
import {WebConfig} from '../../../../common/config/private/WebConfig';
|
import {WebConfig} from '../../../../common/config/private/WebConfig';
|
||||||
import {WebConfigClassBuilder} from 'typeconfig/src/decorators/builders/WebConfigClassBuilder';
|
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 {CookieNames} from '../../../../common/CookieNames';
|
||||||
import {CookieService} from 'ngx-cookie-service';
|
import {CookieService} from 'ngx-cookie-service';
|
||||||
import {DefaultsJobs, JobDTO} from '../../../../common/entities/job/JobDTO';
|
import {DefaultsJobs, JobDTO} from '../../../../common/entities/job/JobDTO';
|
||||||
import {StatisticDTO} from '../../../../common/entities/settings/StatisticDTO';
|
import {StatisticDTO} from '../../../../common/entities/settings/StatisticDTO';
|
||||||
import {ScheduledJobsService} from './scheduled-jobs.service';
|
import {ScheduledJobsService} from './scheduled-jobs.service';
|
||||||
|
import {IWebConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SettingsService {
|
export class SettingsService {
|
||||||
public configPriority = ConfigPriority.basic;
|
public configPriority = ConfigPriority.basic;
|
||||||
public settings: BehaviorSubject<WebConfig>;
|
public settings: BehaviorSubject<IWebConfigClassPrivate<TAGS> & WebConfig>;
|
||||||
private fetchingSettings = false;
|
private fetchingSettings = false;
|
||||||
public availableJobs: BehaviorSubject<JobDTO[]>;
|
public availableJobs: BehaviorSubject<JobDTO[]>;
|
||||||
public statistic: BehaviorSubject<StatisticDTO>;
|
public statistic: BehaviorSubject<StatisticDTO>;
|
||||||
@ -24,7 +25,7 @@ export class SettingsService {
|
|||||||
private cookieService: CookieService) {
|
private cookieService: CookieService) {
|
||||||
this.statistic = new BehaviorSubject(null);
|
this.statistic = new BehaviorSubject(null);
|
||||||
this.availableJobs = new BehaviorSubject([]);
|
this.availableJobs = new BehaviorSubject([]);
|
||||||
this.settings = new BehaviorSubject<WebConfig>(new WebConfig());
|
this.settings = new BehaviorSubject<IWebConfigClassPrivate<TAGS> & WebConfig>(WebConfigClassBuilder.attachPrivateInterface(new WebConfig()));
|
||||||
this.getSettings().catch(console.error);
|
this.getSettings().catch(console.error);
|
||||||
|
|
||||||
if (this.cookieService.check(CookieNames.configPriority)) {
|
if (this.cookieService.check(CookieNames.configPriority)) {
|
||||||
@ -59,7 +60,7 @@ export class SettingsService {
|
|||||||
}
|
}
|
||||||
this.fetchingSettings = true;
|
this.fetchingSettings = true;
|
||||||
try {
|
try {
|
||||||
const wcg = WebConfigClassBuilder.attachInterface(new WebConfig());
|
const wcg = WebConfigClassBuilder.attachPrivateInterface(new WebConfig());
|
||||||
wcg.load(
|
wcg.load(
|
||||||
await this.networkService.getJson<Promise<WebConfig>>('/settings')
|
await this.networkService.getJson<Promise<WebConfig>>('/settings')
|
||||||
);
|
);
|
||||||
@ -70,6 +71,11 @@ export class SettingsService {
|
|||||||
this.fetchingSettings = false;
|
this.fetchingSettings = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public updateSettings(settings: Record<string, any>, settingsPath: string): Promise<void> {
|
||||||
|
return this.networkService.putJson('/settings', {settings, settingsPath});
|
||||||
|
}
|
||||||
|
|
||||||
configPriorityChanged(): void {
|
configPriorityChanged(): void {
|
||||||
// save it for some years
|
// save it for some years
|
||||||
this.cookieService.set(
|
this.cookieService.set(
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
<label class="col-md-2 control-label" [for]="idName">{{name}}</label>
|
<label class="col-md-2 control-label" [for]="idName">{{name}}</label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group" [ngSwitch]="uiType">
|
||||||
<app-gallery-search-field
|
<app-gallery-search-field
|
||||||
*ngIf="Type === 'SearchQuery'"
|
*ngSwitchCase="'SearchQuery'"
|
||||||
[(ngModel)]="state.value"
|
[(ngModel)]="state.value"
|
||||||
[id]="idName"
|
[id]="idName"
|
||||||
[name]="idName"
|
[name]="idName"
|
||||||
@ -24,18 +24,11 @@
|
|||||||
placeholder="Search Query">
|
placeholder="Search Query">
|
||||||
</app-gallery-search-field>
|
</app-gallery-search-field>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group"
|
||||||
|
*ngSwitchCase="'StringInput'">
|
||||||
<input
|
<input
|
||||||
*ngIf="!state.isEnumType &&
|
[type]="HTMLInputType" [min]="state.min" [max]="state.max" class="form-control"
|
||||||
!state.isEnumArrayType &&
|
[placeholder]="placeholder"
|
||||||
Type !== 'boolean' &&
|
|
||||||
Type !== 'SearchQuery' &&
|
|
||||||
ArrayType !== 'MapLayers' &&
|
|
||||||
ArrayType !== 'NavigationLinkConfig' &&
|
|
||||||
ArrayType !== 'JobScheduleConfig' &&
|
|
||||||
ArrayType !== 'UserConfig'"
|
|
||||||
[type]="type" [min]="state.min" [max]="state.max" class="form-control"
|
|
||||||
[placeholder]="PlaceHolder"
|
|
||||||
[title]="title"
|
[title]="title"
|
||||||
[(ngModel)]="StringValue"
|
[(ngModel)]="StringValue"
|
||||||
(ngModelChange)="onChange($event)"
|
(ngModelChange)="onChange($event)"
|
||||||
@ -49,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
*ngIf="state.isEnumType"
|
*ngSwitchCase="'EnumType'"
|
||||||
[id]="idName"
|
[id]="idName"
|
||||||
[name]="idName"
|
[name]="idName"
|
||||||
[title]="title"
|
[title]="title"
|
||||||
@ -62,7 +55,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<bSwitch
|
<bSwitch
|
||||||
*ngIf="Type === 'boolean'"
|
*ngSwitchCase="'Boolean'"
|
||||||
class="switch"
|
class="switch"
|
||||||
[id]="idName"
|
[id]="idName"
|
||||||
[name]="idName"
|
[name]="idName"
|
||||||
@ -83,7 +76,7 @@
|
|||||||
|
|
||||||
<app-settings-workflow
|
<app-settings-workflow
|
||||||
class="w-100"
|
class="w-100"
|
||||||
*ngIf="ArrayType === 'JobScheduleConfig'"
|
*ngSwitchCase="'JobScheduleConfig'"
|
||||||
[(ngModel)]="state.value"
|
[(ngModel)]="state.value"
|
||||||
[id]="idName"
|
[id]="idName"
|
||||||
[name]="idName"
|
[name]="idName"
|
||||||
@ -91,7 +84,7 @@
|
|||||||
(ngModelChange)="onChange($event)">
|
(ngModelChange)="onChange($event)">
|
||||||
</app-settings-workflow>
|
</app-settings-workflow>
|
||||||
|
|
||||||
<ng-container *ngIf="ArrayType === 'MapLayers'">
|
<ng-container *ngSwitchCase="'MapLayers'">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -131,7 +124,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="ArrayType === 'NavigationLinkConfig'">
|
<ng-container *ngSwitchCase="'NavigationLinkConfig'">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mt-1 mb-1 bg-light" *ngFor="let link of state.value; let i = index">
|
<div class="row mt-1 mb-1 bg-light" *ngFor="let link of state.value; let i = index">
|
||||||
<div class="col ps-0">
|
<div class="col ps-0">
|
||||||
@ -194,7 +187,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="ArrayType === 'UserConfig'">
|
<ng-container *ngSwitchCase="'UserConfig'">
|
||||||
<div class="container ps-0 pe-0">
|
<div class="container ps-0 pe-0">
|
||||||
<div class="row ms-0 me-0 mt-1 mb-1 bg-light" *ngFor="let item of state.value; let i = index">
|
<div class="row ms-0 me-0 mt-1 mb-1 bg-light" *ngFor="let item of state.value; let i = index">
|
||||||
<div class="col ps-0">
|
<div class="col ps-0">
|
||||||
@ -239,14 +232,14 @@
|
|||||||
<div class="row me-0">
|
<div class="row me-0">
|
||||||
<div class="col pe-0">
|
<div class="col pe-0">
|
||||||
<button class="btn btn-primary float-end"
|
<button class="btn btn-primary float-end"
|
||||||
(click)="AddNew()" i18n>+ Add Link
|
(click)="AddNew()" i18n>+ Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="state.isEnumArrayType">
|
<ng-container *ngSwitchCase="'EnumArray'">
|
||||||
<ng-container *ngFor="let _ of state.value; let i=index">
|
<ng-container *ngFor="let _ of state.value; let i=index">
|
||||||
<div class="row col-12 mt-1 m-0 p-0">
|
<div class="row col-12 mt-1 m-0 p-0">
|
||||||
<div class="col p-0">
|
<div class="col p-0">
|
||||||
@ -263,7 +256,7 @@
|
|||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="state.type === 'array'">
|
<ng-container>
|
||||||
<div class="col-auto pe-0">
|
<div class="col-auto pe-0">
|
||||||
<button class="btn btn-secondary float-end"
|
<button class="btn btn-secondary float-end"
|
||||||
[id]="'list_btn_'+idName+i"
|
[id]="'list_btn_'+idName+i"
|
||||||
@ -274,12 +267,12 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="state.type === 'array'">
|
<ng-container>
|
||||||
<div class="col-12 p-0">
|
<div class="col-12 p-0">
|
||||||
<button class="btn btn-primary mt-1 float-end"
|
<button class="btn btn-primary mt-1 float-end"
|
||||||
[id]="'btn_add_'+idName"
|
[id]="'btn_add_'+idName"
|
||||||
[name]="'btn_add_'+idName"
|
[name]="'btn_add_'+idName"
|
||||||
(click)="AddNew()">+Add
|
(click)="AddNew()" i18n>+Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -292,13 +285,14 @@
|
|||||||
class="oi oi-warning text-warning warning-icon ms-2" *ngIf="dockerWarning && changed"></span>
|
class="oi oi-warning text-warning warning-icon ms-2" *ngIf="dockerWarning && changed"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<small class="form-text text-muted" *ngIf="description">{{description}}
|
<small class="form-text text-muted" *ngIf="description">{{description}}
|
||||||
<span *ngIf="Type==='array' && (state.arrayType === 'string' || isNumberArray)" i18n>';' separated list.</span>
|
<span *ngIf="type==='array' && (state.arrayType === 'string' || isNumberArray)" i18n>';' separated list.</span>
|
||||||
<a *ngIf="state.tags?.githubIssue"
|
<a *ngIf="state.tags?.githubIssue"
|
||||||
[href]="'https://github.com/bpatrik/pigallery2/issues/'+state.tags?.githubIssue">See
|
[href]="'https://github.com/bpatrik/pigallery2/issues/'+state.tags?.githubIssue">
|
||||||
|
<ng-container i18n>See</ng-container>
|
||||||
#{{state.tags?.githubIssue}}.</a>
|
#{{state.tags?.githubIssue}}.</a>
|
||||||
</small>
|
</small>
|
||||||
<ng-content></ng-content>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
@ -1,4 +1,4 @@
|
|||||||
import {Component, forwardRef, Input, OnChanges} from '@angular/core';
|
import {Component, forwardRef, OnChanges} from '@angular/core';
|
||||||
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator,} from '@angular/forms';
|
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator,} from '@angular/forms';
|
||||||
import {Utils} from '../../../../../../common/Utils';
|
import {Utils} from '../../../../../../common/Utils';
|
||||||
import {propertyTypes} from 'typeconfig/common';
|
import {propertyTypes} from 'typeconfig/common';
|
||||||
@ -57,11 +57,15 @@ export class SettingsEntryComponent
|
|||||||
state: IState;
|
state: IState;
|
||||||
isNumberArray = false;
|
isNumberArray = false;
|
||||||
isNumber = false;
|
isNumber = false;
|
||||||
type = 'text';
|
HTMLInputType = 'text';
|
||||||
title: string;
|
title: string;
|
||||||
idName: string;
|
idName: string;
|
||||||
private readonly GUID = Utils.GUID();
|
private readonly GUID = Utils.GUID();
|
||||||
NavigationLinkTypes = NavigationLinkTypes;
|
NavigationLinkTypes = NavigationLinkTypes;
|
||||||
|
public type: string | object;
|
||||||
|
public arrayType: string;
|
||||||
|
public uiType: string;
|
||||||
|
|
||||||
|
|
||||||
constructor(private searchQueryParserService: SearchQueryParserService,
|
constructor(private searchQueryParserService: SearchQueryParserService,
|
||||||
public settingsService: SettingsService,
|
public settingsService: SettingsService,
|
||||||
@ -93,12 +97,8 @@ export class SettingsEntryComponent
|
|||||||
return this.state.shouldHide && this.state.shouldHide();
|
return this.state.shouldHide && this.state.shouldHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
get PlaceHolder(): string {
|
|
||||||
return this.placeholder || this.state.tags?.hint || this.state.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultStr(): string {
|
get defaultStr(): string {
|
||||||
if (this.Type === 'SearchQuery') {
|
if (this.type === 'SearchQuery') {
|
||||||
return (
|
return (
|
||||||
'\'' + this.searchQueryParserService.stringify(this.state.default) + '\''
|
'\'' + this.searchQueryParserService.stringify(this.state.default) + '\''
|
||||||
);
|
);
|
||||||
@ -111,26 +111,6 @@ export class SettingsEntryComponent
|
|||||||
return this.state.default;
|
return this.state.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
get Type(): string | object {
|
|
||||||
return this.state.tags?.uiType || this.state.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ArrayType(): string {
|
|
||||||
if (this.state.arrayType === MapLayers) {
|
|
||||||
return 'MapLayers';
|
|
||||||
}
|
|
||||||
if (this.state.arrayType === NavigationLinkConfig) {
|
|
||||||
return 'NavigationLinkConfig';
|
|
||||||
}
|
|
||||||
if (this.state.arrayType === UserConfig) {
|
|
||||||
return 'UserConfig';
|
|
||||||
}
|
|
||||||
if (this.state.arrayType === JobScheduleConfig) {
|
|
||||||
return 'JobScheduleConfig';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.arrayType;
|
|
||||||
}
|
|
||||||
|
|
||||||
get StringValue(): string {
|
get StringValue(): string {
|
||||||
if (
|
if (
|
||||||
@ -192,6 +172,44 @@ export class SettingsEntryComponent
|
|||||||
if (!this.state) {
|
if (!this.state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cache type overrides
|
||||||
|
this.type = this.state.tags?.uiType || this.state.type;
|
||||||
|
this.arrayType = null;
|
||||||
|
if (this.state.arrayType === MapLayers) {
|
||||||
|
this.arrayType = 'MapLayers';
|
||||||
|
} else if (this.state.arrayType === NavigationLinkConfig) {
|
||||||
|
this.arrayType = 'NavigationLinkConfig';
|
||||||
|
} else if (this.state.arrayType === UserConfig) {
|
||||||
|
this.arrayType = 'UserConfig';
|
||||||
|
} else if (this.state.arrayType === JobScheduleConfig) {
|
||||||
|
this.arrayType = 'JobScheduleConfig';
|
||||||
|
} else {
|
||||||
|
this.arrayType = this.state.arrayType;
|
||||||
|
}
|
||||||
|
this.uiType = this.arrayType;
|
||||||
|
if (!this.state.isEnumType &&
|
||||||
|
!this.state.isEnumArrayType &&
|
||||||
|
this.type !== 'boolean' &&
|
||||||
|
this.type !== 'SearchQuery' &&
|
||||||
|
this.arrayType !== 'MapLayers' &&
|
||||||
|
this.arrayType !== 'NavigationLinkConfig' &&
|
||||||
|
this.arrayType !== 'JobScheduleConfig' &&
|
||||||
|
this.arrayType !== 'UserConfig') {
|
||||||
|
this.uiType = 'StringInput';
|
||||||
|
}
|
||||||
|
if (this.type === 'SearchQuery') {
|
||||||
|
this.uiType = 'SearchQuery';
|
||||||
|
} else if (this.state.isEnumType) {
|
||||||
|
this.uiType = 'EnumType';
|
||||||
|
} else if (this.type === 'boolean') {
|
||||||
|
this.uiType = 'Boolean';
|
||||||
|
} else if (this.state.isEnumArrayType) {
|
||||||
|
this.uiType = 'EnumArray';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.placeholder = this.state.tags?.hint || this.state.default;
|
||||||
|
|
||||||
if (this.state.tags?.uiOptions) {
|
if (this.state.tags?.uiOptions) {
|
||||||
this.state.isEnumType = true;
|
this.state.isEnumType = true;
|
||||||
}
|
}
|
||||||
@ -218,11 +236,11 @@ export class SettingsEntryComponent
|
|||||||
|
|
||||||
|
|
||||||
if (this.isNumber) {
|
if (this.isNumber) {
|
||||||
this.type = 'number';
|
this.HTMLInputType = 'number';
|
||||||
} else if (this.state.type === 'password') {
|
} else if (this.state.type === 'password') {
|
||||||
this.type = 'password';
|
this.HTMLInputType = 'password';
|
||||||
} else {
|
} else {
|
||||||
this.type = 'text';
|
this.HTMLInputType = 'text';
|
||||||
}
|
}
|
||||||
this.description = this.description || this.state.description;
|
this.description = this.description || this.state.description;
|
||||||
if (this.state.tags) {
|
if (this.state.tags) {
|
@ -0,0 +1,28 @@
|
|||||||
|
.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;
|
||||||
|
}
|
@ -84,7 +84,7 @@
|
|||||||
{{job.description}}
|
{{job.description}}
|
||||||
</div>
|
</div>
|
||||||
<app-settings-job-button
|
<app-settings-job-button
|
||||||
*ngIf="!job.relevant || job.relevant(globalSettingsService.settings | async)"
|
*ngIf="!job.relevant || job.relevant(settingsService.settings | async)"
|
||||||
class="mt-2 mb-1 mb-md-0 mt-md-0 float-left me-2"
|
class="mt-2 mb-1 mb-md-0 mt-md-0 float-left me-2"
|
||||||
[soloRun]="true"
|
[soloRun]="true"
|
||||||
(jobError)="error=$event"
|
(jobError)="error=$event"
|
||||||
@ -95,7 +95,7 @@
|
|||||||
|
|
||||||
<ng-container *ngFor="let job of rStates.tags?.uiJob">
|
<ng-container *ngFor="let job of rStates.tags?.uiJob">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="getProgress(job.job) && !job.hideProgress && (!job.relevant || job.relevant(globalSettingsService.settings | async))">
|
*ngIf="getProgress(job.job) && !job.hideProgress && (!job.relevant || job.relevant(settingsService.settings | async))">
|
||||||
<hr class="mt-1"/>
|
<hr class="mt-1"/>
|
||||||
<app-settings-job-progress
|
<app-settings-job-progress
|
||||||
class="d-block mb-2"
|
class="d-block mb-2"
|
||||||
|
@ -1,59 +1,284 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||||
import {NavigationService} from '../../../model/navigation.service';
|
import {NavigationService} from '../../../model/navigation.service';
|
||||||
import {NotificationService} from '../../../model/notification.service';
|
import {NotificationService} from '../../../model/notification.service';
|
||||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
|
||||||
import {SettingsService} from '../settings.service';
|
import {SettingsService} from '../settings.service';
|
||||||
import {WebConfig} from '../../../../../common/config/private/WebConfig';
|
import {WebConfig} from '../../../../../common/config/private/WebConfig';
|
||||||
import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
|
|
||||||
import {JobProgressDTO} from '../../../../../common/entities/job/JobProgressDTO';
|
import {JobProgressDTO} from '../../../../../common/entities/job/JobProgressDTO';
|
||||||
import {JobDTOUtils} from '../../../../../common/entities/job/JobDTO';
|
import {JobDTOUtils} from '../../../../../common/entities/job/JobDTO';
|
||||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||||
|
import {FormControl} from '../../../../../../node_modules/@angular/forms';
|
||||||
|
import {Subscription} from 'rxjs';
|
||||||
|
import {IWebConfigClassPrivate} from '../../../../../../node_modules/typeconfig/src/decorators/class/IWebConfigClass';
|
||||||
|
import {ConfigPriority, TAGS} from '../../../../../common/config/public/ClientConfig';
|
||||||
|
import {Utils} from '../../../../../common/Utils';
|
||||||
|
import {UserRoles} from '../../../../../common/entities/UserDTO';
|
||||||
|
import {WebConfigClassBuilder} from '../../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder';
|
||||||
|
import {ErrorDTO} from '../../../../../common/entities/Error';
|
||||||
|
import {ISettingsComponent} from './ISettingsComponent';
|
||||||
|
|
||||||
|
|
||||||
|
interface ConfigState {
|
||||||
|
value: {
|
||||||
|
[key: string]: RecursiveState;
|
||||||
|
};
|
||||||
|
default: {
|
||||||
|
[key: string]: RecursiveState;
|
||||||
|
};
|
||||||
|
readonly?: boolean;
|
||||||
|
tags?: TAGS;
|
||||||
|
volatile?: boolean;
|
||||||
|
isEnumType?: boolean;
|
||||||
|
isConfigType?: boolean;
|
||||||
|
isConfigArrayType?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecursiveState extends ConfigState {
|
||||||
|
value: any;
|
||||||
|
default: any;
|
||||||
|
volatile?: any;
|
||||||
|
tags?: any;
|
||||||
|
isConfigType?: any;
|
||||||
|
isConfigArrayType?: any;
|
||||||
|
isEnumType?: any;
|
||||||
|
readonly?: any;
|
||||||
|
toJSON?: any;
|
||||||
|
onChange?: any;
|
||||||
|
original?: any;
|
||||||
|
shouldHide?: any;
|
||||||
|
|
||||||
|
[key: string]: RecursiveState;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings-template',
|
selector: 'app-settings-template',
|
||||||
templateUrl: './template.component.html',
|
templateUrl: './template.component.html',
|
||||||
styleUrls: ['./template.component.css',
|
styleUrls: ['./template.component.css']
|
||||||
'../_abstract/abstract.settings.component.css']
|
|
||||||
})
|
})
|
||||||
export class TemplateComponent extends SettingsComponentDirective<any> implements OnInit {
|
export class TemplateComponent 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: IWebConfigClassPrivate<TAGS> & WebConfig) => ConfigState;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
authService: AuthenticationService,
|
protected authService: AuthenticationService,
|
||||||
navigation: NavigationService,
|
private navigation: NavigationService,
|
||||||
notification: NotificationService,
|
protected notification: NotificationService,
|
||||||
settingsService: AbstractSettingsService,
|
public settingsService: SettingsService,
|
||||||
globalSettingsService: SettingsService,
|
|
||||||
public jobsService: ScheduledJobsService,
|
public jobsService: ScheduledJobsService,
|
||||||
) {
|
) {
|
||||||
super(
|
|
||||||
authService,
|
|
||||||
navigation,
|
|
||||||
settingsService,
|
|
||||||
notification,
|
|
||||||
globalSettingsService
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
if (
|
||||||
// @ts-ignore
|
!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();
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.ConfigPath) {
|
if (!this.ConfigPath) {
|
||||||
this.setSliceFN(c => ({value: c, isConfigType: true, type: WebConfig}));
|
this.setSliceFN(c => ({value: c as any, isConfigType: true, type: WebConfig} as any));
|
||||||
} else {
|
} else {
|
||||||
this.setSliceFN(c => c.__state[this.ConfigPath]);
|
this.setSliceFN(c => c.__state[this.ConfigPath]);
|
||||||
}
|
}
|
||||||
this.name = this.states.tags?.name || this.ConfigPath;
|
this.name = this.states.tags?.name || this.ConfigPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeys(states: any) {
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.subscription != null) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.settingsSubscription != null) {
|
||||||
|
this.settingsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setSliceFN(sliceFN?: (s: IWebConfigClassPrivate<TAGS> & WebConfig) => ConfigState) {
|
||||||
|
if (sliceFN) {
|
||||||
|
this.sliceFN = sliceFN;
|
||||||
|
this.settingsSubscription = this.settingsService.settings.subscribe(
|
||||||
|
this.onNewSettings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: IWebConfigClassPrivate<TAGS> & 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.settingsService.configPriority ||
|
||||||
|
(this.settingsService.configPriority === ConfigPriority.basic &&
|
||||||
|
state.tags?.dockerSensitive && this.settingsService.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);
|
||||||
|
};
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this.getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async save(): Promise<boolean> {
|
||||||
|
this.inProgress = true;
|
||||||
|
this.error = '';
|
||||||
|
try {
|
||||||
|
const state = WebConfigClassBuilder.attachInterface(this.states.value).toJSON();
|
||||||
|
await this.settingsService.updateSettings(state, 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<void> {
|
||||||
|
await this.settingsService.getSettings();
|
||||||
|
this.changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeys(states: any): string[] {
|
||||||
|
if (states.keys) {
|
||||||
|
return states.keys;
|
||||||
|
}
|
||||||
const s = states.value.__state;
|
const s = states.value.__state;
|
||||||
return Object.keys(s).sort((a, b) => {
|
const keys = Object.keys(s).sort((a, b) => {
|
||||||
if ((s[a].isConfigType || s[a].isConfigArrayType) !== (s[b].isConfigType || s[b].isConfigArrayType)) {
|
if ((s[a].isConfigType || s[a].isConfigArrayType) !== (s[b].isConfigType || s[b].isConfigArrayType)) {
|
||||||
if (s[a].isConfigType || s[a].isConfigArrayType) {
|
if (s[a].isConfigType || s[a].isConfigArrayType) {
|
||||||
return 1;
|
return 1;
|
||||||
@ -68,6 +293,8 @@ export class TemplateComponent extends SettingsComponentDirective<any> implement
|
|||||||
return (s[a].tags?.name as string || a).localeCompare(s[b].tags?.name || b);
|
return (s[a].tags?.name as string || a).localeCompare(s[b].tags?.name || b);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
states.keys = keys;
|
||||||
|
return states.keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProgress(jobName: string): JobProgressDTO {
|
getProgress(jobName: string): JobProgressDTO {
|
||||||
|
Loading…
Reference in New Issue
Block a user