let currentUser = null;
// Auth check
fetch('/api/auth/me').then(r => r.ok ? r.json() : null).then(u => {
currentUser = u;
updateNavbar();
updateSidebar();
});
function updateNavbar() {
if (currentUser) {
document.getElementById('userName').textContent = currentUser.name;
document.getElementById('userMenu').classList.remove('d-none');
document.getElementById('loginBtn').classList.add('d-none');
} else {
document.getElementById('userMenu').classList.add('d-none');
document.getElementById('loginBtn').classList.remove('d-none');
}
}
function updateSidebar() {
const isAdmin = currentUser && currentUser.role === 'admin';
document.querySelectorAll('.sidebar-admin').forEach(el => {
el.style.display = isAdmin ? '' : 'none';
});
}
// 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();