Added channel selection on Top users statistics

This commit is contained in:
Pablo Revilla
2025-04-17 14:31:25 -07:00
parent d1fb800534
commit 94dba95489

View File

@@ -1,160 +1,150 @@
{% extends "base.html" %}
{% block css %}
/* General table styling */
<style>
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
table th, table td {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
cursor: pointer; /* Makes the column headers clickable */
cursor: pointer;
}
table th {
background-color: #333;
color: #fff;
font-weight: bold;
}
table tbody tr:nth-child(odd) {
background-color: #272b2f; /* Slightly lighter than #2a2a2a */
background-color: #272b2f;
}
table tbody tr:nth-child(even) {
background-color: #212529; /* Slightly lighter than #181818 */
background-color: #212529;
}
table tbody tr:hover {
background-color: #555; /* Light hover effect */
background-color: #555;
}
table td {
color: #ddd;
}
table th, table td {
border-radius: 8px;
}
/* Responsive Table for smaller screens */
@media (max-width: 768px) {
@media (max-width: 600px) {
table th, table td {
padding: 8px;
}
}
h1 {
text-align: center;
color: #ddd;
margin-top: 20px;
margin-bottom: 20px;
}
/* Bell curve chart container */
#bellCurveChart {
height: 400px;
width: 100%;
width: 90%;
margin-top: 30px;
}
#stats {
text-align: center;
margin-top: 20px;
color: #fff;
}
#channelFilter {
display: block;
margin: 0 auto 20px auto;
padding: 8px;
font-size: 16px;
border-radius: 6px;
background-color: #333;
color: white;
border: 1px solid #666;
}
</style>
{% endblock %}
{% block body %}
<h1>Top Traffic Nodes</h1>
<h1>Top Traffic Nodes</h1>
<!-- Chart Description -->
<div id="stats">
<!-- Channel Filter -->
<select id="channelFilter">
<option value="all">All Channels</option>
</select>
<!-- Chart Description -->
<div id="stats">
<p>This chart shows a bell curve (normal distribution) based on the total <strong>"Times Seen"</strong> values for all nodes. It helps visualize how frequently nodes are heard, relative to the average.</p>
<p> This "Time Seen" value is the closest that we can get to Mesh utilization by node.</p>
</div>
<div id="stats">
<p>This "Time Seen" value is the closest that we can get to Mesh utilization by node.</p>
<p><strong>Mean: </strong><span id="mean"></span> - <strong>Standard Deviation: </strong><span id="stdDev"></span></p>
</div>
</div>
<!-- Bell Curve Chart -->
<div id="bellCurveChart"></div>
<!-- Chart -->
<div id="bellCurveChart"></div>
{% if nodes %}
<!-- Table -->
{% if nodes %}
<div class="container">
<table id="trafficTable">
<thead>
<tr>
<th onclick="sortTable(0)">Long Name</th>
<th onclick="sortTable(1)">Short Name</th>
<th onclick="sortTable(2)">Channel</th>
<th onclick="sortTable(3)">Packets Sent</th>
<th onclick="sortTable(4)">Times Seen</th>
</tr>
</thead>
<tbody>
{% for node in nodes %}
<tr>
<td><a href="/packet_list/{{ node.node_id }}">{{ node.long_name }}</a></td>
<td>{{ node.short_name }}</td>
<td>{{ node.channel }}</td>
<td><a href="/top?node_id={{ node.node_id }}">{{ node.total_packets_sent }}</a></td>
<td>{{ node.total_times_seen }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table id="trafficTable">
<thead>
<tr>
<th onclick="sortTable(0)">Long Name</th>
<th onclick="sortTable(1)">Short Name</th>
<th onclick="sortTable(2)">Channel</th>
<th onclick="sortTable(3)">Packets Sent</th>
<th onclick="sortTable(4)">Times Seen</th>
</tr>
</thead>
<tbody>
{% for node in nodes %}
<tr>
<td><a href="/packet_list/{{ node.node_id }}">{{ node.long_name }}</a></td>
<td>{{ node.short_name }}</td>
<td>{{ node.channel }}</td>
<td><a href="/top?node_id={{ node.node_id }}">{{ node.total_packets_sent }}</a></td>
<td>{{ node.total_times_seen }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
{% else %}
<p style="text-align: center;">No top traffic nodes available.</p>
{% endif %}
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.2/dist/echarts.min.js"></script>
<script>
// Get the nodes data from the backend
const nodes = {{ nodes | tojson }};
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.2/dist/echarts.min.js"></script>
<script>
const nodes = {{ nodes | tojson }};
let myChart;
// Extract total_times_seen values
const timesSeenValues = nodes.map(node => node.total_times_seen);
function normalDistribution(x, mean, stdDev) {
return (1 / (stdDev * Math.sqrt(2 * Math.PI))) * Math.exp(-0.5 * Math.pow((x - mean) / stdDev, 2));
}
// Calculate mean and standard deviation
const mean = timesSeenValues.reduce((sum, value) => sum + value, 0) / timesSeenValues.length;
const stdDev = Math.sqrt(timesSeenValues.reduce((sum, value) => sum + Math.pow(value - mean, 2), 0) / timesSeenValues.length);
function calculateStats(filteredNodes) {
const values = filteredNodes.map(n => n.total_times_seen);
const mean = values.reduce((sum, v) => sum + v, 0) / values.length || 0;
const stdDev = Math.sqrt(values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length) || 0;
return { mean, stdDev, values };
}
// Display mean and standard deviation
document.getElementById('mean').textContent = mean.toFixed(2);
document.getElementById('stdDev').textContent = stdDev.toFixed(2);
// Function to calculate the normal distribution value
function normalDistribution(x, mean, stdDev) {
return (1 / (stdDev * Math.sqrt(2 * Math.PI))) * Math.exp(-0.5 * Math.pow((x - mean) / stdDev, 2));
}
// Generate x and y values for the bell curve
const xData = [];
const yData = [];
const min = Math.min(...timesSeenValues);
const max = Math.max(...timesSeenValues);
const step = (max - min) / 100;
function renderChart(values, mean, stdDev) {
const min = Math.min(...values);
const max = Math.max(...values);
const step = (max - min) / 100 || 1;
const xData = [], yData = [];
for (let x = min; x <= max; x += step) {
xData.push(x);
yData.push(normalDistribution(x, mean, stdDev));
}
// ECharts setup
const myChart = echarts.init(document.getElementById('bellCurveChart'));
const option = {
animation: false,
tooltip: {
trigger: 'axis'
},
tooltip: { trigger: 'axis' },
xAxis: {
name: 'Total Times Seen',
type: 'value',
@@ -163,46 +153,83 @@ h1 {
},
yAxis: {
name: 'Probability Density',
type: 'value',
type: 'value'
},
series: [{
data: xData.map((x, i) => [x, yData[i]]),
type: 'line',
smooth: true,
color: 'blue',
lineStyle: {
width: 3
}
lineStyle: { width: 3 }
}]
};
myChart.setOption(option);
}
// Function to sort the table columns
function sortTable(n) {
const table = document.getElementById("trafficTable");
const rows = Array.from(table.rows).slice(1); // Skip header
const isNumeric = !isNaN(rows[0].cells[n].innerText);
let sortedRows;
function filterByChannel() {
const selected = document.getElementById('channelFilter').value;
const filtered = selected === 'all' ? nodes : nodes.filter(n => n.channel === selected);
if (isNumeric) {
sortedRows = rows.sort((a, b) => {
return parseFloat(a.cells[n].innerText) - parseFloat(b.cells[n].innerText);
});
} else {
sortedRows = rows.sort((a, b) => {
return a.cells[n].innerText.toLowerCase().localeCompare(b.cells[n].innerText.toLowerCase());
});
}
if (table.rows[0].cells[n].getAttribute('data-sort-direction') === 'asc') {
sortedRows.reverse();
table.rows[0].cells[n].setAttribute('data-sort-direction', 'desc');
} else {
table.rows[0].cells[n].setAttribute('data-sort-direction', 'asc');
}
sortedRows.forEach(row => table.tBodies[0].appendChild(row));
// Update table
const tbody = document.querySelector('#trafficTable tbody');
tbody.innerHTML = '';
for (const node of filtered) {
const row = `<tr>
<td><a href="/packet_list/${node.node_id}">${node.long_name}</a></td>
<td>${node.short_name}</td>
<td>${node.channel}</td>
<td><a href="/top?node_id=${node.node_id}">${node.total_packets_sent}</a></td>
<td>${node.total_times_seen}</td>
</tr>`;
tbody.insertAdjacentHTML('beforeend', row);
}
</script>
// Recalculate stats & chart
const { mean, stdDev, values } = calculateStats(filtered);
document.getElementById('mean').textContent = mean.toFixed(2);
document.getElementById('stdDev').textContent = stdDev.toFixed(2);
renderChart(values, mean, stdDev);
}
function populateChannelFilter() {
const select = document.getElementById('channelFilter');
const channels = [...new Set(nodes.map(n => n.channel))].sort();
for (const ch of channels) {
const opt = document.createElement('option');
opt.value = ch;
opt.textContent = ch;
select.appendChild(opt);
}
select.addEventListener('change', filterByChannel);
}
function sortTable(n) {
const table = document.getElementById("trafficTable");
const rows = Array.from(table.rows).slice(1);
const isNumeric = !isNaN(rows[0].cells[n].innerText);
const dir = table.rows[0].cells[n].getAttribute('data-sort-direction') === 'asc' ? 'desc' : 'asc';
table.rows[0].cells[n].setAttribute('data-sort-direction', dir);
rows.sort((a, b) => {
const valA = isNumeric ? parseFloat(a.cells[n].innerText) : a.cells[n].innerText.toLowerCase();
const valB = isNumeric ? parseFloat(b.cells[n].innerText) : b.cells[n].innerText.toLowerCase();
return (valA > valB ? 1 : -1) * (dir === 'asc' ? 1 : -1);
});
rows.forEach(row => table.tBodies[0].appendChild(row));
}
function init() {
myChart = echarts.init(document.getElementById('bellCurveChart'));
populateChannelFilter();
filterByChannel();
window.addEventListener('resize', () => {
if (myChart) myChart.resize();
});
}
document.addEventListener('DOMContentLoaded', init);
</script>
{% endblock %}