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 %}
-
- {% 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]