diff --git a/static/config.html b/static/config.html
new file mode 100644
index 0000000..ecf2e5b
--- /dev/null
+++ b/static/config.html
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/css/style.css b/static/css/style.css
index cd9573e..74fa40c 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -80,6 +80,22 @@
color: var(--bs-secondary-color);
}
+.sidebar-group-label {
+ display: block;
+ padding: .75rem .75rem .2rem;
+ font-size: .65rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: .08em;
+ color: var(--bs-secondary-color);
+ border-top: 1px solid var(--tblr-border-color, var(--bs-border-color));
+ margin-top: .25rem;
+}
+
+.sidebar-link-sub {
+ padding-left: 1.5rem;
+}
+
/* ── Mobile sidebar ──────────────────────────────────────────── */
.sidebar-backdrop {
diff --git a/static/index.html b/static/index.html
index becb37a..3dddfcb 100644
--- a/static/index.html
+++ b/static/index.html
@@ -3,7 +3,7 @@
diff --git a/static/js/app.js b/static/js/app.js
index 597a054..6ecbb1f 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -1,17 +1,18 @@
-// MeshDD-Bot – Shared page module
+// MeshDD-Dashboard – Shared page module
// Provides: initPage(), escapeHtml(), applyTheme()
// ── Sidebar definition ────────────────────────────────────────
const _SIDEBAR_LINKS = [
- { href: '/', icon: 'bi-speedometer2', label: 'Dashboard', admin: false, user: false },
- { href: '/scheduler', icon: 'bi-clock-history', label: 'Scheduler', admin: true, user: false },
- { href: '/nina', icon: 'bi-shield-exclamation', label: 'NINA', admin: true, user: false },
- { href: '/map', icon: 'bi-map', label: 'Karte', admin: false, user: false },
- { href: '/packets', icon: 'bi-reception-4', label: 'Pakete', admin: false, user: false },
- { href: '/messages', icon: 'bi-chat-dots', label: 'Nachrichten', admin: false, user: true },
- { href: '/settings', icon: 'bi-gear', label: 'Einstellungen',admin: true, user: false },
- { href: '/admin', icon: 'bi-people', label: 'Benutzer', admin: true, user: false },
+ { href: '/', icon: 'bi-speedometer2', label: 'Dashboard', admin: false, user: false },
+ { href: '/map', icon: 'bi-map', label: 'Karte', admin: false, user: false },
+ { href: '/packets', icon: 'bi-reception-4', label: 'Pakete', admin: false, user: false },
+ { href: '/messages', icon: 'bi-chat-dots', label: 'Nachrichten', admin: false, user: true },
+ { type: 'group', label: 'Konfigurationen',admin: true },
+ { href: '/scheduler', icon: 'bi-clock-history', label: 'Scheduler', admin: true, user: false, sub: true },
+ { href: '/nina', icon: 'bi-shield-exclamation', label: 'NINA', admin: true, user: false, sub: true },
+ { href: '/config', icon: 'bi-sliders', label: 'Einstellungen', admin: true, user: false, sub: true },
+ { href: '/admin', icon: 'bi-people', label: 'Benutzer', admin: true, user: false },
];
function _injectSidebar() {
@@ -20,10 +21,15 @@ function _injectSidebar() {
const currentPath = window.location.pathname;
sidebar.innerHTML = '';
diff --git a/static/js/config.js b/static/js/config.js
new file mode 100644
index 0000000..ef13d83
--- /dev/null
+++ b/static/js/config.js
@@ -0,0 +1,112 @@
+let links = [];
+
+initPage({ onAuth: (user) => {
+ if (!user || user.role !== 'admin') {
+ window.location.href = '/';
+ }
+}});
+
+// ── Links table ───────────────────────────────────────────────
+
+function renderLinks() {
+ const tbody = document.getElementById('linksList');
+ if (links.length === 0) {
+ tbody.innerHTML = '
| Keine Links. |
';
+ return;
+ }
+ tbody.innerHTML = links.map((l, i) =>
+ `
+ | ${escapeHtml(l.label)} |
+ ${escapeHtml(l.url)} |
+
+
+ |
+
`
+ ).join('');
+}
+
+function removeLink(idx) {
+ links.splice(idx, 1);
+ renderLinks();
+}
+
+document.getElementById('btnAddLink').addEventListener('click', () => {
+ const label = document.getElementById('newLinkLabel').value.trim();
+ const url = document.getElementById('newLinkUrl').value.trim();
+ if (!label || !url) return;
+ links.push({ label, url });
+ renderLinks();
+ document.getElementById('newLinkLabel').value = '';
+ document.getElementById('newLinkUrl').value = '';
+});
+
+// ── Load ─────────────────────────────────────────────────────
+
+async function loadConfig() {
+ try {
+ const resp = await fetch('/api/config');
+ if (!resp.ok) return;
+ const cfg = await resp.json();
+
+ document.getElementById('botName').value = cfg.bot?.name ?? '';
+ document.getElementById('botPrefix').value = cfg.bot?.command_prefix ?? '';
+ document.getElementById('meshHost').value = cfg.meshtastic?.host ?? '';
+ document.getElementById('meshPort').value = cfg.meshtastic?.port ?? '';
+ document.getElementById('webPort').value = cfg.web?.port ?? '';
+ document.getElementById('onlineThreshold').value = cfg.web?.online_threshold ?? '';
+
+ links = Array.isArray(cfg.links) ? [...cfg.links] : [];
+ renderLinks();
+ } catch (e) {
+ console.error('Config load failed:', e);
+ }
+}
+
+// ── Save ──────────────────────────────────────────────────────
+
+document.getElementById('btnSave').addEventListener('click', async () => {
+ const payload = {
+ bot: {
+ name: document.getElementById('botName').value.trim(),
+ command_prefix: document.getElementById('botPrefix').value.trim(),
+ },
+ meshtastic: {
+ host: document.getElementById('meshHost').value.trim(),
+ port: parseInt(document.getElementById('meshPort').value) || 4403,
+ },
+ web: {
+ port: parseInt(document.getElementById('webPort').value) || 8081,
+ online_threshold: parseInt(document.getElementById('onlineThreshold').value) || 900,
+ },
+ links: [...links],
+ };
+
+ const statusEl = document.getElementById('saveStatus');
+ try {
+ const resp = await fetch('/api/config', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload),
+ });
+ if (resp.ok) {
+ statusEl.textContent = 'Gespeichert ✓';
+ statusEl.className = 'small text-success';
+ statusEl.classList.remove('d-none');
+ setTimeout(() => statusEl.classList.add('d-none'), 3000);
+ } else {
+ statusEl.textContent = 'Fehler beim Speichern';
+ statusEl.className = 'small text-danger';
+ statusEl.classList.remove('d-none');
+ }
+ } catch (e) {
+ console.error('Save failed:', e);
+ statusEl.textContent = 'Netzwerkfehler';
+ statusEl.className = 'small text-danger';
+ statusEl.classList.remove('d-none');
+ }
+});
+
+loadConfig();
diff --git a/static/login.html b/static/login.html
index 1df0d50..ec5d3cc 100644
--- a/static/login.html
+++ b/static/login.html
@@ -3,7 +3,7 @@
-
MeshDD-Bot Login
+
MeshDD-Dashboard Login
@@ -12,7 +12,7 @@