diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b1fc2..b062d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [0.8.1] - 2026-02-19 + +### Added +- **NINA Wiederholungsintervall** (`resend_interval`): Zweiter konfigurierbarer Intervall, + der aktive Warnmeldungen in regelmäßigen Abständen erneut ins Mesh sendet + (nur wenn `send_to_mesh=true`). Standard: 3600 Sekunden (1 Stunde). +- **NINA AGS-Code-Tabelle**: AGS-Codes werden jetzt in einer Tabelle mit Lösch-Button + je Zeile angezeigt – übersichtlicher als die bisherigen Badge-Einträge. + +### Fixed +- **Badge-Lesbarkeit**: Severity-Badges in der Alerts-Tabelle haben jetzt explizite + Textfarben (`text-white` / `text-dark`) ohne `bg-opacity`, damit der Text auf allen + Hintergründen und in beiden Themes lesbar bleibt. +- **colspan**: Leere Zeile in der Alerts-Tabelle korrekt auf 5 Spalten gesetzt. + ## [0.8.0] - 2026-02-19 ### Added diff --git a/conf/nina.yaml b/conf/nina.yaml index c66f842..1f66473 100644 --- a/conf/nina.yaml +++ b/conf/nina.yaml @@ -1,6 +1,7 @@ enabled: false send_to_mesh: false # true = ins Mesh senden | false = nur Weboberfläche (Monitor-Modus) poll_interval: 300 +resend_interval: 3600 # Aktive Warnungen alle N Sekunden erneut ins Mesh senden channel: 0 min_severity: Severe ags_codes: diff --git a/config.yaml b/config.yaml index 6295f5f..b7dfb6b 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -version: "0.8.0" +version: "0.8.1" bot: name: "MeshDD-Bot" diff --git a/meshbot/nina.py b/meshbot/nina.py index e872dfa..9fa98b4 100644 --- a/meshbot/nina.py +++ b/meshbot/nina.py @@ -58,6 +58,7 @@ DEFAULT_CONFIG = { "enabled": False, "send_to_mesh": True, "poll_interval": 300, + "resend_interval": 3600, "channel": 0, "min_severity": "Severe", "ags_codes": [], @@ -88,9 +89,11 @@ class NinaBot: self.ws_manager = ws_manager self.config: dict = {} self._mtime: float = 0.0 - self._known: dict[str, str] = {} # normalised_id -> sent (de-dup) + self._known: dict[str, str] = {} # normalised_id -> sent (de-dup) + self._active: dict[str, dict] = {} # normalised_id -> {text, channel, headline, severity, id, sent} self._running = False self._task: asyncio.Task | None = None + self._resend_task: asyncio.Task | None = None self._load() # ── Config ────────────────────────────────────────────────────────────── @@ -149,16 +152,18 @@ class NinaBot: async def start(self): self._running = True self._task = asyncio.create_task(self._poll_loop()) + self._resend_task = asyncio.create_task(self._resend_loop()) logger.info("NinaBot started") async def stop(self): self._running = False - if self._task: - self._task.cancel() - try: - await self._task - except asyncio.CancelledError: - pass + for task in (self._task, self._resend_task): + if task: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass # ── Hot-reload ─────────────────────────────────────────────────────────── @@ -187,6 +192,20 @@ class NinaBot: interval = max(60, int(self.config.get("poll_interval", 300))) await asyncio.sleep(interval) + async def _resend_loop(self): + """Re-broadcast all active warnings at resend_interval when send_to_mesh is enabled.""" + while self._running: + interval = max(60, int(self.config.get("resend_interval", 3600))) + await asyncio.sleep(interval) + try: + if self.config.get("enabled") and self.config.get("send_to_mesh", True): + if self._active: + logger.info("NINA resend: %d aktive Warnmeldungen", len(self._active)) + for entry in list(self._active.values()): + await self.send_callback(entry["text"], entry["channel"]) + except Exception: + logger.exception("NINA resend error") + async def _check_alerts(self): min_level = SEVERITY_ORDER.get(self.config.get("min_severity", "Severe"), 2) channel = int(self.config.get("channel", 0)) @@ -377,6 +396,21 @@ class NinaBot: text: str, channel: int, ): + dedup_key = self._normalise_id(identifier) + + # Keep _active up to date for re-broadcast + if msg_type == "Cancel": + self._active.pop(dedup_key, None) + else: + self._active[dedup_key] = { + "text": text, + "channel": channel, + "headline": headline, + "severity": severity, + "id": identifier, + "sent": sent, + } + if self.config.get("send_to_mesh", True): await self.send_callback(text, channel) else: diff --git a/nina.yaml b/nina.yaml index c66f842..1f66473 100644 --- a/nina.yaml +++ b/nina.yaml @@ -1,6 +1,7 @@ enabled: false send_to_mesh: false # true = ins Mesh senden | false = nur Weboberfläche (Monitor-Modus) poll_interval: 300 +resend_interval: 3600 # Aktive Warnungen alle N Sekunden erneut ins Mesh senden channel: 0 min_severity: Severe ags_codes: diff --git a/static/js/nina.js b/static/js/nina.js index 3d28b65..856646f 100644 --- a/static/js/nina.js +++ b/static/js/nina.js @@ -8,17 +8,21 @@ initPage({ onAuth: (user) => { currentUser = user; } }); // ── AGS code list ──────────────────────────────────────────────────────────── function renderAgsList() { - const container = document.getElementById('agsList'); + const tbody = document.getElementById('agsList'); if (agsCodes.length === 0) { - container.innerHTML = 'Keine AGS-Codes konfiguriert.'; + tbody.innerHTML = '
${escapeHtml(code)}| AGS-Code | ++ |
|---|