diff --git a/static/css/style.css b/static/css/style.css index 1f92510..df64761 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,211 +1,41 @@ -:root { - --bg-primary: #1a1a2e; - --bg-secondary: #16213e; - --bg-card: #0f3460; - --text-primary: #e0e0e0; - --text-secondary: #a0a0b0; - --accent: #00d4ff; - --accent-dim: #0088aa; - --green: #00e676; - --orange: #ff9100; - --red: #ff5252; - --border: #2a2a4a; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; - background: var(--bg-primary); - color: var(--text-primary); - min-height: 100vh; -} - -header { - background: var(--bg-secondary); - border-bottom: 1px solid var(--border); - padding: 1rem 2rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -header h1 { - font-size: 1.5rem; - color: var(--accent); -} - -header .status { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.9rem; -} - .status-dot { + display: inline-block; width: 10px; height: 10px; border-radius: 50%; - background: var(--red); + background: var(--bs-danger); } .status-dot.connected { - background: var(--green); - box-shadow: 0 0 6px var(--green); + background: var(--bs-success); + box-shadow: 0 0 6px var(--bs-success); } -.nav-links { - display: flex; - gap: 1rem; -} - -.nav-links a { - color: var(--accent); - text-decoration: none; - padding: 0.4rem 0.8rem; - border: 1px solid var(--accent-dim); - border-radius: 4px; - font-size: 0.85rem; - transition: background 0.2s; -} - -.nav-links a:hover { - background: var(--accent-dim); - color: #fff; -} - -.container { - max-width: 1400px; - margin: 0 auto; - padding: 1.5rem; -} - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 1.5rem; -} - -.stat-card { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: 8px; - padding: 1.2rem; - text-align: center; -} - -.stat-card .value { - font-size: 2rem; - font-weight: bold; - color: var(--accent); -} - -.stat-card .label { - color: var(--text-secondary); - font-size: 0.85rem; - margin-top: 0.3rem; -} - -.panels { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1.5rem; -} - -@media (max-width: 900px) { - .panels { - grid-template-columns: 1fr; - } -} - -.panel { - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; -} - -.panel-header { - background: var(--bg-card); - padding: 0.8rem 1rem; - font-weight: 600; - border-bottom: 1px solid var(--border); -} - -.panel-body { - padding: 0; - max-height: 500px; - overflow-y: auto; -} - -table { - width: 100%; - border-collapse: collapse; -} - -table th, -table td { - padding: 0.6rem 0.8rem; - text-align: left; - border-bottom: 1px solid var(--border); - font-size: 0.85rem; -} - -table th { - background: var(--bg-card); - color: var(--text-secondary); - font-weight: 600; - position: sticky; - top: 0; -} - -table tr:hover { - background: rgba(0, 212, 255, 0.05); -} - -.message-list { - list-style: none; -} - -.message-item { - padding: 0.7rem 1rem; - border-bottom: 1px solid var(--border); - font-size: 0.85rem; -} - -.message-item .meta { - color: var(--text-secondary); - font-size: 0.75rem; - margin-bottom: 0.2rem; -} - -.message-item .text { - color: var(--text-primary); +.battery-bar { + display: inline-flex; + align-items: center; + gap: 6px; } .battery-indicator { - display: inline-block; - width: 24px; - height: 12px; - border: 1px solid var(--text-secondary); + width: 28px; + height: 13px; + border: 1.5px solid var(--bs-secondary); border-radius: 2px; position: relative; + display: inline-block; + vertical-align: middle; } .battery-indicator::after { content: ''; position: absolute; - right: -4px; + right: -5px; top: 2px; width: 3px; - height: 6px; - background: var(--text-secondary); - border-radius: 0 1px 1px 0; + height: 7px; + background: var(--bs-secondary); + border-radius: 0 2px 2px 0; } .battery-fill { @@ -213,15 +43,15 @@ table tr:hover { border-radius: 1px; } -::-webkit-scrollbar { +.table-responsive::-webkit-scrollbar { width: 6px; } -::-webkit-scrollbar-track { - background: var(--bg-secondary); +.table-responsive::-webkit-scrollbar-track { + background: transparent; } -::-webkit-scrollbar-thumb { - background: var(--border); +.table-responsive::-webkit-scrollbar-thumb { + background: var(--bs-border-color); border-radius: 3px; } diff --git a/static/index.html b/static/index.html index f02f0b7..354ee10 100644 --- a/static/index.html +++ b/static/index.html @@ -1,71 +1,113 @@ - + MeshDD-Bot Dashboard + + -
-

MeshDD-Bot

-
+ -
-
-
-
0
-
Nodes
+
+ +
+
+
+
+
0
+
Nodes
+
+
-
-
0
-
Mit Position
+
+
+
+
0
+
Mit Position
+
+
-
-
0
-
Nachrichten
+
+
+
+
0
+
Nachrichten
+
+
-
-
0
-
Textnachrichten
+
+
+
+
0
+
Textnachrichten
+
+
-
-
-
Nodes
-
- - - - - - - - - - - -
NameIDSNRBatterieZuletzt gesehen
+ +
+ +
+
+
+ + Nodes + 0 +
+
+ + + + + + + + + + + + +
NameIDHardwareSNRBatterieZuletzt gesehen
+
-
-
Nachrichten
-
-
    + +
    +
    +
    + + Nachrichten +
    +
    +
    +
    + diff --git a/static/js/dashboard.js b/static/js/dashboard.js index b5c1a4c..cd82513 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -2,6 +2,7 @@ const nodesTable = document.getElementById('nodesTable'); const messagesList = document.getElementById('messagesList'); const statusDot = document.getElementById('statusDot'); const statusText = document.getElementById('statusText'); +const nodeCountBadge = document.getElementById('nodeCountBadge'); let nodes = {}; let ws; @@ -48,38 +49,49 @@ function connectWebSocket() { function renderNodes() { const sorted = Object.values(nodes).sort((a, b) => (b.last_seen || 0) - (a.last_seen || 0)); + nodeCountBadge.textContent = sorted.length; nodesTable.innerHTML = sorted.map(node => { const name = node.long_name || node.short_name || node.node_id; - const snr = node.snr != null ? node.snr.toFixed(1) : '-'; + const shortId = node.node_id ? node.node_id.slice(-4) : '?'; + const hw = node.hw_model || '-'; + const snr = node.snr != null ? `${node.snr.toFixed(1)} dB` : '-'; const battery = renderBattery(node.battery); const lastSeen = node.last_seen ? timeAgo(node.last_seen) : '-'; - const shortId = node.node_id ? node.node_id.slice(-4) : '?'; + const onlineClass = isOnline(node.last_seen) ? 'text-success' : ''; return ` - ${escapeHtml(name)} - ${escapeHtml(shortId)} - ${snr} - ${battery} - ${lastSeen} + ${escapeHtml(name)} + ${escapeHtml(shortId)} + ${escapeHtml(hw)} + ${snr} + ${battery} + ${lastSeen} `; }).join(''); } function renderBattery(level) { - if (level == null) return '-'; - let color = '#00e676'; - if (level < 20) color = '#ff5252'; - else if (level < 50) color = '#ff9100'; - return `
    ${level}%`; + if (level == null) return '-'; + let colorClass = 'bg-success'; + if (level < 20) colorClass = 'bg-danger'; + else if (level < 50) colorClass = 'bg-warning'; + return ` + + ${level}% + `; } function addMessage(msg) { - const li = document.createElement('li'); - li.className = 'message-item'; + const item = document.createElement('div'); + item.className = 'list-group-item list-group-item-action py-2 px-3'; const time = msg.timestamp ? new Date(msg.timestamp * 1000).toLocaleTimeString('de-DE') : ''; const from = msg.from_node ? msg.from_node.slice(-4) : '?'; - li.innerHTML = `
    ${time} von ${escapeHtml(from)}
    ${escapeHtml(msg.payload || '')}
    `; - messagesList.prepend(li); - // Keep max 100 messages + item.innerHTML = ` +
    + ${escapeHtml(from)} + ${time} +
    +
    ${escapeHtml(msg.payload || '')}
    `; + messagesList.prepend(item); while (messagesList.children.length > 100) { messagesList.removeChild(messagesList.lastChild); } @@ -92,6 +104,11 @@ function updateStats(stats) { document.getElementById('statTextMessages').textContent = stats.text_messages || 0; } +function isOnline(lastSeen) { + if (!lastSeen) return false; + return (Date.now() / 1000 - lastSeen) < 900; // < 15 min +} + function timeAgo(timestamp) { const seconds = Math.floor(Date.now() / 1000 - timestamp); if (seconds < 60) return `${seconds}s`;