mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-10-30 18:51:57 +01:00
some updates
This commit is contained in:
1 parent
012150cba9
commit
fb3a0154dd
7 files changed
+68
-22
No files matched your search
@@ -23,6 +23,9 @@ The project serves as a real-time monitoring and diagnostic tool for the Meshtas
|
|||||||
|
|
||||||
### Version 3.0.6 — March 2026
|
### 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.
|
- 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.
|
- 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.
|
- 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.
|
- Ingestion/API reliability: improved duplicate `PacketSeen` handling, guarded invalid node info packets, and made traceroute/edge APIs more tolerant of decode failures.
|
||||||
|
|||||||
@@ -227,9 +227,32 @@ Response Example
|
|||||||
|
|
||||||
## 6. Config API
|
## 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`
|
### GET `/api/config`
|
||||||
Returns a safe subset of server configuration.
|
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
|
Response Example
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
+14
-5
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime, timedelta
|
||||||
|
|
||||||
from sqlalchemy import func, select, update
|
from sqlalchemy import func, select, update
|
||||||
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
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:
|
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:
|
async with mqtt_database.async_session() as session:
|
||||||
node_count = (await session.execute(select(func.count()).select_from(Node))).scalar_one()
|
node_count = (await session.execute(select(func.count()).select_from(Node))).scalar_one()
|
||||||
packet_count = (
|
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()
|
).scalar_one()
|
||||||
gateway_count = (
|
gateway_count = (
|
||||||
await session.execute(
|
await session.execute(
|
||||||
@@ -124,7 +133,7 @@ async def capture_daily_snapshot() -> None:
|
|||||||
).scalar_one()
|
).scalar_one()
|
||||||
captured_at_us = int(time.time() * 1_000_000)
|
captured_at_us = int(time.time() * 1_000_000)
|
||||||
values = {
|
values = {
|
||||||
"snapshot_date": today,
|
"snapshot_date": snapshot_date,
|
||||||
"node_count": node_count,
|
"node_count": node_count,
|
||||||
"packet_count": packet_count,
|
"packet_count": packet_count,
|
||||||
"gateway_count": gateway_count,
|
"gateway_count": gateway_count,
|
||||||
@@ -151,7 +160,7 @@ async def capture_daily_snapshot() -> None:
|
|||||||
else:
|
else:
|
||||||
snapshot = (
|
snapshot = (
|
||||||
await session.execute(
|
await session.execute(
|
||||||
select(DailySnapshot).where(DailySnapshot.snapshot_date == today)
|
select(DailySnapshot).where(DailySnapshot.snapshot_date == snapshot_date)
|
||||||
)
|
)
|
||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
if snapshot is None:
|
if snapshot is None:
|
||||||
|
|||||||
@@ -275,23 +275,11 @@ const PACKET_LANDING_DURATION_MS = 1000;
|
|||||||
|
|
||||||
const portMap = window.PORT_LABEL_MAP;
|
const portMap = window.PORT_LABEL_MAP;
|
||||||
|
|
||||||
// Fallback colors for channels that are not Meshtastic presets.
|
// Generated colors for channels without a configured override.
|
||||||
// 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.
|
|
||||||
const palette = ["#18aa18","#976049","#a5508c","#1898dc","#3a8846","#dc1884",
|
const palette = ["#18aa18","#976049","#a5508c","#1898dc","#3a8846","#dc1884",
|
||||||
"#b8813d","#b64f35","#408b18","#4c719e","#be379c","#18aa52"];
|
"#b8813d","#b64f35","#408b18","#4c719e","#be379c","#18aa52"];
|
||||||
|
|
||||||
const channelColorOverrides = {
|
const channelColorOverrides = new Map();
|
||||||
longfast: "#4363d8",
|
|
||||||
longslow: "#000075",
|
|
||||||
verylongslow: "#808000",
|
|
||||||
mediumslow: "#008080",
|
|
||||||
mediumfast: "#e6194b",
|
|
||||||
shortslow: "#911eb4",
|
|
||||||
shortfast: "#f58231",
|
|
||||||
longmoderate: "#46f0f0",
|
|
||||||
shortturbo: "#f032e6"
|
|
||||||
};
|
|
||||||
|
|
||||||
const colorMap = new Map();
|
const colorMap = new Map();
|
||||||
const channelSet = new Set();
|
const channelSet = new Set();
|
||||||
@@ -316,7 +304,7 @@ function hashToColor(str){
|
|||||||
const channel = String(str || "Unknown");
|
const channel = String(str || "Unknown");
|
||||||
if(colorMap.has(channel)) return colorMap.get(channel);
|
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];
|
const c = override || palette[Math.floor(hashToUnit(channel) * palette.length) % palette.length];
|
||||||
colorMap.set(channel,c);
|
colorMap.set(channel,c);
|
||||||
return c;
|
return c;
|
||||||
@@ -477,6 +465,9 @@ async function waitForConfig() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const cfg = await window._siteConfigPromise;
|
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 || {};
|
return cfg.site || {};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading site config:", err);
|
console.error("Error loading site config:", err);
|
||||||
@@ -525,7 +516,8 @@ initMapPolling();
|
|||||||
|
|
||||||
fetch('/api/nodes?days_active=3')
|
fetch('/api/nodes?days_active=3')
|
||||||
.then(r=>r.json())
|
.then(r=>r.json())
|
||||||
.then(data=>{
|
.then(async data=>{
|
||||||
|
await waitForConfig();
|
||||||
if(!data.nodes) return;
|
if(!data.nodes) return;
|
||||||
|
|
||||||
nodes = data.nodes.map(n=>({
|
nodes = data.nodes.map(n=>({
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ seenTableBody.innerHTML = Object.keys(hopGroups)
|
|||||||
Escape HTML helper
|
Escape HTML helper
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
function escapeHtml(unsafe) {
|
function escapeHtml(unsafe) {
|
||||||
return (unsafe ?? "").replace(/[&<"'>]/g, m => ({
|
return String(unsafe ?? "").replace(/[&<"'>]/g, m => ({
|
||||||
"&":"&",
|
"&":"&",
|
||||||
"<":"<",
|
"<":"<",
|
||||||
">":">",
|
">":">",
|
||||||
|
|||||||
@@ -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_lat": get_float(site, "map_bottom_right_lat", 36.0),
|
||||||
"map_bottom_right_lon": get_float(site, "map_bottom_right_lon", -121.0),
|
"map_bottom_right_lon": get_float(site, "map_bottom_right_lon", -121.0),
|
||||||
"map_interval": get_int(site, "map_interval", 3),
|
"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),
|
"firehose_interval": get_int(site, "firehose_interval", 3),
|
||||||
"weekly_net_message": get_str(
|
"weekly_net_message": get_str(
|
||||||
site, "weekly_net_message", "Weekly Mesh check-in message."
|
site, "weekly_net_message", "Weekly Mesh check-in message."
|
||||||
|
|||||||
@@ -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.
|
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
|
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
|
# MQTT Broker Configuration
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|||||||
Reference in new issue
Block a user