diff --git a/meshview/store.py b/meshview/store.py
index 87ef868..fd27e51 100644
--- a/meshview/store.py
+++ b/meshview/store.py
@@ -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):
"""
diff --git a/meshview/templates/base.html b/meshview/templates/base.html
index 54a5ac1..436b396 100644
--- a/meshview/templates/base.html
+++ b/meshview/templates/base.html
@@ -38,7 +38,7 @@
Bay Area Mesh - http://bayme.sh
+ - Weekly Net - Map - Top Traffic
Loading...
diff --git a/meshview/templates/node.html b/meshview/templates/node.html
index a348752..434bffe 100644
--- a/meshview/templates/node.html
+++ b/meshview/templates/node.html
@@ -53,6 +53,7 @@
Role
{{node.role}}
+ Get node traffic totals
{% include "node_graphs.html" %}
{% else %}
diff --git a/meshview/templates/node_traffic.html b/meshview/templates/node_traffic.html
new file mode 100644
index 0000000..a8aca5b
--- /dev/null
+++ b/meshview/templates/node_traffic.html
@@ -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 %}
+
+ {{ traffic[0].long_name }} (last 24 hours)
+
+
+
+ | Port Number |
+ Packet Count |
+
+
+
+ {% for port in traffic %}
+
+ |
+ {% 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 %}
+ |
+ {{ port.packet_count }} |
+
+ {% else %}
+
+ | No traffic data available for this node. |
+
+ {% endfor %}
+
+
+
+
+
+{% endblock %}
diff --git a/meshview/templates/packet.html b/meshview/templates/packet.html
index a6d7cc7..c832b97 100644
--- a/meshview/templates/packet.html
+++ b/meshview/templates/packet.html
@@ -13,7 +13,6 @@
{%- endif -%}
)
- ->
{{packet.to_node.long_name}}(
{%- if not to_me -%}
@@ -40,7 +39,7 @@
payload
{% if packet.pretty_payload %}
- {{packet.pretty_payload}}
+
{{packet.pretty_payload}}
{% endif %}
{% if packet.raw_mesh_packet.decoded and packet.raw_mesh_packet.decoded.portnum == 70 %}
diff --git a/meshview/templates/packet_list.html b/meshview/templates/packet_list.html
index f90c58f..24f2f8c 100644
--- a/meshview/templates/packet_list.html
+++ b/meshview/templates/packet_list.html
@@ -1,4 +1,4 @@
-
+
{% for packet in packets %}
{% include 'packet.html' %}
{% else %}
diff --git a/meshview/templates/top.html b/meshview/templates/top.html
new file mode 100644
index 0000000..b875f77
--- /dev/null
+++ b/meshview/templates/top.html
@@ -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 %}
+
Top Traffic Nodes (last 24 hours)
+
+
+
+ | Node Name |
+ Role |
+ Packet Count |
+
+
+
+ {% for node in nodes %}
+
+ | {{ node[1] }} |
+ {{ node[2] }} |
+ {{ node[3] }} |
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/meshview/web.py b/meshview/web.py
index 3a62550..39b5fbc 100644
--- a/meshview/web.py
+++ b/meshview/web.py
@@ -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):