From fc01cb6a8539ebce638d832661a5d158679cdba3 Mon Sep 17 00:00:00 2001 From: Pablo Revilla Date: Fri, 21 Nov 2025 13:47:22 -0800 Subject: [PATCH] Finishing up all the pages for the 3.0 release. Now all pages are functional. --- meshview/templates/buttons.html | 16 -- meshview/templates/datalist.html | 7 - meshview/templates/new_packet.html | 48 +++-- meshview/templates/node.html | 234 ------------------------ meshview/templates/node_traffic.html | 109 ------------ meshview/templates/packet_details.html | 132 -------------- meshview/templates/packet_index.html | 24 --- meshview/templates/packet_list.html | 7 - meshview/templates/search.html | 21 --- meshview/templates/search_form.html | 44 ----- meshview/templates/stats.html | 236 ++++++++++++++++++++++--- meshview/templates/traceroute.html | 94 ---------- meshview/web.py | 89 +--------- 13 files changed, 251 insertions(+), 810 deletions(-) delete mode 100644 meshview/templates/buttons.html delete mode 100644 meshview/templates/datalist.html delete mode 100644 meshview/templates/node.html delete mode 100644 meshview/templates/node_traffic.html delete mode 100644 meshview/templates/packet_details.html delete mode 100644 meshview/templates/packet_index.html delete mode 100644 meshview/templates/packet_list.html delete mode 100644 meshview/templates/search.html delete mode 100644 meshview/templates/search_form.html delete mode 100644 meshview/templates/traceroute.html diff --git a/meshview/templates/buttons.html b/meshview/templates/buttons.html deleted file mode 100644 index 9959bff..0000000 --- a/meshview/templates/buttons.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - TX/RX - - - Uplinked - -
diff --git a/meshview/templates/datalist.html b/meshview/templates/datalist.html deleted file mode 100644 index 5c65c4b..0000000 --- a/meshview/templates/datalist.html +++ /dev/null @@ -1,7 +0,0 @@ - - {% for option in node_options %} - - {% endfor %} - diff --git a/meshview/templates/new_packet.html b/meshview/templates/new_packet.html index a0c354e..63a5971 100644 --- a/meshview/templates/new_packet.html +++ b/meshview/templates/new_packet.html @@ -48,10 +48,10 @@ display: none; } -/* --- Source Marker (bigger, no animation) --- */ +/* --- Source Marker: 2px bigger than gateway (26px vs 24px) --- */ .source-marker { - width: 36px; - height: 36px; + width: 26px; + height: 26px; background: rgba(255,0,0,0.55); border: 3px solid #ff0000; border-radius: 50%; @@ -268,17 +268,17 @@ document.addEventListener("DOMContentLoaded", async () => { const allBounds = []; - /* ------------------------------------------------------- - PACKET SOURCE MARKER: bigger red circle, no animation - -------------------------------------------------------- */ + /* -------------------------------------------------------------------- + SOURCE MARKER (26px vs 24px) + --------------------------------------------------------------------- */ if (lat && lon) { allBounds.push([lat, lon]); const sourceIcon = L.divIcon({ html: `
`, className: "", - iconSize: [36, 36], - iconAnchor: [18, 18] + iconSize: [26, 26], + iconAnchor: [13, 13] }); const sourceMarker = L.marker([lat, lon], { @@ -303,14 +303,25 @@ document.addEventListener("DOMContentLoaded", async () => { /* ------------------------- Helpers -------------------------- */ - function hopColor(hop){ - const c=[ - "#ff3b30","#ff6b22","#ff9f0c", - "#ffd60a","#87d957","#57d9c4","#3db2ff" + + /* 0 = warmest → 7 = coldest */ + function hopColor(hopValue){ + const colors = [ + "#ff3b30", // 0 red + "#ff6b22", + "#ff9f0c", + "#ffd60a", + "#87d957", + "#57d9c4", + "#3db2ff", + "#1e63ff" // 7 deep cold blue ]; - if(!hop||hop<1)return"#aaa"; - if(hop>7)hop=7; - return c[hop-1]; + + let h = Number(hopValue); + if (isNaN(h)) return "#aaa"; + if (h < 0) h = 0; + if (h > 7) h = 7; + return colors[h]; } function haversine(lat1,lon1,lat2,lon2){ @@ -360,9 +371,9 @@ document.addEventListener("DOMContentLoaded", async () => { const start = Number(s.hop_start ?? 0); const limit = Number(s.hop_limit ?? 0); - const hopValue = limit - start; + const hopValue = start - limit; - const color = hopColor(Number(s.hop_limit)); + const color = hopColor(hopValue); const iconHtml = `
{ Signal
RSSI: ${s.rx_rssi ?? "—"}
SNR: ${s.rx_snr ?? "—"}

- Hop Distance
- hop_limit (${limit}) − hop_start (${start}) = ${hopValue}

+ Hops: ${hopValue}
Distance
${ distKm diff --git a/meshview/templates/node.html b/meshview/templates/node.html deleted file mode 100644 index ef8ca3c..0000000 --- a/meshview/templates/node.html +++ /dev/null @@ -1,234 +0,0 @@ -{% extends "base.html" %} - -{% block css %} - /* Styles for the node info card */ - #node_info { - height: 100%; - } - - /* Styles for the map */ - #map { - height: 100%; - min-height: 400px; - } - - /* Styles for packet details section */ - #packet_details { - height: 95vh; - overflow: scroll; - top: 3em; - } - - /* Ensure inline display for details */ - div.tab-pane > dl { - display: inline-block; - } - - /* Set the maximum width of the page to 900px */ - .container { - max-width: 900px; - margin: 0 auto; /* Center the content horizontally */ - } -{% endblock %} - -{% block body %} -
-
-
-
- -
- {% if node %} -
- {{node.short_name}} -

{{node.long_name}}

-
-
-
- {% if trace %} -
- {% endif %} -
NodeID
-
{{node.node_id|node_id_to_hex}}
-
Channel
-
{{node.channel}}
-
HW Model
-
{{node.hw_model}}
-
Role
-
{{node.role}}
- {% if node.firmware %} -
Firmware
-
{{node.firmware}}
- {% endif %} -
- Get node traffic totals - {% include "node_graphs.html" %} -
- {% else %} -
- A NodeInfo has not been seen. -
- {% endif %} -
-
-
- -
-
- -
-
- -
-
- {% include 'packet_list.html' %} -
- -
-
-
- - - -{% if trace %} - - -{% endif %} - -{% endblock %} \ No newline at end of file diff --git a/meshview/templates/node_traffic.html b/meshview/templates/node_traffic.html deleted file mode 100644 index 571114a..0000000 --- a/meshview/templates/node_traffic.html +++ /dev/null @@ -1,109 +0,0 @@ -{% extends "base.html" %} - -{% block css %} -.table-title { - font-size: 2rem; - text-align: center; - margin-bottom: 20px; - } - - .traffic-table { - width: 50%; - border-collapse: collapse; - margin: 0 auto; - font-family: Arial, sans-serif; - } - - .traffic-table th, - .traffic-table td { - padding: 10px 15px; - text-align: left; - border: 1px solid #474b4e; - } - - .traffic-table th { - background-color: #272b2f; - color: white; - } - - .traffic:nth-of-type(odd) { - background-color: #272b2f; /* Lighter than #2a2a2a */ - } - - .traffic { - border: 1px solid #474b4e; - padding: 8px; - margin-bottom: 4px; - border-radius: 8px; - } - - .traffic:nth-of-type(even) { - background-color: #212529; /* Slightly lighter than the previous #181818 */ - } - - .footer { - text-align: center; - margin-top: 20px; - } - -{% endblock %} - -{% block body %} -
-

- {% if traffic %} - {{ traffic[0].long_name }} (last 24 hours) - {% else %} - No Traffic Data Available - {% endif %} -

- - - - - - - - - {% for port in traffic %} - - - - - {% else %} - - - - {% endfor %} - -
Port NumberPacket Count
- {% if port.portnum == 1 %} - TEXT_MESSAGE_APP - {% elif port.portnum == 3 %} - POSITION_APP - {% elif port.portnum == 4 %} - NODEINFO_APP - {% elif port.portnum == 5 %} - ROUTING_APP - {% elif port.portnum == 8 %} - WAYPOINT_APP - {% elif port.portnum == 67 %} - TELEMETRY_APP - {% elif port.portnum == 70 %} - TRACEROUTE_APP - {% elif port.portnum == 71 %} - NEIGHBORINFO_APP - {% elif port.portnum == 73 %} - MAP_REPORT_APP - {% elif port.portnum == 0 %} - UNKNOWN_APP - {% else %} - {{ port.portnum }} - {% endif %} - {{ port.packet_count }}
No traffic data available for this node.
-
- - -{% endblock %} diff --git a/meshview/templates/packet_details.html b/meshview/templates/packet_details.html deleted file mode 100644 index b93aeec..0000000 --- a/meshview/templates/packet_details.html +++ /dev/null @@ -1,132 +0,0 @@ -
- -{% for seen in packets_seen %} -
-
- {{seen.node.long_name}}( - - {{seen.node_id|node_id_to_hex}} - - ) -
-
-
-
-
Import Time
-
{{seen.import_time.strftime('%-I:%M:%S %p - %m-%d-%Y')}}
-
rx_time
-
{{seen.rx_time|format_timestamp}}
-
hop_limit
-
{{seen.hop_limit}}
-
hop_start
-
{{seen.hop_start}}
-
channel
-
{{seen.channel}}
-
rx_snr
-
{{seen.rx_snr}}
-
rx_rssi
-
{{seen.rx_rssi}}
-
topic
-
{{seen.topic}}
-
-
-
-
-{% endfor %} - -{% if map_center %} - - -{% endif %} diff --git a/meshview/templates/packet_index.html b/meshview/templates/packet_index.html deleted file mode 100644 index 9d1dca8..0000000 --- a/meshview/templates/packet_index.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base.html" %} -{% block css %} - - /* Set the maximum width of the page to 900px */ - .container { - max-width: 900px; - margin: 0 auto; /* Center the content horizontally */ - } -{% endblock %} -{% block body %} -
-
-
- {% include 'packet.html' %} -
-
-
-
-
-{% endblock %} diff --git a/meshview/templates/packet_list.html b/meshview/templates/packet_list.html deleted file mode 100644 index 24f2f8c..0000000 --- a/meshview/templates/packet_list.html +++ /dev/null @@ -1,7 +0,0 @@ -
- {% for packet in packets %} - {% include 'packet.html' %} - {% else %} - No packets found. - {% endfor %} -
diff --git a/meshview/templates/search.html b/meshview/templates/search.html deleted file mode 100644 index 8c9d2e7..0000000 --- a/meshview/templates/search.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "base.html" %} - - -{% block body %} - -{% include "search_form.html" %} - - - -{% endblock %} diff --git a/meshview/templates/search_form.html b/meshview/templates/search_form.html deleted file mode 100644 index 94cc99c..0000000 --- a/meshview/templates/search_form.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
- - {% include "datalist.html" %} - {% set options = { - 1: "Text Message", - 3: "Position", - 4: "Node Info", - 67: "Telemetry", - 70: "Traceroute", - 71: "Neighbor Info", - } - %} - - -
-
diff --git a/meshview/templates/stats.html b/meshview/templates/stats.html index 828a8a1..29287fc 100644 --- a/meshview/templates/stats.html +++ b/meshview/templates/stats.html @@ -93,26 +93,31 @@ {% block body %}
-

Mesh Statistics - Summary (all available in Database)

+

+ Mesh Statistics - Summary (all available in Database) +

+

Total Nodes

-
{{ "{:,}".format(total_nodes) }}
+
0

Total Packets

-
{{ "{:,}".format(total_packets) }}
+
0

Total Packets Seen

-
{{ "{:,}".format(total_packets_seen) }}
+
0
-

Packets per Day - All Ports (Last 14 Days)

+

+ Packets per Day - All Ports (Last 14 Days) +

Total: 0
@@ -121,7 +126,9 @@
-

Packet Types - Last 24 Hours

+

+ Packet Types - Last 24 Hours +

@@ -131,7 +138,9 @@
-

Packets per Day - Text Messages (Port 1, Last 14 Days)

+

+ Packets per Day - Text Messages (Port 1, Last 14 Days) +

Total: 0
@@ -140,7 +149,9 @@
-

Packets per Hour - All Ports

+

+ Packets per Hour - All Ports +

Total: 0
@@ -148,7 +159,9 @@
-

Packets per Hour - Text Messages (Port 1)

+

+ Packets per Hour - Text Messages (Port 1) +

Total: 0
@@ -214,17 +227,123 @@ async function fetchStats(period_type,length,portnum=null,channel=null){ }catch{return [];} } -async function fetchNodes(){ try{ const res=await fetch("/api/nodes"); const json=await res.json(); return json.nodes||[];}catch{return [];} } -async function fetchChannels(){ try{ const res = await fetch("/api/channels"); const json = await res.json(); return json.channels || [];}catch{return [];} } +async function fetchNodes(){ + try{ + const res=await fetch("/api/nodes"); + const json=await res.json(); + return json.nodes||[]; + }catch{ + return []; + } +} -function processCountField(nodes,field){ const counts={}; nodes.forEach(n=>{ const key=n[field]||"Unknown"; counts[key]=(counts[key]||0)+1; }); return Object.entries(counts).map(([name,value])=>({name,value})); } -function updateTotalCount(domId,data){ const el=document.getElementById(domId); if(!el||!data.length) return; const total=data.reduce((acc,d)=>acc+(d.count??d.packet_count??0),0); el.textContent=`Total: ${total.toLocaleString()}`; } -function prepareTopN(data,n=20){ data.sort((a,b)=>b.value-a.value); let top=data.slice(0,n); if(data.length>n){ const otherValue=data.slice(n).reduce((sum,item)=>sum+item.value,0); top.push({name:"Other", value:otherValue}); } return top; } +async function fetchChannels(){ + try{ + const res = await fetch("/api/channels"); + const json = await res.json(); + return json.channels || []; + }catch{ + return []; + } +} + +function processCountField(nodes,field){ + const counts={}; + nodes.forEach(n=>{ + const key=n[field]||"Unknown"; + counts[key]=(counts[key]||0)+1; + }); + return Object.entries(counts).map(([name,value])=>({name,value})); +} + +function updateTotalCount(domId,data){ + const el=document.getElementById(domId); + if(!el||!data.length) return; + const total=data.reduce((acc,d)=>acc+(d.count??d.packet_count??0),0); + el.textContent=`Total: ${total.toLocaleString()}`; +} + +function prepareTopN(data,n=20){ + data.sort((a,b)=>b.value-a.value); + let top=data.slice(0,n); + if(data.length>n){ + const otherValue=data.slice(n).reduce((sum,item)=>sum+item.value,0); + top.push({name:"Other", value:otherValue}); + } + return top; +} // --- Chart Rendering --- -function renderChart(domId,data,type,color){ const el=document.getElementById(domId); if(!el) return; const chart=echarts.init(el); const periods=data.map(d=>(d.period??d.period===0)?d.period.toString():''); const counts=data.map(d=>d.count??d.packet_count??0); chart.setOption({backgroundColor:'#272b2f', tooltip:{trigger:'axis'}, grid:{left:'6%', right:'6%', bottom:'18%'}, xAxis:{type:'category', data:periods, axisLine:{lineStyle:{color:'#aaa'}}, axisLabel:{rotate:45,color:'#ccc'}}, yAxis:{type:'value', axisLine:{lineStyle:{color:'#aaa'}}, axisLabel:{color:'#ccc'}}, series:[{data:counts,type:type,smooth:type==='line',itemStyle:{color:color}, areaStyle:type==='line'?{}:undefined}]}); return chart; } +function renderChart(domId,data,type,color){ + const el=document.getElementById(domId); + if(!el) return; + const chart=echarts.init(el); + const periods=data.map(d=>(d.period??d.period===0)?d.period.toString():''); + const counts=data.map(d=>d.count??d.packet_count??0); + chart.setOption({ + backgroundColor:'#272b2f', + tooltip:{trigger:'axis'}, + grid:{left:'6%', right:'6%', bottom:'18%'}, + xAxis:{ + type:'category', + data:periods, + axisLine:{lineStyle:{color:'#aaa'}}, + axisLabel:{rotate:45,color:'#ccc'} + }, + yAxis:{ + type:'value', + axisLine:{lineStyle:{color:'#aaa'}}, + axisLabel:{color:'#ccc'} + }, + series:[{ + data:counts, + type:type, + smooth:type==='line', + itemStyle:{color:color}, + areaStyle:type==='line'?{}:undefined + }] + }); + return chart; +} -function renderPieChart(elId,data,name){ const el=document.getElementById(elId); if(!el) return; const chart=echarts.init(el); const top20=prepareTopN(data,20); chart.setOption({backgroundColor:"#272b2f", tooltip:{trigger:"item", formatter: params=>`${params.name}: ${Math.round(params.percent)}% (${params.value})`}, series:[{name:name, type:"pie", radius:["30%","70%"], center:["50%","50%"], avoidLabelOverlap:true, itemStyle:{borderRadius:6,borderColor:"#272b2f",borderWidth:2}, label:{show:true,formatter:"{b}\n{d}%", color:"#ccc", fontSize:10}, labelLine:{show:true,length:10,length2:6}, data:top20}]}); return chart; } +function renderPieChart(elId,data,name){ + const el=document.getElementById(elId); + if(!el) return; + const chart=echarts.init(el); + const top20=prepareTopN(data,20); + chart.setOption({ + backgroundColor:"#272b2f", + tooltip:{ + trigger:"item", + formatter: params=>`${params.name}: ${Math.round(params.percent)}% (${params.value})` + }, + series:[{ + name:name, + type:"pie", + radius:["30%","70%"], + center:["50%","50%"], + avoidLabelOverlap:true, + itemStyle:{ + borderRadius:6, + borderColor:"#272b2f", + borderWidth:2 + }, + label:{ + show:true, + formatter:"{b}\n{d}%", + color:"#ccc", + fontSize:10 + }, + labelLine:{ + show:true, + length:10, + length2:6 + }, + data:top20 + }] + }); + return chart; +} // --- Packet Type Pie Chart --- async function fetchPacketTypeBreakdown(channel=null) { @@ -234,8 +353,10 @@ async function fetchPacketTypeBreakdown(channel=null) { const total = (data || []).reduce((sum,d)=>sum+(d.count??d.packet_count??0),0); return {portnum: pn, count: total}; }); + const allData = await fetchStats('hour',24,null,channel); const totalAll = allData.reduce((sum,d)=>sum+(d.count??d.packet_count??0),0); + const results = await Promise.all(requests); const trackedTotal = results.reduce((sum,d)=>sum+d.count,0); const other = Math.max(totalAll - trackedTotal,0); @@ -250,40 +371,102 @@ let chartHwModel, chartRole, chartChannel; let chartPacketTypes; async function init(){ + // Channel selector const channels = await fetchChannels(); const select = document.getElementById("channelSelect"); - channels.forEach(ch=>{ const opt = document.createElement("option"); opt.value = ch; opt.textContent = ch; select.appendChild(opt); }); + channels.forEach(ch=>{ + const opt = document.createElement("option"); + opt.value = ch; + opt.textContent = ch; + select.appendChild(opt); + }); + // Daily all ports const dailyAllData=await fetchStats('day',14); updateTotalCount('total_daily_all',dailyAllData); chartDailyAll=renderChart('chart_daily_all',dailyAllData,'line','#66bb6a'); + // Daily port 1 const dailyPort1Data=await fetchStats('day',14,1); updateTotalCount('total_daily_portnum_1',dailyPort1Data); chartDailyPortnum1=renderChart('chart_daily_portnum_1',dailyPort1Data,'bar','#ff5722'); + // Hourly all ports const hourlyAllData=await fetchStats('hour',24); updateTotalCount('total_hourly_all',hourlyAllData); chartHourlyAll=renderChart('chart_hourly_all',hourlyAllData,'bar','#03dac6'); + // Hourly per port 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 totalIds=['total_portnum_1','total_portnum_3','total_portnum_4','total_portnum_67','total_portnum_70','total_portnum_71']; - const allData=await Promise.all(portnums.map(pn=>fetchStats('hour',24,pn))); - for(let i=0;ifetchStats('hour',24,pn))); + for(let i=0;id.count>0).map(d=>({ name: d.portnum==="other" ? "Other" : (PORTNUM_LABELS[d.portnum]||`Port ${d.portnum}`), value: d.count })); + const formatted = packetTypesData + .filter(d=>d.count>0) + .map(d=>({ + name: d.portnum==="other" + ? "Other" + : (PORTNUM_LABELS[d.portnum]||`Port ${d.portnum}`), + value: d.count + })); chartPacketTypes = renderPieChart("chart_packet_types",formatted,"Packet Types (Last 24h)"); + + // Total packet + total seen from /api/stats/count + try { + const countsRes = await fetch("/api/stats/count"); + if (countsRes.ok) { + const countsJson = await countsRes.json(); + const elPackets = document.getElementById("summary_packets"); + const elSeen = document.getElementById("summary_seen"); + if (elPackets) { + elPackets.textContent = (countsJson.total_packets || 0).toLocaleString(); + } + if (elSeen) { + elSeen.textContent = (countsJson.total_seen || 0).toLocaleString(); + } + } + } catch (err) { + console.error("Failed to load /api/stats/count:", err); + } } -window.addEventListener('resize',()=>{ [chartHourlyAll,chartPortnum1,chartPortnum3,chartPortnum4,chartPortnum67,chartPortnum70,chartPortnum71, chartDailyAll,chartDailyPortnum1,chartHwModel,chartRole,chartChannel,chartPacketTypes].forEach(c=>c?.resize()); }); +window.addEventListener('resize',()=>{ + [ + chartHourlyAll, + chartPortnum1, + chartPortnum3, + chartPortnum4, + chartPortnum67, + chartPortnum70, + chartPortnum71, + chartDailyAll, + chartDailyPortnum1, + chartHwModel, + chartRole, + chartChannel, + chartPacketTypes + ].forEach(c=>c?.resize()); +}); const modal=document.getElementById("chartModal"); const modalChartEl=document.getElementById("modalChart"); @@ -345,11 +528,19 @@ document.querySelectorAll(".export-btn").forEach(btn=>{ document.getElementById("channelSelect").addEventListener("change", async (e)=>{ const channel = e.target.value; const packetTypesData = await fetchPacketTypeBreakdown(channel); - const formatted = packetTypesData.filter(d=>d.count>0).map(d=>({ name: d.portnum==="other" ? "Other" : (PORTNUM_LABELS[d.portnum]||`Port ${d.portnum}`), value: d.count })); + const formatted = packetTypesData + .filter(d=>d.count>0) + .map(d=>({ + name: d.portnum==="other" + ? "Other" + : (PORTNUM_LABELS[d.portnum]||`Port ${d.portnum}`), + value: d.count + })); chartPacketTypes?.dispose(); chartPacketTypes = renderPieChart("chart_packet_types",formatted,"Packet Types (Last 24h)"); }); +// Kick everything off init(); // --- Load config and translations --- @@ -383,6 +574,5 @@ async function loadConfigAndTranslations() { // Call after init loadConfigAndTranslations(); - {% endblock %} diff --git a/meshview/templates/traceroute.html b/meshview/templates/traceroute.html deleted file mode 100644 index c2e329a..0000000 --- a/meshview/templates/traceroute.html +++ /dev/null @@ -1,94 +0,0 @@ -{% block head %} - -{% endblock %} - -{% block body %} -
- - -{% endblock %} diff --git a/meshview/web.py b/meshview/web.py index b71816c..6cd61c7 100644 --- a/meshview/web.py +++ b/meshview/web.py @@ -269,6 +269,14 @@ async def top(request): content_type="text/html", ) +@routes.get("/stats") +async def stats(request): + template = env.get_template("stats.html") + return web.Response( + text=template.render(), + content_type="text/html", + ) + # Keep !! @routes.get("/graph/traceroute/{packet_id}") @@ -377,7 +385,7 @@ async def graph_traceroute(request): content_type="image/svg+xml", ) - +''' @routes.get("/stats") async def stats(request): try: @@ -399,86 +407,7 @@ async def stats(request): status=500, content_type="text/plain", ) - - ''' -@routes.get("/top") -async def top(request): - import time - - try: - # Check if performance metrics should be displayed - show_perf = request.query.get("perf", "").lower() in ("true", "1", "yes") - - # Start overall timing - start_time = time.perf_counter() - timing_data = None - - node_id = request.query.get("node_id") # Get node_id from the URL query parameters - - if node_id: - # If node_id is provided, fetch traffic data for the specific node - db_start = time.perf_counter() - node_traffic = await store.get_node_traffic(int(node_id)) - db_time = time.perf_counter() - db_start - - template = env.get_template("node_traffic.html") - html_content = template.render( - traffic=node_traffic, node_id=node_id, site_config=CONFIG - ) - else: - # Otherwise, fetch top traffic nodes as usual - db_start = time.perf_counter() - top_nodes = await store.get_top_traffic_nodes() - db_time = time.perf_counter() - db_start - - # Data processing timing - process_start = time.perf_counter() - - # Count records processed - total_packets = sum(node.get('total_packets_sent', 0) for node in top_nodes) - total_seen = sum(node.get('total_times_seen', 0) for node in top_nodes) - - process_time = time.perf_counter() - process_start - - # Calculate total time - total_time = time.perf_counter() - start_time - - # Only include timing_data if perf parameter is set - if show_perf: - timing_data = { - 'db_query_ms': f"{db_time * 1000:.2f}", - 'processing_ms': f"{process_time * 1000:.2f}", - 'total_ms': f"{total_time * 1000:.2f}", - 'node_count': len(top_nodes), - 'total_packets': total_packets, - 'total_seen': total_seen, - } - - template = env.get_template("top.html") - html_content = template.render( - nodes=top_nodes, - timing_data=timing_data, - site_config=CONFIG, - SOFTWARE_RELEASE=SOFTWARE_RELEASE, - ) - - return web.Response( - text=html_content, - content_type="text/html", - ) - except Exception as e: - logger.error(f"Error in /top: {e}") - template = env.get_template("error.html") - rendered = template.render( - error_message="An error occurred in /top", - error_details=traceback.format_exc(), - site_config=CONFIG, - SOFTWARE_RELEASE=SOFTWARE_RELEASE, - ) - return web.Response(text=rendered, status=500, content_type="text/html") -''' - async def run_server(): # Wait for database migrations to complete before starting web server