diff --git a/CHANGELOG.md b/CHANGELOG.md index 34bf832..14ea6da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 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 ### Added diff --git a/config.yaml b/config.yaml index 137b882..563ca9e 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -version: "0.6.13" +version: "0.6.14" bot: name: "MeshDD-Bot" diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 3a36f2e..2c22bda 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -17,6 +17,7 @@ let msgChannelFilter = 'all'; let chartChannel = null; let chartHops = null; let chartHardware = null; +let chartPacketTypes = null; initPage({ onAuth: (user) => { currentUser = user; @@ -203,6 +204,7 @@ function updateStats(stats) { updateBotStatus({ connected: stats.bot_connected, uptime: stats.uptime }); } updateChannelChart(stats); + updatePacketTypeChart(stats); const chBreakdown = document.getElementById('channelBreakdown'); const chCounts = stats.channel_breakdown || {}; @@ -424,10 +426,33 @@ _thead.addEventListener('click', (e) => { renderNodes(); }); -// ── Charts (Prio 5) ─────────────────────────────────── +// ── Charts ──────────────────────────────────────────── const HOP_COLORS = ['#2196F3', '#4CAF50', '#FF9800', '#F44336', '#9C27B0', '#795548']; 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() { 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)' }; @@ -468,6 +493,15 @@ function initCharts() { 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) { @@ -480,6 +514,16 @@ function updateChannelChart(stats) { 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() { if (!chartHops || !chartHardware) return; const nodeList = Object.values(nodes); @@ -508,7 +552,7 @@ document.addEventListener('themechange', () => { const d = _chartThemeDefaults(); Chart.defaults.color = d.color; Chart.defaults.borderColor = d.borderColor; - [chartChannel, chartHops, chartHardware].forEach(c => c && c.update()); + [chartChannel, chartHops, chartHardware, chartPacketTypes].forEach(c => c && c.update()); }); initCharts();