mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-03-04 23:27:46 +01:00
Added more analytics by reporting on the number and kinds of packets each node sends in a 24 hour period.
This commit is contained in:
@@ -3,6 +3,7 @@ from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import lazyload
|
||||
from meshview import database
|
||||
from meshview.models import Packet, PacketSeen, Node, Traceroute
|
||||
from sqlalchemy import text
|
||||
|
||||
async def get_node(node_id):
|
||||
async with database.async_session() as session:
|
||||
@@ -211,6 +212,68 @@ async def get_nodes_mediumslow():
|
||||
|
||||
return result.scalars()
|
||||
|
||||
async def get_top_traffic_nodes():
|
||||
async with database.async_session() as session:
|
||||
result = await session.execute(text("""
|
||||
SELECT
|
||||
n.node_id,
|
||||
n.long_name,
|
||||
n.role,
|
||||
COUNT(p.id) AS packet_count
|
||||
FROM
|
||||
packet p
|
||||
JOIN
|
||||
node n
|
||||
ON
|
||||
p.from_node_id = n.node_id
|
||||
WHERE
|
||||
p.import_time >= DATETIME('now', '-1 day')
|
||||
GROUP BY
|
||||
n.long_name, n.role
|
||||
ORDER BY
|
||||
packet_count DESC
|
||||
LIMIT 100;
|
||||
"""))
|
||||
|
||||
return result.fetchall() # Returns a list of tuples
|
||||
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
|
||||
async def get_node_traffic(node_id: int):
|
||||
try:
|
||||
async with database.async_session() as session:
|
||||
result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
node.long_name, packet.portnum,
|
||||
COUNT(*) AS packet_count
|
||||
FROM packet
|
||||
JOIN node ON packet.from_node_id = node.node_id OR packet.to_node_id = node.node_id
|
||||
WHERE node.node_id = :node_id
|
||||
AND packet.import_time >= DATETIME('now', '-1 day')
|
||||
GROUP BY packet.portnum
|
||||
ORDER BY packet_count DESC;
|
||||
"""), {"node_id": node_id}
|
||||
)
|
||||
|
||||
# Map the result to include node.long_name and packet data
|
||||
traffic_data = [{
|
||||
"long_name": row[0], # node.long_name
|
||||
"portnum": row[1], # packet.portnum
|
||||
"packet_count": row[2] # COUNT(*) as packet_count
|
||||
} for row in result.all()]
|
||||
|
||||
return traffic_data
|
||||
|
||||
except Exception as e:
|
||||
# Log the error or handle it as needed
|
||||
print(f"Error fetching node traffic: {str(e)}")
|
||||
return []
|
||||
|
||||
|
||||
|
||||
async def get_nodes(role=None, channel=None, hw_model=None):
|
||||
"""
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<br><div style="text-align:center"><strong>Bay Area Mesh - http://bayme.sh</strong></div>
|
||||
<div style="text-align:center">Quick Links: <a href="/nodelist">Nodes</a> - <a href="/chat">Conversations</a> - <a href="/firehose">See <strong>everything</strong> </a>
|
||||
- Mesh Graph <a href="/nodegraph/LongFast">LF</a> - <a href="/nodegraph/MediumSlow">MS </a> - <a href="/stats">Stats </a>
|
||||
- <a href="/net">Weekly Net</a> - <a href="/map">Map</a></div><br>
|
||||
- <a href="/net">Weekly Net</a> - <a href="/map">Map</a> - <a href="/top">Top Traffic</a></div><br>
|
||||
<div id="spinner" class="spinner-border secondary-primary htmx-indicator position-absolute top-50 start-50" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<dt>Role</dt>
|
||||
<dd>{{node.role}}</dd>
|
||||
</dl>
|
||||
<a href="/top?node_id={{node.node_id}}" >Get node traffic totals</a>
|
||||
{% include "node_graphs.html" %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
105
meshview/templates/node_traffic.html
Normal file
105
meshview/templates/node_traffic.html
Normal file
@@ -0,0 +1,105 @@
|
||||
{% 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 %}
|
||||
<section>
|
||||
<h2 class="table-title">{{ traffic[0].long_name }} (last 24 hours)</h2>
|
||||
<table class="traffic-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Port Number</th>
|
||||
<th>Packet Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for port in traffic %}
|
||||
<tr class="traffic">
|
||||
<td>
|
||||
{% 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 == 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
|
||||
{% elif port.portnum == 0 %}
|
||||
UNKNOWN_APP
|
||||
{% elif port.portnum == 0 %}
|
||||
UNKNOWN_APP
|
||||
{% else %}
|
||||
{{ port.portnum }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ port.packet_count }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="2">No traffic data available for this node.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<a href="/top">Back to Top Nodes</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
@@ -13,7 +13,6 @@
|
||||
{%- endif -%}
|
||||
)
|
||||
</span>
|
||||
->
|
||||
<span {% if to_me %} class="fw-bold" {% endif %}>
|
||||
{{packet.to_node.long_name}}(
|
||||
{%- if not to_me -%}
|
||||
@@ -40,7 +39,7 @@
|
||||
<dt>payload</dt>
|
||||
<dd>
|
||||
{% if packet.pretty_payload %}
|
||||
<div>{{packet.pretty_payload}}<div>
|
||||
<div>{{packet.pretty_payload}}</div>
|
||||
{% endif %}
|
||||
{% if packet.raw_mesh_packet.decoded and packet.raw_mesh_packet.decoded.portnum == 70 %}
|
||||
<ul>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="col" id="packet_list" sse-swap="{{packet_event | default('packet')}}" hx-swap="afterbegin">
|
||||
<div class="col" id="packet_list">
|
||||
{% for packet in packets %}
|
||||
{% include 'packet.html' %}
|
||||
{% else %}
|
||||
|
||||
65
meshview/templates/top.html
Normal file
65
meshview/templates/top.html
Normal file
@@ -0,0 +1,65 @@
|
||||
{% extends "base.html" %}
|
||||
{% block css %}
|
||||
.table-title {
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.traffic-table {
|
||||
width: 60%;
|
||||
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 */
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h2 class="table-title">Top Traffic Nodes (last 24 hours)</h2>
|
||||
<table class="traffic-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node Name</th>
|
||||
<th>Role</th>
|
||||
<th>Packet Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for node in nodes %}
|
||||
<tr class="traffic">
|
||||
<td><a href="/packet_list/{{ node[0] }}">{{ node[1] }}</a></td> <!-- long_name -->
|
||||
<td>{{ node[2] }}</td>
|
||||
<td><a href="/top?node_id={{ node[0] }}">{{ node[3] }}</a></td> <!-- packet_count -->
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@@ -23,7 +23,6 @@ from meshview import decode_payload
|
||||
import gc
|
||||
import psutil
|
||||
|
||||
|
||||
env = Environment(loader=PackageLoader("meshview"), autoescape=select_autoescape())
|
||||
|
||||
# Optimize garbage collection frequency
|
||||
@@ -1190,6 +1189,35 @@ async def stats(request):
|
||||
content_type="text/plain",
|
||||
)
|
||||
|
||||
@routes.get("/top")
|
||||
async def top(request):
|
||||
try:
|
||||
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
|
||||
node_traffic = await store.get_node_traffic(int(node_id))
|
||||
print(node_traffic)
|
||||
template = env.get_template("node_traffic.html") # Render a different template
|
||||
html_content = template.render(traffic=node_traffic, node_id=node_id)
|
||||
else:
|
||||
# Otherwise, fetch top traffic nodes as usual
|
||||
top_nodes = await store.get_top_traffic_nodes()
|
||||
template = env.get_template("top.html")
|
||||
html_content = template.render(nodes=top_nodes)
|
||||
|
||||
return web.Response(
|
||||
text=html_content,
|
||||
content_type="text/html",
|
||||
)
|
||||
except Exception as e:
|
||||
return web.Response(
|
||||
text=f"An error occurred: {str(e)}",
|
||||
status=500,
|
||||
content_type="text/plain",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@routes.get("/chat")
|
||||
async def chat(request):
|
||||
|
||||
Reference in New Issue
Block a user