From ac4ac9264f015028b5862fa855363b6d65b75d3f Mon Sep 17 00:00:00 2001 From: Pablo Revilla Date: Wed, 5 Nov 2025 20:46:12 -0800 Subject: [PATCH] Worked on /api/packet. Needed to modify - Store.py to read the new time data - api.py to present the new time data - firehose.html chat.html and map.html now use the new apis and the time is the browser local time --- meshview/templates/net.html | 211 ++++++++++++++++++++++++++---------- meshview/web.py | 132 +++++++--------------- meshview/web_api/api.py | 12 +- 3 files changed, 201 insertions(+), 154 deletions(-) diff --git a/meshview/templates/net.html b/meshview/templates/net.html index af9efe6..554771c 100644 --- a/meshview/templates/net.html +++ b/meshview/templates/net.html @@ -1,75 +1,172 @@ {% extends "base.html" %} {% block css %} -.timestamp { - min-width:10em; -} -.chat-packet:nth-of-type(odd){ - background-color: #3a3a3a; /* Lighter than #2a2a2a */ -} +.timestamp { min-width: 10em; color: #ccc; } + +.chat-packet:nth-of-type(odd) { background-color: #3a3a3a; } .chat-packet { border-bottom: 1px solid #555; - padding: 8px; - border-radius: 8px; /* Adjust the value to make the corners more or less rounded */ + padding: 3px 6px; + border-radius: 6px; + margin: 0; } -.chat-packet:nth-of-type(even){ - background-color: #333333; /* Slightly lighter than the previous #181818 */ + +.chat-packet > [class^="col-"] { + padding-left: 10px !important; + padding-right: 10px !important; + padding-top: 1px !important; + padding-bottom: 1px !important; } + +.chat-packet:nth-of-type(even) { background-color: #333333; } + +.channel { font-style: italic; color: #bbb; } +.channel a { font-style: normal; color: #999; } + +@keyframes flash { 0% { background-color: #ffe066; } 100% { background-color: inherit; } } +.chat-packet.flash { animation: flash 3.5s ease-out; } + +.replying-to { font-size: 0.8em; color: #aaa; margin-top: 2px; padding-left: 10px; } +.replying-to .reply-preview { color: #aaa; } + +#weekly-message { margin: 15px 0; font-weight: bold; color: #ffeb3b; } +#total-count { margin-bottom: 10px; font-style: italic; color: #ccc; } {% endblock %} {% block body %}
- {{ site_config["site"]["weekly_net_message"] }}

+ +
Loading weekly message...
+ +
Total messages: 0
-
- Number of Check-ins: {{ packets|length }} -
-
- -
- {% for packet in packets %} -
- - {{ packet.import_time.strftime('%-I:%M:%S %p - %m-%d-%Y') }} - - - ✉️ {{ packet.from_node.channel }} - - - - {{ packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }} - - - - {{ packet.payload }} - -
- {% else %} - No packets found. - {% endfor %} +
+
+
+ {% endblock %} diff --git a/meshview/web.py b/meshview/web.py index 0d503ac..faeea7d 100644 --- a/meshview/web.py +++ b/meshview/web.py @@ -210,6 +210,43 @@ 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( + text=env.get_template("net.html").render(), + 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" + ) + +@routes.get("/nodelist") +async def nodelist(request): + template = env.get_template("nodelist.html") + return web.Response( + text=template.render(), + content_type="text/html", + ) + +@routes.get("/firehose") +async def firehose(request): + return web.Response( + text=env.get_template("firehose.html").render(), + 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", + ) def generate_response(request, body, raw_node_id="", node=None): if "HX-Request" in request.headers: @@ -434,14 +471,6 @@ async def packet_details(request): ) -@routes.get("/firehose") -async def packet_details_firehose(request): - return web.Response( - text=env.get_template("firehose.html").render(), - content_type="text/html", - ) - - @routes.get("/packet/{packet_id}") async def packet(request): try: @@ -1069,75 +1098,6 @@ async def graph_network(request): ) -@routes.get("/nodelist") -async def nodelist(request): - try: - template = env.get_template("nodelist.html") - return web.Response( - text=template.render(site_config=CONFIG, SOFTWARE_RELEASE=SOFTWARE_RELEASE), - content_type="text/html", - ) - except Exception: - template = env.get_template("error.html") - rendered = template.render( - error_message="An error occurred while loading the node list page.", - error_details=traceback.format_exc(), - site_config=CONFIG, - SOFTWARE_RELEASE=SOFTWARE_RELEASE, - ) - return web.Response(text=rendered, status=500, content_type="text/html") - - -@routes.get("/net") -async def net(request): - try: - # Fetch packets for the given node ID and port number - after_time = datetime.datetime.now() - timedelta(days=6) - packets = await store.get_packets(portnum=PortNum.TEXT_MESSAGE_APP, after=after_time) - - # Convert packets to UI packets - ui_packets = [Packet.from_model(p) for p in packets] - # Precompile regex for performance - seq_pattern = re.compile(r"seq \d+$") - - # Filter packets: exclude "seq \d+$" but include those containing Tag - filtered_packets = [ - p - for p in ui_packets - if not seq_pattern.match(p.payload) - and (CONFIG["site"]["net_tag"]).lower() in p.payload.lower() - ] - - # Render template - template = env.get_template("net.html") - return web.Response( - text=template.render( - packets=filtered_packets, site_config=CONFIG, SOFTWARE_RELEASE=SOFTWARE_RELEASE - ), - content_type="text/html", - ) - - except web.HTTPException: - raise # Let aiohttp handle HTTP exceptions properly - - except Exception as e: - logger.error(f"Error processing net request: {e}") - template = env.get_template("error.html") - rendered = template.render( - error_message="An internal server error occurred.", - error_details=traceback.format_exc(), - site_config=CONFIG, - SOFTWARE_RELEASE=SOFTWARE_RELEASE, - ) - return web.Response(text=rendered, status=500, 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") - - @routes.get("/stats") async def stats(request): try: @@ -1240,24 +1200,6 @@ async def top(request): return web.Response(text=rendered, status=500, content_type="text/html") -@routes.get("/chat") -async def chat(request): - try: - template = env.get_template("chat.html") - return web.Response( - text=template.render(), - content_type="text/html", - ) - except Exception as e: - logger.error(f"Error in /chat: {e}") - template = env.get_template("error.html") - rendered = template.render( - error_message="An error occurred while processing your request.", - error_details=traceback.format_exc(), - ) - return web.Response(text=rendered, status=500, content_type="text/html") - - # Assuming the route URL structure is /nodegraph @routes.get("/nodegraph") async def nodegraph(request): diff --git a/meshview/web_api/api.py b/meshview/web_api/api.py index fc6131d..4962505 100644 --- a/meshview/web_api/api.py +++ b/meshview/web_api/api.py @@ -97,6 +97,7 @@ async def api_packets(request): limit_str = request.query.get("limit", "50") since_str = request.query.get("since") portnum = request.query.get("portnum") + contains = request.query.get("contains") # <-- new query parameter # Clamp limit between 1 and 100 try: @@ -126,10 +127,17 @@ async def api_packets(request): if str(portnum) == str(PortNum.TEXT_MESSAGE_APP): # Filter out empty or "seq N" payloads ui_packets = [ - p for p in ui_packets if p.payload and not SEQ_REGEX.fullmatch(p.payload) + p for p in ui_packets + if p.payload and not SEQ_REGEX.fullmatch(p.payload) ] - # Sort newest first + # Apply "contains" filter if provided + if contains: + ui_packets = [ + p for p in ui_packets if contains.lower() in p.payload.lower() + ] + + # Sort newest first and limit ui_packets.sort(key=lambda p: p.import_time_us, reverse=True) ui_packets = ui_packets[:limit]