feat: v0.6.14 - Pakettypen-Diagramm im Dashboard

Viertes Chart-Panel zeigt Pakettyp-Verteilung der letzten 24h als
Doughnut-Diagramm, farblich nach portnum kodiert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-18 18:02:15 +01:00
parent 9306cce209
commit 0fd401a395
3 changed files with 53 additions and 3 deletions

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## [0.6.14] - 2026-02-18
### Added
- **Dashboard**: Viertes Diagramm "Pakettypen (24h)" Doughnut-Chart mit Pakettyp-Verteilung
der letzten 24h aus der `packets`-Tabelle, farblich nach Typ kodiert.
## [0.6.13] - 2026-02-18 ## [0.6.13] - 2026-02-18
### Added ### Added

View file

@ -1,4 +1,4 @@
version: "0.6.13" version: "0.6.14"
bot: bot:
name: "MeshDD-Bot" name: "MeshDD-Bot"

View file

@ -17,6 +17,7 @@ let msgChannelFilter = 'all';
let chartChannel = null; let chartChannel = null;
let chartHops = null; let chartHops = null;
let chartHardware = null; let chartHardware = null;
let chartPacketTypes = null;
initPage({ onAuth: (user) => { initPage({ onAuth: (user) => {
currentUser = user; currentUser = user;
@ -203,6 +204,7 @@ function updateStats(stats) {
updateBotStatus({ connected: stats.bot_connected, uptime: stats.uptime }); updateBotStatus({ connected: stats.bot_connected, uptime: stats.uptime });
} }
updateChannelChart(stats); updateChannelChart(stats);
updatePacketTypeChart(stats);
const chBreakdown = document.getElementById('channelBreakdown'); const chBreakdown = document.getElementById('channelBreakdown');
const chCounts = stats.channel_breakdown || {}; const chCounts = stats.channel_breakdown || {};
@ -424,10 +426,33 @@ _thead.addEventListener('click', (e) => {
renderNodes(); renderNodes();
}); });
// ── Charts (Prio 5) ─────────────────────────────────── // ── Charts ────────────────────────────────────────────
const HOP_COLORS = ['#2196F3', '#4CAF50', '#FF9800', '#F44336', '#9C27B0', '#795548']; const HOP_COLORS = ['#2196F3', '#4CAF50', '#FF9800', '#F44336', '#9C27B0', '#795548'];
const CHART_COLORS = ['#0dcaf0', '#198754', '#ffc107', '#dc3545', '#6610f2', '#0d6efd', '#20c997', '#fd7e14']; const CHART_COLORS = ['#0dcaf0', '#198754', '#ffc107', '#dc3545', '#6610f2', '#0d6efd', '#20c997', '#fd7e14'];
const PORTNUM_LABELS = {
TEXT_MESSAGE_APP: 'Text',
POSITION_APP: 'Position',
NODEINFO_APP: 'NodeInfo',
TELEMETRY_APP: 'Telemetry',
ROUTING_APP: 'Routing',
ADMIN_APP: 'Admin',
TRACEROUTE_APP: 'Traceroute',
NEIGHBORINFO_APP: 'Neighbor',
RANGE_TEST_APP: 'RangeTest',
};
const PORTNUM_COLORS = {
TEXT_MESSAGE_APP: '#0dcaf0',
POSITION_APP: '#198754',
NODEINFO_APP: '#0d6efd',
TELEMETRY_APP: '#ffc107',
ROUTING_APP: '#6c757d',
ADMIN_APP: '#dc3545',
TRACEROUTE_APP: '#6f42c1',
NEIGHBORINFO_APP: '#20c997',
RANGE_TEST_APP: '#fd7e14',
};
function _chartThemeDefaults() { function _chartThemeDefaults() {
const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark'; const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
return { color: dark ? '#adb5bd' : '#495057', borderColor: dark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.07)' }; return { color: dark ? '#adb5bd' : '#495057', borderColor: dark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.07)' };
@ -468,6 +493,15 @@ function initCharts() {
scales: { x: { beginAtZero: true, ticks: { stepSize: 1, font: { size: 10 } } }, y: { ticks: { font: { size: 10 } } } } scales: { x: { beginAtZero: true, ticks: { stepSize: 1, font: { size: 10 } } }, y: { ticks: { font: { size: 10 } } } }
} }
}); });
chartPacketTypes = new Chart(document.getElementById('chartPacketTypes'), {
type: 'doughnut',
data: { labels: [], datasets: [{ data: [], backgroundColor: [], borderWidth: 1 }] },
options: {
responsive: true, maintainAspectRatio: false,
plugins: { legend: { position: 'right', labels: { boxWidth: 10, font: { size: 10 } } } }
}
});
} }
function updateChannelChart(stats) { function updateChannelChart(stats) {
@ -480,6 +514,16 @@ function updateChannelChart(stats) {
chartChannel.update('none'); chartChannel.update('none');
} }
function updatePacketTypeChart(stats) {
if (!chartPacketTypes) return;
const breakdown = stats.packet_type_breakdown || {};
const entries = Object.entries(breakdown).sort((a, b) => b[1] - a[1]);
chartPacketTypes.data.labels = entries.map(([t]) => PORTNUM_LABELS[t] || (t ? t.replace(/_APP$/, '') : '?'));
chartPacketTypes.data.datasets[0].data = entries.map(([, cnt]) => cnt);
chartPacketTypes.data.datasets[0].backgroundColor = entries.map(([t]) => PORTNUM_COLORS[t] || '#adb5bd');
chartPacketTypes.update('none');
}
function updateNodeCharts() { function updateNodeCharts() {
if (!chartHops || !chartHardware) return; if (!chartHops || !chartHardware) return;
const nodeList = Object.values(nodes); const nodeList = Object.values(nodes);
@ -508,7 +552,7 @@ document.addEventListener('themechange', () => {
const d = _chartThemeDefaults(); const d = _chartThemeDefaults();
Chart.defaults.color = d.color; Chart.defaults.color = d.color;
Chart.defaults.borderColor = d.borderColor; Chart.defaults.borderColor = d.borderColor;
[chartChannel, chartHops, chartHardware].forEach(c => c && c.update()); [chartChannel, chartHops, chartHardware, chartPacketTypes].forEach(c => c && c.update());
}); });
initCharts(); initCharts();