diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7db7422..a3a0139 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## [0.08.13] - 2026-02-20
+
+### Added
+- **Scheduler: neue Template-Variablen** (closes #15):
+ - `{nodes_online}` – Nodes mit `last_seen < 15 Min` (Live-Status, analog Dashboard-Schwellwert)
+ - `{version}` – aktuelle Bot-Version aus `config.yaml`
+- **Scheduler Variablen-Badges theme-aware**: Badge-Klasse von `bg-secondary` auf
+ `bg-secondary-subtle text-secondary-emphasis border-secondary-subtle` umgestellt –
+ korrekte Lesbarkeit in Hell- und Dunkel-Theme.
+
## [0.08.12] - 2026-02-20
### Changed
diff --git a/config.yaml b/config.yaml
index 2944c6f..24fe139 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,4 +1,4 @@
-version: "0.08.12"
+version: "0.08.13"
bot:
name: "MeshDD-Bot"
diff --git a/meshbot/database.py b/meshbot/database.py
index ff3b48f..26ef1d9 100644
--- a/meshbot/database.py
+++ b/meshbot/database.py
@@ -207,6 +207,10 @@ class Database:
"SELECT COUNT(*) FROM nodes WHERE last_seen >= ?", (day_ago,)
) as c:
stats["nodes_24h"] = (await c.fetchone())[0]
+ async with self.db.execute(
+ "SELECT COUNT(*) FROM nodes WHERE last_seen >= ?", (now - 900,)
+ ) as c:
+ stats["nodes_online"] = (await c.fetchone())[0]
async with self.db.execute(
"SELECT COUNT(*) FROM commands WHERE timestamp >= ?", (day_ago,)
) as c:
diff --git a/meshbot/scheduler.py b/meshbot/scheduler.py
index 4a7cfab..b657af7 100644
--- a/meshbot/scheduler.py
+++ b/meshbot/scheduler.py
@@ -5,6 +5,8 @@ from datetime import datetime
import yaml
+from meshbot import config
+
logger = logging.getLogger(__name__)
SCHEDULER_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "scheduler.yaml")
@@ -95,12 +97,14 @@ class Scheduler:
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", "?")),
+ "{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", "?")),
+ "{nodes_online}": str(stats.get("nodes_online", "?")),
+ "{version}": config.get("version", "?"),
}
for key, val in replacements.items():
text = text.replace(key, val)
diff --git a/static/js/scheduler.js b/static/js/scheduler.js
index 4069b6a..f8a9403 100644
--- a/static/js/scheduler.js
+++ b/static/js/scheduler.js
@@ -8,12 +8,14 @@ initPage({ onAuth: (user) => { currentUser = user; } });
// Template variables available in message jobs
const MSG_VARS = [
- { key: '{time}', label: '{time}', desc: 'Uhrzeit (HH:MM)' },
- { key: '{date}', label: '{date}', desc: 'Datum (TT.MM.JJJJ)' },
- { key: '{datetime}', label: '{datetime}', desc: 'Datum + Uhrzeit' },
- { key: '{weekday}', label: '{weekday}', desc: 'Wochentag (Montag…)' },
- { key: '{nodes}', label: '{nodes}', desc: 'Anzahl Nodes gesamt' },
- { key: '{nodes_24h}', label: '{nodes_24h}', desc: 'Aktive Nodes (24h)' },
+ { key: '{time}', label: '{time}', desc: 'Uhrzeit (HH:MM)' },
+ { key: '{date}', label: '{date}', desc: 'Datum (TT.MM.JJJJ)' },
+ { key: '{datetime}', label: '{datetime}', desc: 'Datum + Uhrzeit' },
+ { key: '{weekday}', label: '{weekday}', desc: 'Wochentag (Montag…)' },
+ { key: '{nodes}', label: '{nodes}', desc: 'Anzahl Nodes gesamt' },
+ { key: '{nodes_24h}', label: '{nodes_24h}', desc: 'Aktive Nodes (24h)' },
+ { key: '{nodes_online}', label: '{nodes_online}', desc: 'Nodes online (< 15 Min)' },
+ { key: '{version}', label: '{version}', desc: 'Bot-Version' },
];
// Type toggle label + variable hints
@@ -25,7 +27,7 @@ function updateCommandLabel() {
const badges = document.getElementById('varBadges');
if (isMsg) {
badges.innerHTML = MSG_VARS.map(v =>
- `${escapeHtml(v.label)}`
+ `${escapeHtml(v.label)}`
).join('');
hints.classList.remove('d-none');
} else {