initial files
This commit is contained in:
153
public/views/desktop.hbs
Normal file
153
public/views/desktop.hbs
Normal file
@@ -0,0 +1,153 @@
|
||||
<head>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="javascript/main.js"></script>
|
||||
<script src="javascript/customModal.js"></script>
|
||||
<script src="javascript/pluginAPI.js"></script>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles/default.css">
|
||||
<link rel="stylesheet" href="styles/contextMenu.css">
|
||||
<link rel="stylesheet" href="styles/userNotification.css">
|
||||
<link rel="stylesheet" href="styles/table.css">
|
||||
<link rel="stylesheet" href="styles/jsonTree.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="styles/colors.css" />
|
||||
<link rel="stylesheet" href="styles/os.css" />
|
||||
|
||||
<div id="message-container"></div>
|
||||
<div id="copy-toast"></div>
|
||||
<title>Radix OS</title>
|
||||
</head>
|
||||
|
||||
<div id="desktop">
|
||||
<div id="windows"></div>
|
||||
|
||||
<!-- Startmenu -->
|
||||
<div id="start-menu" class="hidden">
|
||||
<div class="start-header"><img id="start-menu-icon"><span>Radix OS</span></div>
|
||||
<ul class="start-list">
|
||||
{{#groupBy startMenuItems "section"}}
|
||||
|
||||
<li class="start-submenu-head">
|
||||
<span><i>{{this.key}}</i></span>
|
||||
</li>
|
||||
{{#each items}}
|
||||
{{#ifSingle this.menu.items}}
|
||||
{{#if this.authorized}}
|
||||
<li class="start-item {{#unless ../this.active}}unload{{/unless}}" data-active="{{#equaler ../this.active "&&" this.authorized}}true{{else}}false{{/equaler}}" data-appname="{{../this.name}}" data-appview="{{this.view}}" data-viewlabel="{{this.label}}">
|
||||
{{#if this.icon}}
|
||||
<img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/{{this.icon}}" class="start-icon" />
|
||||
{{else}}
|
||||
{{/if}}
|
||||
<span>{{this.label}}</span>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<li class="start-item has-submenu">
|
||||
<img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/folder.png" class="start-icon" style="position:absolute;left:12px;"/>
|
||||
<span class="menu-label">{{this.menu.label}}</span>
|
||||
{{!-- {{#if this.version}}<small>v{{this.version}}</small>{{/if}} --}}
|
||||
|
||||
<ul class="submenu">
|
||||
{{#each this.menu.items}}
|
||||
{{#equaler this.label "==" "hr"}}
|
||||
<li><hr></li>
|
||||
{{else}}
|
||||
{{#if this.authorized}}
|
||||
<li class="start-item {{#unless ../this.active}}unload{{/unless}}" data-active="{{#equaler ../this.active "&&" this.authorized}}true{{else}}false{{/equaler}}" data-appname="{{../this.name}}" data-appview="{{this.view}}" data-viewlabel="{{this.label}}">
|
||||
{{#if this.icon}}
|
||||
<img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/{{this.icon}}" class="start-icon" />
|
||||
{{else}}
|
||||
{{/if}}
|
||||
<span>{{this.label}}</span>
|
||||
{{#if this.version}}<small>v{{this.version}}</small>{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/equaler}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</li>
|
||||
{{/ifSingle}}
|
||||
{{/each}}
|
||||
{{/groupBy}}
|
||||
{{!-- [Function restart] in javascript/main.js --}}
|
||||
</ul>
|
||||
|
||||
<div class="start-item-sys-container">
|
||||
<button class="monolyth start-sys-item" data-tooltip="Neustart" onclick="restart();" data-tooltip-mode="always">⟳</button>
|
||||
<button class="monolyth start-sys-item" data-tooltip="Abmelden" onclick="logout()" data-tooltip-mode="always">🔓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Taskbar -->
|
||||
<div id="taskbar">
|
||||
<button id="start-btn">☰</button>
|
||||
<div id="taskbar-windows"></div>
|
||||
<button style="margin-right:0;" class="monolyth notify-button pulse">
|
||||
<img class="icon" src="/images/notifybubble.png">
|
||||
<div id="notify-bubble"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="javascript/tutorial.js"></script>
|
||||
<script src="javascript/notifyBubble.js"></script>
|
||||
<script src="javascript/contextMenu.js"></script>
|
||||
<script src="javascript/tableFilter.js"></script>
|
||||
<script src="javascript/requiredFields.js"></script>
|
||||
<script src="javascript/loadOnce.js"></script>
|
||||
<script src="javascript/JSON.js"></script>
|
||||
<script src="javascript/os.js"></script>
|
||||
<script>
|
||||
addRestartHook(() => localStorage.setItem('openWindows', JSON.stringify({ })));
|
||||
|
||||
fetch('/api/Plugins/loadScripts', { method: 'POST', headers: {'Content-Type': 'application/json'} })
|
||||
.then(res => res.json())
|
||||
.then(scripts => {
|
||||
(scripts).forEach(script => {
|
||||
reloadPluginScript(script);
|
||||
});
|
||||
});
|
||||
|
||||
loadServerStyles(getCookie('theme'));
|
||||
|
||||
const notifyButtons = document.querySelectorAll('.notify-button');
|
||||
const trayNotifyButton = document.querySelector('#taskbar > .notify-button')
|
||||
const notify = new NotifyBubble(trayNotifyButton, "#notify-bubble");
|
||||
|
||||
document.addEventListener("contextmenu", evt => evt.preventDefault());
|
||||
|
||||
document.querySelectorAll('#start-menu .start-item.has-submenu').forEach(item => {
|
||||
item.addEventListener('click', evt => {
|
||||
// Toggle nur dieses Submenu
|
||||
item.classList.toggle('open');
|
||||
});
|
||||
});
|
||||
|
||||
async function logout() {
|
||||
const response = await fetch('/logout', { method: 'POST', headers: {'Content-Type': 'application/json'} });
|
||||
if (response.ok) {
|
||||
window.history.pushState({ }, '', '/login');
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/api/NotifyTray/getTrays')
|
||||
.then(res => res.json())
|
||||
.then(trays => {
|
||||
notify.clear();
|
||||
if (trays.length > 0) {
|
||||
trays.forEach(tray => {
|
||||
notify.addItem(tray, () => {
|
||||
if(tray.JSON) {
|
||||
//execution of tray action, e.g. open window
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
127
public/views/eventlog.hbs
Normal file
127
public/views/eventlog.hbs
Normal file
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Event Log</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card static">
|
||||
<div class="table-wrapper">
|
||||
<table id="eventTable" >
|
||||
<thead class="no-wrap">
|
||||
<tr>
|
||||
<th class="text-align:left" onclick="">ID</th>
|
||||
<th class="text-align:left" onclick="">Datum</th>
|
||||
<th class="text-align:left" onclick="">Level</th>
|
||||
<th class="text-align:left" onclick="">Plugin</th>
|
||||
<th class="text-align:left" onclick="">Message</th>
|
||||
<th class="text-align:left" onclick="">Trace</th>
|
||||
<th class="text-align:left" onclick="">User</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each logs}}
|
||||
<tr>
|
||||
<td>{{this.ID}}</td>
|
||||
<td class="no-wrap">{{dateFormat this.Date "yyyy-mm-dd HH:MM:SS"}}</td>
|
||||
<td class="{{this.Level_ID}}">
|
||||
{{LevelDisplayName}}
|
||||
</td>
|
||||
<td>{{this.PluginName}}</td>
|
||||
<td><span>{{replaceAll this.Message "||" "<br>"}}</span> <span class="copy-icon" onclick="copyToClipboard(`{{replaceAll this.Message "||" "<br>"}}`)">⧉</span></td>
|
||||
<td>{{this.Trace}}</td>
|
||||
<td class="no-wrap">{{this.ClearTextUser}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="pointer-events:auto;position:absolute; bottom:18px;right:18px;grid-template-columns: 1fr;">
|
||||
<button class="redbutton" id="confirmClearLog" data-tooltip="Löscht alle Einträge aus der Datenbank">Log leeren</button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const eventTable = document.getElementById("eventTable");
|
||||
let mappedLogs = [];
|
||||
|
||||
|
||||
confirmClearLog.onclick = evt => {
|
||||
feedbox(
|
||||
{ title: `Hast Du Sie nicht mehr Alle?`,
|
||||
message: `<p>Damit werden alle Einträge aus dem EventLog entfernt!</p>`,
|
||||
buttons: {
|
||||
yes: {
|
||||
text: 'Ich weiß, <b>Geht auf meinen Nacken!</b>',
|
||||
onClick: async () => await clearLog()
|
||||
},
|
||||
no: {
|
||||
text: 'Dazu habe ich nicht die Eier'
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function clearLog() {
|
||||
const res = await fetch(`/api/eventlog/clearlog`, { method: 'POST' });
|
||||
const data = await res.json();
|
||||
vt.source([]);
|
||||
}
|
||||
|
||||
|
||||
const vt = virtualTable({
|
||||
tableEl: eventTable,
|
||||
rowHeight: 40,
|
||||
buffer: 5,
|
||||
groupKey: null, // optional zum Gruppieren
|
||||
filterConfig:{
|
||||
exceptedColumns: [''],
|
||||
columnModes:{
|
||||
Plugin: 'dropdown',
|
||||
Level: 'dropdown',
|
||||
User: 'dropdown'
|
||||
},
|
||||
checkboxFilter:{
|
||||
column:'Level_ID',
|
||||
rules:[
|
||||
{ label:'Erfolgreich', test:v => parseInt(v) === 0 },
|
||||
{ label:'Information', test:v => parseInt(v) === 1 },
|
||||
{ label:'Warnung', test:v => parseInt(v) === 2 },
|
||||
{ label:'Fehler', test:v => parseInt(v) === 4 },
|
||||
{ label:'Absturz', test:v => parseInt(v) === 8 },
|
||||
]
|
||||
}
|
||||
},
|
||||
customRender: (row, tr) => {
|
||||
tr.style.height = '40px';
|
||||
createTd(tr, row['ID'], { classes: [ 'text-align:left'] });
|
||||
createTd(tr, row['Datum']);
|
||||
createTd(tr, row['Level']);
|
||||
createTd(tr, row['Plugin']);
|
||||
createTd(tr, row['Message'], { classes: [ 'no-wrap' ], attributes: { 'data-tooltip': formatHtml(row['Message']) } });
|
||||
createTd(tr, row['Trace'], { attributes: { 'data-tooltip': formatHtml(row['Trace']) } });
|
||||
createTd(tr, row['User']);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
fetch('/api/eventlog/getLogs', { method: 'POST' })
|
||||
.then(logs => logs.json())
|
||||
.then(logs => {
|
||||
mappedLogs = logs.map(row => ({ ...row, Plugin: row['PluginName'], Level: row['LevelDisplayName'], Datum: dateFormat(row['Date'], 'yyyy-mm-dd HH:MM:SS'), User: row['ClearTextUser'] }))
|
||||
vt.addData(mappedLogs.length != 0 ? mappedLogs : {});
|
||||
});
|
||||
|
||||
|
||||
adminSocket.on('eventlog_table', (data) => {
|
||||
data = {...data, Plugin: data['PluginName'], Level: data['LevelDisplayName'], Datum: dateFormat(data['Date'], 'yyyy-mm-dd HH:MM:SS'), User: data['ClearTextUser'] };
|
||||
mappedLogs.unshift(data)
|
||||
vt.source(mappedLogs);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
public/views/help/Hilfe.html
Normal file
36
public/views/help/Hilfe.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Bilder, Symbole und Zeichnungen wurden durch KI generiert und teilweise aus bereits existierenden, lizenzierten Quellen im Auftrag der Stadt Frankfurt am Main verwendet.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Der Programmcode wurde ebenfalls durch KI unterstützend erstellt. Dies betrifft ausschließlich die Darstellungs- und Designlogik. Alle Verarbeitungen personenbezogener Daten liegen in der Obhut des nutzenden Betriebs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Alle Inhalte der in RadixOS enthaltenen Plugins dienen ausschließlich dem dienstlichen Betrieb sowie der Weiterentwicklung interner Machine-Learning-Prozesse
|
||||
<i>(automatisiertes Lernen computergestützter Verfahren)</i>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Datenbanken, Inhalte sowie personenbezogene Daten werden nicht an Dritte weitergegeben oder öffentlich zugänglich gemacht. RadixOS ist als geschlossenes System konzipiert.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b style="color:var(--theme-accent-default-backcolor)">
|
||||
Das Urheberrecht an – RADIX OS – liegt beim Entwickler
|
||||
</b>
|
||||
|
||||
</p>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
</html>
|
||||
11
public/views/integrated/development.hbs
Normal file
11
public/views/integrated/development.hbs
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
test
|
||||
</body>
|
||||
</html>
|
||||
106
public/views/integrated/help.hbs
Normal file
106
public/views/integrated/help.hbs
Normal file
@@ -0,0 +1,106 @@
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Hilfe</title>
|
||||
|
||||
<style>
|
||||
|
||||
ul#help li a,
|
||||
ul#help li a:visited {
|
||||
text-decoration: none;
|
||||
color: var(--theme-container-card-color);
|
||||
}
|
||||
|
||||
ul#help {
|
||||
display:flex;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
left: 0;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
flex-wrap: nowrap;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
ul#help li {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container static" style="height: 100vh;">
|
||||
|
||||
<div id="helpTabs" class="tabs" style="overflow-y: auto;scrollbar-width: thin; padding:0;flex: 0 0 auto;"></div>
|
||||
|
||||
|
||||
<div class="card static" style="overflow-y:auto;flex: 1 1 auto;" >
|
||||
<div id="tabWrapper" class="tab-contents" ></div>
|
||||
</div>
|
||||
<div id="helpFooter" class="card static" style="height:auto;bottom:0;text-align:left;flex:0 0 auto;flex-direction:row;justify-content:space-between;gap:4px">
|
||||
<div class="selectable">
|
||||
<div style="color:var(--theme-accent-default-backcolor);font-weight:bold;">
|
||||
<span>© Radix OS</span> <span id="year"></span>
|
||||
<span>Manuel Sowada</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
function showTabs() {
|
||||
fetch(`/api/help/getTabs`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(async data => {
|
||||
data.forEach(async t => {
|
||||
const tabElement = document.createElement('div');
|
||||
tabElement.className = 'tab';
|
||||
tabElement.dataset.tab = t.name;
|
||||
tabElement.textContent = t.name;
|
||||
tabElement.addEventListener('click', async () => {
|
||||
Array.from(document.querySelectorAll('.tab')).forEach(t => t.classList.remove('active'));
|
||||
tabElement.classList.add('active');
|
||||
tabWrapper.innerHTML = '';
|
||||
|
||||
const response = await fetch('/api/help/getHelp', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ name: t.name })
|
||||
});
|
||||
const item = await response.json();
|
||||
|
||||
const html = `
|
||||
<div>
|
||||
<p>${response.status === 500 ? 'Hilfe nicht verfügbar' : item.html}</p>
|
||||
${item.description ? `<hr /><p>${item.description}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const container = document.querySelector('#tabWrapper');
|
||||
container.innerHTML = html;
|
||||
});
|
||||
document.getElementById('helpTabs').appendChild(tabElement);
|
||||
})
|
||||
})
|
||||
}
|
||||
showTabs();
|
||||
|
||||
year.textContent = new Date().getFullYear();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
35
public/views/integrated/serverconfig.hbs
Normal file
35
public/views/integrated/serverconfig.hbs
Normal file
@@ -0,0 +1,35 @@
|
||||
<header>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</header>
|
||||
|
||||
<body>
|
||||
<div class="container static">
|
||||
<div class="card" style="overflow: auto;height:100vh">
|
||||
<div id="jsonConfigTree" class="json-tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
<script>
|
||||
fetch('/api/getConfig', { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const tree = createJsonTree({
|
||||
container: document.getElementById("jsonConfigTree"),
|
||||
data: json,
|
||||
expandInitially: true,
|
||||
onSave: json => {
|
||||
fetch('/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(json, null, 2)
|
||||
}).then(() => writeEventLog(0, 'Serverconfig', tree.getChanges()) );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
101
public/views/integrated/serverinfo.hbs
Normal file
101
public/views/integrated/serverinfo.hbs
Normal file
@@ -0,0 +1,101 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<span style="font-weight: bold;">Dienst</span>
|
||||
<button class="redbutton" id="shutdownButton">Abschalten</button>
|
||||
<button class="yellowbutton" id="restartButton">Neustart</button>
|
||||
<div>
|
||||
<span>PID:</span> <span class="selectable" id="pid"></span>
|
||||
<span class="copy-icon" onclick="copyToClipboard(`${document.getElementById('pid').textContent}`)">⧉</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="table-wrapper" style="max-height: 350px;">
|
||||
<table id="releaseNotes">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-align:left">Erledigt</th>
|
||||
<th class="text-align:left">Timestamp</th>
|
||||
<th class="text-align:left">User</th>
|
||||
<th class="text-align:left">Note</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div id="package" style="overflow:auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
shutdownButton.onclick = evt => {
|
||||
fetch('/api/shutdown', { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
console.log(json);
|
||||
});
|
||||
};
|
||||
|
||||
restartButton.onclick = evt => {
|
||||
fetch('/api/restart', { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
console.log(json);
|
||||
});
|
||||
};
|
||||
|
||||
fetch('/api/getServerInfo', { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
|
||||
document.querySelector('#pid').innerHTML = json.pid;
|
||||
|
||||
|
||||
const vt = virtualTable({
|
||||
tableEl: document.getElementById('releaseNotes'),
|
||||
data: [],
|
||||
buffer: 5,
|
||||
rowHeight: 30,
|
||||
filterConfig:{
|
||||
exceptedColumns: [ 'Erledigt' ],
|
||||
columnModes: {
|
||||
'datetime': 'text',
|
||||
'sAMAccountName': 'text',
|
||||
'Note': 'text'
|
||||
},
|
||||
checkboxFilter: {
|
||||
column: 'finish',
|
||||
rules: [
|
||||
{ label: 'Nur erledigte', test: v => v === true },
|
||||
{ label: 'Nur unerledigte', test: v => v === false },
|
||||
]
|
||||
}
|
||||
},
|
||||
customRender: (row, tr) => {
|
||||
createTd(tr,
|
||||
`
|
||||
<label class="cb cb-modern">
|
||||
<input id="id1" data-status="{{name}}" type="checkbox" ${row.finish ? 'checked' : ''} >
|
||||
<span class="cb-box" aria-hidden="true"></span>
|
||||
</label>
|
||||
`, { });
|
||||
createTd(tr, row.datetime, { });
|
||||
createTd(tr, row.sAMAccountName, { });
|
||||
createTd(tr, row.value, { attributes: { "data-tooltip": row.value } });
|
||||
}
|
||||
});
|
||||
|
||||
vt.addData(json.releaseNotes)
|
||||
document.querySelector('#package').innerHTML = JSON.stringify(json.package).split(',').join('<br>');
|
||||
/*
|
||||
createJsonTree({
|
||||
container: document.getElementById("package-json"),
|
||||
data: json,
|
||||
expandInitially: true,
|
||||
onSave: () => { }
|
||||
});
|
||||
*/
|
||||
});
|
||||
</script>
|
||||
30
public/views/integrated/styleconfig.hbs
Normal file
30
public/views/integrated/styleconfig.hbs
Normal file
@@ -0,0 +1,30 @@
|
||||
<header>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</header>
|
||||
|
||||
|
||||
<div style="width:100%;height:calc(100% - 64px);">
|
||||
<div id="jsonStyleTree" class="json-tree"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
fetch('/api/getStyles', { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const tree = createJsonTree({
|
||||
container: document.getElementById("jsonStyleTree"),
|
||||
data: json,
|
||||
expandInitially: true,
|
||||
onSave: json => {
|
||||
fetch('/style', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(json, null, 2)
|
||||
}).then(() => writeEventLog(0, 'Stylesheet', JSON.stringify(tree.getChanges())) );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
113
public/views/integrated/usersettings.hbs
Normal file
113
public/views/integrated/usersettings.hbs
Normal file
@@ -0,0 +1,113 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<label><b>Farbgebung</b></label>
|
||||
<select id="themeSwitch" style="width: 100%;">
|
||||
<option value="dark">Dunkel</option>
|
||||
<option value="light">Hell</option>
|
||||
<option value="modern">Modern</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<label><b>Schrift</b></label>
|
||||
<select id="fontSelector" style="width: 100%;">
|
||||
<optgroup label="Sans-Serif (Windows Standard)">
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Calibri">Calibri</option>
|
||||
<option value="Candara">Candara</option>
|
||||
<option value="Segoe UI">Segoe UI</option>
|
||||
<option value="Tahoma">Tahoma</option>
|
||||
<option value="Trebuchet MS">Trebuchet MS</option>
|
||||
<option value="Verdana">Verdana</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Serif (Windows Standard)">
|
||||
<option value="Cambria">Cambria</option>
|
||||
<option value="Constantia">Constantia</option>
|
||||
<option value="Georgia">Georgia</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
<option value="Palatino Linotype">Palatino Linotype</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Monospace / Fixed-Width">
|
||||
<option value="Consolas">Consolas</option>
|
||||
<option value="Courier New">Courier New</option>
|
||||
<option value="Lucida Console">Lucida Console</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Fun / Decorative (Windows enthält einige)">
|
||||
<option value="Comic Sans MS">Comic Sans MS</option>
|
||||
<option value="Impact">Impact</option>
|
||||
<option value="Segoe Script">Segoe Script</option>
|
||||
<option value="Segoe Print">Segoe Print</option>
|
||||
</optgroup>
|
||||
|
||||
</select>
|
||||
<div style="display:flex;flex-direction:row;align-items:center;">
|
||||
<input type="range" id="sizeSlider" min="10" max="32" value="18" style="width:100%;">
|
||||
<span id="sizeValue">18px</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="display:flex;flex:1;flex-direction:column;">
|
||||
<label><b>Vollbildmodus</b></label>
|
||||
<button class="bluebutton" id="fullscreenBtn">Einschalten</button>
|
||||
<label>
|
||||
Der Vollbildmodus hat den Vorteil, dass der komplette Bildschirm mit dieser Webseite ausgefüllt wird.<br><i style="color:var(--theme-accent-default-backcolor)">Sehr nützlich für mehr als einen Bildschirme</i>
|
||||
</label>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
const themeSwitch = document.getElementById("themeSwitch");
|
||||
const selector = document.getElementById("fontSelector");
|
||||
const slider = document.getElementById("sizeSlider");
|
||||
const label = document.getElementById("sizeValue");
|
||||
const fullScreenBtn = document.getElementById("fullscreenBtn");
|
||||
|
||||
fullScreenBtn.addEventListener("click", goFullscreen);
|
||||
function goFullscreen() {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
document.documentElement.requestFullscreen();
|
||||
}
|
||||
fullScreenBtn.textContent = !document.fullscreenElement ? "Ausschalten" : "Einschalten";
|
||||
}
|
||||
|
||||
if(savedTheme) { themeSwitch.value = savedTheme; }
|
||||
if(savedFontFamily) { selector.value = savedFontFamily; }
|
||||
if(savedFontSize) { slider.value = savedFontSize; label.textContent = savedFontSize + 'px'; }
|
||||
|
||||
|
||||
themeSwitch.addEventListener('change', (evt) => {
|
||||
const theme = evt.target.value;
|
||||
switchTheme(theme);
|
||||
})
|
||||
|
||||
|
||||
function applySettings() {
|
||||
switchTheme(themeSwitch.value);
|
||||
setFontFamily(selector.value);
|
||||
setFontSize(slider.value)
|
||||
}
|
||||
applySettings();
|
||||
|
||||
selector.addEventListener("change", () => {
|
||||
setFontFamily(selector.value);
|
||||
});
|
||||
|
||||
|
||||
slider.addEventListener("input", () => {
|
||||
setFontSize(parseInt(slider.value))
|
||||
label.textContent = slider.value + 'px';
|
||||
});
|
||||
|
||||
|
||||
|
||||
// jede option bekommt ihre eigene Schrift
|
||||
[...selector.options].forEach(option => {
|
||||
option.style.fontFamily = option.value;
|
||||
});
|
||||
</script>
|
||||
9
public/views/layouts/default.hbs
Normal file
9
public/views/layouts/default.hbs
Normal file
@@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
{{{body}}}
|
||||
</body>
|
||||
</html>
|
||||
187
public/views/login.hbs
Normal file
187
public/views/login.hbs
Normal file
@@ -0,0 +1,187 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="javascript/main.js"></script>
|
||||
<script src="javascript/customModal.js"></script>
|
||||
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles/default.css">
|
||||
<link rel="stylesheet" href="styles/contextMenu.css">
|
||||
<link rel="stylesheet" href="styles/userNotification.css">
|
||||
<link rel="stylesheet" href="styles/table.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="styles/colors.css" />
|
||||
<link rel="stylesheet" href="styles/os.css" />
|
||||
|
||||
<div id="message-container"></div>
|
||||
<div id="copy-toast"></div>
|
||||
<title>Radix OS</title>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="desktop">
|
||||
{{!-- <form id="login-form" action="/login" method="POST"> --}}
|
||||
<form id="login-form" class="container" style="width:50vw;height:100vh;align-content:center;">
|
||||
<div class="static card" style="flex-direction:column">
|
||||
<label for="sAMAccountName">Benutzername:</label>
|
||||
<input type="text" {{!-- autocomplete="username" --}} id="sAMAccountName" name="sAMAccountName" placeholder="< Vorname.Nachname >" data-tooltip="Melde dich mit deinem Windows-Benutzernamen an.<br>Ist dies deine erste Anmeldung, dann vergib ein neues Kennwort!<br><i style='color:green;'>Hat keine Auswirkungen auf deine Windows-Anmeldung</i>" required>
|
||||
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" autocomplete="current-password" id="password" name="password" required>
|
||||
<br>
|
||||
<button class="bluebutton" type="submit">Einloggen</button>
|
||||
</div>
|
||||
</form>
|
||||
<button class="yellowbutton" id="openSettings" type="button" style="position:absolute;top:10px;right:10px;">Einstellungen</button>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
<script src="javascript/notifyBubble.js"></script>
|
||||
<script src="javascript/tableFilter.js"></script>
|
||||
<script src="javascript/requiredFields.js"></script>
|
||||
<script src="javascript/loadOnce.js"></script>
|
||||
<script src="javascript/JSON.js"></script>
|
||||
<script type="text/javascript">
|
||||
const sAMAccountName = document.querySelector('#sAMAccountName');
|
||||
const form = document.getElementById('login-form');
|
||||
|
||||
|
||||
loadServerStyles(getCookie('theme'));
|
||||
setCSSVariable('fontFamily', getCookie('fontFamily') ?? 'Verdana');
|
||||
setCSSVariable('fontSize', getCookie('fontSize') ?? '14');
|
||||
setCSSVariable('theme', getCookie('theme') ?? 'light');
|
||||
|
||||
|
||||
form.addEventListener('submit', async e => {
|
||||
try {
|
||||
e.preventDefault(); // verhindert Reload
|
||||
let error = null;
|
||||
const data = new FormData(form);
|
||||
const obj = Object.fromEntries(data.entries());
|
||||
|
||||
// Sende Daten via Fetch an Backend
|
||||
const resSendLoginData = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(obj)
|
||||
});
|
||||
|
||||
if(resSendLoginData.ok) {
|
||||
const loginData = await resSendLoginData.json();
|
||||
|
||||
if(resSendLoginData.status === 200) {
|
||||
setTimeout(() => {
|
||||
window.history.pushState({ }, '', '/');
|
||||
location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
if(resSendLoginData.status === 401) {
|
||||
|
||||
}
|
||||
showMessage('Login', `${dateFormat(new Date(), 'yyyy-mm-dd HH:MM:SS')}<br><br>${loginData.message}`, data.levelId, () => { window.history.pushState({ }, '', '/'); location.reload(); } , 5000);
|
||||
} else {
|
||||
const errorData = await resSendLoginData.json();
|
||||
showMessage('Login', `${dateFormat(new Date(), 'yyyy-mm-dd HH:MM:SS')}<br><br>${errorData.message}`, errorData.levelId, () => { window.history.pushState({ }, '', '/'); location.reload(); } , 5000);
|
||||
}
|
||||
} catch(err) {
|
||||
alert(err)
|
||||
}
|
||||
});
|
||||
|
||||
sAMAccountName.addEventListener('blur', async () => {
|
||||
if(sAMAccountName.value.trim() === '') {
|
||||
sAMAccountName.classList.remove('valid', 'invalid');
|
||||
sAMAccountName.removeAttribute('data-tooltip');
|
||||
sAMAccountName.setAttribute('data-tooltip', "Melde dich mit deinem Windows-Benutzernamen an.<br>Ist dies deine erste Anmeldung, dann vergib ein neues Kennwort!<br><i style='color:green;'>Hat keine Auswirkungen auf deine Windows-Anmeldung</i>")
|
||||
return;
|
||||
}
|
||||
const responseVerifyAccount = await fetch('/checkLoginName', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sAMAccountName: sAMAccountName.value })
|
||||
});
|
||||
if(responseVerifyAccount.ok || responseVerifyAccount.status === 200) {
|
||||
sAMAccountName.classList.add('valid');
|
||||
sAMAccountName.setAttribute('data-tooltip', `Du kannst dich mit dem Benutzername <b style="color:green;">${sAMAccountName.value}</b> anmelden`)
|
||||
} else {
|
||||
sAMAccountName.classList.add('invalid');
|
||||
sAMAccountName.setAttribute('data-tooltip', `Der Benutzername <b style="color:red;">${sAMAccountName.value}</b> wurde nicht gefunden`)
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#openSettings').onclick = async evt => {
|
||||
feedbox(
|
||||
{ title: `Einstellungen`,
|
||||
message: /*html*/`
|
||||
<div class="container">
|
||||
<div class="card static">
|
||||
<label>Farbgebung</label>
|
||||
<select id="themeSwitch" onchange="switchTheme(this.value)">
|
||||
<option value="dark" ${getCookie('theme') == 'dark' ? 'selected' : ''}>Dunkel</option>
|
||||
<option value="light" ${getCookie('theme') == 'light' ? 'selected' : ''}>Hell</option>
|
||||
<option value="modern" ${getCookie('theme') == 'modern' ? 'selected' : ''}>Modern</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card static">
|
||||
<label>Schrift</label>
|
||||
<select id="fontSelector" size="8" onchange="setFontFamily(this.value)">
|
||||
<optgroup label="Sans-Serif (Windows Standard)">
|
||||
<option style="font-family:Arial" value="Arial">Arial</option>
|
||||
<option style="font-family:Calibri" value="Calibri">Calibri</option>
|
||||
<option style="font-family:Candara" value="Candara">Candara</option>
|
||||
<option style="font-family:Segoe UI" value="Segoe UI">Segoe UI</option>
|
||||
<option style="font-family:Tahoma" value="Tahoma">Tahoma</option>
|
||||
<option style="font-family:Trebuchet MS" value="Trebuchet MS">Trebuchet MS</option>
|
||||
<option style="font-family:Verdana" value="Verdana">Verdana</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Serif (Windows Standard)">
|
||||
<option style="font-family:Cambria" value="Cambria">Cambria</option>
|
||||
<option style="font-family:Constantia" value="Constantia">Constantia</option>
|
||||
<option style="font-family:Georgia" value="Georgia">Georgia</option>
|
||||
<option style="font-family:Times New Roman" value="Times New Roman">Times New Roman</option>
|
||||
<option style="font-family:Palatino Linotype" value="Palatino Linotype">Palatino Linotype</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Monospace / Fixed-Width">
|
||||
<option style="font-family:Consolas" value="Consolas">Consolas</option>
|
||||
<option style="font-family:Courier New" value="Courier New">Courier New</option>
|
||||
<option style="font-family:Lucida Console" value="Lucida Console">Lucida Console</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Fun / Decorative (Windows enthält einige)">
|
||||
<option style="font-family:Comic Sans MS" value="Comic Sans MS">Comic Sans MS</option>
|
||||
<option style="font-family:Impact" value="Impact">Impact</option>
|
||||
<option style="font-family:Segoe Script" value="Segoe Script">Segoe Script</option>
|
||||
<option style="font-family:Segoe Print" value="Segoe Print">Segoe Print</option>
|
||||
</optgroup>
|
||||
|
||||
</select>
|
||||
<div style="display:flex;flex-direction:row;align-items:center">
|
||||
<input type="range" id="sizeSlider" min="10" max="32" value="${savedFontSize ?? '14'}" onchange="setFontSize(parseInt(this.value)); sizeValue.textContent = this.value + 'px';">
|
||||
<span id="sizeValue">${savedFontSize ?? '14'}px</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
buttons: {
|
||||
yes: {
|
||||
text: '<b>Fertig</b>',
|
||||
onClick: async () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
||||
|
||||
16
public/views/partials/child.hbs
Normal file
16
public/views/partials/child.hbs
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="window" data-winid="{{name}}-{{@index}}" data-plugin="{{name}}">
|
||||
<div class="window-titlebar">
|
||||
<div class="title">{{{viewLabel}}}</div>
|
||||
<div class="controls">
|
||||
{{#if this.printable}}
|
||||
<button data-tooltip="Drucken" id="printbutton">🖨</button>
|
||||
{{/if}}
|
||||
<button class="close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window-content">
|
||||
{{{contentHtml}}}
|
||||
</div>
|
||||
<div class="window-resize-handle"></div>
|
||||
</div>
|
||||
|
||||
19
public/views/partials/window.hbs
Normal file
19
public/views/partials/window.hbs
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="window" data-winid="{{appname}}-{{@index}}" data-plugin="{{appname}}">
|
||||
<div class="window-titlebar">
|
||||
<img src="{{#equaler section "==" "Plugin"}}{{appname}}{{/equaler}}/images/{{icon}}" class="window-icon" />
|
||||
<div class="title">{{appname}} [{{label}}]</div>
|
||||
<div class="controls">
|
||||
{{#if tutorial}}
|
||||
<button class="tutorial" id="tutorial-{{appname}}-{{label}}" onclick="tutorial.start()" data-tooltip="Startet eine interaktive Einführung">💡</button>
|
||||
{{/if}}
|
||||
<button class="minimize" style="transform:translateY(0px)">🗕</button>
|
||||
<button class="maximize">▢</button>
|
||||
<button class="close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window-content">
|
||||
{{{contentHtml}}}
|
||||
</div>
|
||||
<div class="window-resize-handle"></div>
|
||||
</div>
|
||||
|
||||
242
public/views/plugindashboard.hbs
Normal file
242
public/views/plugindashboard.hbs
Normal file
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Plugin Dashboard</title>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="pluginTabs" class="tabs"></div>
|
||||
<div class="card static">
|
||||
<div class="tab-contents container static" id="pluginsTabWrapper" style="height:100vh;grid-template-columns: repeat(3, 1fr);"></div>
|
||||
</div>
|
||||
<div class="grid" style="pointer-events:auto;position:absolute; bottom:18px;right:18px;grid-template-columns: 1fr;">
|
||||
<button class="bluebutton" id="createNewPlugin">Neues Plugin</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
createNewPlugin.onclick = () => {
|
||||
feedbox({
|
||||
title: 'Neues Plugin erstellen',
|
||||
message: `
|
||||
<input id="newPluginName" placeholder="Name des Plugins" />
|
||||
`,
|
||||
buttons: {
|
||||
yes: {
|
||||
text: '<b>Erstellen</b>',
|
||||
onClick: () => {
|
||||
const name = document.getElementById('newPluginName').value;
|
||||
pluginAPI.create(name);
|
||||
}
|
||||
},
|
||||
no: {
|
||||
text: 'Abbrechen',
|
||||
onClick: () => { /* automatisch geschlossen */ }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
{"name":"Demo",
|
||||
"description":"Beschreibung hier einfügen",
|
||||
"version":"1.0.0.0",
|
||||
"menu":{
|
||||
"label":"Demo",
|
||||
"items":[
|
||||
{"label":"Demo",
|
||||
"view":"index",
|
||||
"defaultSize":{"width":"600px","height":"150px"},
|
||||
"icon":"app.png",
|
||||
"permissions":["*"]}
|
||||
]},
|
||||
"config":{},
|
||||
"active":true
|
||||
}
|
||||
}*/
|
||||
|
||||
fetch('/api/plugins/getAll', { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
data.forEach(async metaData => {
|
||||
createTab(pluginTabs, metaData.name, async (tabElement) => {
|
||||
pluginsTabWrapper.innerHTML = '';
|
||||
|
||||
const objectsContainer = document.createElement('div');
|
||||
objectsContainer.className = 'card grid';
|
||||
objectsContainer.style = ``;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card static row';
|
||||
card.style = `gap:0 10px;min-height:fit-content;justify-content: center;`;
|
||||
pluginsTabWrapper.appendChild(card);
|
||||
|
||||
const sortedKeys = Object.keys(metaData).sort((a, b) => {
|
||||
if (typeof metaData[a] === 'object' && typeof metaData[b] !== 'object') {
|
||||
return 1;
|
||||
} else if (typeof metaData[a] !== 'object' && typeof metaData[b] === 'object') {
|
||||
return -1;
|
||||
} else {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
});
|
||||
|
||||
for (const key of sortedKeys) { // each plugin-metaData
|
||||
if (metaData.hasOwnProperty(key) && metaData[key] !== null && !key.includes('Path')) { // only first level
|
||||
const value = metaData[key];
|
||||
const container = document.createElement('div');
|
||||
|
||||
if(typeof value === 'string') {
|
||||
container.style = `flex:${key === 'description' ? '1 1' : '0 0'} auto;`;
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.style = `font-weight:bold`;
|
||||
label.textContent = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
container.appendChild(label);
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.style="width:100%"
|
||||
input.value = value;
|
||||
input.setAttribute('data-name', metaData.name);
|
||||
input.setAttribute('data-field', key);
|
||||
|
||||
new AttachOnBlurChange(input, async (newValue, oldValue) => {
|
||||
if(key === 'name') {
|
||||
await pluginAPI.rename(oldValue, newValue);
|
||||
return;
|
||||
}
|
||||
await pluginAPI.update(`${metaData.name}`, { [key]: newValue});
|
||||
});
|
||||
|
||||
container.appendChild(input);
|
||||
|
||||
card.appendChild(container);
|
||||
}
|
||||
if(typeof value === 'boolean') {
|
||||
container.style = `flex:0;flex-direction:column; display:flex;align-items:center;`;
|
||||
const label = document.createElement('label');
|
||||
label.style = `font-weight:bold`;
|
||||
label.textContent = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
container.appendChild(label);
|
||||
|
||||
const checkbox = document.createElement('label');
|
||||
checkbox.className = 'cb cb-modern';
|
||||
checkbox.innerHTML = `
|
||||
<input onchange="pluginAPI.activation('${metaData.name}', this.checked);" id="plugin-${metaData.name}" data-status="${metaData.name}" type="checkbox" ${metaData.active ? 'checked' : ''}>
|
||||
<span class="cb-box" aria-hidden="true"></span>
|
||||
`;
|
||||
container.appendChild(checkbox);
|
||||
|
||||
card.appendChild(container);
|
||||
}
|
||||
|
||||
if(typeof value === 'object') {
|
||||
const objectCard = document.createElement('div');
|
||||
objectCard.className = 'card';
|
||||
objectCard.style = `height:100%`;
|
||||
objectCard.innerHTML = `
|
||||
<label style="font-weight:bold">${key}</label>
|
||||
<div style="overflow:auto" id="${metaData.name}-${key}"></div>
|
||||
`;
|
||||
objectsContainer.appendChild(objectCard);
|
||||
pluginsTabWrapper.appendChild(objectsContainer);
|
||||
const menu = createJsonTree({
|
||||
container: document.getElementById(`${metaData.name}-${key}`),
|
||||
data: metaData[key],
|
||||
schema: {
|
||||
"active": { type: "boolean" }
|
||||
},
|
||||
onChange: (data) => { },
|
||||
onSave: async (data) => {
|
||||
await pluginAPI.update(metaData.name, { [key]: data });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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 }))
|
||||
*/
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user