// Labels for display const deviceLabels = { long_name: "Name", short_name: "Kurzname", node_num: "Node-Nummer", hw_model: "Hardware", firmware_version: "Firmware", role: "Role", max_channels: "Max Channels", }; const loraLabels = { region: "Region", modem_preset: "Modem-Preset", hop_limit: "Hop-Limit", tx_power: "TX-Power", bandwidth: "Bandwidth", frequency_offset: "Freq-Offset", tx_enabled: "TX aktiviert", use_preset: "Preset nutzen", }; const positionLabels = { gps_mode: "GPS-Modus", position_broadcast_secs: "Broadcast-Interval (s)", fixed_position: "Feste Position", }; const powerLabels = { mesh_sds_timeout_secs: "Mesh-SDS-Timeout (s)", ls_secs: "LS-Secs", min_wake_secs: "Min-Wake-Secs", is_power_saving: "Energiesparmodus", }; function renderKeyValue(tableId, data, labels) { const tbody = document.getElementById(tableId); const rows = []; for (const [key, label] of Object.entries(labels)) { const val = data[key]; if (val === "" || val === undefined || val === null) continue; const display = typeof val === "boolean" ? (val ? "Ja" : "Nein") : String(val); rows.push(`${escapeHtml(label)}${escapeHtml(display)}`); } tbody.innerHTML = rows.length > 0 ? rows.join("") : 'Keine Daten'; } function renderChannels(channels) { const tbody = document.getElementById("channelsTable"); if (!channels || channels.length === 0) { tbody.innerHTML = 'Keine Channels'; return; } tbody.innerHTML = channels.map(ch => `${ch.index}${escapeHtml(ch.name)}${escapeHtml(ch.role)}${escapeHtml(ch.psk)}` ).join(""); } function renderBtNet(bluetooth, network) { const tbody = document.getElementById("btNetTable"); const rows = []; const btLabels = { enabled: "BT aktiviert", mode: "BT-Modus" }; const netLabels = { wifi_enabled: "WiFi aktiviert", ntp_server: "NTP-Server" }; for (const [key, label] of Object.entries(btLabels)) { const val = bluetooth[key]; if (val === "" || val === undefined || val === null) continue; const display = typeof val === "boolean" ? (val ? "Ja" : "Nein") : String(val); rows.push(`${escapeHtml(label)}${escapeHtml(display)}`); } for (const [key, label] of Object.entries(netLabels)) { const val = network[key]; if (val === "" || val === undefined || val === null) continue; const display = typeof val === "boolean" ? (val ? "Ja" : "Nein") : String(val); rows.push(`${escapeHtml(label)}${escapeHtml(display)}`); } tbody.innerHTML = rows.length > 0 ? rows.join("") : 'Keine Daten'; } async function loadConfig() { try { const resp = await fetch("/api/node/config"); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); renderKeyValue("deviceTable", data.device || {}, deviceLabels); renderKeyValue("loraTable", data.lora || {}, loraLabels); renderChannels(data.channels || []); renderKeyValue("positionTable", data.position || {}, positionLabels); renderKeyValue("powerTable", data.power || {}, powerLabels); renderBtNet(data.bluetooth || {}, data.network || {}); } catch (e) { console.error("Failed to load config:", e); document.getElementById("deviceTable").innerHTML = 'Fehler beim Laden'; } } function escapeHtml(str) { if (!str) return ""; const div = document.createElement("div"); div.textContent = str; return div.innerHTML; } // Theme toggle const themeToggle = document.getElementById("themeToggle"); const themeIcon = document.getElementById("themeIcon"); function applyTheme(theme) { document.documentElement.setAttribute("data-bs-theme", theme); themeIcon.className = theme === "dark" ? "bi bi-sun-fill" : "bi bi-moon-fill"; localStorage.setItem("theme", theme); } applyTheme(localStorage.getItem("theme") || "dark"); themeToggle.addEventListener("click", () => { const current = document.documentElement.getAttribute("data-bs-theme"); applyTheme(current === "dark" ? "light" : "dark"); }); // Sidebar toggle (mobile) const sidebarToggle = document.getElementById("sidebarToggle"); const sidebar = document.getElementById("sidebar"); const sidebarBackdrop = document.getElementById("sidebarBackdrop"); if (sidebarToggle) { sidebarToggle.addEventListener("click", () => sidebar.classList.toggle("open")); sidebarBackdrop.addEventListener("click", () => sidebar.classList.remove("open")); } loadConfig();