styles and startmenuItems

This commit is contained in:
2026-04-23 15:01:32 +02:00
parent 12d7de5065
commit 0876e754eb
11 changed files with 458 additions and 233 deletions

View File

@@ -2,14 +2,17 @@
createJsonTree({ createJsonTree({
container: domElement, container: domElement,
data: jsonData, data: jsonData,
expandInitially = true // optional: expand all nodes
onChange: (data) => { }, // optional: callback on change onChange: (data) => { }, // optional: callback on change
onSave: (data) => { } // optional: if set, a save button will be added onSave: (data) => { } // optional: if set, a save button will be added
}); });
*/function createJsonTree({ */
function createJsonTree({
container, container,
data, data,
onChange = () => {}, onChange = () => {},
onSave = null onSave = null,
expandInitially = true
}) { }) {
container.innerHTML = ""; container.innerHTML = "";
container.classList.add("json-tree-root"); container.classList.add("json-tree-root");
@@ -19,6 +22,8 @@ createJsonTree({
let lastSnapshot = clone(data); let lastSnapshot = clone(data);
const expandedPaths = new Set();
function clone(obj) { function clone(obj) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
@@ -104,9 +109,28 @@ createJsonTree({
render(); 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() { function render() {
container.innerHTML = ""; container.innerHTML = "";
if (onSave) {
const controls = document.createElement("div"); const controls = document.createElement("div");
controls.className = "json-controls"; controls.className = "json-controls";
@@ -120,18 +144,18 @@ createJsonTree({
redoBtn.textContent = "Redo"; redoBtn.textContent = "Redo";
redoBtn.onclick = redo; redoBtn.onclick = redo;
controls.appendChild(undoBtn);
controls.appendChild(redoBtn);
if (onSave) {
const saveBtn = document.createElement("button"); const saveBtn = document.createElement("button");
saveBtn.className = "monolyth"; saveBtn.className = "monolyth";
saveBtn.textContent = "Save"; saveBtn.textContent = "Save";
saveBtn.onclick = save; saveBtn.onclick = save;
controls.appendChild(undoBtn);
controls.appendChild(redoBtn);
controls.appendChild(saveBtn); controls.appendChild(saveBtn);
}
container.appendChild(controls); container.appendChild(controls);
}
container.appendChild(renderNode(data, null, null, "root", 0)); container.appendChild(renderNode(data, null, null, "root", 0));
} }
@@ -177,10 +201,24 @@ createJsonTree({
const children = document.createElement("div"); const children = document.createElement("div");
children.className = "json-children"; children.className = "json-children";
keyLabel.onclick = toggle.onclick = () => { const toggleState = () => {
children.classList.toggle("collapsed"); 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 = () => { addBtn.onclick = () => {
let newKey = ""; let newKey = "";
let newValue; let newValue;
@@ -210,7 +248,6 @@ createJsonTree({
if (!isArray) { if (!isArray) {
const input = document.querySelector('#newJsonKeyName'); const input = document.querySelector('#newJsonKeyName');
newKey = input?.value?.trim(); newKey = input?.value?.trim();
if (!newKey) return; if (!newKey) return;
} }
@@ -331,6 +368,7 @@ createJsonTree({
wrapper.appendChild(keySpan); wrapper.appendChild(keySpan);
wrapper.appendChild(span); wrapper.appendChild(span);
if (key !== null) wrapper.appendChild(removeBtn); if (key !== null) wrapper.appendChild(removeBtn);
return wrapper; return wrapper;

View File

@@ -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-content { display: flex; flex-direction: column; flex:1; padding:8px; overflow: auto; }
.window[class="max"] .window-resize-handle { display: none; } .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-n { top: -4px;cursor: var(--theme-cursor-resize-vertical); }
.window-resize-s { bottom: -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-e { right: -4px;cursor: var(--theme-cursor-resize-horizontal); }
.window-resize-w { left: -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-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-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-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-se { bottom: -6px;right: -6px;cursor: var(--theme-cursor-resize-270); }
.window-resize-sw { bottom: -6px;left: -6px;cursor: var(--theme-cursor-resize-45); } .window-resize-sw { bottom: -6px;left: -6px;cursor: var(--theme-cursor-resize-45); }

View File

@@ -17,7 +17,7 @@ table.border * { border:1px solid white; }
/* #region performance optimizing */ /* #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 */ /* #endregion */

View File

@@ -8,8 +8,8 @@
<span class="copy-icon" onclick="copyToClipboard(`${document.getElementById('pid').textContent}`)">⧉</span> <span class="copy-icon" onclick="copyToClipboard(`${document.getElementById('pid').textContent}`)">⧉</span>
</div> </div>
</div> </div>
<div class="card"> <div class="card" styles="max-height:350px;">
<div class="table-wrapper" style="max-height: 350px;"> <div class="table-wrapper">
<table id="releaseNotes"> <table id="releaseNotes">
<thead> <thead>
<tr> <tr>
@@ -24,7 +24,7 @@
</table> </table>
</div> </div>
</div> </div>
<div class="card"> <div class="card static" style="min-height:150px">
<div id="package" style="overflow:auto"></div> <div id="package" style="overflow:auto"></div>
</div> </div>
</div> </div>
@@ -88,14 +88,13 @@
}); });
vt.addData(json.releaseNotes) vt.addData(json.releaseNotes)
document.querySelector('#package').innerHTML = JSON.stringify(json.package).split(',').join('<br>'); //document.querySelector('#package').innerHTML = JSON.stringify(json.package).split(',').join('<br>');
/*
createJsonTree({ createJsonTree({
container: document.getElementById("package-json"), container: document.getElementById("package"),
data: json, data: json.package,
expandInitially: true, expandInitially: true
onSave: () => { }
}); });
*/
}); });
</script> </script>

View File

@@ -21,7 +21,7 @@
</div> </div>
</body> </body>
<script type="text/javascript"> <script defer type="text/javascript">
createNewPlugin.onclick = () => { createNewPlugin.onclick = () => {
feedbox({ feedbox({
@@ -62,16 +62,42 @@
"active":true "active":true
} }
}*/ }*/
fetch('/api/plugins/integrated', { method: 'POST' })
.then(res => res.json())
.then(data => {
createTab(pluginTabs, 'Integrierte Plugins', { onclick:
async (tabElement) => {
pluginsTabWrapper.innerHTML = '';
const objectsContainer = document.createElement('div');
objectsContainer.className = 'card';
createJsonTree({
container: objectsContainer,
data: data,
expandInitially: true,
onSave: () => {}
})
pluginsTabWrapper.appendChild(objectsContainer);
}
})
})
fetch('/api/plugins/getAll', { method: 'POST' }) fetch('/api/plugins/getAll', { method: 'POST' })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
data.forEach(async metaData => { data.forEach(async metaData => {
createTab(pluginTabs, metaData.name, async (tabElement) => { createTab(pluginTabs, metaData.name, { onclick:
async (tabElement) => {
pluginsTabWrapper.innerHTML = ''; pluginsTabWrapper.innerHTML = '';
const objectsContainer = document.createElement('div'); const objectsContainer = document.createElement('div');
objectsContainer.className = 'card grid'; objectsContainer.className = 'card static';
objectsContainer.style = ``; objectsContainer.style = ``;
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'card static row'; card.className = 'card static row';
@@ -140,8 +166,8 @@
if(typeof value === 'object') { if(typeof value === 'object') {
const objectCard = document.createElement('div'); const objectCard = document.createElement('div');
objectCard.className = 'card'; objectCard.className = 'card static';
objectCard.style = `height:100%`; objectCard.style = `min-height:400px;`;
objectCard.innerHTML = ` objectCard.innerHTML = `
<label style="font-weight:bold">${key}</label> <label style="font-weight:bold">${key}</label>
<div style="overflow:auto" id="${metaData.name}-${key}"></div> <div style="overflow:auto" id="${metaData.name}-${key}"></div>
@@ -151,9 +177,6 @@
const menu = createJsonTree({ const menu = createJsonTree({
container: document.getElementById(`${metaData.name}-${key}`), container: document.getElementById(`${metaData.name}-${key}`),
data: metaData[key], data: metaData[key],
schema: {
"active": { type: "boolean" }
},
onChange: (data) => { }, onChange: (data) => { },
onSave: async (data) => { onSave: async (data) => {
await pluginAPI.update(metaData.name, { [key]: data }); await pluginAPI.update(metaData.name, { [key]: data });
@@ -163,80 +186,10 @@
} }
} }
pluginsTabWrapper.appendChild(objectsContainer); pluginsTabWrapper.appendChild(objectsContainer);
/*pluginsTabWrapper.innerHTML = `
<div class="card static row" style="grid-column: span 3;height:50px;">
<label class="cb cb-modern">
<label for="plugin-${plugin.name}" style="font-weight:bold">Aktiv</label>
<input onchange="pluginAPI.activation('${plugin.name}', this.checked);" id="plugin-${plugin.name}" data-status="${plugin.name}" type="checkbox" ${plugin.active ? 'checked' : ''}>
<span class="cb-box" aria-hidden="true"></span>
</label>
</div>
<div class="card static row" style="gap:0 10px;min-height:fit-content;">
<div style="flex:0 0 auto">
<label style="font-weight:bold">Name</label>
<input type="text" data-name="${plugin.name}" data-field="name" value="${plugin.name}" />
</div>
<div style="flex:0 0 auto">
<label style="font-weight:bold">Version</label>
<input type="text" data-name="${plugin.name}" data-field="version" value="${plugin.version}" />
</div>
<div style="flex:1 1 auto">
<label style="font-weight:bold">Beschreibung</label>
<input type="text" data-name="${plugin.name}" data-field="description" style="width:100%" value="${plugin.description}" />
</div>
</div>
<div class="card static" style="grid-column: span 3;">
<label style="font-weight:bold">Startmenüeinträge</label>
<div style="overflow:auto" id="${plugin.name}-menu"></div>
</div>
`;
createJsonTree({
container: document.getElementById(`${plugin.name}-menu`),
data: plugin,
schema: {
"active": { type: "boolean" }
} }
}); });
*/
});
/*
{
"name": "TEST",
"description": "Remote Desktop Verbindungen über MSRA realisieren",
"version": "0.2025.11.07",
"config": {},
"menu": {
"label": "TEST",
"items": [
{
"label": "Remoteunterstützung",
"view": "index",
"icon": "remoteServer.png",
"permissions": [
"Administration"
]
}
]
},
"active": true
}
*/
/*
new AttachOnBlurChange(document.querySelector(`[data-name="${plugin.name}"][data-field="name"]`), async (newValue, oldValue) => { await pluginAPI.rename(`${plugin.name}`, newValue); });
new AttachOnBlurChange(document.querySelector(`[data-name="${plugin.name}"][data-field="version"]`), async (newValue, oldValue) => await pluginAPI.update(`${plugin.name}`, { version: newValue }))
new AttachOnBlurChange(document.querySelector(`[data-name="${plugin.name}"][data-field="description"]`), async (newValue, oldValue) => await pluginAPI.update(`${plugin.name}`, { description: newValue }))
*/
}); });
pluginTabs.querySelectorAll('.tab')[0]?.click();
}) })
</script> </script>
</html> </html>

View File

@@ -48,7 +48,7 @@ module.exports = ([
{ {
label: 'Plugins', label: 'Plugins',
view: 'plugindashboard', view: 'plugindashboard',
defaultSize: { width: "1000px", height: "400px" }, defaultSize: { width: "900px", height: "800px" },
icon: "plugins.png", icon: "plugins.png",
permissions: [ 'Administration' ] permissions: [ 'Administration' ]
} }

View File

@@ -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": [
"*"
]
}
]
}
}
]

View File

@@ -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) => { app.post('/api/plugins/:name/update', async (req, res) => {
const { name } = req.params; const { name } = req.params;

View File

@@ -31,7 +31,7 @@ class FileSystemManager {
} }
/** /**
* Liest rekursiv Dateien und gibt nur die gewünschten Attribute zurück. * Liest rekursiv Dateien und gibt nur die gewünschten Attribute zurück.
* *
* @param {string} dirPath - Startverzeichnis * @param {string} dirPath - Startverzeichnis
@@ -86,13 +86,10 @@ class FileSystemManager {
}); });
} }
return results; return results;
} }
/**
/**
* Sammelt verschiedene Pattern-Ergebnisse aus Dateien mehrerer Ordner. * Sammelt verschiedene Pattern-Ergebnisse aus Dateien mehrerer Ordner.
* *
* @param {Object} options * @param {Object} options
@@ -102,12 +99,12 @@ class FileSystemManager {
* @param {boolean} [options.recursive=true] - Unterordner durchsuchen? * @param {boolean} [options.recursive=true] - Unterordner durchsuchen?
* @returns {Array} - kombinierte Ergebnisse aller Pattern * @returns {Array} - kombinierte Ergebnisse aller Pattern
*/ */
collectFromFiles({ collectFromFiles({
folderPaths, folderPaths,
extension = '.js', extension = '.js',
patterns, patterns,
recursive = true recursive = true
}) { }) {
const results = []; const results = [];
// if only one path selected // if only one path selected
@@ -156,7 +153,9 @@ collectFromFiles({
} }
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 // Check file-/path
exists(path) { exists(path) {

View File

@@ -1,6 +1,6 @@
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const startMenuItemContext = require('@models/integratedStartmenuItems.js')
module.exports = (app, socketManager, namespace, pluginManager, authenticationModel, fileSystemManager, eventManager, activeDirectory) => { module.exports = (app, socketManager, namespace, pluginManager, authenticationModel, fileSystemManager, eventManager, activeDirectory) => {
const mainSocket = socketManager.namespaces.get(namespace); const mainSocket = socketManager.namespaces.get(namespace);

View File

@@ -4,7 +4,7 @@ const { permission } = require('process');
const { dirname } = require('path'); const { dirname } = require('path');
const { File: HotReload } = require(`@services/hotReload.js`); const { File: HotReload } = require(`@services/hotReload.js`);
const { service } = require(`@root/server.js`); const { service } = require(`@root/server.js`);
let integratedStartmenuItems = require('@models/integratedStartmenuItems'); // let integratedStartmenuItems = require('@models/integratedStartmenuItems');
global.path = { global.path = {
@@ -20,17 +20,17 @@ global.json = {
configuration: new HotReload(path.join(global.path.source, 'models', 'configuration.json')), configuration: new HotReload(path.join(global.path.source, 'models', 'configuration.json')),
stylesheet: new HotReload(path.join(global.path.source, 'models', 'stylesheet.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' }), 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) { module.exports = startMenuItems = async function (app, sAMAccountName) {
function safeClone(obj) {
function safeClone(obj) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
delete integratedStartmenuItems;
integratedStartmenuItems = safeClone(json.startMenuItems.live); const integratedStartmenuItems = safeClone(global.json.startMenuItems.live);
const plugins = service const plugins = service
.get('pluginManager') .get('pluginManager')
@@ -50,13 +50,18 @@ function safeClone(obj) {
const authorized = const authorized =
item.label === 'hr' || item.label === 'hr' ||
item.permissions.includes('Administration') 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('*') || : item.permissions.includes('*') ||
( (
await Promise.all( await Promise.all(
item.permissions.map(async permission => item.permissions.map(async permission =>
(await service.get('activeDirectoryManager').getGroup(permission)) && (await service.get('activeDirectoryManager').getGroup(permission)) &&
(await service.get('activeDirectoryManager').isUserMemberOfRecursive(sAMAccountName, permission)) (await service.get('activeDirectoryManager').isUserMemberOfRecursive(
sAMAccountName,
permission
))
) )
) )
).some(Boolean); ).some(Boolean);
@@ -81,6 +86,63 @@ function safeClone(obj) {
return [...getAllPlugins]; 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];
// };
/** /**