From fb3a0154dd264023b7c4a0a0ecc623ab9a81cc59 Mon Sep 17 00:00:00 2001 From: pablorevilla-meshtastic Date: Thu, 15 Oct 2026 22:49:55 -0700 Subject: [PATCH] some updates --- README.md | 3 +++ docs/API_Documentation.md | 23 +++++++++++++++++++++++ meshview/mqtt_store.py | 19 ++++++++++++++----- meshview/templates/map.html | 24 ++++++++---------------- meshview/templates/packet.html | 2 +- meshview/web_api/api.py | 6 ++++++ sample.config.ini | 13 +++++++++++++ 7 files changed, 68 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index df01437..b8b7225 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ The project serves as a real-time monitoring and diagnostic tool for the Meshtas ### Version 3.0.6 — March 2026 - Daily snapshots: added the `daily_snapshot` table, scheduled snapshot capture, and `/api/snapshots/daily` for historical node, packet, and gateway counts. + Each new snapshot stores packets imported during the previous complete UTC day, + labeled with that day's date. Node and gateway counts are database totals at capture + time. Existing historical snapshots retain their previously stored packet totals. - Stats/UI: expanded stats views with snapshot data and added channel filters to Chat and Firehose. - Map/node fixes: corrected node location handling, map popup behavior, packet page display, and node list sorting. - Ingestion/API reliability: improved duplicate `PacketSeen` handling, guarded invalid node info packets, and made traceroute/edge APIs more tolerant of decode failures. diff --git a/docs/API_Documentation.md b/docs/API_Documentation.md index 5531c26..5b13098 100644 --- a/docs/API_Documentation.md +++ b/docs/API_Documentation.md @@ -227,9 +227,32 @@ Response Example ## 6. Config API +### GET `/api/snapshots/daily` + +Returns stored daily snapshots. For new snapshots, `packet_count` is the number of +packet records imported during `date` (00:00 UTC inclusive to the next midnight +exclusive). The scheduled capture records the previous complete UTC day. Repeated +observations of the same packet are not counted separately. Node and gateway counts +remain database totals at capture time. Older snapshots retain the cumulative packet +counts originally stored; they are not automatically converted. + ### GET `/api/config` Returns a safe subset of server configuration. +`site.map_channel_colors` contains channel color overrides from the optional +`[map_channel_colors]` INI section. Each entry is `channel = #RRGGBB`; invalid colors +are omitted. Channel matching on the map ignores case, spaces, hyphens, and underscores. +Unlisted channels use generated colors. For example: + +```ini +[map_channel_colors] +LongFast = #4363d8 +LocalChat = #18aa18 +``` + +Restart the web server and reload the map after changing these settings. Omitting +the section uses generated colors for every channel. + Response Example ```json { diff --git a/meshview/mqtt_store.py b/meshview/mqtt_store.py index e72e7e6..c3b9acb 100644 --- a/meshview/mqtt_store.py +++ b/meshview/mqtt_store.py @@ -1,7 +1,7 @@ import logging import re import time -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from sqlalchemy import func, select, update from sqlalchemy.dialects.postgresql import insert as pg_insert @@ -110,12 +110,21 @@ async def ensure_node_exists(session, node_id: int | None, channel: str | None) async def capture_daily_snapshot() -> None: - today = datetime.now(UTC).date() + """Record packet traffic for the previous complete UTC day.""" + day_end = datetime.now(UTC).replace(hour=0, minute=0, second=0, microsecond=0) + day_start = day_end - timedelta(days=1) + start_us = int(day_start.timestamp() * 1_000_000) + end_us = int(day_end.timestamp() * 1_000_000) + snapshot_date = day_start.date() async with mqtt_database.async_session() as session: node_count = (await session.execute(select(func.count()).select_from(Node))).scalar_one() packet_count = ( - await session.execute(select(func.count()).select_from(Packet)) + await session.execute( + select(func.count()) + .select_from(Packet) + .where(Packet.import_time_us >= start_us, Packet.import_time_us < end_us) + ) ).scalar_one() gateway_count = ( await session.execute( @@ -124,7 +133,7 @@ async def capture_daily_snapshot() -> None: ).scalar_one() captured_at_us = int(time.time() * 1_000_000) values = { - "snapshot_date": today, + "snapshot_date": snapshot_date, "node_count": node_count, "packet_count": packet_count, "gateway_count": gateway_count, @@ -151,7 +160,7 @@ async def capture_daily_snapshot() -> None: else: snapshot = ( await session.execute( - select(DailySnapshot).where(DailySnapshot.snapshot_date == today) + select(DailySnapshot).where(DailySnapshot.snapshot_date == snapshot_date) ) ).scalar_one_or_none() if snapshot is None: diff --git a/meshview/templates/map.html b/meshview/templates/map.html index 4ca8c13..d92eab0 100644 --- a/meshview/templates/map.html +++ b/meshview/templates/map.html @@ -275,23 +275,11 @@ const PACKET_LANDING_DURATION_MS = 1000; const portMap = window.PORT_LABEL_MAP; -// Fallback colors for channels that are not Meshtastic presets. -// Every entry clears 3:1 contrast against both the light map tiles and the dark -// filter panel, and stays perceptually far from every channelColorOverrides value. +// Generated colors for channels without a configured override. const palette = ["#18aa18","#976049","#a5508c","#1898dc","#3a8846","#dc1884", "#b8813d","#b64f35","#408b18","#4c719e","#be379c","#18aa52"]; -const channelColorOverrides = { - longfast: "#4363d8", - longslow: "#000075", - verylongslow: "#808000", - mediumslow: "#008080", - mediumfast: "#e6194b", - shortslow: "#911eb4", - shortfast: "#f58231", - longmoderate: "#46f0f0", - shortturbo: "#f032e6" -}; +const channelColorOverrides = new Map(); const colorMap = new Map(); const channelSet = new Set(); @@ -316,7 +304,7 @@ function hashToColor(str){ const channel = String(str || "Unknown"); if(colorMap.has(channel)) return colorMap.get(channel); - const override = channelColorOverrides[normalizeChannelName(channel)]; + const override = channelColorOverrides.get(normalizeChannelName(channel)); const c = override || palette[Math.floor(hashToUnit(channel) * palette.length) % palette.length]; colorMap.set(channel,c); return c; @@ -477,6 +465,9 @@ async function waitForConfig() { } try { const cfg = await window._siteConfigPromise; + for(const [channel, color] of Object.entries(cfg.site?.map_channel_colors || {})){ + channelColorOverrides.set(normalizeChannelName(channel), color); + } return cfg.site || {}; } catch (err) { console.error("Error loading site config:", err); @@ -525,7 +516,8 @@ initMapPolling(); fetch('/api/nodes?days_active=3') .then(r=>r.json()) - .then(data=>{ + .then(async data=>{ + await waitForConfig(); if(!data.nodes) return; nodes = data.nodes.map(n=>({ diff --git a/meshview/templates/packet.html b/meshview/templates/packet.html index 358cb50..1db8451 100644 --- a/meshview/templates/packet.html +++ b/meshview/templates/packet.html @@ -535,7 +535,7 @@ seenTableBody.innerHTML = Object.keys(hopGroups) Escape HTML helper ----------------------------------------------*/ function escapeHtml(unsafe) { - return (unsafe ?? "").replace(/[&<"'>]/g, m => ({ + return String(unsafe ?? "").replace(/[&<"'>]/g, m => ({ "&":"&", "<":"<", ">":">", diff --git a/meshview/web_api/api.py b/meshview/web_api/api.py index 29bbd09..bf33b32 100644 --- a/meshview/web_api/api.py +++ b/meshview/web_api/api.py @@ -744,6 +744,12 @@ async def api_config(request): "map_bottom_right_lat": get_float(site, "map_bottom_right_lat", 36.0), "map_bottom_right_lon": get_float(site, "map_bottom_right_lon", -121.0), "map_interval": get_int(site, "map_interval", 3), + + "map_channel_colors": { + channel: color.strip() + for channel, color in CONFIG.get("map_channel_colors", {}).items() + if re.fullmatch(r"#[0-9a-fA-F]{6}", color.strip()) + }, "firehose_interval": get_int(site, "firehose_interval", 3), "weekly_net_message": get_str( site, "weekly_net_message", "Weekly Mesh check-in message." diff --git a/sample.config.ini b/sample.config.ini index df24db5..e70f02f 100644 --- a/sample.config.ini +++ b/sample.config.ini @@ -59,6 +59,19 @@ firehose_interal=3 weekly_net_message = Weekly Mesh check-in. We will keep it open on every Wednesday from 5:00pm for checkins. The message format should be (LONG NAME) - (CITY YOU ARE IN) #BayMeshNet. net_tag = #BayMeshNet +# Map channel color overrides. Use six-digit hex colors; other channels get generated colors. +# Names ignore case, spaces, hyphens, and underscores. Restart the web server after editing. +[map_channel_colors] +LongFast = #4363d8 +LongSlow = #000075 +VeryLongSlow = #808000 +MediumSlow = #008080 +MediumFast = #e6194b +ShortSlow = #911eb4 +ShortFast = #f58231 +LongModerate = #46f0f0 +ShortTurbo = #f032e6 + # ------------------------- # MQTT Broker Configuration # -------------------------