diff --git a/public/javascript/JSON.js b/public/javascript/JSON.js index f78b59f..108ecd6 100644 --- a/public/javascript/JSON.js +++ b/public/javascript/JSON.js @@ -2,14 +2,17 @@ createJsonTree({ container: domElement, data: jsonData, + expandInitially = true // optional: expand all nodes onChange: (data) => { }, // optional: callback on change onSave: (data) => { } // optional: if set, a save button will be added }); -*/function createJsonTree({ +*/ +function createJsonTree({ container, data, onChange = () => {}, - onSave = null + onSave = null, + expandInitially = true }) { container.innerHTML = ""; container.classList.add("json-tree-root"); @@ -19,6 +22,8 @@ createJsonTree({ let lastSnapshot = clone(data); + const expandedPaths = new Set(); + function clone(obj) { return JSON.parse(JSON.stringify(obj)); } @@ -104,34 +109,53 @@ createJsonTree({ render(); } + function collectPaths(obj, path = "root") { + if (typeof obj !== "object" || obj === null) return; + + expandedPaths.add(path); + + const entries = Array.isArray(obj) + ? obj.map((v, i) => [i, v]) + : Object.entries(obj); + + for (const [k, v] of entries) { + collectPaths(v, `${path}.${k}`); + } + } + + if (expandInitially) { + collectPaths(data); + } + function render() { container.innerHTML = ""; - const controls = document.createElement("div"); - controls.className = "json-controls"; - - const undoBtn = document.createElement("button"); - undoBtn.className = "monolyth"; - undoBtn.textContent = "Undo"; - undoBtn.onclick = undo; - - const redoBtn = document.createElement("button"); - redoBtn.className = "monolyth"; - redoBtn.textContent = "Redo"; - redoBtn.onclick = redo; - - controls.appendChild(undoBtn); - controls.appendChild(redoBtn); - if (onSave) { + const controls = document.createElement("div"); + controls.className = "json-controls"; + + const undoBtn = document.createElement("button"); + undoBtn.className = "monolyth"; + undoBtn.textContent = "Undo"; + undoBtn.onclick = undo; + + const redoBtn = document.createElement("button"); + redoBtn.className = "monolyth"; + redoBtn.textContent = "Redo"; + redoBtn.onclick = redo; + const saveBtn = document.createElement("button"); saveBtn.className = "monolyth"; saveBtn.textContent = "Save"; saveBtn.onclick = save; + + controls.appendChild(undoBtn); + controls.appendChild(redoBtn); controls.appendChild(saveBtn); + + container.appendChild(controls); } - container.appendChild(controls); container.appendChild(renderNode(data, null, null, "root", 0)); } @@ -177,10 +201,24 @@ createJsonTree({ const children = document.createElement("div"); children.className = "json-children"; - keyLabel.onclick = toggle.onclick = () => { - children.classList.toggle("collapsed"); + const toggleState = () => { + if (expandedPaths.has(path)) { + expandedPaths.delete(path); + children.classList.add("collapsed"); + } else { + expandedPaths.add(path); + children.classList.remove("collapsed"); + } }; + keyLabel.onclick = toggle.onclick = toggleState; + + if (expandedPaths.has(path)) { + children.classList.remove("collapsed"); + } else { + children.classList.add("collapsed"); + } + addBtn.onclick = () => { let newKey = ""; let newValue; @@ -210,7 +248,6 @@ createJsonTree({ if (!isArray) { const input = document.querySelector('#newJsonKeyName'); newKey = input?.value?.trim(); - if (!newKey) return; } @@ -331,6 +368,7 @@ createJsonTree({ wrapper.appendChild(keySpan); wrapper.appendChild(span); + if (key !== null) wrapper.appendChild(removeBtn); return wrapper; diff --git a/public/styles/os.css b/public/styles/os.css index ba3f2e3..f3b5f7c 100644 --- a/public/styles/os.css +++ b/public/styles/os.css @@ -14,16 +14,19 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va .window-content { display: flex; flex-direction: column; flex:1; padding:8px; overflow: auto; } .window[class="max"] .window-resize-handle { display: none; } -.window-resize-n, .window-resize-s { position: absolute; left: 8px; right: 8px; height: 8px; z-index: 10; } +.window-resize-s { position: absolute; left: 8px; right: 8px; height: 8px; z-index: 10; } +.window-resize-n { position: absolute; left: 8px; right: 16px; height: 8px; z-index: 10; } .window-resize-n { top: -4px;cursor: var(--theme-cursor-resize-vertical); } .window-resize-s { bottom: -4px;cursor: var(--theme-cursor-resize-vertical); } -.window-resize-e, .window-resize-w { position: absolute;top: 8px;bottom: 8px;width: 8px;z-index: 10; } +.window-resize-w { position: absolute;top: 8px;bottom: 8px;width: 8px;z-index: 10; } +.window-resize-e { position: absolute;top: 16px;bottom: 8px;width: 8px;z-index: 10; } .window-resize-e { right: -4px;cursor: var(--theme-cursor-resize-horizontal); } .window-resize-w { left: -4px;cursor: var(--theme-cursor-resize-horizontal); } -.window-resize-ne, .window-resize-nw, .window-resize-se, .window-resize-sw { position: absolute;width: 16px;height: 16px;z-index: 11; } -.window-resize-ne { top: -6px;right: -6px;cursor: var(--theme-cursor-resize-45); } +.window-resize-nw, .window-resize-se, .window-resize-sw { position: absolute;width: 16px;height: 16px;z-index: 11; } +.window-resize-ne { position: absolute;width: 8px;height: 8x;z-index: 11; } +.window-resize-ne { top: -3px;right: -3px;cursor: var(--theme-cursor-resize-45); } .window-resize-nw { top: -6px;left: -6px;cursor: var(--theme-cursor-resize-270); } .window-resize-se { bottom: -6px;right: -6px;cursor: var(--theme-cursor-resize-270); } .window-resize-sw { bottom: -6px;left: -6px;cursor: var(--theme-cursor-resize-45); } diff --git a/public/styles/table.css b/public/styles/table.css index 54090b4..d4c76b7 100644 --- a/public/styles/table.css +++ b/public/styles/table.css @@ -17,7 +17,7 @@ table.border * { border:1px solid white; } /* #region performance optimizing */ -table thead { position:sticky; top:0; z-index:20; will-change:transform; transform:translateZ(0);} +table thead { position:sticky; top:0; z-index:20; } /* #endregion */ diff --git a/public/views/integrated/serverinfo.hbs b/public/views/integrated/serverinfo.hbs index 4e32335..32d0b59 100644 --- a/public/views/integrated/serverinfo.hbs +++ b/public/views/integrated/serverinfo.hbs @@ -8,8 +8,8 @@ -
-
+
+
@@ -24,8 +24,8 @@
-
-
+
+
@@ -88,14 +88,13 @@ }); vt.addData(json.releaseNotes) - document.querySelector('#package').innerHTML = JSON.stringify(json.package).split(',').join('
'); -/* + //document.querySelector('#package').innerHTML = JSON.stringify(json.package).split(',').join('
'); + createJsonTree({ - container: document.getElementById("package-json"), - data: json, - expandInitially: true, - onSave: () => { } + container: document.getElementById("package"), + data: json.package, + expandInitially: true }); -*/ + }); \ No newline at end of file diff --git a/public/views/plugindashboard.hbs b/public/views/plugindashboard.hbs index e232c8a..249b7cf 100644 --- a/public/views/plugindashboard.hbs +++ b/public/views/plugindashboard.hbs @@ -21,7 +21,7 @@
- \ No newline at end of file diff --git a/src/models/integratedStartmenuItems.js b/src/models/__remove__integratedStartmenuItems.js similarity index 93% rename from src/models/integratedStartmenuItems.js rename to src/models/__remove__integratedStartmenuItems.js index b07dae2..ce77856 100644 --- a/src/models/integratedStartmenuItems.js +++ b/src/models/__remove__integratedStartmenuItems.js @@ -48,7 +48,7 @@ module.exports = ([ { label: 'Plugins', view: 'plugindashboard', - defaultSize: { width: "1000px", height: "400px" }, + defaultSize: { width: "900px", height: "800px" }, icon: "plugins.png", permissions: [ 'Administration' ] } diff --git a/src/models/integratedStartMenuItems.json b/src/models/integratedStartMenuItems.json new file mode 100644 index 0000000..2e9187e --- /dev/null +++ b/src/models/integratedStartMenuItems.json @@ -0,0 +1,154 @@ +[ + { + "section": "System", + "name": "Server", + "active": true, + "menu": { + "label": "Server", + "items": [ + { + "label": "Styles", + "view": "styleconfig", + "icon": "brush.png", + "permissions": [ + "Administration" + ] + }, + { + "label": "Configs", + "view": "serverconfig", + "icon": "app.png", + "permissions": [ + "Administration" + ] + } + ] + }, + "defaultSize": { + "width": 800, + "height": 600 + } + }, + { + "section": "System", + "name": "EventLog", + "active": true, + "menu": { + "label": "EventLog", + "items": [ + { + "label": "EventLog", + "view": "eventlog", + "defaultSize": { + "width": "1200px", + "height": "1200px" + }, + "icon": "eventlog.ico", + "permissions": [ + "Administration" + ] + } + ] + }, + "defaultSize": { + "width": "1200px", + "height": "1200px" + } + }, + { + "section": "System", + "name": "Plugins", + "active": true, + "menu": { + "label": "Plugins", + "items": [ + { + "label": "Plugins", + "view": "plugindashboard", + "defaultSize": { + "width": "900px", + "height": "800px" + }, + "icon": "plugins.png", + "permissions": [ + "Administration" + ] + } + ] + }, + "defaultSize": { + "width": "900px", + "height": "800px" + } + }, + { + "section": "System", + "name": "Info", + "active": true, + "menu": { + "label": "Info", + "items": [ + { + "label": "Info", + "view": "serverinfo", + "defaultSize": { + "width": "900px", + "height": "500px" + }, + "icon": "serverinfo.png", + "permissions": [ + "Administration" + ] + } + ] + }, + "defaultSize": { + "width": "900px", + "height": "500px" + } + }, + { + "section": "Benutzer", + "name": "Einstellungen", + "active": true, + "menu": { + "label": "Einstellungen", + "items": [ + { + "label": "Einstellungen", + "view": "usersettings", + "defaultSize": { + "width": "460px", + "height": "515px" + }, + "icon": "app.png", + "permissions": [ + "*" + ] + } + ] + }, + "defaultSize": { + "width": "460px", + "height": "515px" + } + }, + { + "section": "Benutzer", + "name": "Hilfe", + "active": true, + "menu": { + "label": "Hilfe", + "items": [ + { + "label": "Hilfe", + "view": "help", + "icon": "help.png", + "permissions": [ + "*" + ] + } + ] + } + } +] \ No newline at end of file diff --git a/src/routes/adminRoutes.js b/src/routes/adminRoutes.js index a8b4888..ccf7852 100644 --- a/src/routes/adminRoutes.js +++ b/src/routes/adminRoutes.js @@ -122,6 +122,13 @@ module.exports = { } }); + app.post('/api/plugins/integrated', async (req, res) => { + try { + res.status(200).json(global.json.startMenuItems.live); + } catch (error) { + res.status(500).json({ error: error.message }); + } + }); app.post('/api/plugins/:name/update', async (req, res) => { const { name } = req.params; diff --git a/src/services/fileSystemManager.js b/src/services/fileSystemManager.js index 0d8197a..6c37b63 100644 --- a/src/services/fileSystemManager.js +++ b/src/services/fileSystemManager.js @@ -31,132 +31,131 @@ class FileSystemManager { } -/** - * Liest rekursiv Dateien und gibt nur die gewünschten Attribute zurück. - * - * @param {string} dirPath - Startverzeichnis - * @param {string[]} attributes - gewünschte Attribute: - * ['name','fullPath','size','lastModified','isDirectory','extension', ...] - * @param {string|null} sortBy - Attribut zum Sortieren (z.B. 'lastModified' oder 'name') - * @param {string} order - 'asc' oder 'desc' - */ - readFiles(dirPath, attributes = ['name', 'fullPath'], sortBy = null, order = 'asc') { - let results = []; + /** + * Liest rekursiv Dateien und gibt nur die gewünschten Attribute zurück. + * + * @param {string} dirPath - Startverzeichnis + * @param {string[]} attributes - gewünschte Attribute: + * ['name','fullPath','size','lastModified','isDirectory','extension', ...] + * @param {string|null} sortBy - Attribut zum Sortieren (z.B. 'lastModified' oder 'name') + * @param {string} order - 'asc' oder 'desc' + */ + readFiles(dirPath, attributes = ['name', 'fullPath'], sortBy = null, order = 'asc') { + let results = []; - const items = fs.readdirSync(dirPath); + const items = fs.readdirSync(dirPath); - for (const item of items) { - const fullPath = path.join(dirPath, item); - const stats = fs.statSync(fullPath); - const isDir = stats.isDirectory(); + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stats = fs.statSync(fullPath); + const isDir = stats.isDirectory(); - // Objekt mit ALLEN möglichen Infos - const allInfo = { - name: item, - nameWithoutExt: item.substring(0, item.indexOf('.')), - fullPath: fullPath, - size: stats.size, - lastModified: stats.mtime, - created: stats.birthtime, - isDirectory: isDir, - extension: isDir ? null : path.extname(item) - }; + // Objekt mit ALLEN möglichen Infos + const allInfo = { + name: item, + nameWithoutExt: item.substring(0, item.indexOf('.')), + fullPath: fullPath, + size: stats.size, + lastModified: stats.mtime, + created: stats.birthtime, + isDirectory: isDir, + extension: isDir ? null : path.extname(item) + }; - if (isDir) { - // rekursiv weitermachen - results = results.concat(this.readFiles(fullPath, attributes, null, order)); - } else { - // nur gewünschte Attribute ausgeben - const filtered = {}; - for (const attr of attributes) { - if (allInfo[attr] !== undefined) { - filtered[attr] = allInfo[attr]; + if (isDir) { + // rekursiv weitermachen + results = results.concat(this.readFiles(fullPath, attributes, null, order)); + } else { + // nur gewünschte Attribute ausgeben + const filtered = {}; + for (const attr of attributes) { + if (allInfo[attr] !== undefined) { + filtered[attr] = allInfo[attr]; + } } + results.push(filtered); } - results.push(filtered); } + + // Sortieren, falls gewünscht + if (sortBy) { + results.sort((a, b) => { + if (a[sortBy] < b[sortBy]) return order === 'asc' ? -1 : 1; + if (a[sortBy] > b[sortBy]) return order === 'asc' ? 1 : -1; + return 0; + }); + } + return results; } - // Sortieren, falls gewünscht - if (sortBy) { - results.sort((a, b) => { - if (a[sortBy] < b[sortBy]) return order === 'asc' ? -1 : 1; - if (a[sortBy] > b[sortBy]) return order === 'asc' ? 1 : -1; - return 0; - }); - } - return results; -} + /** + * Sammelt verschiedene Pattern-Ergebnisse aus Dateien mehrerer Ordner. + * + * @param {Object} options + * @param {string|string[]} options.folderPaths - Pfad oder Array von Pfaden zu Ordnern + * @param {string} [options.extension='.js'] - Dateiendung + * @param {Array<{ name: string, pattern: RegExp, mapFn?: Function }>} options.patterns - Liste von Pattern-Definitionen + * @param {boolean} [options.recursive=true] - Unterordner durchsuchen? + * @returns {Array} - kombinierte Ergebnisse aller Pattern + */ + collectFromFiles({ + folderPaths, + extension = '.js', + patterns, + recursive = true + }) { + const results = []; + // if only one path selected + const paths = Array.isArray(folderPaths) ? folderPaths : [folderPaths]; + function readDir(dir) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); -/** - * Sammelt verschiedene Pattern-Ergebnisse aus Dateien mehrerer Ordner. - * - * @param {Object} options - * @param {string|string[]} options.folderPaths - Pfad oder Array von Pfaden zu Ordnern - * @param {string} [options.extension='.js'] - Dateiendung - * @param {Array<{ name: string, pattern: RegExp, mapFn?: Function }>} options.patterns - Liste von Pattern-Definitionen - * @param {boolean} [options.recursive=true] - Unterordner durchsuchen? - * @returns {Array} - kombinierte Ergebnisse aller Pattern - */ -collectFromFiles({ - folderPaths, - extension = '.js', - patterns, - recursive = true -}) { - const results = []; + if (entry.isDirectory() && recursive) { + readDir(fullPath); + } else if (entry.isFile() && entry.name.endsWith(extension)) { + const content = fs.readFileSync(fullPath, 'utf8'); - // if only one path selected - const paths = Array.isArray(folderPaths) ? folderPaths : [folderPaths]; + // 👉 NEU: fallback wenn keine patterns + if (!patterns || patterns.length === 0) { + results.push({ + file: entry.name, + fullPath + }); + continue; + } - function readDir(dir) { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory() && recursive) { - readDir(fullPath); - } else if (entry.isFile() && entry.name.endsWith(extension)) { - const content = fs.readFileSync(fullPath, 'utf8'); - - // 👉 NEU: fallback wenn keine patterns - if (!patterns || patterns.length === 0) { - results.push({ - file: entry.name, - fullPath - }); - continue; - } - - for (const { name, pattern, mapFn } of patterns) { - let match; - while ((match = pattern.exec(content)) !== null) { - const mapped = mapFn - ? mapFn(match, entry.name, fullPath, name) - : { file: entry.name, type: name, match: match[0] }; - results.push(mapped); + for (const { name, pattern, mapFn } of patterns) { + let match; + while ((match = pattern.exec(content)) !== null) { + const mapped = mapFn + ? mapFn(match, entry.name, fullPath, name) + : { file: entry.name, type: name, match: match[0] }; + results.push(mapped); + } } } } } - } - // Run through multiple paths - for (const dir of paths) { - if (fs.existsSync(dir)) { - readDir(path.resolve(dir)); - } else { - console.warn(`Ordner nicht gefunden: ${dir}`); + // Run through multiple paths + for (const dir of paths) { + if (fs.existsSync(dir)) { + readDir(path.resolve(dir)); + } else { + console.warn(`Ordner nicht gefunden: ${dir}`); + } } + + return results; } - return results; -} + @@ -171,6 +170,16 @@ collectFromFiles({ } } + loadFile(path) { + try { + const rawData = fs.readFileSync(path, 'utf8'); + return rawData; + } catch (err) { + console.log(err) + return err + } + } + // Check file-/path exists(path) { diff --git a/src/sockets/mainSocket.js b/src/sockets/mainSocket.js index 5bd9423..bdbe2a3 100644 --- a/src/sockets/mainSocket.js +++ b/src/sockets/mainSocket.js @@ -1,6 +1,6 @@ const path = require('path'); const fs = require('fs'); -const startMenuItemContext = require('@models/integratedStartmenuItems.js') + module.exports = (app, socketManager, namespace, pluginManager, authenticationModel, fileSystemManager, eventManager, activeDirectory) => { const mainSocket = socketManager.namespaces.get(namespace); diff --git a/utils.js b/utils.js index d5fb9d9..f2efa34 100644 --- a/utils.js +++ b/utils.js @@ -4,7 +4,7 @@ const { permission } = require('process'); const { dirname } = require('path'); const { File: HotReload } = require(`@services/hotReload.js`); const { service } = require(`@root/server.js`); -let integratedStartmenuItems = require('@models/integratedStartmenuItems'); +// let integratedStartmenuItems = require('@models/integratedStartmenuItems'); global.path = { @@ -20,17 +20,17 @@ global.json = { configuration: new HotReload(path.join(global.path.source, 'models', 'configuration.json')), stylesheet: new HotReload(path.join(global.path.source, 'models', 'stylesheet.json')), indexRoutes: new HotReload(path.join(global.path.source, 'routes', 'indexRoutes.js'), { historyLimit: 50, fileType: 'js' }), - startMenuItems: new HotReload(path.join(global.path.source, 'models', 'integratedStartmenuItems.js'), { historyLimit: 50, fileType: 'js' }) + startMenuItems: new HotReload(path.join(global.path.source, 'models', 'integratedStartMenuItems.json')) } -module.exports = startMenuItems = async function(app, sAMAccountName) { -function safeClone(obj) { - return JSON.parse(JSON.stringify(obj)); -} - delete integratedStartmenuItems; +module.exports = startMenuItems = async function (app, sAMAccountName) { - integratedStartmenuItems = safeClone(json.startMenuItems.live); + function safeClone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + + const integratedStartmenuItems = safeClone(global.json.startMenuItems.live); const plugins = service .get('pluginManager') @@ -50,13 +50,18 @@ function safeClone(obj) { const authorized = item.label === 'hr' || item.permissions.includes('Administration') - ? global.json.configuration.live.administration.some(name => name.toLowerCase() === sAMAccountName.toLowerCase()) + ? global.json.configuration.live.administration.some( + name => name.toLowerCase() === sAMAccountName.toLowerCase() + ) : item.permissions.includes('*') || ( await Promise.all( item.permissions.map(async permission => (await service.get('activeDirectoryManager').getGroup(permission)) && - (await service.get('activeDirectoryManager').isUserMemberOfRecursive(sAMAccountName, permission)) + (await service.get('activeDirectoryManager').isUserMemberOfRecursive( + sAMAccountName, + permission + )) ) ) ).some(Boolean); @@ -81,6 +86,63 @@ function safeClone(obj) { return [...getAllPlugins]; }; +// module.exports = startMenuItems = async function(app, sAMAccountName) { +// function safeClone(obj) { +// return JSON.parse(JSON.stringify(obj)); +// } +// delete integratedStartmenuItems; + +// integratedStartmenuItems = safeClone(json.startMenuItems.live); + +// const plugins = service +// .get('pluginManager') +// .getStatus() +// .map(({ config, ...plugin }) => ({ +// ...safeClone(plugin), +// section: 'Plugin' +// })); + +// let getAllPlugins = [...plugins, ...integratedStartmenuItems]; + +// for (const plugin of getAllPlugins) { + +// plugin.menu.items = await Promise.all( +// (plugin.menu.items || []).map(async item => { + +// const authorized = +// item.label === 'hr' || +// item.permissions.includes('Administration') +// ? global.json.configuration.live.administration.some(name => name.toLowerCase() === sAMAccountName.toLowerCase()) +// : item.permissions.includes('*') || +// ( +// await Promise.all( +// item.permissions.map(async permission => +// (await service.get('activeDirectoryManager').getGroup(permission)) && +// (await service.get('activeDirectoryManager').isUserMemberOfRecursive(sAMAccountName, permission)) +// ) +// ) +// ).some(Boolean); + +// return { +// ...safeClone(item), +// authorized +// }; +// }) +// ); + +// plugin.onlyAdministration = +// plugin.menu.items.every(item => !item.authorized) && +// !global.json.configuration.live.administration.includes(sAMAccountName); +// } + +// getAllPlugins = getAllPlugins +// .filter(plugin => !plugin.onlyAdministration) +// .filter(plugin => plugin.active); + +// app.locals.startMenuItems = getAllPlugins; + +// return [...getAllPlugins]; +// }; /**