- app.js: Sidebar um Gruppen-Support erweitert; Konfigurationen-Gruppe mit Scheduler, NINA, Einstellungen (/config) als Untereinträge - style.css: .sidebar-group-label + .sidebar-link-sub - config.py: save()-Funktion für persistentes Schreiben in config.yaml - webserver.py: GET/PUT /api/config + GET /config Route (Admin) - static/config.html + static/js/config.js: neue Konfigurationsseite (Bot, Meshtastic, Web, Links editierbar) - Alle HTML-Dateien: MeshDD-Bot → MeshDD-Dashboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
113 lines
4.3 KiB
JavaScript
113 lines
4.3 KiB
JavaScript
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 = '<tr><td colspan="3" class="text-center text-body-secondary py-2"><small>Keine Links.</small></td></tr>';
|
|
return;
|
|
}
|
|
tbody.innerHTML = links.map((l, i) =>
|
|
`<tr>
|
|
<td><small>${escapeHtml(l.label)}</small></td>
|
|
<td><small class="text-body-secondary">${escapeHtml(l.url)}</small></td>
|
|
<td class="text-end">
|
|
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-1"
|
|
onclick="removeLink(${i})" title="Entfernen">
|
|
<i class="bi bi-trash" style="font-size:.75rem"></i>
|
|
</button>
|
|
</td>
|
|
</tr>`
|
|
).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();
|