From b1362ffef515804c3c174dfa2c1cec32369f3d1a Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 24 Dec 2023 08:59:36 +0100 Subject: [PATCH 1/8] Refactor extension settings --- .../model/extension/ExtensionConfigWrapper.ts | 39 ++++++++-- .../model/extension/ExtensionManager.ts | 31 +++++--- .../model/extension/ExtensionObject.ts | 2 +- src/common/config/private/PrivateConfig.ts | 33 +------- .../{ => subconfigs}/MessagingConfig.ts | 2 +- .../subconfigs/ServerExtensionsConfig.ts | 78 +++++++++++++++++++ .../settings-entry.component.ts | 4 + 7 files changed, 139 insertions(+), 50 deletions(-) rename src/common/config/private/{ => subconfigs}/MessagingConfig.ts (97%) create mode 100644 src/common/config/private/subconfigs/ServerExtensionsConfig.ts diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index b11e5eaa..cad0ad24 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -1,9 +1,9 @@ -import {IConfigClass} from 'typeconfig/common'; +import {ConfigProperty, IConfigClass} from 'typeconfig/common'; import {Config, PrivateConfigClass} from '../../../common/config/private/Config'; import {ConfigClassBuilder} from 'typeconfig/node'; import {IExtensionConfig} from './IExtension'; -import {Utils} from '../../../common/Utils'; import {ObjectManagers} from '../ObjectManagers'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; /** * Wraps to original config and makes sure all extension related config is loaded @@ -29,11 +29,29 @@ export class ExtensionConfigWrapper { export class ExtensionConfig implements IExtensionConfig { public template: new() => C; - constructor(private readonly extensionId: string) { + constructor(private readonly extensionFolder: string) { + } + + private findConfig(config: PrivateConfigClass) { + let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); + if (!c) { + c = new ServerExtensionsEntryConfig(this.extensionFolder); + config.Extensions.extensions.push(c); + } + + if (!config.Extensions.extensions2[this.extensionFolder]) { + Object.defineProperty(config.Extensions.extensions2, this.extensionFolder, + ConfigProperty({type: ServerExtensionsEntryConfig})(config.Extensions.extensions2, this.extensionFolder)); + // config.Extensions.extensions2[this.extensionFolder] = c as any; + + config.Extensions.extensions2[this.extensionFolder] = c; + + } + return config.Extensions.extensions2[this.extensionFolder]; } public getConfig(): C { - return Config.Extensions.configs[this.extensionId] as C; + return this.findConfig(Config).configs as C; } public setTemplate(template: new() => C): void { @@ -45,8 +63,15 @@ export class ExtensionConfig implements IExtensionConfig { 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; + + const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); + const extConf = this.findConfig(config); + // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); + //extConf.configs = confTemplate; + Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, + ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); + console.log(config.Extensions.extensions2[this.extensionFolder].configs); + config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; + console.log(config.Extensions.extensions2[this.extensionFolder].configs); } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index f02dfb28..11f50aa4 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -12,6 +12,7 @@ import {SQLConnection} from '../database/SQLConnection'; import {ExtensionObject} from './ExtensionObject'; import {ExtensionDecoratorObject} from './ExtensionDecorator'; import * as util from 'util'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; // eslint-disable-next-line @typescript-eslint/no-var-requires const exec = util.promisify(require('child_process').exec); @@ -70,13 +71,23 @@ export class ExtensionManager implements IObjectManager { return; } - Config.Extensions.list = fs - .readdirSync(ProjectPath.ExtensionFolder) - .filter((f): boolean => - fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() - ); - Config.Extensions.list.sort(); - Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list)); + + const extList = fs + .readdirSync(ProjectPath.ExtensionFolder) + .filter((f): boolean => + fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() + ); + extList.sort(); + + // delete not existing extensions + Config.Extensions.extensions = Config.Extensions.extensions.filter(ec => extList.indexOf(ec.path) !== -1); + + // Add new extensions + const ePaths = Config.Extensions.extensions.map(ec => ec.path); + extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep => + Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); + + Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path))); } private createUniqueExtensionObject(name: string, folder: string): IExtensionObject { @@ -95,8 +106,8 @@ export class ExtensionManager implements IObjectManager { private async initExtensions() { - for (let i = 0; i < Config.Extensions.list.length; ++i) { - const extFolder = Config.Extensions.list[i]; + for (let i = 0; i < Config.Extensions.extensions.length; ++i) { + const extFolder = Config.Extensions.extensions[i].path; let extName = extFolder; const extPath = path.join(ProjectPath.ExtensionFolder, extFolder); const serverExtPath = path.join(extPath, 'server.js'); @@ -122,7 +133,7 @@ export class ExtensionManager implements IObjectManager { const ext = require(serverExtPath); if (typeof ext?.init === 'function') { Logger.debug(LOG_TAG, 'Running init on extension: ' + extFolder); - await ext?.init(this.createUniqueExtensionObject(extName, extPath)); + await ext?.init(this.createUniqueExtensionObject(extName, extFolder)); } } if (Config.Extensions.cleanUpUnusedTables) { diff --git a/src/backend/model/extension/ExtensionObject.ts b/src/backend/model/extension/ExtensionObject.ts index 3ff1aec6..254c8b9d 100644 --- a/src/backend/model/extension/ExtensionObject.ts +++ b/src/backend/model/extension/ExtensionObject.ts @@ -26,7 +26,7 @@ export class ExtensionObject implements IExtensionObject { events: IExtensionEvents) { const logger = createLoggerWrapper(`[Extension][${extensionId}]`); this._app = new ExtensionApp(); - this.config = new ExtensionConfig(extensionId); + this.config = new ExtensionConfig(folder); this.db = new ExtensionDB(logger); this.paths = ProjectPath; this.Logger = logger; diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index a02dd3af..a537011d 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -11,7 +11,6 @@ import { } from '../../entities/job/JobScheduleDTO'; import { ClientConfig, - ClientExtensionsConfig, ClientGPXCompressingConfig, ClientMediaConfig, ClientMetaFileConfig, @@ -32,7 +31,8 @@ import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/Sear import {SortByTypes} from '../../entities/SortingMethods'; import {UserRoles} from '../../entities/UserDTO'; import {MediaPickDTO} from '../../entities/MediaPickDTO'; -import {MessagingConfig} from './MessagingConfig'; +import {ServerExtensionsConfig} from './subconfigs/ServerExtensionsConfig'; +import {MessagingConfig} from './subconfigs/MessagingConfig'; declare let $localize: (s: TemplateStringsArray) => string; @@ -1025,35 +1025,6 @@ export class ServerServiceConfig extends ClientServiceConfig { } -@SubConfigClass({softReadonly: true}) -export class ServerExtensionsConfig extends ClientExtensionsConfig { - - @ConfigProperty({ - tags: { - name: $localize`Extension folder`, - priority: ConfigPriority.underTheHood, - dockerSensitive: true - }, - description: $localize`Folder where the app stores the extensions. Extensions live in their sub-folders.`, - }) - folder: string = 'extensions'; - - @ConfigProperty({volatile: true}) - list: string[] = []; - - @ConfigProperty({type: 'object'}) - configs: Record = {}; - - @ConfigProperty({ - tags: { - name: $localize`Clean up unused tables`, - priority: ConfigPriority.underTheHood, - }, - description: $localize`Automatically removes all tables from the DB that are not used anymore.`, - }) - cleanUpUnusedTables: boolean = true; -} - @SubConfigClass({softReadonly: true}) export class ServerEnvironmentConfig { @ConfigProperty({volatile: true}) diff --git a/src/common/config/private/MessagingConfig.ts b/src/common/config/private/subconfigs/MessagingConfig.ts similarity index 97% rename from src/common/config/private/MessagingConfig.ts rename to src/common/config/private/subconfigs/MessagingConfig.ts index 067a19f7..54ec6602 100644 --- a/src/common/config/private/MessagingConfig.ts +++ b/src/common/config/private/subconfigs/MessagingConfig.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; -import {ConfigPriority, TAGS} from '../public/ClientConfig'; +import {ConfigPriority, TAGS} from '../../public/ClientConfig'; declare let $localize: (s: TemplateStringsArray) => string; diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts new file mode 100644 index 00000000..22f7ad66 --- /dev/null +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-inferrable-types */ +import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; +import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig'; +import {IConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IConfigClass'; + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsEntryConfig { + + constructor(path: string = '') { + this.path = path; + } + + @ConfigProperty({ + tags: { + name: $localize`Enabled`, + priority: ConfigPriority.advanced, + }, + }) + enabled: boolean = true; + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + path: string = ''; + + @ConfigProperty({ + tags: { + name: $localize`Config`, + priority: ConfigPriority.advanced + } + }) + configs: IConfigClassPrivate; +} + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsConfig extends ClientExtensionsConfig { + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + dockerSensitive: true + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + folder: string = 'extensions'; + + + @ConfigProperty({ + arrayType: ServerExtensionsEntryConfig, + tags: { + name: $localize`Installed extensions`, + priority: ConfigPriority.advanced + } + }) + extensions: ServerExtensionsEntryConfig[] = []; + + @ConfigProperty({ + tags: { + name: $localize`Installed extensions2`, + priority: ConfigPriority.advanced + } + }) + extensions2: Record = {}; + + @ConfigProperty({ + tags: { + name: $localize`Clean up unused tables`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Automatically removes all tables from the DB that are not used anymore.`, + }) + cleanUpUnusedTables: boolean = true; +} diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 333add00..1d235785 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -19,6 +19,7 @@ import {enumToTranslatedArray} from '../../../EnumTranslations'; import {BsModalService} from 'ngx-bootstrap/modal'; import {CustomSettingsEntries} from '../CustomSettingsEntries'; import {GroupByTypes, SortByTypes} from '../../../../../../common/entities/SortingMethods'; +import { ServerExtensionsEntryConfig } from '../../../../../../common/config/private/subconfigs/ServerExtensionsConfig'; interface IState { shouldHide(): boolean; @@ -232,6 +233,8 @@ export class SettingsEntryComponent this.arrayType = 'MapPathGroupThemeConfig'; } else if (this.state.arrayType === UserConfig) { this.arrayType = 'UserConfig'; + } else if (this.state.arrayType === ServerExtensionsEntryConfig) { + this.arrayType = 'ServerExtensionsEntryConfig'; } else if (this.state.arrayType === JobScheduleConfig) { this.arrayType = 'JobScheduleConfig'; } else { @@ -253,6 +256,7 @@ export class SettingsEntryComponent this.arrayType !== 'MapLayers' && this.arrayType !== 'NavigationLinkConfig' && this.arrayType !== 'MapPathGroupConfig' && + this.arrayType !== 'ServerExtensionsEntryConfig' && this.arrayType !== 'MapPathGroupThemeConfig' && this.arrayType !== 'JobScheduleConfig' && this.arrayType !== 'UserConfig') { From 90b620de00ac6247eafd7c7e747137ac92bab51c Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 24 Dec 2023 08:59:36 +0100 Subject: [PATCH 2/8] Refactor extension settings --- .../model/extension/ExtensionConfigWrapper.ts | 39 ++++++++-- .../model/extension/ExtensionManager.ts | 31 +++++--- .../model/extension/ExtensionObject.ts | 2 +- src/common/config/private/PrivateConfig.ts | 33 +------- .../{ => subconfigs}/MessagingConfig.ts | 2 +- .../subconfigs/ServerExtensionsConfig.ts | 78 +++++++++++++++++++ .../settings-entry.component.ts | 4 + 7 files changed, 139 insertions(+), 50 deletions(-) rename src/common/config/private/{ => subconfigs}/MessagingConfig.ts (97%) create mode 100644 src/common/config/private/subconfigs/ServerExtensionsConfig.ts diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index b11e5eaa..cad0ad24 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -1,9 +1,9 @@ -import {IConfigClass} from 'typeconfig/common'; +import {ConfigProperty, IConfigClass} from 'typeconfig/common'; import {Config, PrivateConfigClass} from '../../../common/config/private/Config'; import {ConfigClassBuilder} from 'typeconfig/node'; import {IExtensionConfig} from './IExtension'; -import {Utils} from '../../../common/Utils'; import {ObjectManagers} from '../ObjectManagers'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; /** * Wraps to original config and makes sure all extension related config is loaded @@ -29,11 +29,29 @@ export class ExtensionConfigWrapper { export class ExtensionConfig implements IExtensionConfig { public template: new() => C; - constructor(private readonly extensionId: string) { + constructor(private readonly extensionFolder: string) { + } + + private findConfig(config: PrivateConfigClass) { + let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); + if (!c) { + c = new ServerExtensionsEntryConfig(this.extensionFolder); + config.Extensions.extensions.push(c); + } + + if (!config.Extensions.extensions2[this.extensionFolder]) { + Object.defineProperty(config.Extensions.extensions2, this.extensionFolder, + ConfigProperty({type: ServerExtensionsEntryConfig})(config.Extensions.extensions2, this.extensionFolder)); + // config.Extensions.extensions2[this.extensionFolder] = c as any; + + config.Extensions.extensions2[this.extensionFolder] = c; + + } + return config.Extensions.extensions2[this.extensionFolder]; } public getConfig(): C { - return Config.Extensions.configs[this.extensionId] as C; + return this.findConfig(Config).configs as C; } public setTemplate(template: new() => C): void { @@ -45,8 +63,15 @@ export class ExtensionConfig implements IExtensionConfig { 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; + + const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); + const extConf = this.findConfig(config); + // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); + //extConf.configs = confTemplate; + Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, + ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); + console.log(config.Extensions.extensions2[this.extensionFolder].configs); + config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; + console.log(config.Extensions.extensions2[this.extensionFolder].configs); } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index f02dfb28..11f50aa4 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -12,6 +12,7 @@ import {SQLConnection} from '../database/SQLConnection'; import {ExtensionObject} from './ExtensionObject'; import {ExtensionDecoratorObject} from './ExtensionDecorator'; import * as util from 'util'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; // eslint-disable-next-line @typescript-eslint/no-var-requires const exec = util.promisify(require('child_process').exec); @@ -70,13 +71,23 @@ export class ExtensionManager implements IObjectManager { return; } - Config.Extensions.list = fs - .readdirSync(ProjectPath.ExtensionFolder) - .filter((f): boolean => - fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() - ); - Config.Extensions.list.sort(); - Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list)); + + const extList = fs + .readdirSync(ProjectPath.ExtensionFolder) + .filter((f): boolean => + fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() + ); + extList.sort(); + + // delete not existing extensions + Config.Extensions.extensions = Config.Extensions.extensions.filter(ec => extList.indexOf(ec.path) !== -1); + + // Add new extensions + const ePaths = Config.Extensions.extensions.map(ec => ec.path); + extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep => + Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); + + Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path))); } private createUniqueExtensionObject(name: string, folder: string): IExtensionObject { @@ -95,8 +106,8 @@ export class ExtensionManager implements IObjectManager { private async initExtensions() { - for (let i = 0; i < Config.Extensions.list.length; ++i) { - const extFolder = Config.Extensions.list[i]; + for (let i = 0; i < Config.Extensions.extensions.length; ++i) { + const extFolder = Config.Extensions.extensions[i].path; let extName = extFolder; const extPath = path.join(ProjectPath.ExtensionFolder, extFolder); const serverExtPath = path.join(extPath, 'server.js'); @@ -122,7 +133,7 @@ export class ExtensionManager implements IObjectManager { const ext = require(serverExtPath); if (typeof ext?.init === 'function') { Logger.debug(LOG_TAG, 'Running init on extension: ' + extFolder); - await ext?.init(this.createUniqueExtensionObject(extName, extPath)); + await ext?.init(this.createUniqueExtensionObject(extName, extFolder)); } } if (Config.Extensions.cleanUpUnusedTables) { diff --git a/src/backend/model/extension/ExtensionObject.ts b/src/backend/model/extension/ExtensionObject.ts index 3ff1aec6..254c8b9d 100644 --- a/src/backend/model/extension/ExtensionObject.ts +++ b/src/backend/model/extension/ExtensionObject.ts @@ -26,7 +26,7 @@ export class ExtensionObject implements IExtensionObject { events: IExtensionEvents) { const logger = createLoggerWrapper(`[Extension][${extensionId}]`); this._app = new ExtensionApp(); - this.config = new ExtensionConfig(extensionId); + this.config = new ExtensionConfig(folder); this.db = new ExtensionDB(logger); this.paths = ProjectPath; this.Logger = logger; diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index 5375b9dd..721c02fc 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -11,7 +11,6 @@ import { } from '../../entities/job/JobScheduleDTO'; import { ClientConfig, - ClientExtensionsConfig, ClientGPXCompressingConfig, ClientMediaConfig, ClientMetaFileConfig, @@ -30,7 +29,8 @@ import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/Sear import {SortByTypes} from '../../entities/SortingMethods'; import {UserRoles} from '../../entities/UserDTO'; import {MediaPickDTO} from '../../entities/MediaPickDTO'; -import {MessagingConfig} from './MessagingConfig'; +import {ServerExtensionsConfig} from './subconfigs/ServerExtensionsConfig'; +import {MessagingConfig} from './subconfigs/MessagingConfig'; declare let $localize: (s: TemplateStringsArray) => string; @@ -966,35 +966,6 @@ export class ServerServiceConfig extends ClientServiceConfig { } -@SubConfigClass({softReadonly: true}) -export class ServerExtensionsConfig extends ClientExtensionsConfig { - - @ConfigProperty({ - tags: { - name: $localize`Extension folder`, - priority: ConfigPriority.underTheHood, - dockerSensitive: true - }, - description: $localize`Folder where the app stores the extensions. Extensions live in their sub-folders.`, - }) - folder: string = 'extensions'; - - @ConfigProperty({volatile: true}) - list: string[] = []; - - @ConfigProperty({type: 'object'}) - configs: Record = {}; - - @ConfigProperty({ - tags: { - name: $localize`Clean up unused tables`, - priority: ConfigPriority.underTheHood, - }, - description: $localize`Automatically removes all tables from the DB that are not used anymore.`, - }) - cleanUpUnusedTables: boolean = true; -} - @SubConfigClass({softReadonly: true}) export class ServerEnvironmentConfig { @ConfigProperty({volatile: true}) diff --git a/src/common/config/private/MessagingConfig.ts b/src/common/config/private/subconfigs/MessagingConfig.ts similarity index 97% rename from src/common/config/private/MessagingConfig.ts rename to src/common/config/private/subconfigs/MessagingConfig.ts index 067a19f7..54ec6602 100644 --- a/src/common/config/private/MessagingConfig.ts +++ b/src/common/config/private/subconfigs/MessagingConfig.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; -import {ConfigPriority, TAGS} from '../public/ClientConfig'; +import {ConfigPriority, TAGS} from '../../public/ClientConfig'; declare let $localize: (s: TemplateStringsArray) => string; diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts new file mode 100644 index 00000000..22f7ad66 --- /dev/null +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-inferrable-types */ +import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; +import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig'; +import {IConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IConfigClass'; + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsEntryConfig { + + constructor(path: string = '') { + this.path = path; + } + + @ConfigProperty({ + tags: { + name: $localize`Enabled`, + priority: ConfigPriority.advanced, + }, + }) + enabled: boolean = true; + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + path: string = ''; + + @ConfigProperty({ + tags: { + name: $localize`Config`, + priority: ConfigPriority.advanced + } + }) + configs: IConfigClassPrivate; +} + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsConfig extends ClientExtensionsConfig { + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + dockerSensitive: true + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + folder: string = 'extensions'; + + + @ConfigProperty({ + arrayType: ServerExtensionsEntryConfig, + tags: { + name: $localize`Installed extensions`, + priority: ConfigPriority.advanced + } + }) + extensions: ServerExtensionsEntryConfig[] = []; + + @ConfigProperty({ + tags: { + name: $localize`Installed extensions2`, + priority: ConfigPriority.advanced + } + }) + extensions2: Record = {}; + + @ConfigProperty({ + tags: { + name: $localize`Clean up unused tables`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Automatically removes all tables from the DB that are not used anymore.`, + }) + cleanUpUnusedTables: boolean = true; +} diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 333add00..1d235785 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -19,6 +19,7 @@ import {enumToTranslatedArray} from '../../../EnumTranslations'; import {BsModalService} from 'ngx-bootstrap/modal'; import {CustomSettingsEntries} from '../CustomSettingsEntries'; import {GroupByTypes, SortByTypes} from '../../../../../../common/entities/SortingMethods'; +import { ServerExtensionsEntryConfig } from '../../../../../../common/config/private/subconfigs/ServerExtensionsConfig'; interface IState { shouldHide(): boolean; @@ -232,6 +233,8 @@ export class SettingsEntryComponent this.arrayType = 'MapPathGroupThemeConfig'; } else if (this.state.arrayType === UserConfig) { this.arrayType = 'UserConfig'; + } else if (this.state.arrayType === ServerExtensionsEntryConfig) { + this.arrayType = 'ServerExtensionsEntryConfig'; } else if (this.state.arrayType === JobScheduleConfig) { this.arrayType = 'JobScheduleConfig'; } else { @@ -253,6 +256,7 @@ export class SettingsEntryComponent this.arrayType !== 'MapLayers' && this.arrayType !== 'NavigationLinkConfig' && this.arrayType !== 'MapPathGroupConfig' && + this.arrayType !== 'ServerExtensionsEntryConfig' && this.arrayType !== 'MapPathGroupThemeConfig' && this.arrayType !== 'JobScheduleConfig' && this.arrayType !== 'UserConfig') { From d33e29cd82cac17b3118c188a3f0b038f488d1dc Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Mon, 19 Feb 2024 22:15:57 +0100 Subject: [PATCH 3/8] Add basic extension UI #784 --- package-lock.json | 11 ++- package.json | 2 +- src/backend/middlewares/RenderingMWs.ts | 1 + .../model/extension/ExtensionConfigWrapper.ts | 37 +++---- .../model/extension/ExtensionManager.ts | 22 +++-- .../subconfigs/ServerExtensionsConfig.ts | 12 +-- src/common/config/public/ClientConfig.ts | 3 +- src/frontend/app/ui/admin/admin.component.ts | 16 ++-- .../template/CustomSettingsEntries.ts | 21 +++- .../settings-entry.component.html | 20 ++-- .../settings-entry.component.ts | 2 +- .../settings/template/template.component.html | 96 ++++++++++++------- .../settings/template/template.component.ts | 59 +++++++----- 13 files changed, 175 insertions(+), 127 deletions(-) diff --git a/package-lock.json b/package-lock.json index b758647b..7f1fc96f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", - "typeconfig": "2.1.2", + "typeconfig": "2.2.7", "typeorm": "0.3.12", "xml2js": "0.6.2" }, @@ -20385,8 +20385,9 @@ } }, "node_modules/typeconfig": { - "version": "2.1.2", - "license": "MIT", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", + "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", "dependencies": { "minimist": "1.2.8" } @@ -35319,7 +35320,9 @@ } }, "typeconfig": { - "version": "2.1.2", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", + "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", "requires": { "minimist": "1.2.8" } diff --git a/package.json b/package.json index 5f300ea9..8971a16f 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", - "typeconfig": "2.1.2", + "typeconfig": "2.2.7", "typeorm": "0.3.12", "xml2js": "0.6.2" }, diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 404c1814..2576db38 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -119,6 +119,7 @@ export class RenderingMWs { skipTags: {secret: true} as TAGS }) as PrivateConfigClass ); + console.log(message.result.Extensions.extensions); res.json(message); } diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index cad0ad24..2b61ab2c 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -1,4 +1,4 @@ -import {ConfigProperty, IConfigClass} from 'typeconfig/common'; +import {IConfigClass} from 'typeconfig/common'; import {Config, PrivateConfigClass} from '../../../common/config/private/Config'; import {ConfigClassBuilder} from 'typeconfig/node'; import {IExtensionConfig} from './IExtension'; @@ -32,22 +32,14 @@ export class ExtensionConfig implements IExtensionConfig { constructor(private readonly extensionFolder: string) { } - private findConfig(config: PrivateConfigClass) { - let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); - if (!c) { - c = new ServerExtensionsEntryConfig(this.extensionFolder); - config.Extensions.extensions.push(c); - } + private findConfig(config: PrivateConfigClass): ServerExtensionsEntryConfig { + let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); + if (!c) { + c = new ServerExtensionsEntryConfig(this.extensionFolder); + config.Extensions.extensions.push(c); + } + return c; - if (!config.Extensions.extensions2[this.extensionFolder]) { - Object.defineProperty(config.Extensions.extensions2, this.extensionFolder, - ConfigProperty({type: ServerExtensionsEntryConfig})(config.Extensions.extensions2, this.extensionFolder)); - // config.Extensions.extensions2[this.extensionFolder] = c as any; - - config.Extensions.extensions2[this.extensionFolder] = c; - - } - return config.Extensions.extensions2[this.extensionFolder]; } public getConfig(): C { @@ -67,11 +59,12 @@ export class ExtensionConfig implements IExtensionConfig { const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); const extConf = this.findConfig(config); // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); - //extConf.configs = confTemplate; - Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, - ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); - console.log(config.Extensions.extensions2[this.extensionFolder].configs); - config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; - console.log(config.Extensions.extensions2[this.extensionFolder].configs); + extConf.configs = confTemplate; + console.log(((config as any).toJSON({attachState: true})).Extensions.extensions); + /* Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, + ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); + console.log(config.Extensions.extensions2[this.extensionFolder].configs); + config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; + console.log(config.Extensions.extensions2[this.extensionFolder].configs);*/ } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index 11f50aa4..795a6b09 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -73,10 +73,10 @@ export class ExtensionManager implements IObjectManager { const extList = fs - .readdirSync(ProjectPath.ExtensionFolder) - .filter((f): boolean => - fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() - ); + .readdirSync(ProjectPath.ExtensionFolder) + .filter((f): boolean => + fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() + ); extList.sort(); // delete not existing extensions @@ -85,7 +85,7 @@ export class ExtensionManager implements IObjectManager { // Add new extensions const ePaths = Config.Extensions.extensions.map(ec => ec.path); extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep => - Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); + Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path))); } @@ -118,10 +118,14 @@ export class ExtensionManager implements IObjectManager { } if (fs.existsSync(packageJsonPath)) { - Logger.silly(LOG_TAG, `Running: "npm install --prefer-offline --no-audit --progress=false --omit=dev" in ${extPath}`); - await exec('npm install --no-audit --progress=false --omit=dev', { - cwd: extPath - }); + if (fs.existsSync(path.join(extPath, 'node_modules'))) { + Logger.debug(LOG_TAG, `node_modules folder exists. Skipping "npm install".`); + } else { + Logger.silly(LOG_TAG, `Running: "npm install --prefer-offline --no-audit --progress=false --omit=dev" in ${extPath}`); + await exec('npm install --no-audit --progress=false --omit=dev', { + cwd: extPath + }); + } // eslint-disable-next-line @typescript-eslint/no-var-requires const pkg = require(packageJsonPath); if (pkg.name) { diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts index 22f7ad66..4d62097e 100644 --- a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig'; -import {IConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IConfigClass'; +import {GenericConfigType} from 'typeconfig/src/GenericConfigType'; @SubConfigClass({softReadonly: true}) export class ServerExtensionsEntryConfig { @@ -28,12 +28,13 @@ export class ServerExtensionsEntryConfig { path: string = ''; @ConfigProperty({ + type: GenericConfigType, tags: { name: $localize`Config`, priority: ConfigPriority.advanced } }) - configs: IConfigClassPrivate; + configs: GenericConfigType; } @SubConfigClass({softReadonly: true}) @@ -59,13 +60,6 @@ export class ServerExtensionsConfig extends ClientExtensionsConfig { }) extensions: ServerExtensionsEntryConfig[] = []; - @ConfigProperty({ - tags: { - name: $localize`Installed extensions2`, - priority: ConfigPriority.advanced - } - }) - extensions2: Record = {}; @ConfigProperty({ tags: { diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index 3ac5b769..5d24388d 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -11,7 +11,7 @@ declare let $localize: (s: TemplateStringsArray) => string; if (typeof $localize === 'undefined') { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - global.$localize = (s) => s; + global.$localize = (s) => s[0]; } @@ -1034,7 +1034,6 @@ export class ThemesConfig { name: $localize`Selected theme css`, //this is a 'hack' to the UI settings. UI will only show the selected setting's css uiDisabled: (sb: ThemesConfig) => !sb.enabled, relevant: (c: ThemesConfig) => c.selectedTheme !== 'default', - uiType: 'SelectedThemeSettings' } as TAGS, description: $localize`Adds these css settings as it is to the end of the body tag of the page.` }) diff --git a/src/frontend/app/ui/admin/admin.component.ts b/src/frontend/app/ui/admin/admin.component.ts index 04feb789..5fb5d307 100644 --- a/src/frontend/app/ui/admin/admin.component.ts +++ b/src/frontend/app/ui/admin/admin.component.ts @@ -30,12 +30,12 @@ export class AdminComponent implements OnInit, AfterViewInit { public readonly configPaths: string[] = []; constructor( - private authService: AuthenticationService, - private navigation: NavigationService, - public viewportScroller: ViewportScroller, - public notificationService: NotificationService, - public settingsService: SettingsService, - private piTitleService: PiTitleService + private authService: AuthenticationService, + private navigation: NavigationService, + public viewportScroller: ViewportScroller, + public notificationService: NotificationService, + public settingsService: SettingsService, + private piTitleService: PiTitleService ) { this.configPriorities = enumToTranslatedArray(ConfigPriority); this.configStyles = enumToTranslatedArray(ConfigStyle); @@ -50,8 +50,8 @@ export class AdminComponent implements OnInit, AfterViewInit { ngOnInit(): void { if ( - !this.authService.isAuthenticated() || - this.authService.user.value.role < UserRoles.Admin + !this.authService.isAuthenticated() || + this.authService.user.value.role < UserRoles.Admin ) { this.navigation.toLogin(); return; diff --git a/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts b/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts index b2346b6c..d7470668 100644 --- a/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts +++ b/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts @@ -1,5 +1,15 @@ import {propertyTypes} from 'typeconfig/common'; -import {ClientGroupingConfig, ClientSortingConfig, SVGIconConfig} from '../../../../../common/config/public/ClientConfig'; +import { + ClientGroupingConfig, + ClientSortingConfig, + MapLayers, + MapPathGroupConfig, + MapPathGroupThemeConfig, + NavigationLinkConfig, + SVGIconConfig, + ThemeConfig +} from '../../../../../common/config/public/ClientConfig'; +import {JobScheduleConfig, UserConfig} from '../../../../../common/config/private/PrivateConfig'; /** * Configuration in these class have a custom UI @@ -8,6 +18,13 @@ export class CustomSettingsEntries { public static readonly entries = [ {c: ClientSortingConfig, name: 'ClientSortingConfig'}, {c: ClientGroupingConfig, name: 'ClientGroupingConfig'}, + {c: MapLayers, name: 'MapLayers'}, + {c: JobScheduleConfig, name: 'JobScheduleConfig'}, + {c: UserConfig, name: 'UserConfig'}, + {c: NavigationLinkConfig, name: 'NavigationLinkConfig'}, + {c: MapPathGroupThemeConfig, name: 'MapPathGroupThemeConfig'}, + {c: MapPathGroupConfig, name: 'MapPathGroupConfig'}, + {c: ThemeConfig, name: 'ThemeConfig'}, {c: SVGIconConfig, name: 'SVGIconConfig'}, ]; @@ -46,7 +63,7 @@ export class CustomSettingsEntries { return cN; } - public static iS(s: { tags?: { uiType?: string }, type?: propertyTypes }) { + public static iS(s: { tags?: { uiType?: string }, type?: propertyTypes, arrayType?: propertyTypes }) { const c = this.getConfigName(s); return this.entries.findIndex(e => e.name == c) !== -1; } diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html index f6bdbfbe..a04a9801 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html @@ -12,7 +12,6 @@ [hidden]="shouldHide">
-
- +
@@ -295,7 +296,7 @@ - +
@@ -357,7 +358,7 @@
- +
@@ -403,7 +404,7 @@
- +
@@ -468,7 +469,8 @@
- + +
diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 1d235785..4857ba25 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -258,7 +258,7 @@ export class SettingsEntryComponent this.arrayType !== 'MapPathGroupConfig' && this.arrayType !== 'ServerExtensionsEntryConfig' && this.arrayType !== 'MapPathGroupThemeConfig' && - this.arrayType !== 'JobScheduleConfig' && + this.arrayType !== 'JobScheduleConfig-Array' && this.arrayType !== 'UserConfig') { this.uiType = 'StringInput'; } diff --git a/src/frontend/app/ui/settings/template/template.component.html b/src/frontend/app/ui/settings/template/template.component.html index 35f7fd8d..35af7b9b 100644 --- a/src/frontend/app/ui/settings/template/template.component.html +++ b/src/frontend/app/ui/settings/template/template.component.html @@ -3,7 +3,7 @@
- {{Name}} + {{ Name }}
@@ -22,7 +22,7 @@
- + - {{Name}} config is not supported with these settings. + {{ Name }} config is not supported with these settings.
@@ -72,46 +72,71 @@ let-confPath="confPath"> - - - -
-
-
- - {{rStates?.value.__state[ck].tags?.name || ck}} -
- + + +
+
+
{{ rStates?.value.__state[ck].tags?.name || ck }}
+
+
+
+
+ +
- -
-
-
{{rStates?.value.__state[ck].tags?.name || ck}}
-
-
-
+ + + + + + + + + + +
+
+
+ + {{ rStates?.value.__state[ck].tags?.name || ck }} +
+
-
- -
+ + +
+
+
{{ rStates?.value.__state[ck].tags?.name || ck }}
+
+
+
+
+
+
+ +
+
@@ -125,12 +150,14 @@ >
+
+
diff --git a/src/frontend/app/ui/settings/template/template.component.ts b/src/frontend/app/ui/settings/template/template.component.ts index 6c115e31..70aa8f9c 100644 --- a/src/frontend/app/ui/settings/template/template.component.ts +++ b/src/frontend/app/ui/settings/template/template.component.ts @@ -79,11 +79,11 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting public readonly ConfigStyle = ConfigStyle; constructor( - protected authService: AuthenticationService, - private navigation: NavigationService, - protected notification: NotificationService, - public settingsService: SettingsService, - public jobsService: ScheduledJobsService, + protected authService: AuthenticationService, + private navigation: NavigationService, + protected notification: NotificationService, + public settingsService: SettingsService, + public jobsService: ScheduledJobsService, ) { } @@ -97,7 +97,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting this.nestedConfigs = []; for (const key of this.getKeys(this.states)) { if (this.states.value.__state[key].isConfigType && - this.states?.value.__state[key].tags?.uiIcon) { + this.states?.value.__state[key].tags?.uiIcon) { this.nestedConfigs.push({ id: this.ConfigPath + '.' + key, name: this.states?.value.__state[key].tags?.name, @@ -112,8 +112,8 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting ngOnInit(): void { if ( - !this.authService.isAuthenticated() || - this.authService.user.value.role < UserRoles.Admin + !this.authService.isAuthenticated() || + this.authService.user.value.role < UserRoles.Admin ) { this.navigation.toLogin(); return; @@ -143,7 +143,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting if (sliceFN) { this.sliceFN = sliceFN; this.settingsSubscription = this.settingsService.settings.subscribe( - this.onNewSettings + this.onNewSettings ); } } @@ -171,31 +171,31 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting } if (state.tags && - ((state.tags.relevant && !state.tags.relevant(parent.value)) - || state.tags.secret)) { + ((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.value.__state && - Object.keys(state.value.__state).findIndex(k => !st.value.__state[k].shouldHide()) === -1) { + Object.keys(state.value.__state).findIndex(k => !st.value.__state[k].shouldHide()) === -1) { return true; } } const forcedVisibility = !(state.tags?.priority > this.settingsService.configPriority || - //if this value should not change in Docker, lets hide it - (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 + (this.settingsService.configPriority === ConfigPriority.basic && + state.tags?.dockerSensitive && this.settingsService.settings.value.Environment.isDocker)); if (state.isConfigArrayType) { for (let i = 0; i < state.value?.length; ++i) { for (const k of Object.keys(state.value[i].__state)) { if (!Utils.equalsFilter( - state.value[i]?.__state[k]?.value, - state.default[i] ? state.default[i][k] : undefined, - ['default', '__propPath', '__created', '__prototype', '__rootConfig'])) { + state.value[i]?.__state[k]?.value, + state.default[i] ? state.default[i][k] : undefined, + ['default', '__propPath', '__created', '__prototype', '__rootConfig'])) { return false; } @@ -206,10 +206,10 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting return (!forcedVisibility && - Utils.equalsFilter(state.value, state.default, - ['__propPath', '__created', '__prototype', '__rootConfig']) && - Utils.equalsFilter(state.original, state.default, - ['__propPath', '__created', '__prototype', '__rootConfig'])); + Utils.equalsFilter(state.value, state.default, + ['__propPath', '__created', '__prototype', '__rootConfig']) && + Utils.equalsFilter(state.original, state.default, + ['__propPath', '__created', '__prototype', '__rootConfig'])); }; }; @@ -246,7 +246,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting } if (typeof state.original === 'object') { return Utils.equalsFilter(state.value, state.original, - ['__propPath', '__created', '__prototype', '__rootConfig', '__state']); + ['__propPath', '__created', '__prototype', '__rootConfig', '__state']); } if (typeof state.original !== 'undefined') { return state.value === state.original; @@ -271,10 +271,18 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting this.getSettings(); } + /** + * main template should list it + * @param c + */ isExpandableConfig(c: ConfigState) { return c.isConfigType && !CustomSettingsEntries.iS(c); } + isExpandableArrayConfig(c: ConfigState) { + return c.isConfigArrayType && !CustomSettingsEntries.iS(c); + } + public async save(): Promise { this.inProgress = true; @@ -284,8 +292,8 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting await this.settingsService.updateSettings(state, this.ConfigPath); await this.getSettings(); this.notification.success( - this.Name + ' ' + $localize`settings saved`, - $localize`Success` + this.Name + ' ' + $localize`settings saved`, + $localize`Success` ); this.inProgress = false; return true; @@ -328,7 +336,6 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting return 1; } return (s[a].tags?.name as string || a).localeCompare(s[b].tags?.name || b); - }); states.keys = keys; return states.keys; From cb90d08c881fe5d4ae76e2a45cbe89ea48c1323c Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 2 Mar 2024 22:18:31 +0100 Subject: [PATCH 4/8] Add basic extension UI #784 --- package-lock.json | 14 +++++++------- package.json | 2 +- src/backend/middlewares/RenderingMWs.ts | 1 - src/backend/middlewares/SharingMWs.ts | 1 - src/backend/middlewares/admin/SettingsMWs.ts | 6 +++--- .../model/extension/ExtensionConfigWrapper.ts | 10 ++-------- src/backend/model/extension/ExtensionManager.ts | 4 ++++ src/backend/model/jobs/jobs/TopPickSendJob.ts | 1 - .../private/subconfigs/ServerExtensionsConfig.ts | 1 + .../search-field-base.gallery.component.ts | 1 - .../settings-entry/settings-entry.component.ts | 1 - 11 files changed, 18 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f1fc96f..a0390b3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", - "typeconfig": "2.2.7", + "typeconfig": "2.2.11", "typeorm": "0.3.12", "xml2js": "0.6.2" }, @@ -20385,9 +20385,9 @@ } }, "node_modules/typeconfig": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", - "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.11.tgz", + "integrity": "sha512-Knj+1kbIJ4zOZlUm2TPSWZUoiOW4txrmPyf6oyuBhaDQDlGxpSL5jobF3vVV9mZElK1V3ZQVeTgvGaiDyeT8mQ==", "dependencies": { "minimist": "1.2.8" } @@ -35320,9 +35320,9 @@ } }, "typeconfig": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", - "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.11.tgz", + "integrity": "sha512-Knj+1kbIJ4zOZlUm2TPSWZUoiOW4txrmPyf6oyuBhaDQDlGxpSL5jobF3vVV9mZElK1V3ZQVeTgvGaiDyeT8mQ==", "requires": { "minimist": "1.2.8" } diff --git a/package.json b/package.json index 8971a16f..f7f93af7 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", - "typeconfig": "2.2.7", + "typeconfig": "2.2.11", "typeorm": "0.3.12", "xml2js": "0.6.2" }, diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 2576db38..404c1814 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -119,7 +119,6 @@ export class RenderingMWs { skipTags: {secret: true} as TAGS }) as PrivateConfigClass ); - console.log(message.result.Extensions.extensions); res.json(message); } diff --git a/src/backend/middlewares/SharingMWs.ts b/src/backend/middlewares/SharingMWs.ts index 943a3c1f..e4552561 100644 --- a/src/backend/middlewares/SharingMWs.ts +++ b/src/backend/middlewares/SharingMWs.ts @@ -171,7 +171,6 @@ export class SharingMWs { sharing, forceUpdate ); - console.log(req.resultPipe); return next(); } catch (err) { return next( diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index 0e7af1ca..7e8d37bc 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -1,12 +1,12 @@ import {NextFunction, Request, Response} from 'express'; import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {Logger} from '../../Logger'; import {Config} from '../../../common/config/private/Config'; import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics'; import {ConfigClassBuilder} from 'typeconfig/node'; import {TAGS} from '../../../common/config/public/ClientConfig'; import {ObjectManagers} from '../../model/ObjectManagers'; import {ExtensionConfigWrapper} from '../../model/extension/ExtensionConfigWrapper'; +import {Logger} from '../../Logger'; const LOG_TAG = '[SettingsMWs]'; @@ -21,8 +21,8 @@ export class SettingsMWs { */ public static async updateSettings(req: Request, res: Response, next: NextFunction): Promise { if ((typeof req.body === 'undefined') - || (typeof req.body.settings === 'undefined') - || (typeof req.body.settingsPath !== 'string')) { + || (typeof req.body.settings === 'undefined') + || (typeof req.body.settingsPath !== 'string')) { return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed')); } diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index 2b61ab2c..ae932d18 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -12,12 +12,13 @@ export class ExtensionConfigWrapper { static async original(): Promise { const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass()); try { - await pc.load(); + await pc.load(); // loading the basic configs but we do not know the extension config hierarchy yet if (ObjectManagers.isReady()) { for (const ext of Object.values(ObjectManagers.getInstance().ExtensionManager.extObjects)) { ext.config.loadToConfig(ConfigClassBuilder.attachPrivateInterface(pc)); } } + await pc.load(); // loading the extension related configs } catch (e) { console.error('Error during loading original config. Reverting to defaults.'); console.error(e); @@ -58,13 +59,6 @@ export class ExtensionConfig implements IExtensionConfig { const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); const extConf = this.findConfig(config); - // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); extConf.configs = confTemplate; - console.log(((config as any).toJSON({attachState: true})).Extensions.extensions); - /* Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, - ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); - console.log(config.Extensions.extensions2[this.extensionFolder].configs); - config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; - console.log(config.Extensions.extensions2[this.extensionFolder].configs);*/ } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index 795a6b09..f5256861 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -109,6 +109,10 @@ export class ExtensionManager implements IObjectManager { for (let i = 0; i < Config.Extensions.extensions.length; ++i) { const extFolder = Config.Extensions.extensions[i].path; let extName = extFolder; + + if(Config.Extensions.extensions[i].enabled === false){ + Logger.silly(LOG_TAG, `Skipping ${extFolder} initiation. Extension is disabled.`); + } const extPath = path.join(ProjectPath.ExtensionFolder, extFolder); const serverExtPath = path.join(extPath, 'server.js'); const packageJsonPath = path.join(extPath, 'package.json'); diff --git a/src/backend/model/jobs/jobs/TopPickSendJob.ts b/src/backend/model/jobs/jobs/TopPickSendJob.ts index 2ba0450c..c748e19f 100644 --- a/src/backend/model/jobs/jobs/TopPickSendJob.ts +++ b/src/backend/model/jobs/jobs/TopPickSendJob.ts @@ -100,7 +100,6 @@ export class TopPickSendJob extends Job<{ arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index); this.Progress.Processed++; - // console.log(this.mediaList); return false; } diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts index 4d62097e..0efcbf03 100644 --- a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -19,6 +19,7 @@ export class ServerExtensionsEntryConfig { enabled: boolean = true; @ConfigProperty({ + readonly: true, tags: { name: $localize`Extension folder`, priority: ConfigPriority.underTheHood, diff --git a/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts b/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts index e304504d..ba8b4e9c 100644 --- a/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts +++ b/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts @@ -167,7 +167,6 @@ export class GallerySearchFieldBaseComponent 0, this.rawSearchText.length - token.current.length ) + item.queryHint; - console.log('aa'); this.onChange(); this.emptyAutoComplete(); } diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 4857ba25..548e898e 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -467,7 +467,6 @@ export class SettingsEntryComponent const reader = new FileReader(); reader.onload = () => { - console.log(reader.result); const parser = new DOMParser(); const doc = parser.parseFromString(reader.result as string, 'image/svg+xml'); try { From 8a8fc57c6736d568100ba1d3bc8dc34e01ad1c7e Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 24 Dec 2023 08:59:36 +0100 Subject: [PATCH 5/8] Refactor extension settings --- .../model/extension/ExtensionConfigWrapper.ts | 39 ++++++++-- .../model/extension/ExtensionManager.ts | 31 +++++--- .../model/extension/ExtensionObject.ts | 2 +- src/common/config/private/PrivateConfig.ts | 33 +------- .../{ => subconfigs}/MessagingConfig.ts | 2 +- .../subconfigs/ServerExtensionsConfig.ts | 78 +++++++++++++++++++ .../settings-entry.component.ts | 4 + 7 files changed, 139 insertions(+), 50 deletions(-) rename src/common/config/private/{ => subconfigs}/MessagingConfig.ts (97%) create mode 100644 src/common/config/private/subconfigs/ServerExtensionsConfig.ts diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index b11e5eaa..cad0ad24 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -1,9 +1,9 @@ -import {IConfigClass} from 'typeconfig/common'; +import {ConfigProperty, IConfigClass} from 'typeconfig/common'; import {Config, PrivateConfigClass} from '../../../common/config/private/Config'; import {ConfigClassBuilder} from 'typeconfig/node'; import {IExtensionConfig} from './IExtension'; -import {Utils} from '../../../common/Utils'; import {ObjectManagers} from '../ObjectManagers'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; /** * Wraps to original config and makes sure all extension related config is loaded @@ -29,11 +29,29 @@ export class ExtensionConfigWrapper { export class ExtensionConfig implements IExtensionConfig { public template: new() => C; - constructor(private readonly extensionId: string) { + constructor(private readonly extensionFolder: string) { + } + + private findConfig(config: PrivateConfigClass) { + let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); + if (!c) { + c = new ServerExtensionsEntryConfig(this.extensionFolder); + config.Extensions.extensions.push(c); + } + + if (!config.Extensions.extensions2[this.extensionFolder]) { + Object.defineProperty(config.Extensions.extensions2, this.extensionFolder, + ConfigProperty({type: ServerExtensionsEntryConfig})(config.Extensions.extensions2, this.extensionFolder)); + // config.Extensions.extensions2[this.extensionFolder] = c as any; + + config.Extensions.extensions2[this.extensionFolder] = c; + + } + return config.Extensions.extensions2[this.extensionFolder]; } public getConfig(): C { - return Config.Extensions.configs[this.extensionId] as C; + return this.findConfig(Config).configs as C; } public setTemplate(template: new() => C): void { @@ -45,8 +63,15 @@ export class ExtensionConfig implements IExtensionConfig { 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; + + const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); + const extConf = this.findConfig(config); + // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); + //extConf.configs = confTemplate; + Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, + ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); + console.log(config.Extensions.extensions2[this.extensionFolder].configs); + config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; + console.log(config.Extensions.extensions2[this.extensionFolder].configs); } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index f02dfb28..11f50aa4 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -12,6 +12,7 @@ import {SQLConnection} from '../database/SQLConnection'; import {ExtensionObject} from './ExtensionObject'; import {ExtensionDecoratorObject} from './ExtensionDecorator'; import * as util from 'util'; +import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig'; // eslint-disable-next-line @typescript-eslint/no-var-requires const exec = util.promisify(require('child_process').exec); @@ -70,13 +71,23 @@ export class ExtensionManager implements IObjectManager { return; } - Config.Extensions.list = fs - .readdirSync(ProjectPath.ExtensionFolder) - .filter((f): boolean => - fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() - ); - Config.Extensions.list.sort(); - Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list)); + + const extList = fs + .readdirSync(ProjectPath.ExtensionFolder) + .filter((f): boolean => + fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() + ); + extList.sort(); + + // delete not existing extensions + Config.Extensions.extensions = Config.Extensions.extensions.filter(ec => extList.indexOf(ec.path) !== -1); + + // Add new extensions + const ePaths = Config.Extensions.extensions.map(ec => ec.path); + extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep => + Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); + + Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path))); } private createUniqueExtensionObject(name: string, folder: string): IExtensionObject { @@ -95,8 +106,8 @@ export class ExtensionManager implements IObjectManager { private async initExtensions() { - for (let i = 0; i < Config.Extensions.list.length; ++i) { - const extFolder = Config.Extensions.list[i]; + for (let i = 0; i < Config.Extensions.extensions.length; ++i) { + const extFolder = Config.Extensions.extensions[i].path; let extName = extFolder; const extPath = path.join(ProjectPath.ExtensionFolder, extFolder); const serverExtPath = path.join(extPath, 'server.js'); @@ -122,7 +133,7 @@ export class ExtensionManager implements IObjectManager { const ext = require(serverExtPath); if (typeof ext?.init === 'function') { Logger.debug(LOG_TAG, 'Running init on extension: ' + extFolder); - await ext?.init(this.createUniqueExtensionObject(extName, extPath)); + await ext?.init(this.createUniqueExtensionObject(extName, extFolder)); } } if (Config.Extensions.cleanUpUnusedTables) { diff --git a/src/backend/model/extension/ExtensionObject.ts b/src/backend/model/extension/ExtensionObject.ts index 3ff1aec6..254c8b9d 100644 --- a/src/backend/model/extension/ExtensionObject.ts +++ b/src/backend/model/extension/ExtensionObject.ts @@ -26,7 +26,7 @@ export class ExtensionObject implements IExtensionObject { events: IExtensionEvents) { const logger = createLoggerWrapper(`[Extension][${extensionId}]`); this._app = new ExtensionApp(); - this.config = new ExtensionConfig(extensionId); + this.config = new ExtensionConfig(folder); this.db = new ExtensionDB(logger); this.paths = ProjectPath; this.Logger = logger; diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index 5375b9dd..721c02fc 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -11,7 +11,6 @@ import { } from '../../entities/job/JobScheduleDTO'; import { ClientConfig, - ClientExtensionsConfig, ClientGPXCompressingConfig, ClientMediaConfig, ClientMetaFileConfig, @@ -30,7 +29,8 @@ import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/Sear import {SortByTypes} from '../../entities/SortingMethods'; import {UserRoles} from '../../entities/UserDTO'; import {MediaPickDTO} from '../../entities/MediaPickDTO'; -import {MessagingConfig} from './MessagingConfig'; +import {ServerExtensionsConfig} from './subconfigs/ServerExtensionsConfig'; +import {MessagingConfig} from './subconfigs/MessagingConfig'; declare let $localize: (s: TemplateStringsArray) => string; @@ -966,35 +966,6 @@ export class ServerServiceConfig extends ClientServiceConfig { } -@SubConfigClass({softReadonly: true}) -export class ServerExtensionsConfig extends ClientExtensionsConfig { - - @ConfigProperty({ - tags: { - name: $localize`Extension folder`, - priority: ConfigPriority.underTheHood, - dockerSensitive: true - }, - description: $localize`Folder where the app stores the extensions. Extensions live in their sub-folders.`, - }) - folder: string = 'extensions'; - - @ConfigProperty({volatile: true}) - list: string[] = []; - - @ConfigProperty({type: 'object'}) - configs: Record = {}; - - @ConfigProperty({ - tags: { - name: $localize`Clean up unused tables`, - priority: ConfigPriority.underTheHood, - }, - description: $localize`Automatically removes all tables from the DB that are not used anymore.`, - }) - cleanUpUnusedTables: boolean = true; -} - @SubConfigClass({softReadonly: true}) export class ServerEnvironmentConfig { @ConfigProperty({volatile: true}) diff --git a/src/common/config/private/MessagingConfig.ts b/src/common/config/private/subconfigs/MessagingConfig.ts similarity index 97% rename from src/common/config/private/MessagingConfig.ts rename to src/common/config/private/subconfigs/MessagingConfig.ts index 067a19f7..54ec6602 100644 --- a/src/common/config/private/MessagingConfig.ts +++ b/src/common/config/private/subconfigs/MessagingConfig.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; -import {ConfigPriority, TAGS} from '../public/ClientConfig'; +import {ConfigPriority, TAGS} from '../../public/ClientConfig'; declare let $localize: (s: TemplateStringsArray) => string; diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts new file mode 100644 index 00000000..22f7ad66 --- /dev/null +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-inferrable-types */ +import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; +import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig'; +import {IConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IConfigClass'; + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsEntryConfig { + + constructor(path: string = '') { + this.path = path; + } + + @ConfigProperty({ + tags: { + name: $localize`Enabled`, + priority: ConfigPriority.advanced, + }, + }) + enabled: boolean = true; + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + path: string = ''; + + @ConfigProperty({ + tags: { + name: $localize`Config`, + priority: ConfigPriority.advanced + } + }) + configs: IConfigClassPrivate; +} + +@SubConfigClass({softReadonly: true}) +export class ServerExtensionsConfig extends ClientExtensionsConfig { + + @ConfigProperty({ + tags: { + name: $localize`Extension folder`, + priority: ConfigPriority.underTheHood, + dockerSensitive: true + }, + description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`, + }) + folder: string = 'extensions'; + + + @ConfigProperty({ + arrayType: ServerExtensionsEntryConfig, + tags: { + name: $localize`Installed extensions`, + priority: ConfigPriority.advanced + } + }) + extensions: ServerExtensionsEntryConfig[] = []; + + @ConfigProperty({ + tags: { + name: $localize`Installed extensions2`, + priority: ConfigPriority.advanced + } + }) + extensions2: Record = {}; + + @ConfigProperty({ + tags: { + name: $localize`Clean up unused tables`, + priority: ConfigPriority.underTheHood, + }, + description: $localize`Automatically removes all tables from the DB that are not used anymore.`, + }) + cleanUpUnusedTables: boolean = true; +} diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 333add00..1d235785 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -19,6 +19,7 @@ import {enumToTranslatedArray} from '../../../EnumTranslations'; import {BsModalService} from 'ngx-bootstrap/modal'; import {CustomSettingsEntries} from '../CustomSettingsEntries'; import {GroupByTypes, SortByTypes} from '../../../../../../common/entities/SortingMethods'; +import { ServerExtensionsEntryConfig } from '../../../../../../common/config/private/subconfigs/ServerExtensionsConfig'; interface IState { shouldHide(): boolean; @@ -232,6 +233,8 @@ export class SettingsEntryComponent this.arrayType = 'MapPathGroupThemeConfig'; } else if (this.state.arrayType === UserConfig) { this.arrayType = 'UserConfig'; + } else if (this.state.arrayType === ServerExtensionsEntryConfig) { + this.arrayType = 'ServerExtensionsEntryConfig'; } else if (this.state.arrayType === JobScheduleConfig) { this.arrayType = 'JobScheduleConfig'; } else { @@ -253,6 +256,7 @@ export class SettingsEntryComponent this.arrayType !== 'MapLayers' && this.arrayType !== 'NavigationLinkConfig' && this.arrayType !== 'MapPathGroupConfig' && + this.arrayType !== 'ServerExtensionsEntryConfig' && this.arrayType !== 'MapPathGroupThemeConfig' && this.arrayType !== 'JobScheduleConfig' && this.arrayType !== 'UserConfig') { From 9172f89e78a9fc0638deab6d105288d00031195b Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Mon, 19 Feb 2024 22:15:57 +0100 Subject: [PATCH 6/8] Add basic extension UI #784 --- package-lock.json | 11 ++- package.json | 2 +- src/backend/middlewares/RenderingMWs.ts | 1 + .../model/extension/ExtensionConfigWrapper.ts | 37 +++---- .../model/extension/ExtensionManager.ts | 22 +++-- .../subconfigs/ServerExtensionsConfig.ts | 12 +-- src/common/config/public/ClientConfig.ts | 3 +- src/frontend/app/ui/admin/admin.component.ts | 16 ++-- .../template/CustomSettingsEntries.ts | 21 +++- .../settings-entry.component.html | 20 ++-- .../settings-entry.component.ts | 2 +- .../settings/template/template.component.html | 96 ++++++++++++------- .../settings/template/template.component.ts | 59 +++++++----- 13 files changed, 175 insertions(+), 127 deletions(-) diff --git a/package-lock.json b/package-lock.json index e71ebb12..c4770b33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-node-iptc": "1.0.11", - "typeconfig": "2.1.2", + "typeconfig": "2.2.7", "typeorm": "0.3.12", "xml2js": "0.6.2" }, @@ -20361,8 +20361,9 @@ } }, "node_modules/typeconfig": { - "version": "2.1.2", - "license": "MIT", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", + "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", "dependencies": { "minimist": "1.2.8" } @@ -35279,7 +35280,9 @@ } }, "typeconfig": { - "version": "2.1.2", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", + "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", "requires": { "minimist": "1.2.8" } diff --git a/package.json b/package.json index 7219c2c0..d5b78dfb 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-node-iptc": "1.0.11", - "typeconfig": "2.1.2", + "typeconfig": "2.2.7", "typeorm": "0.3.12", "xml2js": "0.6.2" }, diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 404c1814..2576db38 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -119,6 +119,7 @@ export class RenderingMWs { skipTags: {secret: true} as TAGS }) as PrivateConfigClass ); + console.log(message.result.Extensions.extensions); res.json(message); } diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index cad0ad24..2b61ab2c 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -1,4 +1,4 @@ -import {ConfigProperty, IConfigClass} from 'typeconfig/common'; +import {IConfigClass} from 'typeconfig/common'; import {Config, PrivateConfigClass} from '../../../common/config/private/Config'; import {ConfigClassBuilder} from 'typeconfig/node'; import {IExtensionConfig} from './IExtension'; @@ -32,22 +32,14 @@ export class ExtensionConfig implements IExtensionConfig { constructor(private readonly extensionFolder: string) { } - private findConfig(config: PrivateConfigClass) { - let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); - if (!c) { - c = new ServerExtensionsEntryConfig(this.extensionFolder); - config.Extensions.extensions.push(c); - } + private findConfig(config: PrivateConfigClass): ServerExtensionsEntryConfig { + let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder); + if (!c) { + c = new ServerExtensionsEntryConfig(this.extensionFolder); + config.Extensions.extensions.push(c); + } + return c; - if (!config.Extensions.extensions2[this.extensionFolder]) { - Object.defineProperty(config.Extensions.extensions2, this.extensionFolder, - ConfigProperty({type: ServerExtensionsEntryConfig})(config.Extensions.extensions2, this.extensionFolder)); - // config.Extensions.extensions2[this.extensionFolder] = c as any; - - config.Extensions.extensions2[this.extensionFolder] = c; - - } - return config.Extensions.extensions2[this.extensionFolder]; } public getConfig(): C { @@ -67,11 +59,12 @@ export class ExtensionConfig implements IExtensionConfig { const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); const extConf = this.findConfig(config); // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); - //extConf.configs = confTemplate; - Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, - ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); - console.log(config.Extensions.extensions2[this.extensionFolder].configs); - config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; - console.log(config.Extensions.extensions2[this.extensionFolder].configs); + extConf.configs = confTemplate; + console.log(((config as any).toJSON({attachState: true})).Extensions.extensions); + /* Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, + ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); + console.log(config.Extensions.extensions2[this.extensionFolder].configs); + config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; + console.log(config.Extensions.extensions2[this.extensionFolder].configs);*/ } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index 11f50aa4..795a6b09 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -73,10 +73,10 @@ export class ExtensionManager implements IObjectManager { const extList = fs - .readdirSync(ProjectPath.ExtensionFolder) - .filter((f): boolean => - fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() - ); + .readdirSync(ProjectPath.ExtensionFolder) + .filter((f): boolean => + fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory() + ); extList.sort(); // delete not existing extensions @@ -85,7 +85,7 @@ export class ExtensionManager implements IObjectManager { // Add new extensions const ePaths = Config.Extensions.extensions.map(ec => ec.path); extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep => - Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); + Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep))); Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path))); } @@ -118,10 +118,14 @@ export class ExtensionManager implements IObjectManager { } if (fs.existsSync(packageJsonPath)) { - Logger.silly(LOG_TAG, `Running: "npm install --prefer-offline --no-audit --progress=false --omit=dev" in ${extPath}`); - await exec('npm install --no-audit --progress=false --omit=dev', { - cwd: extPath - }); + if (fs.existsSync(path.join(extPath, 'node_modules'))) { + Logger.debug(LOG_TAG, `node_modules folder exists. Skipping "npm install".`); + } else { + Logger.silly(LOG_TAG, `Running: "npm install --prefer-offline --no-audit --progress=false --omit=dev" in ${extPath}`); + await exec('npm install --no-audit --progress=false --omit=dev', { + cwd: extPath + }); + } // eslint-disable-next-line @typescript-eslint/no-var-requires const pkg = require(packageJsonPath); if (pkg.name) { diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts index 22f7ad66..4d62097e 100644 --- a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig'; -import {IConfigClassPrivate} from '../../../../../node_modules/typeconfig/src/decorators/class/IConfigClass'; +import {GenericConfigType} from 'typeconfig/src/GenericConfigType'; @SubConfigClass({softReadonly: true}) export class ServerExtensionsEntryConfig { @@ -28,12 +28,13 @@ export class ServerExtensionsEntryConfig { path: string = ''; @ConfigProperty({ + type: GenericConfigType, tags: { name: $localize`Config`, priority: ConfigPriority.advanced } }) - configs: IConfigClassPrivate; + configs: GenericConfigType; } @SubConfigClass({softReadonly: true}) @@ -59,13 +60,6 @@ export class ServerExtensionsConfig extends ClientExtensionsConfig { }) extensions: ServerExtensionsEntryConfig[] = []; - @ConfigProperty({ - tags: { - name: $localize`Installed extensions2`, - priority: ConfigPriority.advanced - } - }) - extensions2: Record = {}; @ConfigProperty({ tags: { diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index 3ac5b769..5d24388d 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -11,7 +11,7 @@ declare let $localize: (s: TemplateStringsArray) => string; if (typeof $localize === 'undefined') { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - global.$localize = (s) => s; + global.$localize = (s) => s[0]; } @@ -1034,7 +1034,6 @@ export class ThemesConfig { name: $localize`Selected theme css`, //this is a 'hack' to the UI settings. UI will only show the selected setting's css uiDisabled: (sb: ThemesConfig) => !sb.enabled, relevant: (c: ThemesConfig) => c.selectedTheme !== 'default', - uiType: 'SelectedThemeSettings' } as TAGS, description: $localize`Adds these css settings as it is to the end of the body tag of the page.` }) diff --git a/src/frontend/app/ui/admin/admin.component.ts b/src/frontend/app/ui/admin/admin.component.ts index 04feb789..5fb5d307 100644 --- a/src/frontend/app/ui/admin/admin.component.ts +++ b/src/frontend/app/ui/admin/admin.component.ts @@ -30,12 +30,12 @@ export class AdminComponent implements OnInit, AfterViewInit { public readonly configPaths: string[] = []; constructor( - private authService: AuthenticationService, - private navigation: NavigationService, - public viewportScroller: ViewportScroller, - public notificationService: NotificationService, - public settingsService: SettingsService, - private piTitleService: PiTitleService + private authService: AuthenticationService, + private navigation: NavigationService, + public viewportScroller: ViewportScroller, + public notificationService: NotificationService, + public settingsService: SettingsService, + private piTitleService: PiTitleService ) { this.configPriorities = enumToTranslatedArray(ConfigPriority); this.configStyles = enumToTranslatedArray(ConfigStyle); @@ -50,8 +50,8 @@ export class AdminComponent implements OnInit, AfterViewInit { ngOnInit(): void { if ( - !this.authService.isAuthenticated() || - this.authService.user.value.role < UserRoles.Admin + !this.authService.isAuthenticated() || + this.authService.user.value.role < UserRoles.Admin ) { this.navigation.toLogin(); return; diff --git a/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts b/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts index b2346b6c..d7470668 100644 --- a/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts +++ b/src/frontend/app/ui/settings/template/CustomSettingsEntries.ts @@ -1,5 +1,15 @@ import {propertyTypes} from 'typeconfig/common'; -import {ClientGroupingConfig, ClientSortingConfig, SVGIconConfig} from '../../../../../common/config/public/ClientConfig'; +import { + ClientGroupingConfig, + ClientSortingConfig, + MapLayers, + MapPathGroupConfig, + MapPathGroupThemeConfig, + NavigationLinkConfig, + SVGIconConfig, + ThemeConfig +} from '../../../../../common/config/public/ClientConfig'; +import {JobScheduleConfig, UserConfig} from '../../../../../common/config/private/PrivateConfig'; /** * Configuration in these class have a custom UI @@ -8,6 +18,13 @@ export class CustomSettingsEntries { public static readonly entries = [ {c: ClientSortingConfig, name: 'ClientSortingConfig'}, {c: ClientGroupingConfig, name: 'ClientGroupingConfig'}, + {c: MapLayers, name: 'MapLayers'}, + {c: JobScheduleConfig, name: 'JobScheduleConfig'}, + {c: UserConfig, name: 'UserConfig'}, + {c: NavigationLinkConfig, name: 'NavigationLinkConfig'}, + {c: MapPathGroupThemeConfig, name: 'MapPathGroupThemeConfig'}, + {c: MapPathGroupConfig, name: 'MapPathGroupConfig'}, + {c: ThemeConfig, name: 'ThemeConfig'}, {c: SVGIconConfig, name: 'SVGIconConfig'}, ]; @@ -46,7 +63,7 @@ export class CustomSettingsEntries { return cN; } - public static iS(s: { tags?: { uiType?: string }, type?: propertyTypes }) { + public static iS(s: { tags?: { uiType?: string }, type?: propertyTypes, arrayType?: propertyTypes }) { const c = this.getConfigName(s); return this.entries.findIndex(e => e.name == c) !== -1; } diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html index f6bdbfbe..a04a9801 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html @@ -12,7 +12,6 @@ [hidden]="shouldHide">
-
- +
@@ -295,7 +296,7 @@ - +
@@ -357,7 +358,7 @@
- +
@@ -403,7 +404,7 @@
- +
@@ -468,7 +469,8 @@
- + +
diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 1d235785..4857ba25 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -258,7 +258,7 @@ export class SettingsEntryComponent this.arrayType !== 'MapPathGroupConfig' && this.arrayType !== 'ServerExtensionsEntryConfig' && this.arrayType !== 'MapPathGroupThemeConfig' && - this.arrayType !== 'JobScheduleConfig' && + this.arrayType !== 'JobScheduleConfig-Array' && this.arrayType !== 'UserConfig') { this.uiType = 'StringInput'; } diff --git a/src/frontend/app/ui/settings/template/template.component.html b/src/frontend/app/ui/settings/template/template.component.html index 35f7fd8d..35af7b9b 100644 --- a/src/frontend/app/ui/settings/template/template.component.html +++ b/src/frontend/app/ui/settings/template/template.component.html @@ -3,7 +3,7 @@
- {{Name}} + {{ Name }}
@@ -22,7 +22,7 @@
- + - {{Name}} config is not supported with these settings. + {{ Name }} config is not supported with these settings.
@@ -72,46 +72,71 @@ let-confPath="confPath"> - - - -
-
-
- - {{rStates?.value.__state[ck].tags?.name || ck}} -
- + + +
+
+
{{ rStates?.value.__state[ck].tags?.name || ck }}
+
+
+
+
+ +
- -
-
-
{{rStates?.value.__state[ck].tags?.name || ck}}
-
-
-
+ + + + + + + + + + +
+
+
+ + {{ rStates?.value.__state[ck].tags?.name || ck }} +
+
-
- -
+ + +
+
+
{{ rStates?.value.__state[ck].tags?.name || ck }}
+
+
+
+
+
+
+ +
+
@@ -125,12 +150,14 @@ >
+
+
diff --git a/src/frontend/app/ui/settings/template/template.component.ts b/src/frontend/app/ui/settings/template/template.component.ts index 6c115e31..70aa8f9c 100644 --- a/src/frontend/app/ui/settings/template/template.component.ts +++ b/src/frontend/app/ui/settings/template/template.component.ts @@ -79,11 +79,11 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting public readonly ConfigStyle = ConfigStyle; constructor( - protected authService: AuthenticationService, - private navigation: NavigationService, - protected notification: NotificationService, - public settingsService: SettingsService, - public jobsService: ScheduledJobsService, + protected authService: AuthenticationService, + private navigation: NavigationService, + protected notification: NotificationService, + public settingsService: SettingsService, + public jobsService: ScheduledJobsService, ) { } @@ -97,7 +97,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting this.nestedConfigs = []; for (const key of this.getKeys(this.states)) { if (this.states.value.__state[key].isConfigType && - this.states?.value.__state[key].tags?.uiIcon) { + this.states?.value.__state[key].tags?.uiIcon) { this.nestedConfigs.push({ id: this.ConfigPath + '.' + key, name: this.states?.value.__state[key].tags?.name, @@ -112,8 +112,8 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting ngOnInit(): void { if ( - !this.authService.isAuthenticated() || - this.authService.user.value.role < UserRoles.Admin + !this.authService.isAuthenticated() || + this.authService.user.value.role < UserRoles.Admin ) { this.navigation.toLogin(); return; @@ -143,7 +143,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting if (sliceFN) { this.sliceFN = sliceFN; this.settingsSubscription = this.settingsService.settings.subscribe( - this.onNewSettings + this.onNewSettings ); } } @@ -171,31 +171,31 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting } if (state.tags && - ((state.tags.relevant && !state.tags.relevant(parent.value)) - || state.tags.secret)) { + ((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.value.__state && - Object.keys(state.value.__state).findIndex(k => !st.value.__state[k].shouldHide()) === -1) { + Object.keys(state.value.__state).findIndex(k => !st.value.__state[k].shouldHide()) === -1) { return true; } } const forcedVisibility = !(state.tags?.priority > this.settingsService.configPriority || - //if this value should not change in Docker, lets hide it - (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 + (this.settingsService.configPriority === ConfigPriority.basic && + state.tags?.dockerSensitive && this.settingsService.settings.value.Environment.isDocker)); if (state.isConfigArrayType) { for (let i = 0; i < state.value?.length; ++i) { for (const k of Object.keys(state.value[i].__state)) { if (!Utils.equalsFilter( - state.value[i]?.__state[k]?.value, - state.default[i] ? state.default[i][k] : undefined, - ['default', '__propPath', '__created', '__prototype', '__rootConfig'])) { + state.value[i]?.__state[k]?.value, + state.default[i] ? state.default[i][k] : undefined, + ['default', '__propPath', '__created', '__prototype', '__rootConfig'])) { return false; } @@ -206,10 +206,10 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting return (!forcedVisibility && - Utils.equalsFilter(state.value, state.default, - ['__propPath', '__created', '__prototype', '__rootConfig']) && - Utils.equalsFilter(state.original, state.default, - ['__propPath', '__created', '__prototype', '__rootConfig'])); + Utils.equalsFilter(state.value, state.default, + ['__propPath', '__created', '__prototype', '__rootConfig']) && + Utils.equalsFilter(state.original, state.default, + ['__propPath', '__created', '__prototype', '__rootConfig'])); }; }; @@ -246,7 +246,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting } if (typeof state.original === 'object') { return Utils.equalsFilter(state.value, state.original, - ['__propPath', '__created', '__prototype', '__rootConfig', '__state']); + ['__propPath', '__created', '__prototype', '__rootConfig', '__state']); } if (typeof state.original !== 'undefined') { return state.value === state.original; @@ -271,10 +271,18 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting this.getSettings(); } + /** + * main template should list it + * @param c + */ isExpandableConfig(c: ConfigState) { return c.isConfigType && !CustomSettingsEntries.iS(c); } + isExpandableArrayConfig(c: ConfigState) { + return c.isConfigArrayType && !CustomSettingsEntries.iS(c); + } + public async save(): Promise { this.inProgress = true; @@ -284,8 +292,8 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting await this.settingsService.updateSettings(state, this.ConfigPath); await this.getSettings(); this.notification.success( - this.Name + ' ' + $localize`settings saved`, - $localize`Success` + this.Name + ' ' + $localize`settings saved`, + $localize`Success` ); this.inProgress = false; return true; @@ -328,7 +336,6 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting return 1; } return (s[a].tags?.name as string || a).localeCompare(s[b].tags?.name || b); - }); states.keys = keys; return states.keys; From 1502e8015042072ff6a7ae5be069a4065213c626 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 2 Mar 2024 22:18:31 +0100 Subject: [PATCH 7/8] Add basic extension UI #784 --- package-lock.json | 14 +++++++------- package.json | 2 +- src/backend/middlewares/RenderingMWs.ts | 1 - src/backend/middlewares/SharingMWs.ts | 1 - src/backend/middlewares/admin/SettingsMWs.ts | 6 +++--- .../model/extension/ExtensionConfigWrapper.ts | 10 ++-------- src/backend/model/extension/ExtensionManager.ts | 4 ++++ src/backend/model/jobs/jobs/TopPickSendJob.ts | 1 - .../private/subconfigs/ServerExtensionsConfig.ts | 1 + .../search-field-base.gallery.component.ts | 1 - .../settings-entry/settings-entry.component.ts | 1 - 11 files changed, 18 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4770b33..06dc3a97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-node-iptc": "1.0.11", - "typeconfig": "2.2.7", + "typeconfig": "2.2.11", "typeorm": "0.3.12", "xml2js": "0.6.2" }, @@ -20361,9 +20361,9 @@ } }, "node_modules/typeconfig": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", - "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.11.tgz", + "integrity": "sha512-Knj+1kbIJ4zOZlUm2TPSWZUoiOW4txrmPyf6oyuBhaDQDlGxpSL5jobF3vVV9mZElK1V3ZQVeTgvGaiDyeT8mQ==", "dependencies": { "minimist": "1.2.8" } @@ -35280,9 +35280,9 @@ } }, "typeconfig": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.7.tgz", - "integrity": "sha512-xxMJky/XUsmWss8HM99uPeN+sZYF67AAht3Gajtnbp4k5bxBwplnahU+1N1GUKhmvFuqQoIQbiXsu9WpvznI1g==", + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.2.11.tgz", + "integrity": "sha512-Knj+1kbIJ4zOZlUm2TPSWZUoiOW4txrmPyf6oyuBhaDQDlGxpSL5jobF3vVV9mZElK1V3ZQVeTgvGaiDyeT8mQ==", "requires": { "minimist": "1.2.8" } diff --git a/package.json b/package.json index d5b78dfb..65e65868 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-node-iptc": "1.0.11", - "typeconfig": "2.2.7", + "typeconfig": "2.2.11", "typeorm": "0.3.12", "xml2js": "0.6.2" }, diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 2576db38..404c1814 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -119,7 +119,6 @@ export class RenderingMWs { skipTags: {secret: true} as TAGS }) as PrivateConfigClass ); - console.log(message.result.Extensions.extensions); res.json(message); } diff --git a/src/backend/middlewares/SharingMWs.ts b/src/backend/middlewares/SharingMWs.ts index 943a3c1f..e4552561 100644 --- a/src/backend/middlewares/SharingMWs.ts +++ b/src/backend/middlewares/SharingMWs.ts @@ -171,7 +171,6 @@ export class SharingMWs { sharing, forceUpdate ); - console.log(req.resultPipe); return next(); } catch (err) { return next( diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index 0e7af1ca..7e8d37bc 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -1,12 +1,12 @@ import {NextFunction, Request, Response} from 'express'; import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {Logger} from '../../Logger'; import {Config} from '../../../common/config/private/Config'; import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics'; import {ConfigClassBuilder} from 'typeconfig/node'; import {TAGS} from '../../../common/config/public/ClientConfig'; import {ObjectManagers} from '../../model/ObjectManagers'; import {ExtensionConfigWrapper} from '../../model/extension/ExtensionConfigWrapper'; +import {Logger} from '../../Logger'; const LOG_TAG = '[SettingsMWs]'; @@ -21,8 +21,8 @@ export class SettingsMWs { */ public static async updateSettings(req: Request, res: Response, next: NextFunction): Promise { if ((typeof req.body === 'undefined') - || (typeof req.body.settings === 'undefined') - || (typeof req.body.settingsPath !== 'string')) { + || (typeof req.body.settings === 'undefined') + || (typeof req.body.settingsPath !== 'string')) { return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed')); } diff --git a/src/backend/model/extension/ExtensionConfigWrapper.ts b/src/backend/model/extension/ExtensionConfigWrapper.ts index 2b61ab2c..ae932d18 100644 --- a/src/backend/model/extension/ExtensionConfigWrapper.ts +++ b/src/backend/model/extension/ExtensionConfigWrapper.ts @@ -12,12 +12,13 @@ export class ExtensionConfigWrapper { static async original(): Promise { const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass()); try { - await pc.load(); + await pc.load(); // loading the basic configs but we do not know the extension config hierarchy yet if (ObjectManagers.isReady()) { for (const ext of Object.values(ObjectManagers.getInstance().ExtensionManager.extObjects)) { ext.config.loadToConfig(ConfigClassBuilder.attachPrivateInterface(pc)); } } + await pc.load(); // loading the extension related configs } catch (e) { console.error('Error during loading original config. Reverting to defaults.'); console.error(e); @@ -58,13 +59,6 @@ export class ExtensionConfig implements IExtensionConfig { const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template()); const extConf = this.findConfig(config); - // confTemplate.__loadJSONObject(Utils.clone(extConf.configs || {})); extConf.configs = confTemplate; - console.log(((config as any).toJSON({attachState: true})).Extensions.extensions); - /* Object.defineProperty(config.Extensions.extensions2[this.extensionFolder].configs, this.extensionFolder, - ConfigProperty({type: this.template})(config.Extensions.extensions2[this.extensionFolder], this.extensionFolder)); - console.log(config.Extensions.extensions2[this.extensionFolder].configs); - config.Extensions.extensions2[this.extensionFolder].configs = confTemplate as any; - console.log(config.Extensions.extensions2[this.extensionFolder].configs);*/ } } diff --git a/src/backend/model/extension/ExtensionManager.ts b/src/backend/model/extension/ExtensionManager.ts index 795a6b09..f5256861 100644 --- a/src/backend/model/extension/ExtensionManager.ts +++ b/src/backend/model/extension/ExtensionManager.ts @@ -109,6 +109,10 @@ export class ExtensionManager implements IObjectManager { for (let i = 0; i < Config.Extensions.extensions.length; ++i) { const extFolder = Config.Extensions.extensions[i].path; let extName = extFolder; + + if(Config.Extensions.extensions[i].enabled === false){ + Logger.silly(LOG_TAG, `Skipping ${extFolder} initiation. Extension is disabled.`); + } const extPath = path.join(ProjectPath.ExtensionFolder, extFolder); const serverExtPath = path.join(extPath, 'server.js'); const packageJsonPath = path.join(extPath, 'package.json'); diff --git a/src/backend/model/jobs/jobs/TopPickSendJob.ts b/src/backend/model/jobs/jobs/TopPickSendJob.ts index 2ba0450c..c748e19f 100644 --- a/src/backend/model/jobs/jobs/TopPickSendJob.ts +++ b/src/backend/model/jobs/jobs/TopPickSendJob.ts @@ -100,7 +100,6 @@ export class TopPickSendJob extends Job<{ arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index); this.Progress.Processed++; - // console.log(this.mediaList); return false; } diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts index 4d62097e..0efcbf03 100644 --- a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -19,6 +19,7 @@ export class ServerExtensionsEntryConfig { enabled: boolean = true; @ConfigProperty({ + readonly: true, tags: { name: $localize`Extension folder`, priority: ConfigPriority.underTheHood, diff --git a/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts b/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts index e304504d..ba8b4e9c 100644 --- a/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts +++ b/src/frontend/app/ui/gallery/search/search-field-base/search-field-base.gallery.component.ts @@ -167,7 +167,6 @@ export class GallerySearchFieldBaseComponent 0, this.rawSearchText.length - token.current.length ) + item.queryHint; - console.log('aa'); this.onChange(); this.emptyAutoComplete(); } diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 4857ba25..548e898e 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -467,7 +467,6 @@ export class SettingsEntryComponent const reader = new FileReader(); reader.onload = () => { - console.log(reader.result); const parser = new DOMParser(); const doc = parser.parseFromString(reader.result as string, 'image/svg+xml'); try { From 745e486e1c20a388a2b6f94fb34502a0499d99c6 Mon Sep 17 00:00:00 2001 From: Matthew Blythe Date: Sun, 3 Mar 2024 01:19:21 -0700 Subject: [PATCH 8/8] Add logging elision Allow for anonymous functions in logging calls. The function is only called if the message is logged. (e.g. if the verbosity is turned up high enough.) This allows for expensive operations to be avoided in cases where the logging won't happen. The idea is that this provides a performance benefit. I don't know how "expensive" an operation must be to actually realize a performance benefit, though. I added an example in server.ts... It's probably "expensive" to dump the configuration to JSON, then stringify that JSON for logging. --- src/backend/Logger.ts | 36 +++++++++++++++++++++--------------- src/backend/server.ts | 4 ++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/backend/Logger.ts b/src/backend/Logger.ts index 7d860178..3cb26270 100644 --- a/src/backend/Logger.ts +++ b/src/backend/Logger.ts @@ -1,7 +1,7 @@ import {Config} from '../common/config/private/Config'; import {LogLevel} from '../common/config/private/PrivateConfig'; -export type logFN = (...args: (string | number)[]) => void; +export type logFN = (...args: (string | number | (() => string))[]) => void; const forcedDebug = process.env['NODE_ENV'] === 'debug'; @@ -11,7 +11,8 @@ if (forcedDebug === true) { ); } -export type LoggerFunction = (...args: (string | number)[]) => void; +export type LoggerArgs = (string | number | (() => string)) +export type LoggerFunction = (...args: LoggerArgs[]) => void; export interface ILogger { silly: LoggerFunction; @@ -23,67 +24,67 @@ export interface ILogger { } export const createLoggerWrapper = (TAG: string): ILogger => ({ - silly: (...args: (string | number)[]) => { + silly: (...args: LoggerArgs[]) => { Logger.silly(TAG, ...args); }, - debug: (...args: (string | number)[]) => { + debug: (...args: LoggerArgs[]) => { Logger.debug(TAG, ...args); }, - verbose: (...args: (string | number)[]) => { + verbose: (...args: LoggerArgs[]) => { Logger.verbose(TAG, ...args); }, - info: (...args: (string | number)[]) => { + info: (...args: LoggerArgs[]) => { Logger.info(TAG, ...args); }, - warn: (...args: (string | number)[]) => { + warn: (...args: LoggerArgs[]) => { Logger.warn(TAG, ...args); }, - error: (...args: (string | number)[]) => { + error: (...args: LoggerArgs[]) => { Logger.error(TAG, ...args); } }); export class Logger { - public static silly(...args: (string | number)[]): void { + public static silly(...args: LoggerArgs[]): void { if (!forcedDebug && Config.Server.Log.level < LogLevel.silly) { return; } Logger.log(`[\x1b[35mSILLY\x1b[0m]`, ...args); } - public static debug(...args: (string | number)[]): void { + public static debug(...args: LoggerArgs[]): void { if (!forcedDebug && Config.Server.Log.level < LogLevel.debug) { return; } Logger.log(`[\x1b[34mDEBUG\x1b[0m]`, ...args); } - public static verbose(...args: (string | number)[]): void { + public static verbose(...args: LoggerArgs[]): void { if (!forcedDebug && Config.Server.Log.level < LogLevel.verbose) { return; } Logger.log(`[\x1b[36mVERBS\x1b[0m]`, ...args); } - public static info(...args: (string | number)[]): void { + public static info(...args: LoggerArgs[]): void { if (!forcedDebug && Config.Server.Log.level < LogLevel.info) { return; } Logger.log(`[\x1b[32mINFO_\x1b[0m]`, ...args); } - public static warn(...args: (string | number)[]): void { + public static warn(...args: LoggerArgs[]): void { if (!forcedDebug && Config.Server.Log.level < LogLevel.warn) { return; } Logger.log(`[\x1b[33mWARN_\x1b[0m]`, ...args); } - public static error(...args: (string | number)[]): void { + public static error(...args: LoggerArgs[]): void { Logger.log(`[\x1b[31mERROR\x1b[0m]`, ...args); } - private static log(tag: string, ...args: (string | number)[]): void { + private static log(tag: string, ...args: LoggerArgs[]): void { const date = new Date().toLocaleString(); let LOG_TAG = ''; if ( @@ -95,6 +96,11 @@ export class Logger { LOG_TAG = args[0]; args.shift(); } + args.forEach((element:LoggerArgs, index:number) => { + if(typeof element === "function"){ + args[index] = element(); //execute function, put resulting string in the array + } + }); console.log(date + tag + LOG_TAG, ...args); } } diff --git a/src/backend/server.ts b/src/backend/server.ts index f77c132e..a34b3643 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -67,14 +67,14 @@ export class Server { await ConfigDiagnostics.runDiagnostics(); Logger.verbose( LOG_TAG, - 'using config from ' + + () => 'using config from ' + ( ConfigClassBuilder.attachPrivateInterface(Config) .__options as ConfigClassOptions ).configPath + ':' ); - Logger.verbose(LOG_TAG, JSON.stringify(Config.toJSON({attachDescription: false}), (k, v) => { + Logger.verbose(LOG_TAG, () => JSON.stringify(Config.toJSON({attachDescription: false}), (k, v) => { const MAX_LENGTH = 80; if (typeof v === 'string' && v.length > MAX_LENGTH) { v = v.slice(0, MAX_LENGTH - 3) + '...';