Merge remote-tracking branch 'origin/dev-v3' into dev-v3

This commit is contained in:
Pablo Revilla
2025-11-21 11:43:07 -08:00
3 changed files with 81 additions and 84 deletions
+14 -16
View File
@@ -24,7 +24,9 @@ async def get_fuzzy_nodes(query):
return result.scalars()
async def get_packets(node_id=None, portnum=None, after=None, before=None, limit=None, packet_id=None):
async def get_packets(
node_id=None, portnum=None, after=None, before=None, limit=None, packet_id=None
):
async with database.async_session() as session:
# --- Fast path: fetch by packet_id (uses primary key lookup) ---
if packet_id is not None:
@@ -53,7 +55,6 @@ async def get_packets(node_id=None, portnum=None, after=None, before=None, limit
return packets
async def get_packets_from(node_id=None, portnum=None, since=None, limit=500):
async with database.async_session() as session:
q = select(Packet)
@@ -75,7 +76,6 @@ async def get_packet(packet_id):
return result.scalar_one_or_none()
async def get_packets_seen(packet_id):
async with database.async_session() as session:
result = await session.execute(
@@ -373,11 +373,11 @@ async def get_total_packet_count(
# CASE 1: no filters -> count everything
if (
period_type is None and
length is None and
channel is None and
from_node is None and
to_node is None
period_type is None
and length is None
and channel is None
and from_node is None
and to_node is None
):
async with database.async_session() as session:
q = select(func.count(Packet.id))
@@ -431,19 +431,17 @@ async def get_total_packet_seen_count(
# SPECIAL CASE: direct packet_id lookup
if packet_id is not None:
async with database.async_session() as session:
q = select(func.count(PacketSeen.packet_id)).where(
PacketSeen.packet_id == packet_id
)
q = select(func.count(PacketSeen.packet_id)).where(PacketSeen.packet_id == packet_id)
res = await session.execute(q)
return res.scalar() or 0
# No filters -> return ALL seen entries
if (
period_type is None and
length is None and
channel is None and
from_node is None and
to_node is None
period_type is None
and length is None
and channel is None
and from_node is None
and to_node is None
):
async with database.async_session() as session:
q = select(func.count(PacketSeen.packet_id))
+29 -19
View File
@@ -193,6 +193,7 @@ async def index(request):
starting_url = CONFIG["site"].get("starting", "/map") # default to /map if not set
raise web.HTTPFound(location=starting_url)
@routes.get("/net")
async def net(request):
return web.Response(
@@ -200,21 +201,21 @@ async def net(request):
content_type="text/html",
)
@routes.get("/map")
async def map(request):
template = env.get_template("map.html")
return web.Response(
text=template.render(),
content_type="text/html"
)
return web.Response(text=template.render(), content_type="text/html")
@routes.get("/nodelist")
async def nodelist(request):
template = env.get_template("nodelist.html")
return web.Response(
text=template.render(),
content_type="text/html",
)
template = env.get_template("nodelist.html")
return web.Response(
text=template.render(),
content_type="text/html",
)
@routes.get("/firehose")
async def firehose(request):
@@ -223,21 +224,24 @@ async def firehose(request):
content_type="text/html",
)
@routes.get("/chat")
async def chat(request):
template = env.get_template("chat.html")
return web.Response(
text=template.render(),
content_type="text/html",
)
template = env.get_template("chat.html")
return web.Response(
text=template.render(),
content_type="text/html",
)
@routes.get("/new_packet/{packet_id}")
async def new_packet(request):
template = env.get_template("new_packet.html")
return web.Response(
text=template.render(),
content_type="text/html",
)
template = env.get_template("new_packet.html")
return web.Response(
text=template.render(),
content_type="text/html",
)
@routes.get("/new_node/{from_node_id}")
async def firehose_node(request):
@@ -247,6 +251,7 @@ async def firehose_node(request):
content_type="text/html",
)
@routes.get("/nodegraph")
async def nodegraph(request):
template = env.get_template("nodegraph.html")
@@ -255,6 +260,7 @@ async def nodegraph(request):
content_type="text/html",
)
@routes.get("/top")
async def top(request):
template = env.get_template("top.html")
@@ -263,6 +269,7 @@ async def top(request):
content_type="text/html",
)
# Keep !!
@routes.get("/graph/traceroute/{packet_id}")
async def graph_traceroute(request):
@@ -370,6 +377,7 @@ async def graph_traceroute(request):
content_type="image/svg+xml",
)
@routes.get("/stats")
async def stats(request):
try:
@@ -392,6 +400,7 @@ async def stats(request):
content_type="text/plain",
)
'''
@routes.get("/top")
async def top(request):
@@ -470,6 +479,7 @@ async def top(request):
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
logger.info("Checking database schema status...")
+38 -49
View File
@@ -79,8 +79,8 @@ async def api_nodes(request):
"last_lat": getattr(n, "last_lat", None),
"last_long": getattr(n, "last_long", None),
"channel": n.channel,
#"last_update": n.last_update.isoformat(),
"last_seen_us":n.last_seen_us,
# "last_update": n.last_update.isoformat(),
"last_seen_us": n.last_seen_us,
}
)
@@ -170,14 +170,9 @@ async def api_packets(request):
# --- Text message filtering ---
if portnum == PortNum.TEXT_MESSAGE_APP:
ui_packets = [
p for p in ui_packets
if p.payload and not SEQ_REGEX.fullmatch(p.payload)
]
ui_packets = [p for p in ui_packets if p.payload and not SEQ_REGEX.fullmatch(p.payload)]
if contains:
ui_packets = [
p for p in ui_packets if contains.lower() in p.payload.lower()
]
ui_packets = [p for p in ui_packets if contains.lower() in p.payload.lower()]
# --- Sort descending by import_time_us ---
ui_packets.sort(key=lambda p: p.import_time_us, reverse=True)
@@ -258,13 +253,15 @@ async def api_stats(request):
to_node=node_id,
)
return web.json_response({
"node_id": node_id,
"period_type": period_type,
"length": length,
"sent": sent.get("total", 0),
"seen": seen.get("total", 0),
})
return web.json_response(
{
"node_id": node_id,
"period_type": period_type,
"length": length,
"sent": sent.get("total", 0),
"seen": seen.get("total", 0),
}
)
# ---- Existing full stats mode (unchanged) ----
channel = request.query.get("channel")
@@ -344,21 +341,18 @@ async def api_stats_count(request):
# -------- Case 1: NO FILTERS → return global totals --------
no_filters = (
period_type is None and
length is None and
channel is None and
from_node is None and
to_node is None and
packet_id is None
period_type is None
and length is None
and channel is None
and from_node is None
and to_node is None
and packet_id is None
)
if no_filters:
total_packets = await store.get_total_packet_count()
total_seen = await store.get_total_packet_seen_count()
return web.json_response({
"total_packets": total_packets,
"total_seen": total_seen
})
return web.json_response({"total_packets": total_packets, "total_seen": total_seen})
# -------- Case 2: Apply filters → compute totals --------
total_packets = await store.get_total_packet_count(
@@ -378,13 +372,7 @@ async def api_stats_count(request):
to_node=to_node,
)
return web.json_response({
"total_packets": total_packets,
"total_seen": total_seen
})
return web.json_response({"total_packets": total_packets, "total_seen": total_seen})
@routes.get("/api/edges")
@@ -618,6 +606,7 @@ async def version_endpoint(request):
logger.error(f"Error in /version: {e}")
return web.json_response({"error": "Failed to fetch version info"}, status=500)
@routes.get("/api/packets_seen/{packet_id}")
async def api_packets_seen(request):
try:
@@ -634,22 +623,22 @@ async def api_packets_seen(request):
rows = await store.get_packets_seen(packet_id)
items = []
for row in rows: # <-- FIX: normal for-loop
items.append({
"packet_id": row.packet_id,
"node_id": row.node_id,
"rx_time": row.rx_time,
"hop_limit": row.hop_limit,
"hop_start": row.hop_start,
"channel": row.channel,
"rx_snr": row.rx_snr,
"rx_rssi": row.rx_rssi,
"topic": row.topic,
"import_time": (
row.import_time.isoformat() if row.import_time else None
),
"import_time_us": row.import_time_us,
})
for row in rows: # <-- FIX: normal for-loop
items.append(
{
"packet_id": row.packet_id,
"node_id": row.node_id,
"rx_time": row.rx_time,
"hop_limit": row.hop_limit,
"hop_start": row.hop_start,
"channel": row.channel,
"rx_snr": row.rx_snr,
"rx_rssi": row.rx_rssi,
"topic": row.topic,
"import_time": (row.import_time.isoformat() if row.import_time else None),
"import_time_us": row.import_time_us,
}
)
return web.json_response({"seen": items})