MeshDD-Bot/static/js/app.js
ppfeiffer f2c6ba8e62 feat: v0.6.7 - Dashboard-Upgrade: Tabler-Theme, Charts, Kanalfilter, Node-Suche
- 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>
2026-02-17 22:43:35 +01:00

105 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// MeshDD-Bot Shared page module
// Provides: initPage(), escapeHtml(), applyTheme()
// ── Sidebar definition ────────────────────────────────────────
const _SIDEBAR_LINKS = [
{ href: '/', icon: 'bi-speedometer2', label: 'Dashboard', admin: false },
{ href: '/scheduler', icon: 'bi-clock-history', label: 'Scheduler', admin: true },
{ href: '/map', icon: 'bi-map', label: 'Karte', admin: false },
{ href: '/settings', icon: 'bi-gear', label: 'Einstellungen',admin: true },
{ href: '/admin', icon: 'bi-people', label: 'Benutzer', admin: true },
];
function _injectSidebar() {
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
const currentPath = window.location.pathname;
sidebar.innerHTML = '<nav class="sidebar-nav">' +
_SIDEBAR_LINKS.map(link => {
const active = currentPath === link.href ? ' active' : '';
const adm = link.admin ? ' sidebar-admin' : '';
return `<a href="${link.href}" class="sidebar-link${active}${adm}">` +
`<i class="bi ${link.icon}"></i><span>${link.label}</span></a>`;
}).join('') +
'</nav>';
}
// ── Navbar ────────────────────────────────────────────────────
function _updateNavbar(user) {
const userMenu = document.getElementById('userMenu');
const loginBtn = document.getElementById('loginBtn');
const userName = document.getElementById('userName');
if (user) {
if (userName) userName.textContent = user.name;
if (userMenu) userMenu.classList.remove('d-none');
if (loginBtn) loginBtn.classList.add('d-none');
} else {
if (userMenu) userMenu.classList.add('d-none');
if (loginBtn) loginBtn.classList.remove('d-none');
}
}
function _updateSidebar(user) {
const isAdmin = user && user.role === 'admin';
document.querySelectorAll('.sidebar-admin').forEach(el => {
el.style.display = isAdmin ? '' : 'none';
});
}
// ── Theme ─────────────────────────────────────────────────────
function applyTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
const icon = document.getElementById('themeIcon');
if (icon) icon.className = theme === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-fill';
localStorage.setItem('theme', theme);
document.dispatchEvent(new CustomEvent('themechange', { detail: { theme } }));
}
function _setupTheme() {
applyTheme(localStorage.getItem('theme') || 'dark');
const btn = document.getElementById('themeToggle');
if (btn) {
btn.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-bs-theme');
applyTheme(current === 'dark' ? 'light' : 'dark');
});
}
}
// ── Sidebar toggle (mobile) ───────────────────────────────────
function _setupSidebarToggle() {
const toggle = document.getElementById('sidebarToggle');
const sidebar = document.getElementById('sidebar');
const backdrop = document.getElementById('sidebarBackdrop');
if (toggle && sidebar) toggle.addEventListener('click', () => sidebar.classList.toggle('open'));
if (backdrop && sidebar) backdrop.addEventListener('click', () => sidebar.classList.remove('open'));
}
// ── Utilities ─────────────────────────────────────────────────
function escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// ── Public init ───────────────────────────────────────────────
function initPage({ onAuth = null } = {}) {
_injectSidebar();
_setupTheme();
fetch('/api/auth/me')
.then(r => r.ok ? r.json() : null)
.then(user => {
_updateNavbar(user);
_updateSidebar(user);
_setupSidebarToggle();
if (onAuth) onAuth(user);
});
}