add API code for /api/packets

This commit is contained in:
Pablo Revilla
2025-08-11 15:50:47 -07:00
parent a8313cd921
commit 32dd6b5c96
2 changed files with 289 additions and 31 deletions
+210
View File
@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Documentation - Packet Stats</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
<style>
body {
margin: 0;
background: #ffffff;
color: #000000;
}
#swagger-ui {
background: #ffffff;
color: #000000;
}
/* Override Swagger UI colors for white background */
.swagger-ui {
background-color: #ffffff !important;
color: #000000 !important;
}
.swagger-ui .topbar,
.swagger-ui .info,
.swagger-ui .opblock-summary-description,
.swagger-ui .parameters-col_description,
.swagger-ui .response-col_description {
color: #000000 !important;
}
.swagger-ui .opblock {
background-color: #f9f9f9 !important;
border-color: #ddd !important;
}
.swagger-ui .opblock-summary {
background-color: #eaeaea !important;
color: #000 !important;
}
.swagger-ui .opblock-section-header {
color: #000 !important;
}
.swagger-ui .parameters,
.swagger-ui .response {
background-color: #fafafa !important;
color: #000 !important;
}
.swagger-ui table {
color: #000 !important;
}
.swagger-ui a {
color: #1a0dab !important; /* classic link blue */
}
.swagger-ui input,
.swagger-ui select,
.swagger-ui textarea {
background-color: #fff !important;
color: #000 !important;
border: 1px solid #ccc !important;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
const spec = {
openapi: "3.0.0",
info: {
title: "Packet Statistics API",
version: "1.0.0",
description: "API for retrieving packet statistics over a given period with optional filters."
},
paths: {
"/api/stats": {
get: {
summary: "Get packet statistics",
description: "Returns packet statistics for a given period type and length, with optional filters.",
parameters: [
{
name: "period_type",
in: "query",
required: false,
description: "Type of period to group by (`hour` or `day`). Default is `hour`.",
schema: {
type: "string",
enum: ["hour", "day"]
}
},
{
name: "length",
in: "query",
required: false,
description: "Number of periods to include. Default is 24.",
schema: {
type: "integer",
default: 24
}
},
{
name: "channel",
in: "query",
required: false,
description: "Filter by channel name.",
schema: {
type: "string"
}
},
{
name: "portnum",
in: "query",
required: false,
description: "Filter by port number.",
schema: {
type: "integer"
}
},
{
name: "to_node",
in: "query",
required: false,
description: "Filter by destination node ID.",
schema: {
type: "integer"
}
},
{
name: "from_node",
in: "query",
required: false,
description: "Filter by source node ID.",
schema: {
type: "integer"
}
}
],
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
hourly: {
type: "object",
properties: {
period_type: { type: "string" },
length: { type: "integer" },
filters: { type: "object" },
data: {
type: "array",
items: {
type: "object",
properties: {
period: { type: "string", example: "2025-08-06 19:00" },
node_id: { type: "integer" },
long_name: { type: "string" },
short_name: { type: "string" },
packets: { type: "integer" }
}
}
}
}
}
}
}
}
}
},
"400": {
description: "Invalid request parameters",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string" }
}
}
}
}
}
}
}
}
}
};
window.onload = () => {
SwaggerUIBundle({
spec,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
};
</script>
</body>
</html>
+79 -31
View File
@@ -50,30 +50,49 @@
{% block body %}
<div class="main-container">
<h2 class="main-header">Mesh Statistics</h2>
<h2 class="main-header">Mesh Statistics - Hourly Packet Counts (Last 24 Hours)</h2>
<!-- Hourly Chart -->
<!-- Overall hourly packets -->
<div class="card-section">
<p class="section-header">Packets per Hour (Last 24 Hours)</p>
<div id="chart_hourly" class="chart"></div>
<p class="section-header">Packets per Hour - All Ports</p>
<div id="chart_hourly_all" class="chart"></div>
</div>
<!-- PortNum 1 Hourly Chart -->
<!-- Hourly charts by portnum -->
<div class="card-section">
<p class="section-header">Packets per Hour for Text Messages (Last 24 Hours)</p>
<p class="section-header">Packets per Hour - Text Messages (Port 1)</p>
<div id="chart_portnum_1" class="chart"></div>
</div>
<!-- Daily Chart -->
<div class="card-section">
<p class="section-header">Packets per Day (Last 14 Days)</p>
<div id="chart_daily" class="chart"></div>
<p class="section-header">Packets per Hour - Position (Port 3)</p>
<div id="chart_portnum_3" class="chart"></div>
</div>
<!-- PortNum 1 Daily Chart -->
<div class="card-section">
<p class="section-header">Packets per Day for Text Messages (Last 14 Days)</p>
<div id="chart_daily_portnum_1" class="chart"></div>
<p class="section-header">Packets per Hour - Node Info (Port 4)</p>
<div id="chart_portnum_4" class="chart"></div>
</div>
<div class="card-section">
<p class="section-header">Packets per Hour - Telemetry (Port 67)</p>
<div id="chart_portnum_67" class="chart"></div>
</div>
<div class="card-section">
<p class="section-header">Packets per Hour - Traceroute (Port 70)</p>
<div id="chart_portnum_70" class="chart"></div>
</div>
<div class="card-section">
<p class="section-header">Packets per Hour - Neighbor Info (Port 71)</p>
<div id="chart_portnum_71" class="chart"></div>
</div>
<!-- Daily packets chart -->
<div class="card-section">
<p class="section-header">Packets per Day - All Ports (Last 14 Days)</p>
<div id="chart_daily_all" class="chart"></div>
</div>
</div>
@@ -86,7 +105,7 @@
}
const res = await fetch(url);
if (!res.ok) {
console.error(`Failed to fetch ${period_type} stats:`, res.status, res.statusText);
console.error(`Failed to fetch ${period_type} stats portnum ${portnum}:`, res.status, res.statusText);
return [];
}
const json = await res.json();
@@ -97,20 +116,31 @@
}
}
let chartHourly = null;
// Chart references
let chartHourlyAll = null;
let chartPortnum1 = null;
let chartDaily = null;
let chartDailyPortnum1 = null;
let chartPortnum3 = null;
let chartPortnum4 = null;
let chartPortnum67 = null;
let chartPortnum70 = null;
let chartPortnum71 = null;
let chartDailyAll = null;
function renderChart(domId, data, type, color, isHourly) {
const el = document.getElementById(domId);
if (!el) return;
const chart = echarts.init(el);
if (domId === 'chart_hourly') chartHourly = chart;
else if (domId === 'chart_portnum_1') chartPortnum1 = chart;
else if (domId === 'chart_daily') chartDaily = chart;
else if (domId === 'chart_daily_portnum_1') chartDailyPortnum1 = chart;
switch(domId) {
case 'chart_hourly_all': chartHourlyAll = chart; break;
case 'chart_portnum_1': chartPortnum1 = chart; break;
case 'chart_portnum_3': chartPortnum3 = chart; break;
case 'chart_portnum_4': chartPortnum4 = chart; break;
case 'chart_portnum_67': chartPortnum67 = chart; break;
case 'chart_portnum_70': chartPortnum70 = chart; break;
case 'chart_portnum_71': chartPortnum71 = chart; break;
case 'chart_daily_all': chartDailyAll = chart; break;
}
const periods = data.map(d => {
const p = (d && (d.period || d.period === 0)) ? d.period.toString() : '';
@@ -151,24 +181,42 @@
}
async function init() {
const hourlyData = await fetchStats('hour', 24);
renderChart('chart_hourly', hourlyData, 'bar', '#03dac6', true);
// Overall hourly packets
const hourlyAllData = await fetchStats('hour', 24);
renderChart('chart_hourly_all', hourlyAllData, 'bar', '#03dac6', true);
const portnum1Data = await fetchStats('hour', 24, 1);
renderChart('chart_portnum_1', portnum1Data, 'bar', '#ff5722', true);
// Hourly packets by portnum in parallel
const portnums = [1, 3, 4, 67, 70, 71];
const colors = ['#ff5722', '#2196f3', '#9c27b0', '#ffeb3b', '#795548', '#4caf50'];
const domIds = [
'chart_portnum_1',
'chart_portnum_3',
'chart_portnum_4',
'chart_portnum_67',
'chart_portnum_70',
'chart_portnum_71'
];
const dailyData = await fetchStats('day', 14);
renderChart('chart_daily', dailyData, 'line', '#ffeb3b', false);
const allData = await Promise.all(portnums.map(pn => fetchStats('hour', 24, pn)));
const dailyPortnum1Data = await fetchStats('day', 14, 1);
renderChart('chart_daily_portnum_1', dailyPortnum1Data, 'bar', '#ff7043', false);
for (let i = 0; i < portnums.length; i++) {
renderChart(domIds[i], allData[i], 'bar', colors[i], true);
}
// Daily packets (all ports, last 14 days)
const dailyAllData = await fetchStats('day', 14);
renderChart('chart_daily_all', dailyAllData, 'line', '#66bb6a', false);
}
window.addEventListener('resize', () => {
if (chartHourly) chartHourly.resize();
if (chartHourlyAll) chartHourlyAll.resize();
if (chartPortnum1) chartPortnum1.resize();
if (chartDaily) chartDaily.resize();
if (chartDailyPortnum1) chartDailyPortnum1.resize();
if (chartPortnum3) chartPortnum3.resize();
if (chartPortnum4) chartPortnum4.resize();
if (chartPortnum67) chartPortnum67.resize();
if (chartPortnum70) chartPortnum70.resize();
if (chartPortnum71) chartPortnum71.resize();
if (chartDailyAll) chartDailyAll.resize();
});
init();