Add filter to the portnum distrubation chart

This commit is contained in:
Anton Roslund
2025-04-09 20:05:18 +02:00
parent 056b8d61eb
commit ff763e3cf2
2 changed files with 141 additions and 12 deletions
+11 -1
View File
@@ -57,7 +57,17 @@ För mer info se vår [dokumentation]({{<ref position.md>}}#position-precision).
## Packet typer
Fördelningen av olika pakettyper i nätverket under de senaste dygnet. Diagrammet visar hur nätverket används, inklusive meddelanden, positionstelemetri och andra systempaket.
Fördelningen av olika pakettyper i nätverket under de senaste dygnet. Diagrammet visar hur nätverket används, inklusive meddelanden, positionstelemetri och andra systempaket. Sökrutan till höger kan användas för att visa antalet paket av respektive typ för en given nod.
<div class="d-flex justify-content-end m-0 p-0" style="max-width: 1000px">
<div class="dropdown" style="width: 33%;">
<div class="input-group pe-3">
<input id="nodeSearch" type="text" class="form-control" placeholder="Node Name" autocomplete="off">
<button id="clearFilterBtn" class="btn btn-outline-info" type="button">✕</button>
</div>
<ul id="suggestions" class="dropdown-menu w-100" style="display: none; max-height: 300px; overflow-y: auto; position: absolute; z-index: 1000;"></ul>
</div>
</div>
<div id="portnumDistributionContainer" style="width: 100%;max-width: 1000px;">
<canvas id="portnumDistribution"></canvas>
</div>
+130 -11
View File
@@ -1,31 +1,150 @@
async function portnumDistributionChart() {
let allNodes = [];
let highlightedIndex = -1;
let chartInstance;
async function fetchNodes() {
const res = await fetch('https://map.sthlm-mesh.se/api/v1/nodes/');
const data = await res.json();
const threshold = new Date();
threshold.setDate(threshold.getDate() - 1);
allNodes = data.nodes.filter(node => new Date(node.updated_at) >= threshold);
}
function filterSuggestions(query) {
const lowerQuery = query.toLowerCase();
return allNodes?.filter(node => node.long_name?.toLowerCase().includes(lowerQuery)) || [];
}
function showSuggestions(matches) {
const ul = document.getElementById('suggestions');
ul.innerHTML = '';
if (matches.length === 0) {
ul.innerHTML = '<li class="dropdown-item text-muted disabled">No matching nodes</li>';
ul.style.display = 'block';
return;
}
matches.forEach(node => {
const li = document.createElement('li');
const a = document.createElement('a');
a.className = 'dropdown-item';
a.href = '#';
a.textContent = node.long_name;
a.addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('nodeSearch').value = node.long_name;
ul.style.display = 'none';
portnumDistributionChart(node.node_id);
});
li.appendChild(a);
ul.appendChild(li);
});
ul.style.display = 'block';
}
function updateHighlight(suggestions) {
suggestions.forEach((el, i) => {
if (i === highlightedIndex) {
el.classList.add('active');
} else {
el.classList.remove('active');
}
});
}
document.getElementById('nodeSearch').addEventListener('keydown', (e) => {
const suggestions = document.querySelectorAll('#suggestions .dropdown-item');
if (suggestions.length === 0) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
highlightedIndex = (highlightedIndex + 1) % suggestions.length;
updateHighlight(suggestions);
break;
case 'ArrowUp':
e.preventDefault();
highlightedIndex = (highlightedIndex - 1 + suggestions.length) % suggestions.length;
updateHighlight(suggestions);
break;
case 'Enter':
e.preventDefault();
if (highlightedIndex >= 0) {
suggestions[highlightedIndex].click();
}
break;
case 'Escape':
document.getElementById('nodeSearch').value = '';
document.getElementById('suggestions').style.display = 'none';
portnumDistributionChart(); // Reload full chart
highlightedIndex = -1;
break;
}
});
document.getElementById('nodeSearch').addEventListener('input', (e) => {
highlightedIndex = -1;
const matches = filterSuggestions(e.target.value);
showSuggestions(matches);
});
document.getElementById('clearFilterBtn').addEventListener('click', () => {
highlightedIndex = -1;
document.getElementById('nodeSearch').value = '';
document.getElementById('suggestions').style.display = 'none';
portnumDistributionChart(); // Reload full chart
});
// Main chart function
async function portnumDistributionChart(nodeId = null) {
const canvas = document.getElementById('portnumDistribution');
const ctx = canvas.getContext('2d');
// Loading message
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = '16px Arial';
ctx.fillStyle = 'gray';
ctx.textAlign = 'center';
ctx.fillText('Loading data...', canvas.width / 2, canvas.height / 2);
try {
const response = await fetch('https://map.sthlm-mesh.se/api/v1/stats/portnum-counts');
const url = nodeId
? `https://map.sthlm-mesh.se/api/v1/stats/portnum-counts?nodeId=${nodeId}`
: 'https://map.sthlm-mesh.se/api/v1/stats/portnum-counts';
const response = await fetch(url);
const data = await response.json();
const labels = data.map(entry => entry.label.replace(/_APP$/, ''));
const counts = data.map(entry => entry.count);
// Optional: dynamically size the container
const backgroundColors = counts.map(count => {
if (nodeId) {
// Filtered chart: use tighter thresholds
if (count < 50) return '#7EB26D'; // Green
if (count < 100) return '#EAB839'; // Yellow
return '#BF1B00'; // Red
} else {
// Global chart: use broader thresholds
if (count < 500) return '#7EB26D'; // Green
if (count < 2000) return '#EAB839'; // Yellow
return '#BF1B00'; // Red
}
});
const chartContainer = document.getElementById('portnumDistributionContainer');
chartContainer.style.height = `${labels.length * 35}px`;
const backgroundColors = counts.map(count => {
if (count < 500) return '#7EB26D'; // Green
if (count < 2000) return '#EAB839'; // Yellow
return '#BF1B00'; // Red
});
if (chartInstance) chartInstance.destroy();
new Chart(ctx, {
chartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
@@ -59,4 +178,4 @@ async function portnumDistributionChart() {
}
}
portnumDistributionChart();
fetchNodes().then(() => portnumDistributionChart());