- Tabler 1.4.0 als Admin-Theme: Bootstrap CSS/JS in allen 6 HTML-Seiten ersetzt - style.css komplett ueberarbeitet: Inter-Font, Tabler CSS-Variablen, Schatten, verfeinerte Sidebar (Rounded Active-Links), Hover-Animation auf Info-Boxen, pulsierender Status-Dot - app.js als shared Modul: Duplikation in allen JS-Dateien eliminiert (initPage, applyTheme, escapeHtml, Sidebar-Injektion) - WebSocket Auth-Fix: Nachrichten nur noch an eingeloggte Clients (auth_clients) - Bot-Uptime + Meshtastic-Verbindungsstatus in Dashboard und Stats-API - Dark Mode Kartentiles: CartoDB Dark Matter fuer Karte + Node-Modal - 3 Charts: Kanal-Anfragen (Doughnut), Hop-Verteilung (Bar), Hardware Top 5 - Nodes-Tabelle: Suchfeld, Online-Filter, sortierbare Spalten - Nachrichten Kanalfilter: Filter-Buttons im Nachrichten-Card-Header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4 KiB
JavaScript
104 lines
4 KiB
JavaScript
let currentUser = null;
|
|
|
|
initPage({ onAuth: (user) => { currentUser = user; } });
|
|
|
|
// 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(`<tr><td class="text-body-secondary" style="width:45%">${escapeHtml(label)}</td><td>${escapeHtml(display)}</td></tr>`);
|
|
}
|
|
tbody.innerHTML = rows.length > 0 ? rows.join("") : '<tr><td colspan="2" class="text-center text-body-secondary py-2">Keine Daten</td></tr>';
|
|
}
|
|
|
|
function renderChannels(channels) {
|
|
const tbody = document.getElementById("channelsTable");
|
|
if (!channels || channels.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="4" class="text-center text-body-secondary py-2">Keine Channels</td></tr>';
|
|
return;
|
|
}
|
|
tbody.innerHTML = channels.map(ch =>
|
|
`<tr><td>${ch.index}</td><td>${escapeHtml(ch.name)}</td><td>${escapeHtml(ch.role)}</td><td>${escapeHtml(ch.psk)}</td></tr>`
|
|
).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(`<tr><td class="text-body-secondary" style="width:45%">${escapeHtml(label)}</td><td>${escapeHtml(display)}</td></tr>`);
|
|
}
|
|
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(`<tr><td class="text-body-secondary" style="width:45%">${escapeHtml(label)}</td><td>${escapeHtml(display)}</td></tr>`);
|
|
}
|
|
tbody.innerHTML = rows.length > 0 ? rows.join("") : '<tr><td colspan="2" class="text-center text-body-secondary py-2">Keine Daten</td></tr>';
|
|
}
|
|
|
|
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 =
|
|
'<tr><td colspan="2" class="text-center text-danger py-2">Fehler beim Laden</td></tr>';
|
|
}
|
|
}
|
|
|
|
loadConfig();
|