feat(nina): aktive Warnmeldungen beim Seitenaufruf laden (GET /api/nina/alerts)

_active speichert jetzt msgType/area/monitor_only. get_active_alerts() gibt
sortierte Liste zurück. nina.js lädt beim Init und dedupliziert per ID.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-19 17:37:49 +01:00
parent 6adc3cedcf
commit f36a126200
5 changed files with 43 additions and 1 deletions

View file

@ -1,5 +1,14 @@
# Changelog
## [0.8.9] - 2026-02-19
### Added
- **NINA aktive Warnmeldungen beim Seitenaufruf laden**: Neuer Endpunkt
`GET /api/nina/alerts` gibt alle aktuell aktiven Warnungen aus `_active` zurück.
Die NINA-Seite lädt diese beim Init und zeigt sie in der Tabelle an — auch
Warnungen die bereits vor dem Seitenaufruf ins Mesh gesendet wurden.
Neu eintreffende WS-Events (`nina_alert`) werden per ID dedupliziert.
## [0.8.8] - 2026-02-19
### Changed

View file

@ -1,4 +1,4 @@
version: "0.8.8"
version: "0.8.9"
bot:
name: "MeshDD-Bot"

View file

@ -156,6 +156,9 @@ class NinaBot:
def get_config(self) -> dict:
return {**self.config, "last_poll": self._last_poll}
def get_active_alerts(self) -> list[dict]:
return sorted(self._active.values(), key=lambda x: x.get("sent", ""), reverse=True)
def update_config(self, updates: dict) -> dict:
if "sources" in updates:
self.config["sources"] = {
@ -444,8 +447,11 @@ class NinaBot:
"channel": channel,
"headline": headline,
"severity": severity,
"msgType": msg_type,
"id": identifier,
"sent": sent,
"area": area,
"monitor_only": not self.config.get("send_to_mesh", True),
}
if self.config.get("send_to_mesh", True):

View file

@ -79,6 +79,7 @@ class WebServer:
self.app.router.add_get("/api/node/config", self._api_node_config)
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("/login", self._serve_login)
self.app.router.add_get("/register", self._serve_login)
self.app.router.add_get("/admin", self._serve_admin)
@ -222,6 +223,12 @@ class WebServer:
asyncio.create_task(self.nina.trigger_poll())
return web.json_response(cfg)
async def _api_nina_alerts(self, request: web.Request) -> web.Response:
require_admin_api(request)
if not self.nina:
return web.json_response([])
return web.json_response(self.nina.get_active_alerts())
async def _api_scheduler_get(self, request: web.Request) -> web.Response:
if not self.scheduler:
return web.json_response([], status=200)

View file

@ -241,11 +241,30 @@ function renderAlerts() {
}
function addAlert(alert) {
// Replace existing entry with same id (dedup)
const idx = alerts.findIndex(a => a.id === alert.id);
if (idx !== -1) alerts.splice(idx, 1);
alerts.unshift(alert);
if (alerts.length > MAX_ALERTS) alerts.pop();
renderAlerts();
}
async function loadAlerts() {
try {
const resp = await fetch('/api/nina/alerts');
if (!resp.ok) return;
const data = await resp.json();
if (!Array.isArray(data) || data.length === 0) return;
data.forEach(a => {
alerts.push(a);
});
if (alerts.length > MAX_ALERTS) alerts.length = MAX_ALERTS;
renderAlerts();
} catch (e) {
console.error('NINA alerts load failed:', e);
}
}
// ── WebSocket ─────────────────────────────────────────────────────────────────
function connectWebSocket() {
@ -267,4 +286,5 @@ function connectWebSocket() {
fillDatalist();
loadConfig();
loadAlerts();
connectWebSocket();