diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ea6da..d18d276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [0.6.15] - 2026-02-18 + +### Added +- **Scheduler Template-Variablen**: Nachrichten-Jobs können Platzhalter nutzen: + `{time}`, `{date}`, `{datetime}`, `{weekday}`, `{nodes}`, `{nodes_24h}`. + Werden beim Ausführen serverseitig aufgelöst. +- **Scheduler UI**: Bei Typ „Nachricht" werden klickbare Variablen-Badges + unter dem Eingabefeld angezeigt – Klick fügt Variable an Cursorposition ein. +- **Footer**: Auf allen Seiten fixer Footer mit Copyright „MeshDD / PPfeiffer", + Versionsnummer und Monat/Jahr (MM/YYYY). + +### Fixed +- **Sauberer Shutdown**: WebSocket-Verbindungen werden vor `runner.cleanup()` + explizit geschlossen (`ws_manager.close_all()`), so dass der Prozess nicht + mehr auf hängende WS-Loops wartet. + ## [0.6.14] - 2026-02-18 ### Added diff --git a/config.yaml b/config.yaml index 563ca9e..1abda86 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -version: "0.6.14" +version: "0.6.15" bot: name: "MeshDD-Bot" diff --git a/main.py b/main.py index 4991a75..d9ddb46 100644 --- a/main.py +++ b/main.py @@ -64,6 +64,7 @@ async def main(): finally: logger.info("Shutting down...") bot.disconnect() + await ws_manager.close_all() await runner.cleanup() await db.close() logger.info("Shutdown complete") diff --git a/meshbot/scheduler.py b/meshbot/scheduler.py index e92b471..4a7cfab 100644 --- a/meshbot/scheduler.py +++ b/meshbot/scheduler.py @@ -79,10 +79,33 @@ class Scheduler: job_type = job.get("type", "command") if command and self.bot: if job_type == "message": + command = await self._resolve_vars(command, datetime.now()) await self.bot.send_message(command, channel) else: await self.bot.execute_command(command, channel) + async def _resolve_vars(self, text: str, now: datetime) -> str: + if "{" not in text: + return text + stats: dict = {} + if self.bot and self.bot.db: + try: + stats = await self.bot.db.get_stats() + except Exception: + logger.exception("Error fetching stats for scheduler vars") + weekdays = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] + replacements = { + "{time}": now.strftime("%H:%M"), + "{date}": now.strftime("%d.%m.%Y"), + "{datetime}": now.strftime("%d.%m.%Y %H:%M"), + "{weekday}": weekdays[now.weekday()], + "{nodes}": str(stats.get("total_nodes", "?")), + "{nodes_24h}": str(stats.get("nodes_24h", "?")), + } + for key, val in replacements.items(): + text = text.replace(key, val) + return text + @staticmethod def _matches_cron(cron_expr: str, dt: datetime) -> bool: parts = cron_expr.strip().split() diff --git a/meshbot/webserver.py b/meshbot/webserver.py index 079dc24..02080a8 100644 --- a/meshbot/webserver.py +++ b/meshbot/webserver.py @@ -19,6 +19,15 @@ class WebSocketManager: self.clients: set[web.WebSocketResponse] = set() self.auth_clients: set[web.WebSocketResponse] = set() + async def close_all(self): + for ws in list(self.clients): + try: + await ws.close() + except Exception: + pass + self.clients.clear() + self.auth_clients.clear() + async def broadcast(self, msg_type: str, data: dict | list): message = json.dumps({"type": msg_type, "data": data}) closed = set() diff --git a/static/admin.html b/static/admin.html index b176cb5..eff3c42 100644 --- a/static/admin.html +++ b/static/admin.html @@ -186,6 +186,8 @@ + + diff --git a/static/css/style.css b/static/css/style.css index 81de6f9..8d7fb5c 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -104,6 +104,7 @@ margin-top: 48px; margin-left: 210px; padding: .875rem; + padding-bottom: calc(.875rem + 26px); min-height: calc(100vh - 48px); background: var(--tblr-body-bg, var(--bs-body-bg)); } @@ -253,7 +254,7 @@ .map-wrapper #map { width: 100%; - height: calc(100vh - 48px); + height: calc(100vh - 48px - 26px); } .node-tooltip { font-size: 13px; line-height: 1.6; } @@ -291,6 +292,29 @@ font-size: .65rem !important; } +/* ── Page Footer ─────────────────────────────────────────────── */ + +.page-footer { + position: fixed; + bottom: 0; + left: 210px; + right: 0; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + font-size: .65rem; + color: var(--bs-secondary-color); + background: var(--tblr-bg-surface, var(--bs-body-bg)); + border-top: 1px solid var(--tblr-border-color, var(--bs-border-color)); + z-index: 900; + letter-spacing: .02em; +} + +@media (max-width: 991.98px) { + .page-footer { left: 0; } +} + /* ── Scrollbars ──────────────────────────────────────────────── */ .table-responsive::-webkit-scrollbar, diff --git a/static/index.html b/static/index.html index 885f3d8..8fb1d14 100644 --- a/static/index.html +++ b/static/index.html @@ -101,9 +101,9 @@
- +