From a5ab4550f203f4c6ca1cf07bda339454e599e0c6 Mon Sep 17 00:00:00 2001 From: ppfeiffer Date: Fri, 20 Feb 2026 14:41:17 +0100 Subject: [PATCH] =?UTF-8?q?feat(messages/dashboard):=20Nachrichten=20?= =?UTF-8?q?=C3=B6ffentlich,=20Links-Card,=20Trenner,=20Badge-Fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nachrichten-Seite /messages ohne Login zugänglich (closes #11) - new_message/initial_messages an alle WS-Clients (broadcast statt broadcast_auth) - Dashboard: Nachrichten-Card entfernt, Links-Card (config.yaml) eingefügt - GET /api/links gibt konfigurierte Links aus config.yaml zurück - Nachrichten-Trenner: var(--bs-border-color) statt translucent - msgCount-Badge: bg-secondary-subtle/text-secondary-emphasis (theme-aware) Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 16 ++++++++ config.yaml | 8 +++- meshbot/bot.py | 4 +- meshbot/webserver.py | 10 +++-- static/css/style.css | 4 +- static/index.html | 19 ++++----- static/js/dashboard.js | 92 +++++++++++------------------------------- static/js/messages.js | 10 +---- static/messages.html | 18 +-------- 9 files changed, 71 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3a0139..425c772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [0.08.14] - 2026-02-20 + +### Added +- **Dashboard Links-Card**: Neue Card mit frei konfigurierbaren Links aus `config.yaml` + (`links:` Liste mit `url` + `label`). Ersetzt die Nachrichten-Card im Dashboard (closes #11). +- **`GET /api/links`**: Neuer Endpunkt gibt konfigurierte Links zurück. + +### Changed +- **Nachrichten-Seite öffentlich** (closes #11): Kein Login mehr erforderlich für `/messages`. + `initial_messages` und `new_message` werden an alle WebSocket-Clients gesendet. +- **Dashboard**: Nachrichten-Card entfernt; Nodes-Card auf `col-lg-8` verbreitert. +- **Nachrichtenliste Trenner**: `border-bottom` von `translucent` auf `var(--bs-border-color)` – + deutlich sichtbarer Trenner zwischen Nachrichten. +- **`msgCount`-Badge theme-aware**: `bg-secondary` → `bg-secondary-subtle text-secondary-emphasis` + – Text in Hell- und Dunkel-Theme lesbar. + ## [0.08.13] - 2026-02-20 ### Added diff --git a/config.yaml b/config.yaml index 24fe139..66da1e5 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -version: "0.08.13" +version: "0.08.14" bot: name: "MeshDD-Bot" @@ -17,3 +17,9 @@ database: auth: session_max_age: 86400 + +links: + - url: "https://meshtastic.org" + label: "Meshtastic" + - url: "https://meshmap.net" + label: "MeshMap" diff --git a/meshbot/bot.py b/meshbot/bot.py index f741cd9..b1b2189 100644 --- a/meshbot/bot.py +++ b/meshbot/bot.py @@ -357,7 +357,7 @@ class MeshBot: if not is_own: msg = await self.db.insert_message(str(from_id), str(to_id), channel, portnum, text) if self.ws_manager: - await self.ws_manager.broadcast_auth("new_message", msg) + await self.ws_manager.broadcast("new_message",msg) await self._broadcast_stats() # Process commands @@ -464,7 +464,7 @@ class MeshBot: try: stored = await self.db.insert_message(my_id, "^all", channel, "TEXT_MESSAGE_APP", msg) if self.ws_manager: - await self.ws_manager.broadcast_auth("new_message", stored) + await self.ws_manager.broadcast("new_message",stored) except Exception: logger.exception("Error storing sent message") try: diff --git a/meshbot/webserver.py b/meshbot/webserver.py index e95f1dc..dc65145 100644 --- a/meshbot/webserver.py +++ b/meshbot/webserver.py @@ -80,6 +80,7 @@ class WebServer: self.app.router.add_get("/api/nina/config", self._api_nina_get) self.app.router.add_put("/api/nina/config", self._api_nina_update) self.app.router.add_get("/api/nina/alerts", self._api_nina_alerts) + self.app.router.add_get("/api/links", self._api_links) self.app.router.add_get("/login", self._serve_login) self.app.router.add_get("/register", self._serve_login) self.app.router.add_get("/admin", self._serve_admin) @@ -128,9 +129,8 @@ class WebServer: packets = await self.db.get_recent_packets(200) await ws.send_str(json.dumps({"type": "initial_packets", "data": packets})) - if user: - messages = await self.db.get_recent_messages(50) - await ws.send_str(json.dumps({"type": "initial_messages", "data": messages})) + messages = await self.db.get_recent_messages(50) + await ws.send_str(json.dumps({"type": "initial_messages", "data": messages})) async for msg in ws: pass # We only send, not receive @@ -164,6 +164,10 @@ class WebServer: stats["bot_connected"] = self.bot.is_connected() return web.json_response(stats) + async def _api_links(self, request: web.Request) -> web.Response: + links = config.get("links", []) or [] + return web.json_response(links) + async def _api_send(self, request: web.Request) -> web.Response: require_user_api(request) if not self.bot: diff --git a/static/css/style.css b/static/css/style.css index 7ed3d1f..f271344 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -305,8 +305,8 @@ /* ── Full Messages Page ──────────────────────────────────────── */ .msg-full-item { - padding: .5rem .875rem; - border-bottom: 1px solid var(--bs-border-color-translucent); + padding: .6rem .875rem; + border-bottom: 1px solid var(--bs-border-color); } .msg-full-item:last-child { border-bottom: none; } diff --git a/static/index.html b/static/index.html index b8e5704..cb580a2 100644 --- a/static/index.html +++ b/static/index.html @@ -152,7 +152,7 @@
-
+
Nodes @@ -184,15 +184,16 @@
- -
-
-
- Nachrichten -
+ +
+
+
+ Links
-
-
+
+
diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 72c2c6e..585317d 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -1,5 +1,4 @@ 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'); @@ -13,7 +12,6 @@ let nodeSearch = ''; let nodeOnlineFilter = false; let nodeSortKey = 'last_seen'; let nodeSortDir = -1; -let msgChannelFilter = 'all'; let chartChannel = null; let chartHops = null; let chartHardware = null; @@ -26,12 +24,8 @@ initPage({ onAuth: (user) => { } }); function updateVisibility() { - const loggedIn = !!currentUser; - const sendCard = document.getElementById('sendCard'); - const messagesCard = document.getElementById('messagesCard'); - if (loggedIn) { - sendCard.classList.remove('d-none'); - messagesCard.classList.remove('d-none'); + if (currentUser) { + document.getElementById('sendCard').classList.remove('d-none'); } } @@ -65,22 +59,14 @@ function connectWebSocket() { nodes[msg.data.node_id] = msg.data; renderNodes(); break; - case 'initial_messages': - msg.data.reverse().forEach(m => addMessage(m)); - applyMsgFilter(); - break; case 'channels': channels = msg.data; populateChannelDropdown(); - renderMsgFilterBar(); if (lastStats) updateChannelChart(lastStats); break; case 'my_node_id': myNodeId = msg.data; break; - case 'new_message': - addMessage(msg.data); - break; case 'stats_update': updateStats(msg.data); break; @@ -161,32 +147,6 @@ function renderBattery(level) { return `${level}%`; } -function addMessage(msg) { - const item = document.createElement('div'); - const isSent = myNodeId && msg.from_node === myNodeId; - const chIdx = msg.channel != null ? msg.channel : '?'; - item.className = 'msg-item' + (isSent ? ' msg-sent' : ''); - item.dataset.channel = String(chIdx); - const time = msg.timestamp ? new Date(msg.timestamp * 1000).toLocaleTimeString('de-DE') : ''; - const fromNode = nodes[msg.from_node]; - const from = isSent ? 'Bot' : ((fromNode && fromNode.long_name) ? fromNode.long_name : (msg.from_node || '?')); - const chName = channels[chIdx] || `Ch ${chIdx}`; - const bubbleClass = isSent ? 'msg-bubble msg-bubble-sent' : 'msg-bubble'; - const icon = isSent ? 'bi-send-fill' : 'bi-person-fill'; - item.innerHTML = ` -
- ${escapeHtml(from)} - ${escapeHtml(chName)}${time} -
-
${escapeHtml(msg.payload || '')}
`; - if (msgChannelFilter !== 'all' && String(chIdx) !== msgChannelFilter) { - item.classList.add('d-none'); - } - messagesList.prepend(item); - while (messagesList.children.length > 100) { - messagesList.removeChild(messagesList.lastChild); - } -} function updateBotStatus(status) { const dot = document.getElementById('meshDot'); @@ -238,33 +198,26 @@ function timeAgo(timestamp) { return `${Math.floor(seconds / 86400)}d`; } -// ── Message channel filter (Prio 7) ────────────────── -function renderMsgFilterBar() { - const bar = document.getElementById('msgFilterBar'); - if (!bar) return; - const sorted = Object.entries(channels).sort((a, b) => parseInt(a[0]) - parseInt(b[0])); - const mkBtn = (ch, label) => { - const active = ch === msgChannelFilter; - return ``; - }; - bar.innerHTML = mkBtn('all', 'Alle') + sorted.map(([idx, name]) => mkBtn(String(idx), name)).join(''); - bar.onclick = (e) => { - const btn = e.target.closest('button[data-ch]'); - if (!btn) return; - msgChannelFilter = btn.dataset.ch; - bar.querySelectorAll('button[data-ch]').forEach(b => { - b.classList.toggle('btn-secondary', b.dataset.ch === msgChannelFilter); - b.classList.toggle('btn-outline-secondary', b.dataset.ch !== msgChannelFilter); - }); - applyMsgFilter(); - }; -} - -function applyMsgFilter() { - messagesList.querySelectorAll('.msg-item[data-channel]').forEach(item => { - const visible = msgChannelFilter === 'all' || item.dataset.channel === msgChannelFilter; - item.classList.toggle('d-none', !visible); - }); +// ── Links Card ──────────────────────────────────────── +async function loadLinks() { + try { + const resp = await fetch('/api/links'); + const links = await resp.json(); + const list = document.getElementById('linksList'); + if (!links.length) { + list.innerHTML = '
  • Keine Links konfiguriert
  • '; + return; + } + list.innerHTML = links.map(l => + `
  • + + ${escapeHtml(l.label)} + +
  • ` + ).join(''); + } catch (e) { + console.error('Links load failed:', e); + } } // Send message @@ -566,3 +519,4 @@ document.addEventListener('themechange', () => { }); initCharts(); +loadLinks(); diff --git a/static/js/messages.js b/static/js/messages.js index bb44c04..db275a8 100644 --- a/static/js/messages.js +++ b/static/js/messages.js @@ -22,14 +22,8 @@ let ws; let msgChannelFilter = 'all'; let messages = []; -initPage({ onAuth: (user) => { - if (!user) { - document.getElementById('loginNotice').classList.remove('d-none'); - } else { - document.getElementById('messagesView').classList.remove('d-none'); - connectWebSocket(); - } -}}); +initPage({}); +connectWebSocket(); // ── WebSocket ───────────────────────────────────────────────── function connectWebSocket() { diff --git a/static/messages.html b/static/messages.html index 1b37fd1..6e702b2 100644 --- a/static/messages.html +++ b/static/messages.html @@ -42,26 +42,12 @@
    - -
    -
    -
    - -

    Du musst angemeldet sein, um Nachrichten zu sehen.

    - - Anmelden - -
    -
    -
    - - -
    +
    Nachrichten - 0 + 0