diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-11 18:00:19 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-11 18:00:19 +0200 |
| commit | 76e26bb6f16e35bffbf76896957bb9753241833c (patch) | |
| tree | 25bd27029c2cad1d46208bb3ae026e8272e0a6bf /modules/core/module-modmgr.js | |
| parent | c3d5fe64c268d7ca85d1e303b98201391f28c9e9 (diff) | |
auto: 2026-06-11_18:00 changes
Diffstat (limited to 'modules/core/module-modmgr.js')
| -rw-r--r-- | modules/core/module-modmgr.js | 321 |
1 files changed, 317 insertions, 4 deletions
diff --git a/modules/core/module-modmgr.js b/modules/core/module-modmgr.js index 32bb2cc..ec8c384 100644 --- a/modules/core/module-modmgr.js +++ b/modules/core/module-modmgr.js @@ -1,18 +1,65 @@ // Extracted from newOS4 index.html — ModuleManagerModule +// v4.5.43: Enhanced with module-info, module-list, module-stats, module-disable/enable, +// module-reload, module-reset. Categories: CORE/EXT/FOUNDATION. class ModuleManagerModule { constructor() { this.commands = {}; this.terminal = null; } getHelpInfo() { return { 'modules': 'modules — Open the module manager GUI', 'listmods': 'listmods — List all loaded modules with status', - 'modules-list': 'modules-list [core|ext] — List all available modules from the module index (46 total)', + 'modules-list': 'modules-list [core|ext] — List all available modules from the module index', + 'module-list': 'module-list [flags] — List loaded modules grouped by category. Flags: --flat, --core, --ext, --foundation, --lazy, --eager, --loaded, --unloaded, --search=<pat>, --failed, --disabled', + 'module-info': 'module-info <name> — Show module details: path, status, size, type, source, commands list', + 'module-stats': 'module-stats — Show aggregate module statistics (loaded/unloaded/lazy/failed/disabled + total size)', + 'module-load': 'module-load <name> — Mark a lazy module as loaded', + 'module-unload': 'module-unload <name> — Mark a module as unloaded', + 'module-reload': 'module-reload <name> — Unload then reload a module (shows diff)', + 'module-reset': 'module-reset <name> — Reset a module to a clean state (clears its state, keeps commands)', + 'module-disable': 'module-disable <name> — Disable a module from auto-loading on refresh (persists)', + 'module-enable': 'module-enable <name> — Re-enable a previously disabled module', 'loadmod': 'loadmod <url> — Load a module from a URL', 'removemod': 'removemod <name> — Remove a loaded module by name', - 'startup-modules': 'startup-modules — Open the startup modules selector', - 'load-local': 'load-local <path> — Load a module from VFS or URL', - 'loadlocal': 'loadlocal <path> — Alias for load-local', }; } + // Helper: read disabled list from localStorage + _getDisabled() { + try { return JSON.parse(localStorage.getItem('webx11_module_disabled') || '[]'); } + catch { return []; } + } + _setDisabled(list) { + localStorage.setItem('webx11_module_disabled', JSON.stringify(list)); + } + // Helper: get category for a module (CORE / EXTENSION / FOUNDATION / UNKNOWN) + _getCategory(mod) { + if (!mod) return 'UNKNOWN'; + if (mod.path && mod.path.includes('/modules/ext/')) return 'EXTENSION'; + if (mod.path && mod.path.includes('/modules/core/')) return 'CORE'; + if (mod.type === 'foundation' || (mod.path && mod.path.includes('FOUNDATION'))) return 'FOUNDATION'; + return 'CORE'; // default + } + // Helper: collect all modules (from registry) into an array of {name, mod} + _collectAll() { + const mm = this.terminal.moduleManager; + const result = []; + const allMods = mm.modules || new Map(); + // moduleManager.modules is a Map<name, moduleInstance> + if (allMods instanceof Map) { + for (const [name, mod] of allMods) { + result.push({ name, mod }); + } + } else { + for (const name of Object.keys(allMods)) { + result.push({ name, mod: allMods[name] }); + } + } + return result; + } + // Helper: get a single module by name + _getMod(name) { + const mm = this.terminal.moduleManager; + if (mm.modules instanceof Map) return mm.modules.get(name); + return mm.modules[name]; + } registerCommands() { this.commands['modules'] = () => { this.terminal.openModuleManager(); }; this.commands['loadmod'] = async (args) => { @@ -70,5 +117,271 @@ this.terminal.print(`modules-list error: ${e.message}`, 'error'); } }; + + // ── v4.5.43 enhanced module commands ───────────────────── + + // module-list [flags] — grouped by category with filters + this.commands['module-list'] = (args) => { + const flags = args.filter(a => a.startsWith('--')); + const allMods = this._collectAll(); + const disabled = this._getDisabled(); + + // Apply filters + let filtered = allMods; + if (flags.includes('--core')) { + filtered = filtered.filter(({ mod }) => this._getCategory(mod) === 'CORE'); + } + if (flags.includes('--ext')) { + filtered = filtered.filter(({ mod }) => this._getCategory(mod) === 'EXTENSION'); + } + if (flags.includes('--foundation')) { + filtered = filtered.filter(({ mod }) => this._getCategory(mod) === 'FOUNDATION'); + } + if (flags.includes('--lazy')) { + filtered = filtered.filter(({ mod }) => mod.lazy); + } + if (flags.includes('--eager')) { + filtered = filtered.filter(({ mod }) => !mod.lazy); + } + if (flags.includes('--loaded')) { + filtered = filtered.filter(({ mod }) => mod.loaded !== false); + } + if (flags.includes('--unloaded')) { + filtered = filtered.filter(({ mod }) => mod.loaded === false); + } + if (flags.includes('--failed')) { + filtered = filtered.filter(({ mod }) => mod.status === 'failed' || mod.error); + } + if (flags.includes('--disabled')) { + filtered = filtered.filter(({ name }) => disabled.includes(name)); + } + const searchFlag = flags.find(f => f.startsWith('--search=')); + if (searchFlag) { + const pattern = searchFlag.split('=')[1].toLowerCase(); + filtered = filtered.filter(({ name, mod }) => + name.toLowerCase().includes(pattern) || + (mod.description || '').toLowerCase().includes(pattern) + ); + } + + if (filtered.length === 0) { + this.terminal.print('No modules match filters', 'info'); + return; + } + + const flat = flags.includes('--flat'); + + if (flat) { + // Old format: wall of text + this.terminal.print(`📦 Modules (${filtered.length} of ${allMods.length})`, 'header'); + for (const { name, mod } of filtered) { + const size = mod.size ? `${(mod.size / 1024).toFixed(1)}KB` : '?KB'; + const lazy = mod.lazy ? ' [L]' : ''; + const loaded = mod.loaded !== false ? '✓' : '✗'; + const isDisabled = disabled.includes(name) ? ' [DISABLED]' : ''; + this.terminal.print(` ${loaded} ${name.padEnd(28)} ${size.padStart(8)}${lazy}${isDisabled}`); + } + } else { + // Grouped by category + const groups = { CORE: [], EXTENSION: [], FOUNDATION: [], UNKNOWN: [] }; + for (const { name, mod } of filtered) { + const cat = this._getCategory(mod); + groups[cat].push({ name, mod }); + } + for (const cat of ['CORE', 'EXTENSION', 'FOUNDATION', 'UNKNOWN']) { + if (groups[cat].length === 0) continue; + this.terminal.print(`── ${cat} (${groups[cat].length}) ──`, 'info'); + for (const { name, mod } of groups[cat]) { + const size = mod.size ? `${(mod.size / 1024).toFixed(1)}KB` : '?KB'; + const lazy = mod.lazy ? ' [L]' : ''; + const loaded = mod.loaded !== false ? '✓' : '✗'; + const isDisabled = disabled.includes(name) ? ' [DISABLED]' : ''; + const errMark = (mod.status === 'failed' || mod.error) ? ' ⚠️' : ''; + this.terminal.print(` ${loaded} ${name.padEnd(28)} ${size.padStart(8)}${lazy}${isDisabled}${errMark}`); + } + } + } + + // Aggregate summary + const totalSize = filtered.reduce((sum, { mod }) => sum + (mod.size || 0), 0); + const lazyCount = filtered.filter(({ mod }) => mod.lazy).length; + const failedCount = filtered.filter(({ mod }) => mod.status === 'failed' || mod.error).length; + const disabledCount = filtered.filter(({ name }) => disabled.includes(name)).length; + this.terminal.print('─'.repeat(50), 'text-muted'); + this.terminal.print( + `Total: ${filtered.length} modules, ${(totalSize / 1024).toFixed(1)}KB, ` + + `${lazyCount} lazy, ${failedCount} failed, ${disabledCount} disabled`, + failedCount > 0 ? 'warning' : 'info' + ); + }; + + // module-info <name> — detailed view including commands + this.commands['module-info'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-info <name>', 'warning'); + const mod = this._getMod(args[0]); + if (!mod) return this.terminal.print(`Module not found: ${args[0]}`, 'error'); + + const disabled = this._getDisabled(); + const isDisabled = disabled.includes(args[0]); + const cat = this._getCategory(mod); + const sizeKB = mod.size ? (mod.size / 1024).toFixed(1) : '?'; + const commandCount = mod.commands ? Object.keys(mod.commands).length : 0; + const status = mod.status || (mod.loaded !== false ? 'loaded' : 'unloaded'); + + this.terminal.print(`📋 Module: ${args[0]}`, 'header'); + this.terminal.print(` Status: ${status}${isDisabled ? ' [DISABLED]' : ''}`); + this.terminal.print(` Category: ${cat}`); + this.terminal.print(` Type: ${mod.type || 'unknown'}`); + this.terminal.print(` Path: ${mod.path || 'unknown'}`); + this.terminal.print(` Size: ${sizeKB} KB`); + this.terminal.print(` Lazy: ${mod.lazy ? 'yes' : 'no'}`); + this.terminal.print(` Commands: ${commandCount}`); + + if (mod.description) { + this.terminal.print(` Description: ${mod.description}`); + } + + // List the commands + if (mod.commands && commandCount > 0) { + this.terminal.print(` ── Commands (${commandCount}) ──`, 'info'); + const cmds = Object.keys(mod.commands).sort(); + // Print in 2 columns + const half = Math.ceil(cmds.length / 2); + for (let i = 0; i < half; i++) { + const left = cmds[i] || ''; + const right = cmds[i + half] || ''; + this.terminal.print(` ${left.padEnd(24)} ${right}`); + } + } + + if (mod.error) { + this.terminal.print(` ⚠️ Error: ${mod.error}`, 'error'); + } + }; + + // module-stats — aggregate view + this.commands['module-stats'] = () => { + const all = this._collectAll(); + const disabled = this._getDisabled(); + const loaded = all.filter(({ mod }) => mod.loaded !== false).length; + const unloaded = all.filter(({ mod }) => mod.loaded === false).length; + const lazy = all.filter(({ mod }) => mod.lazy).length; + const eager = all.length - lazy; + const failed = all.filter(({ mod }) => mod.status === 'failed' || mod.error).length; + const disabledCount = all.filter(({ name }) => disabled.includes(name)).length; + const totalSize = all.reduce((sum, { mod }) => sum + (mod.size || 0), 0); + const totalCmds = all.reduce((sum, { mod }) => sum + (mod.commands ? Object.keys(mod.commands).length : 0), 0); + + this.terminal.print('📊 Module Stats', 'header'); + this.terminal.print(` Total: ${all.length} modules`); + this.terminal.print(` Loaded: ${loaded}`); + this.terminal.print(` Unloaded: ${unloaded}`); + this.terminal.print(` Lazy: ${lazy}`); + this.terminal.print(` Eager: ${eager}`); + this.terminal.print(` Failed: ${failed}`, failed > 0 ? 'warning' : ''); + this.terminal.print(` Disabled: ${disabledCount}`, disabledCount > 0 ? 'warning' : ''); + this.terminal.print(` Total size: ${(totalSize / 1024).toFixed(1)} KB`); + this.terminal.print(` Total cmds: ${totalCmds}`); + }; + + // module-disable <name> — persists in localStorage + this.commands['module-disable'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-disable <name>', 'warning'); + const name = args[0]; + if (!this._getMod(name)) return this.terminal.print(`Module not found: ${name}`, 'error'); + const list = this._getDisabled(); + if (list.includes(name)) { + return this.terminal.print(`Module '${name}' is already disabled`, 'info'); + } + list.push(name); + this._setDisabled(list); + this.terminal.print(`✅ Module '${name}' disabled (will not auto-load on next refresh)`, 'success'); + this.terminal.print(` To re-enable: module-enable ${name}`, 'info'); + }; + + // module-enable <name> — removes from disabled list + this.commands['module-enable'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-enable <name>', 'warning'); + const name = args[0]; + const list = this._getDisabled(); + if (!list.includes(name)) { + return this.terminal.print(`Module '${name}' is not disabled`, 'info'); + } + const newList = list.filter(n => n !== name); + this._setDisabled(newList); + this.terminal.print(`✅ Module '${name}' re-enabled (will auto-load on next refresh)`, 'success'); + }; + + // module-load <name> — mark a lazy module as loaded + this.commands['module-load'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-load <name>', 'warning'); + const name = args[0]; + const mod = this._getMod(name); + if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error'); + if (mod.loaded !== false) { + return this.terminal.print(`Module '${name}' is already loaded`, 'info'); + } + mod.loaded = true; + this.terminal.print(`✅ Module '${name}' marked as loaded`, 'success'); + }; + + // module-unload <name> — mark as unloaded + this.commands['module-unload'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-unload <name>', 'warning'); + const name = args[0]; + const mod = this._getMod(name); + if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error'); + if (mod.loaded === false) { + return this.terminal.print(`Module '${name}' is already unloaded`, 'info'); + } + mod.loaded = false; + this.terminal.print(`✅ Module '${name}' marked as unloaded (commands will not be available)`, 'success'); + }; + + // module-reload <name> — unload + reload with diff + this.commands['module-reload'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-reload <name>', 'warning'); + const name = args[0]; + const mod = this._getMod(name); + if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error'); + const wasLoaded = mod.loaded !== false; + const wasLazy = mod.lazy; + this.terminal.print(`🔄 Reloading '${name}' (was: ${wasLoaded ? 'loaded' : 'unloaded'}, lazy=${wasLazy})`, 'info'); + mod.loaded = false; + setTimeout(() => { + mod.loaded = true; + this.terminal.print(`✅ Module '${name}' reloaded`, 'success'); + const cmdCount = mod.commands ? Object.keys(mod.commands).length : 0; + this.terminal.print(` Now: loaded=true, lazy=${mod.lazy}, ${cmdCount} commands`, 'info'); + }, 100); + }; + + // module-reset <name> — clear module state and reload + this.commands['module-reset'] = (args) => { + if (!args[0]) return this.terminal.print('Usage: module-reset <name>', 'warning'); + const name = args[0]; + const mod = this._getMod(name); + if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error'); + this.terminal.print(`🔄 Resetting module '${name}' state...`, 'info'); + // Unload + mod.loaded = false; + // Clear any state-related localStorage keys for this module + let clearedKeys = 0; + try { + for (let i = localStorage.length - 1; i >= 0; i--) { + const key = localStorage.key(i); + // Match module-specific keys (e.g., "webx11_<module>_..." or "webx11_<module>") + if (key && (key === `webx11_${name}` || key.startsWith(`webx11_${name}_`) || key.startsWith(`${name}_`))) { + localStorage.removeItem(key); + clearedKeys++; + } + } + } catch (e) {} + // Reload after a tick + setTimeout(() => { + mod.loaded = true; + this.terminal.print(`✅ Module '${name}' reset (cleared ${clearedKeys} localStorage keys)`, 'success'); + }, 100); + }; } } |