feat: v0.5.7 - Anfragen pro Kanal mit Kanalnamen im Dashboard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-17 16:24:26 +01:00
parent 7bf58a32fb
commit 1d768c6921
6 changed files with 45 additions and 9 deletions

View file

@ -1,5 +1,10 @@
# Changelog
## [0.5.7] - 2026-02-17
### Added
- Anfragen-Aufschluesselung pro Kanal mit Kanalnamen im Dashboard
- Channel-Spalte in der commands-Tabelle (mit DB-Migration fuer bestehende DBs)
## [0.5.6] - 2026-02-17
### Added
- Node-Detail-Modal im Dashboard: Klick auf Node-Zeile oeffnet Modal mit allen Node-Daten

View file

@ -1,4 +1,4 @@
version: "0.5.6"
version: "0.5.7"
bot:
name: "MeshDD-Bot"

View file

@ -324,7 +324,7 @@ class MeshBot:
if response:
await self._send_text(response, channel)
await self.db.insert_command(cmd)
await self.db.insert_command(cmd, channel)
if self.ws_manager:
stats = await self.db.get_stats()
await self.ws_manager.broadcast("stats_update", stats)

View file

@ -15,6 +15,7 @@ class Database:
self.db.row_factory = aiosqlite.Row
await self.db.execute("PRAGMA journal_mode=WAL")
await self._create_tables()
await self._migrate()
logger.info("Database connected: %s", self.db_path)
async def close(self):
@ -55,7 +56,8 @@ class Database:
CREATE TABLE IF NOT EXISTS commands (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL,
command TEXT
command TEXT,
channel INTEGER
);
CREATE TABLE IF NOT EXISTS users (
@ -91,6 +93,14 @@ class Database:
""")
await self.db.commit()
async def _migrate(self):
async with self.db.execute("PRAGMA table_info(commands)") as c:
cols = {row[1] async for row in c}
if "channel" not in cols:
await self.db.execute("ALTER TABLE commands ADD COLUMN channel INTEGER")
await self.db.commit()
logger.info("Migration: added channel column to commands table")
# ── Node methods ──────────────────────────────────
async def upsert_node(self, node_id: str, **kwargs) -> dict:
@ -164,11 +174,11 @@ class Database:
# ── Command methods ───────────────────────────────
async def insert_command(self, command: str):
async def insert_command(self, command: str, channel: int | None = None):
now = time.time()
await self.db.execute(
"INSERT INTO commands (timestamp, command) VALUES (?, ?)",
(now, command),
"INSERT INTO commands (timestamp, command, channel) VALUES (?, ?, ?)",
(now, command, channel),
)
await self.db.commit()
@ -188,6 +198,10 @@ class Database:
"SELECT command, COUNT(*) as cnt FROM commands GROUP BY command ORDER BY cnt DESC"
) as cursor:
stats["command_breakdown"] = {row[0]: row[1] async for row in cursor}
async with self.db.execute(
"SELECT channel, COUNT(*) as cnt FROM commands GROUP BY channel ORDER BY cnt DESC"
) as cursor:
stats["channel_breakdown"] = {row[0]: row[1] async for row in cursor}
return stats
# ── User methods ──────────────────────────────────

View file

@ -104,10 +104,16 @@
<!-- Command Breakdown -->
<div class="card card-outline mb-2">
<div class="card-body py-2 px-3 d-flex align-items-center gap-2 flex-wrap">
<div class="card-body py-2 px-3 d-flex flex-column gap-1">
<div class="d-flex align-items-center gap-2 flex-wrap">
<small class="text-body-secondary"><i class="bi bi-bar-chart-fill me-1"></i>Anfragen:</small>
<span id="commandBreakdown" class="d-flex gap-1 flex-wrap"></span>
</div>
<div class="d-flex align-items-center gap-2 flex-wrap">
<small class="text-body-secondary"><i class="bi bi-broadcast me-1"></i>Kanaele:</small>
<span id="channelBreakdown" class="d-flex gap-1 flex-wrap"></span>
</div>
</div>
</div>
<!-- Send Message (auth-gated) -->

View file

@ -175,6 +175,17 @@ function updateStats(stats) {
} else {
breakdown.innerHTML = '<span class="text-body-secondary small">Noch keine Anfragen</span>';
}
const chBreakdown = document.getElementById('channelBreakdown');
const chCounts = stats.channel_breakdown || {};
if (Object.keys(chCounts).length > 0) {
chBreakdown.innerHTML = Object.entries(chCounts).map(([chIdx, count]) => {
const chName = channels[chIdx] || `Ch ${chIdx}`;
return `<span class="badge bg-info bg-opacity-75">${escapeHtml(chName)} <span class="badge bg-light text-dark ms-1">${count}</span></span>`;
}).join('');
} else {
chBreakdown.innerHTML = '<span class="text-body-secondary small">Noch keine Anfragen</span>';
}
}
function isOnline(lastSeen) {