1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-02 03:37:54 +02:00

Add basic configuring options #753

This commit is contained in:
Patrik J. Braun 2023-11-13 16:51:25 +01:00
parent 4b215c1e57
commit 75d277040d
11 changed files with 185 additions and 97 deletions

View File

@ -9,6 +9,7 @@ import {SharingDTO} from '../../common/entities/SharingDTO';
import {Utils} from '../../common/Utils'; import {Utils} from '../../common/Utils';
import {LoggerRouter} from '../routes/LoggerRouter'; import {LoggerRouter} from '../routes/LoggerRouter';
import {TAGS} from '../../common/config/public/ClientConfig'; import {TAGS} from '../../common/config/public/ClientConfig';
import {ExtensionConfigWrapper} from '../model/extension/ExtensionConfigWrapper';
const forcedDebug = process.env['NODE_ENV'] === 'debug'; const forcedDebug = process.env['NODE_ENV'] === 'debug';
@ -107,7 +108,7 @@ export class RenderingMWs {
req: Request, req: Request,
res: Response res: Response
): Promise<void> { ): Promise<void> {
const originalConf = await Config.original(); const originalConf = await ExtensionConfigWrapper.original();
// These are sensitive information, do not send to the client side // These are sensitive information, do not send to the client side
originalConf.Server.sessionSecret = null; originalConf.Server.sessionSecret = null;
const message = new Message<PrivateConfigClass>( const message = new Message<PrivateConfigClass>(

View File

@ -6,6 +6,7 @@ import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics';
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node'; import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
import {TAGS} from '../../../common/config/public/ClientConfig'; import {TAGS} from '../../../common/config/public/ClientConfig';
import {ObjectManagers} from '../../model/ObjectManagers'; import {ObjectManagers} from '../../model/ObjectManagers';
import {ExtensionConfigWrapper} from '../../model/extension/ExtensionConfigWrapper';
const LOG_TAG = '[SettingsMWs]'; const LOG_TAG = '[SettingsMWs]';
@ -28,7 +29,7 @@ export class SettingsMWs {
try { try {
let settings = req.body.settings; // Top level settings JSON let settings = req.body.settings; // Top level settings JSON
const settingsPath: string = req.body.settingsPath; // Name of the top level settings const settingsPath: string = req.body.settingsPath; // Name of the top level settings
const transformer = await Config.original(); const transformer = await ExtensionConfigWrapper.original();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
transformer[settingsPath] = settings; transformer[settingsPath] = settings;
@ -37,7 +38,7 @@ export class SettingsMWs {
settings = ConfigClassBuilder.attachPrivateInterface(transformer[settingsPath]).toJSON({ settings = ConfigClassBuilder.attachPrivateInterface(transformer[settingsPath]).toJSON({
skipTags: {secret: true} as TAGS skipTags: {secret: true} as TAGS
}); });
const original = await Config.original(); const original = await ExtensionConfigWrapper.original();
// only updating explicitly set config (not saving config set by the diagnostics) // only updating explicitly set config (not saving config set by the diagnostics)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore

View File

@ -0,0 +1,50 @@
import {IConfigClass} from '../../../../node_modules/typeconfig/common';
import {Config, PrivateConfigClass} from '../../../common/config/private/Config';
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
import {IExtensionConfig} from './IExtension';
import {Utils} from '../../../common/Utils';
import {ObjectManagers} from '../ObjectManagers';
/**
* Wraps to original config and makes sure all extension related config is loaded
*/
export class ExtensionConfigWrapper {
static async original(): Promise<PrivateConfigClass & IConfigClass> {
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
try {
await pc.load();
for (const ext of Object.values(ObjectManagers.getInstance().ExtensionManager.extObjects)) {
ext.config.loadToConfig(ConfigClassBuilder.attachPrivateInterface(pc));
}
} catch (e) {
console.error('Error during loading original config. Reverting to defaults.');
console.error(e);
}
return pc;
}
}
export class ExtensionConfig<C> implements IExtensionConfig<C> {
public template: new() => C;
constructor(private readonly extensionId: string) {
}
public getConfig(): C {
return Config.Extensions.configs[this.extensionId] as C;
}
public setTemplate(template: new() => C): void {
this.template = template;
this.loadToConfig(Config);
}
loadToConfig(config: PrivateConfigClass) {
if (!this.template) {
return;
}
const conf = ConfigClassBuilder.attachPrivateInterface(new this.template());
conf.__loadJSONObject(Utils.clone(config.Extensions.configs[this.extensionId] || {}));
config.Extensions.configs[this.extensionId] = conf;
}
}

View File

@ -3,15 +3,13 @@ import {Config} from '../../../common/config/private/Config';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import {IObjectManager} from '../database/IObjectManager'; import {IObjectManager} from '../database/IObjectManager';
import {createLoggerWrapper, Logger} from '../../Logger'; import {Logger} from '../../Logger';
import {IExtensionEvents, IExtensionObject, IServerExtension} from './IExtension'; import {IExtensionEvents, IExtensionObject, IServerExtension} from './IExtension';
import {Server} from '../../server'; import {Server} from '../../server';
import {ExtensionEvent} from './ExtensionEvent'; import {ExtensionEvent} from './ExtensionEvent';
import {ExpressRouterWrapper} from './ExpressRouterWrapper';
import * as express from 'express'; import * as express from 'express';
import {ExtensionApp} from './ExtensionApp';
import {ExtensionDB} from './ExtensionDB';
import {SQLConnection} from '../database/SQLConnection'; import {SQLConnection} from '../database/SQLConnection';
import {ExtensionObject} from './ExtensionObject';
const LOG_TAG = '[ExtensionManager]'; const LOG_TAG = '[ExtensionManager]';
@ -20,7 +18,7 @@ export class ExtensionManager implements IObjectManager {
public static EXTENSION_API_PATH = Config.Server.apiPath + '/extension'; public static EXTENSION_API_PATH = Config.Server.apiPath + '/extension';
events: IExtensionEvents; events: IExtensionEvents;
extObjects: { [key: string]: IExtensionObject } = {}; extObjects: { [key: string]: ExtensionObject<unknown> } = {};
router: express.Router; router: express.Router;
constructor() { constructor() {
@ -65,15 +63,15 @@ export class ExtensionManager implements IObjectManager {
} }
Config.Extensions.list = fs Config.Extensions.list = fs
.readdirSync(ProjectPath.ExtensionFolder) .readdirSync(ProjectPath.ExtensionFolder)
.filter((f): boolean => .filter((f): boolean =>
fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory()
); );
Config.Extensions.list.sort(); Config.Extensions.list.sort();
Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list)); Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list));
} }
private async callServerFN(fn: (ext: IServerExtension, extName: string) => Promise<void>) { private async callServerFN(fn: (ext: IServerExtension<unknown>, extName: string) => Promise<void>) {
for (let i = 0; i < Config.Extensions.list.length; ++i) { for (let i = 0; i < Config.Extensions.list.length; ++i) {
const extName = Config.Extensions.list[i]; const extName = Config.Extensions.list[i];
const extPath = path.join(ProjectPath.ExtensionFolder, extName); const extPath = path.join(ProjectPath.ExtensionFolder, extName);
@ -88,17 +86,9 @@ export class ExtensionManager implements IObjectManager {
} }
} }
private createExtensionObject(name: string): IExtensionObject { private createExtensionObject(name: string): IExtensionObject<unknown> {
if (!this.extObjects[name]) { if (!this.extObjects[name]) {
const logger = createLoggerWrapper(`[Extension][${name}]`); this.extObjects[name] = new ExtensionObject(name, this.router, this.events);
this.extObjects[name] = {
_app: new ExtensionApp(),
db: new ExtensionDB(logger),
paths: ProjectPath,
Logger: logger,
events: this.events,
RESTApi: new ExpressRouterWrapper(this.router, name, logger)
};
} }
return this.extObjects[name]; return this.extObjects[name];
} }

View File

@ -0,0 +1,31 @@
import {IExtensionEvents, IExtensionObject} from './IExtension';
import {ExtensionApp} from './ExtensionApp';
import {ExtensionConfig} from './ExtensionConfigWrapper';
import {ExtensionDB} from './ExtensionDB';
import {ProjectPath} from '../../ProjectPath';
import {ExpressRouterWrapper} from './ExpressRouterWrapper';
import {createLoggerWrapper} from '../../Logger';
import * as express from 'express';
export class ExtensionObject<C> implements IExtensionObject<C> {
public readonly _app;
public readonly config;
public readonly db;
public readonly paths;
public readonly Logger;
public readonly events;
public readonly RESTApi;
constructor(public readonly extensionId: string, extensionRouter: express.Router, events: IExtensionEvents) {
const logger = createLoggerWrapper(`[Extension][${extensionId}]`);
this._app = new ExtensionApp();
this.config = new ExtensionConfig<C>(extensionId);
this.db = new ExtensionDB(logger);
this.paths = ProjectPath;
this.Logger = logger;
this.events = events;
this.RESTApi = new ExpressRouterWrapper(extensionRouter, extensionId, logger);
}
}

View File

@ -6,7 +6,7 @@ import {ProjectPathClass} from '../../ProjectPath';
import {ILogger} from '../../Logger'; import {ILogger} from '../../Logger';
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
import {ParamsDictionary} from 'express-serve-static-core'; import {ParamsDictionary} from 'express-serve-static-core';
import {Connection, EntitySchema} from 'typeorm'; import {Connection} from 'typeorm';
export type IExtensionBeforeEventHandler<I, O> = (input: { inputs: I }, event: { stopPropagation: boolean }) => Promise<{ inputs: I } | O>; export type IExtensionBeforeEventHandler<I, O> = (input: { inputs: I }, event: { stopPropagation: boolean }) => Promise<{ inputs: I } | O>;
@ -66,17 +66,17 @@ export interface IExtensionApp {
export interface IExtensionRESTRoute { export interface IExtensionRESTRoute {
/** /**
* Sends a pigallery2 standard JSON object with payload or error message back to the client. * Sends a pigallery2 standard JSON object with payload or error message back to the client.
* @param paths * @param paths RESTapi path, relative to the extension base endpoint
* @param minRole * @param minRole set to null to omit auer check (ie make the endpoint public)
* @param cb * @param cb function callback
*/ */
jsonResponse(paths: string[], minRole: UserRoles, cb: (params?: ParamsDictionary, body?: any, user?: UserDTO) => Promise<unknown> | unknown): void; jsonResponse(paths: string[], minRole: UserRoles, cb: (params?: ParamsDictionary, body?: any, user?: UserDTO) => Promise<unknown> | unknown): void;
/** /**
* Exposes a standard expressjs middleware * Exposes a standard expressjs middleware
* @param paths * @param paths RESTapi path, relative to the extension base endpoint
* @param minRole * @param minRole set to null to omit auer check (ie make the endpoint public)
* @param mw * @param mw expressjs middleware
*/ */
rawMiddleware(paths: string[], minRole: UserRoles, mw: (req: Request, res: Response, next: NextFunction) => void | Promise<void>): void; rawMiddleware(paths: string[], minRole: UserRoles, mw: (req: Request, res: Response, next: NextFunction) => void | Promise<void>): void;
} }
@ -110,13 +110,24 @@ export interface IExtensionDB {
_getAllTables(): Function[]; _getAllTables(): Function[];
} }
export interface IExtensionObject { export interface IExtensionConfig<C> {
setTemplate(template: new() => C): void;
getConfig(): C;
}
export interface IExtensionObject<C> {
/** /**
* Inner functionality of the app. Use this with caution. * Inner functionality of the app. Use this with caution.
* If you want to go deeper than the standard exposed APIs, you can try doing so here. * If you want to go deeper than the standard exposed APIs, you can try doing so here.
*/ */
_app: IExtensionApp; _app: IExtensionApp;
/**
* Create extension related configuration
*/
config: IExtensionConfig<C>;
/** /**
* Create new SQL tables and access SQL connection * Create new SQL tables and access SQL connection
*/ */
@ -144,12 +155,12 @@ export interface IExtensionObject {
/** /**
* Extension interface. All extension is expected to implement and export these methods * Extension interface. All extension is expected to implement and export these methods
*/ */
export interface IServerExtension { export interface IServerExtension<C> {
/** /**
* Extension init function. Extension should at minimum expose this function. * Extension init function. Extension should at minimum expose this function.
* @param extension * @param extension
*/ */
init(extension: IExtensionObject): Promise<void>; init(extension: IExtensionObject<C>): Promise<void>;
cleanUp?: (extension: IExtensionObject) => Promise<void>; cleanUp?: (extension: IExtensionObject<C>) => Promise<void>;
} }

View File

@ -13,7 +13,7 @@ const upTime = new Date().toISOString();
// TODO: Refactor Config to be injectable globally. // TODO: Refactor Config to be injectable globally.
// This is a bad habit to let the Config know if its in a testing env. // This is a bad habit to let the Config know if its in a testing env.
const isTesting = process.env['NODE_ENV'] == true || ['afterEach', 'after', 'beforeEach', 'before', 'describe', 'it'] const isTesting = process.env['NODE_ENV'] == true || ['afterEach', 'after', 'beforeEach', 'before', 'describe', 'it']
.every((fn) => (global as any)[fn] instanceof Function); .every((fn) => (global as any)[fn] instanceof Function);
@ConfigClass<IConfigClass<TAGS> & ServerConfig>({ @ConfigClass<IConfigClass<TAGS> & ServerConfig>({
configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/tmp/config.json'), configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/tmp/config.json'),
@ -76,30 +76,20 @@ export class PrivateConfigClass extends ServerConfig {
} }
this.Environment.appVersion = this.Environment.appVersion =
require('../../../../package.json').version; require('../../../../package.json').version;
this.Environment.buildTime = this.Environment.buildTime =
require('../../../../package.json').buildTime; require('../../../../package.json').buildTime;
this.Environment.buildCommitHash = this.Environment.buildCommitHash =
require('../../../../package.json').buildCommitHash; require('../../../../package.json').buildCommitHash;
this.Environment.upTime = upTime; this.Environment.upTime = upTime;
this.Environment.isDocker = !!process.env.PI_DOCKER; this.Environment.isDocker = !!process.env.PI_DOCKER;
} }
async original(): Promise<PrivateConfigClass & IConfigClass> {
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
try {
await pc.load();
} catch (e) {
console.error('Error during loading original config. Reverting to defaults.');
console.error(e);
}
return pc;
}
} }
export const Config = ConfigClassBuilder.attachInterface( export const Config = ConfigClassBuilder.attachInterface(
new PrivateConfigClass() new PrivateConfigClass()
); );
try { try {
Config.loadSync(); Config.loadSync();

View File

@ -1014,12 +1014,14 @@ export class ServerServiceConfig extends ClientServiceConfig {
} }
@SubConfigClass<TAGS>({softReadonly: true}) @SubConfigClass<TAGS>({softReadonly: true})
export class ServerExtensionsConfig { export class ServerExtensionsConfig {
@ConfigProperty({volatile: true}) @ConfigProperty({volatile: true})
list: string[] = []; list: string[] = [];
@ConfigProperty({type: 'object'})
configs: Record<string, unknown> = {};
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Clean up unused tables`, name: $localize`Clean up unused tables`,

View File

@ -59,7 +59,7 @@ interface IState {
], ],
}) })
export class SettingsEntryComponent export class SettingsEntryComponent
implements ControlValueAccessor, Validator, OnChanges { implements ControlValueAccessor, Validator, OnChanges {
name: string; name: string;
required: boolean; required: boolean;
dockerWarning: boolean; dockerWarning: boolean;
@ -79,7 +79,10 @@ export class SettingsEntryComponent
public arrayType: string; public arrayType: string;
public uiType: string; public uiType: string;
newThemeModalRef: any; newThemeModalRef: any;
iconModal: { ref?: any, error?: string }; iconModal: {
ref?: any,
error?: string
};
@Input() noChangeDetection = false; @Input() noChangeDetection = false;
public readonly ConfigStyle = ConfigStyle; public readonly ConfigStyle = ConfigStyle;
protected readonly SortByTypes = SortByTypes; protected readonly SortByTypes = SortByTypes;
@ -101,9 +104,9 @@ export class SettingsEntryComponent
for (let i = 0; i < this.state.value?.length; ++i) { for (let i = 0; i < this.state.value?.length; ++i) {
for (const k of Object.keys(this.state.value[i].__state)) { for (const k of Object.keys(this.state.value[i].__state)) {
if (!Utils.equalsFilter( if (!Utils.equalsFilter(
this.state.value[i]?.__state[k]?.value, this.state.value[i]?.__state[k]?.value,
this.state.default[i] ? this.state.default[i][k] : undefined, this.state.default[i] ? this.state.default[i][k] : undefined,
['default', '__propPath', '__created', '__prototype', '__rootConfig'])) { ['default', '__propPath', '__created', '__prototype', '__rootConfig'])) {
return true; return true;
} }
@ -129,7 +132,7 @@ export class SettingsEntryComponent
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) + '\''
); );
} }
@ -143,8 +146,8 @@ export class SettingsEntryComponent
get StringValue(): string { get StringValue(): string {
if ( if (
this.state.type === 'array' && this.state.type === 'array' &&
(this.state.arrayType === 'string' || this.isNumberArray) (this.state.arrayType === 'string' || this.isNumberArray)
) { ) {
return (this.state.value || []).join(';'); return (this.state.value || []).join(';');
} }
@ -162,8 +165,8 @@ export class SettingsEntryComponent
set StringValue(value: string) { set StringValue(value: string) {
if ( if (
this.state.type === 'array' && this.state.type === 'array' &&
(this.state.arrayType === 'string' || this.isNumberArray) (this.state.arrayType === 'string' || this.isNumberArray)
) { ) {
value = value.replace(new RegExp(',', 'g'), ';'); value = value.replace(new RegExp(',', 'g'), ';');
if (!this.allowSpaces) { if (!this.allowSpaces) {
@ -172,14 +175,14 @@ export class SettingsEntryComponent
this.state.value = value.split(';').filter((v: string) => v !== ''); this.state.value = value.split(';').filter((v: string) => v !== '');
if (this.isNumberArray) { if (this.isNumberArray) {
this.state.value = this.state.value this.state.value = this.state.value
.map((v: string) => parseFloat(v)) .map((v: string) => parseFloat(v))
.filter((v: number) => !isNaN(v)); .filter((v: number) => !isNaN(v));
} }
return; return;
} }
if (typeof this.state.value === 'object') { if (typeof this.state.value === 'object') {
this.state.value = JSON.parse(value); this.state.value = JSON.parse(value);
return;
} }
this.state.value = value; this.state.value = value;
@ -194,11 +197,13 @@ export class SettingsEntryComponent
key: 'default', key: 'default',
value: $localize`default` value: $localize`default`
}, ...(this.state.rootConfig as any).__state.availableThemes.value }, ...(this.state.rootConfig as any).__state.availableThemes.value
.map((th: ThemeConfig) => ({key: th.name, value: th.name}))]; .map((th: ThemeConfig) => ({key: th.name, value: th.name}))];
} }
get SelectedThemeSettings(): { theme: string } { get SelectedThemeSettings(): {
theme: string
} {
return (this.state.value as ThemeConfig[]).find(th => th.name === (this.state.rootConfig as any).__state.selectedTheme.value) || {theme: 'N/A'}; return (this.state.value as ThemeConfig[]).find(th => th.name === (this.state.rootConfig as any).__state.selectedTheme.value) || {theme: 'N/A'};
} }
@ -241,16 +246,16 @@ export class SettingsEntryComponent
this.uiType = CustomSettingsEntries.getFullName(this.state); this.uiType = CustomSettingsEntries.getFullName(this.state);
} }
if (!this.state.isEnumType && if (!this.state.isEnumType &&
!this.state.isEnumArrayType && !this.state.isEnumArrayType &&
this.type !== 'boolean' && this.type !== 'boolean' &&
this.type !== 'SearchQuery' && this.type !== 'SearchQuery' &&
!CustomSettingsEntries.iS(this.state) && !CustomSettingsEntries.iS(this.state) &&
this.arrayType !== 'MapLayers' && this.arrayType !== 'MapLayers' &&
this.arrayType !== 'NavigationLinkConfig' && this.arrayType !== 'NavigationLinkConfig' &&
this.arrayType !== 'MapPathGroupConfig' && this.arrayType !== 'MapPathGroupConfig' &&
this.arrayType !== 'MapPathGroupThemeConfig' && this.arrayType !== 'MapPathGroupThemeConfig' &&
this.arrayType !== 'JobScheduleConfig' && this.arrayType !== 'JobScheduleConfig' &&
this.arrayType !== 'UserConfig') { this.arrayType !== 'UserConfig') {
this.uiType = 'StringInput'; this.uiType = 'StringInput';
} }
if (this.type === this.state.tags?.uiType) { if (this.type === this.state.tags?.uiType) {
@ -273,18 +278,18 @@ export class SettingsEntryComponent
this.name = this.state?.tags?.name; this.name = this.state?.tags?.name;
if (this.name) { if (this.name) {
this.idName = this.idName =
this.GUID + this.name.toLowerCase().replace(new RegExp(' ', 'gm'), '-'); this.GUID + this.name.toLowerCase().replace(new RegExp(' ', 'gm'), '-');
} }
this.isNumberArray = this.isNumberArray =
this.state.arrayType === 'unsignedInt' || this.state.arrayType === 'unsignedInt' ||
this.state.arrayType === 'integer' || this.state.arrayType === 'integer' ||
this.state.arrayType === 'float' || this.state.arrayType === 'float' ||
this.state.arrayType === 'positiveFloat'; this.state.arrayType === 'positiveFloat';
this.isNumber = this.isNumber =
this.state.type === 'unsignedInt' || this.state.type === 'unsignedInt' ||
this.state.type === 'integer' || this.state.type === 'integer' ||
this.state.type === 'float' || this.state.type === 'float' ||
this.state.type === 'positiveFloat'; this.state.type === 'positiveFloat';
if (this.isNumber) { if (this.isNumber) {
@ -306,11 +311,16 @@ export class SettingsEntryComponent
} }
} }
getOptionsView(state: IState & { optionsView?: { key: number | string; value: string | number }[] }) { getOptionsView(state: IState & {
optionsView?: {
key: number | string;
value: string | number
}[]
}) {
if (!state.optionsView) { if (!state.optionsView) {
const eClass = state.isEnumType const eClass = state.isEnumType
? state.type ? state.type
: state.arrayType; : state.arrayType;
if (state.tags?.uiOptions) { if (state.tags?.uiOptions) {
state.optionsView = state.tags?.uiOptions.map(o => ({ state.optionsView = state.tags?.uiOptions.map(o => ({
key: o, key: o,
@ -325,11 +335,11 @@ export class SettingsEntryComponent
validate(): ValidationErrors { validate(): ValidationErrors {
if ( if (
!this.required || !this.required ||
(this.state && (this.state &&
typeof this.state.value !== 'undefined' && typeof this.state.value !== 'undefined' &&
this.state.value !== null && this.state.value !== null &&
this.state.value !== '') this.state.value !== '')
) { ) {
return null; return null;
} }
@ -386,8 +396,8 @@ export class SettingsEntryComponent
removeLayer(layer: MapLayers): void { removeLayer(layer: MapLayers): void {
this.state.value.splice( this.state.value.splice(
this.state.value.indexOf(layer), this.state.value.indexOf(layer),
1 1
); );
} }
@ -395,7 +405,7 @@ export class SettingsEntryComponent
addNewTheme(): void { addNewTheme(): void {
const availableThemes = (this.state.rootConfig as any).__state.availableThemes; const availableThemes = (this.state.rootConfig as any).__state.availableThemes;
if (!this.newThemeName || if (!this.newThemeName ||
(availableThemes.value as ThemeConfig[]).find(th => th.name === this.newThemeName)) { (availableThemes.value as ThemeConfig[]).find(th => th.name === this.newThemeName)) {
return; return;
} }
this.state.value = this.newThemeName; this.state.value = this.newThemeName;

View File

@ -7,6 +7,7 @@ import {ProjectPath} from '../../../../../src/backend/ProjectPath';
import {TAGS} from '../../../../../src/common/config/public/ClientConfig'; import {TAGS} from '../../../../../src/common/config/public/ClientConfig';
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers'; import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
import {UserRoles} from '../../../../../src/common/entities/UserDTO'; import {UserRoles} from '../../../../../src/common/entities/UserDTO';
import {ExtensionConfigWrapper} from '../../../../../src/backend/model/extension/ExtensionConfigWrapper';
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
const chai: any = require('chai'); const chai: any = require('chai');
@ -34,7 +35,7 @@ describe('SettingsRouter', () => {
it('it should GET the settings', async () => { it('it should GET the settings', async () => {
Config.Users.authenticationRequired = false; Config.Users.authenticationRequired = false;
Config.Users.unAuthenticatedUserRole = UserRoles.Admin; Config.Users.unAuthenticatedUserRole = UserRoles.Admin;
const originalSettings = await Config.original(); const originalSettings = await ExtensionConfigWrapper.original();
const srv = new Server(); const srv = new Server();
await srv.onStarted.wait(); await srv.onStarted.wait();
const result = await chai.request(srv.App) const result = await chai.request(srv.App)

View File

@ -9,6 +9,7 @@ import {UserRoles} from '../../../../../src/common/entities/UserDTO';
import {ConfigClassBuilder} from '../../../../../node_modules/typeconfig/node'; import {ConfigClassBuilder} from '../../../../../node_modules/typeconfig/node';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import {ExtensionConfigWrapper} from '../../../../../src/backend/model/extension/ExtensionConfigWrapper';
declare const describe: any; declare const describe: any;
@ -74,7 +75,7 @@ describe('Settings middleware', () => {
expect(Config.Users.enforcedUsers.length).to.be.equal(1); expect(Config.Users.enforcedUsers.length).to.be.equal(1);
expect(Config.Users.enforcedUsers[0].name).to.be.equal('Apple'); expect(Config.Users.enforcedUsers[0].name).to.be.equal('Apple');
expect(Config.Users.enforcedUsers.length).to.be.equal(1); expect(Config.Users.enforcedUsers.length).to.be.equal(1);
Config.original().then((cfg) => { ExtensionConfigWrapper.original().then((cfg) => {
try { try {
expect(cfg.Users.enforcedUsers.length).to.be.equal(1); expect(cfg.Users.enforcedUsers.length).to.be.equal(1);
expect(cfg.Users.enforcedUsers[0].name).to.be.equal('Apple'); expect(cfg.Users.enforcedUsers[0].name).to.be.equal('Apple');