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:
parent
f2ffe0fd9d
commit
2db03510c8
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,5 +1,15 @@
|
||||||
# Changelog
|
# 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
|
## [0.8.3] - 2026-02-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version: "0.8.3"
|
version: "0.8.4"
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
name: "MeshDD-Bot"
|
name: "MeshDD-Bot"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from datetime import datetime, timezone
|
||||||
from typing import Callable, Awaitable
|
from typing import Callable, Awaitable
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
@ -111,6 +112,7 @@ class NinaBot:
|
||||||
self._running = False
|
self._running = False
|
||||||
self._task: asyncio.Task | None = None
|
self._task: asyncio.Task | None = None
|
||||||
self._resend_task: asyncio.Task | None = None
|
self._resend_task: asyncio.Task | None = None
|
||||||
|
self._last_poll: str = ""
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
# ── Config ──────────────────────────────────────────────────────────────
|
# ── Config ──────────────────────────────────────────────────────────────
|
||||||
|
|
@ -152,7 +154,7 @@ class NinaBot:
|
||||||
logger.exception("Error saving nina.yaml")
|
logger.exception("Error saving nina.yaml")
|
||||||
|
|
||||||
def get_config(self) -> dict:
|
def get_config(self) -> dict:
|
||||||
return self.config
|
return {**self.config, "last_poll": self._last_poll}
|
||||||
|
|
||||||
def update_config(self, updates: dict) -> dict:
|
def update_config(self, updates: dict) -> dict:
|
||||||
if "sources" in updates:
|
if "sources" in updates:
|
||||||
|
|
@ -199,11 +201,22 @@ class NinaBot:
|
||||||
|
|
||||||
# ── Polling ──────────────────────────────────────────────────────────────
|
# ── 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):
|
async def _poll_loop(self):
|
||||||
while self._running:
|
while self._running:
|
||||||
try:
|
try:
|
||||||
if self.config.get("enabled"):
|
if self.config.get("enabled"):
|
||||||
await self._check_alerts()
|
await self._check_alerts()
|
||||||
|
self._last_poll = datetime.now(timezone.utc).isoformat()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("NINA polling error")
|
logger.exception("NINA polling error")
|
||||||
interval = max(60, int(self.config.get("poll_interval", 300)))
|
interval = max(60, int(self.config.get("poll_interval", 300)))
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,7 @@ class WebServer:
|
||||||
return web.json_response({"error": "NINA not available"}, status=503)
|
return web.json_response({"error": "NINA not available"}, status=503)
|
||||||
updates = await request.json()
|
updates = await request.json()
|
||||||
cfg = self.nina.update_config(updates)
|
cfg = self.nina.update_config(updates)
|
||||||
|
asyncio.create_task(self.nina.trigger_poll())
|
||||||
return web.json_response(cfg)
|
return web.json_response(cfg)
|
||||||
|
|
||||||
async def _api_scheduler_get(self, request: web.Request) -> web.Response:
|
async def _api_scheduler_get(self, request: web.Request) -> web.Response:
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,16 @@ function applyConfig(cfg) {
|
||||||
document.getElementById('srcDwd').checked = src.dwd !== false;
|
document.getElementById('srcDwd').checked = src.dwd !== false;
|
||||||
document.getElementById('srcLhp').checked = src.lhp !== false;
|
document.getElementById('srcLhp').checked = src.lhp !== false;
|
||||||
document.getElementById('srcPolice').checked = !!src.police;
|
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) {
|
function updateStatusBadge(cfg) {
|
||||||
|
|
@ -166,10 +176,13 @@ document.getElementById('btnSaveNina').addEventListener('click', async () => {
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const cfg = await resp.json();
|
const cfg = await resp.json();
|
||||||
updateStatusBadge(cfg);
|
updateStatusBadge(cfg);
|
||||||
|
applyConfig(cfg);
|
||||||
statusEl.textContent = 'Gespeichert ✓';
|
statusEl.textContent = 'Gespeichert ✓';
|
||||||
statusEl.className = 'align-self-center small text-success';
|
statusEl.className = 'align-self-center small text-success';
|
||||||
statusEl.classList.remove('d-none');
|
statusEl.classList.remove('d-none');
|
||||||
setTimeout(() => statusEl.classList.add('d-none'), 3000);
|
setTimeout(() => statusEl.classList.add('d-none'), 3000);
|
||||||
|
// Config nach kurzer Pause nachladen, damit last_poll den abgeschlossenen Poll zeigt
|
||||||
|
setTimeout(loadConfig, 5000);
|
||||||
} else {
|
} else {
|
||||||
statusEl.textContent = 'Fehler beim Speichern';
|
statusEl.textContent = 'Fehler beim Speichern';
|
||||||
statusEl.className = 'align-self-center small text-danger';
|
statusEl.className = 'align-self-center small text-danger';
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
<label for="pollInterval" class="form-label">Abfrage­intervall (Sek.)</label>
|
<label for="pollInterval" class="form-label">Abfrage­intervall (Sek.)</label>
|
||||||
<input type="number" class="form-control form-control-sm" id="pollInterval"
|
<input type="number" class="form-control form-control-sm" id="pollInterval"
|
||||||
min="60" max="3600" step="60" value="300">
|
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>
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<label for="resendInterval" class="form-label">Wieder­holungsintervall (Sek.)</label>
|
<label for="resendInterval" class="form-label">Wieder­holungsintervall (Sek.)</label>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue