v0.8.4: NINA Sofortabfrage nach Speichern + Zeitstempel letzte Abfrage

- Konfiguration speichern löst sofort eine NINA-Abfrage aus (trigger_poll)
- Frontend lädt Config 5s nach Save neu um last_poll-Zeitstempel zu zeigen
- Unterhalb Abfrageintervall-Feld: Datum/Uhrzeit der letzten Abfrage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-19 16:18:15 +01:00
parent f2ffe0fd9d
commit 2db03510c8
6 changed files with 40 additions and 3 deletions

View file

@ -1,5 +1,15 @@
# Changelog
## [0.8.4] - 2026-02-19
### Added
- **NINA Sofortabfrage nach Speichern**: Nach dem Speichern der Konfiguration wird
unmittelbar eine Warnmeldungsabfrage gestartet (kein Warten auf den nächsten
Intervall-Zyklus). Das Frontend lädt die Konfiguration 5 Sekunden nach dem Speichern
automatisch neu, um den Zeitstempel der letzten Abfrage zu aktualisieren.
- **Letzte Abfrage anzeigen**: Unterhalb des Abfrageintervall-Felds wird Datum und Uhrzeit
der letzten erfolgreichen NINA-Abfrage angezeigt (`last_poll`-Feld im Config-Response).
## [0.8.3] - 2026-02-19
### Added

View file

@ -1,4 +1,4 @@
version: "0.8.3"
version: "0.8.4"
bot:
name: "MeshDD-Bot"

View file

@ -1,6 +1,7 @@
import asyncio
import logging
import os
from datetime import datetime, timezone
from typing import Callable, Awaitable
import aiohttp
@ -111,6 +112,7 @@ class NinaBot:
self._running = False
self._task: asyncio.Task | None = None
self._resend_task: asyncio.Task | None = None
self._last_poll: str = ""
self._load()
# ── Config ──────────────────────────────────────────────────────────────
@ -152,7 +154,7 @@ class NinaBot:
logger.exception("Error saving nina.yaml")
def get_config(self) -> dict:
return self.config
return {**self.config, "last_poll": self._last_poll}
def update_config(self, updates: dict) -> dict:
if "sources" in updates:
@ -199,11 +201,22 @@ class NinaBot:
# ── Polling ──────────────────────────────────────────────────────────────
async def trigger_poll(self) -> None:
"""Run _check_alerts immediately (e.g. triggered after config save)."""
if not self.config.get("enabled"):
return
try:
await self._check_alerts()
self._last_poll = datetime.now(timezone.utc).isoformat()
except Exception:
logger.exception("NINA triggered poll error")
async def _poll_loop(self):
while self._running:
try:
if self.config.get("enabled"):
await self._check_alerts()
self._last_poll = datetime.now(timezone.utc).isoformat()
except Exception:
logger.exception("NINA polling error")
interval = max(60, int(self.config.get("poll_interval", 300)))

View file

@ -219,6 +219,7 @@ class WebServer:
return web.json_response({"error": "NINA not available"}, status=503)
updates = await request.json()
cfg = self.nina.update_config(updates)
asyncio.create_task(self.nina.trigger_poll())
return web.json_response(cfg)
async def _api_scheduler_get(self, request: web.Request) -> web.Response:

View file

@ -120,6 +120,16 @@ function applyConfig(cfg) {
document.getElementById('srcDwd').checked = src.dwd !== false;
document.getElementById('srcLhp').checked = src.lhp !== false;
document.getElementById('srcPolice').checked = !!src.police;
const lpEl = document.getElementById('lastPoll');
if (lpEl) {
if (cfg.last_poll) {
const d = new Date(cfg.last_poll);
lpEl.textContent = 'Letzte Abfrage: ' + d.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'medium' });
} else {
lpEl.textContent = '';
}
}
}
function updateStatusBadge(cfg) {
@ -166,10 +176,13 @@ document.getElementById('btnSaveNina').addEventListener('click', async () => {
if (resp.ok) {
const cfg = await resp.json();
updateStatusBadge(cfg);
applyConfig(cfg);
statusEl.textContent = 'Gespeichert ✓';
statusEl.className = 'align-self-center small text-success';
statusEl.classList.remove('d-none');
setTimeout(() => statusEl.classList.add('d-none'), 3000);
// Config nach kurzer Pause nachladen, damit last_poll den abgeschlossenen Poll zeigt
setTimeout(loadConfig, 5000);
} else {
statusEl.textContent = 'Fehler beim Speichern';
statusEl.className = 'align-self-center small text-danger';

View file

@ -75,7 +75,7 @@
<label for="pollInterval" class="form-label">Abfrage&shy;intervall (Sek.)</label>
<input type="number" class="form-control form-control-sm" id="pollInterval"
min="60" max="3600" step="60" value="300">
<div class="form-text">Neue Warnmeldungen</div>
<div class="form-text">Neue Warnmeldungen<span id="lastPoll" class="d-block text-body-secondary"></span></div>
</div>
<div class="col-5">
<label for="resendInterval" class="form-label">Wieder&shy;holungsintervall (Sek.)</label>