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/4] 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/4] 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/4] 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/4] 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 {