Commit Graph
940 Commits
Author SHA1 Message Date
pablorevilla-meshtastic d934f511aa change animated gif 2026-11-09 16:58:20 -08:00
pablorevilla-meshtastic e17d10099a Merge remote-tracking branch 'origin/develop' into develop 2026-11-09 14:00:17 -08:00
pablorevilla-meshtastic 549bdf5404 Credit version 4.0.0 contributors 2026-11-09 14:00:00 -08:00
Óscar García Amor a298347f14 feat(node): add power metrics chart (#190)
* fix(node): read telemetry fields from their block

The telemetry charts matched fields with unanchored regexes over the
whole payload, so "voltage:" also matched "ch1_voltage:" (power_metrics)
and environment_metrics voltage, plotting them as the device voltage.
The same applied to "temperature:" and other shared field names.

Parse each value only inside its own metrics block (device_metrics or
environment_metrics), match the exact field name and accept negative
values (e.g. temperatures below zero).

* feat(node): add power metrics chart

Plot power_metrics telemetry (ch1..ch8 voltage and current) on the node
page. Each channel that reported data gets a voltage series on the left
axis (V) and a dashed current series on the right axis (mA).

The legacy voltage/current fields of environment_metrics, reported by INA
sensors and to be deprecated in favor of PowerMetrics, are shown in the
same chart as "Environment" series.

The chart is hidden when the node sends no power data and supports the
usual expand and CSV export actions. Translations added for en, es, ru.
2026-11-09 13:54:22 -08:00
pablorevilla-meshtastic 234565b757 Merge develop into master for version 4.0.0 2026-11-09 13:49:27 -08:00
pablorevilla-meshtastic e30f4ec474 Document version 4.0.0 changes and upgrade steps 2026-11-09 13:48:05 -08:00
pablorevilla-meshtastic 779c3e9bc1 update the defualt altitude 2026-11-09 10:05:58 -08:00
pablorevilla-meshtastic 1732c7e718 fixed the router report 2026-11-09 09:56:20 -08:00
pablorevilla-meshtastic de602e468d more language changes 2026-11-06 22:23:03 -08:00
pablorevilla-meshtastic 7227fa14f4 added translation detials to new pages 2026-11-06 17:09:24 -08:00
pablorevilla-meshtastic 2810278db5 update kiosk page 2026-11-05 17:08:18 -08:00
pablorevilla-meshtastic ff3bd61242 make better use of space. 2026-11-05 17:01:11 -08:00
pablorevilla-meshtastic d61b3794bd Added all the scripts locally. 2026-11-04 15:09:13 -08:00
pablorevilla-meshtastic ae7a69b1d0 Map error update 3.0.9.1 2026-11-04 10:19:41 -08:00
Mike Benner c9300c0612 Pin leaflet.heat to 0.2.0 on master (#188)
Backport PR #188 to master, applying the map change to templates/map.html. Pin the compatible classic-script release and verify its contents with SRI.
2026-11-04 10:02:51 -08:00
Mike BennerandClaude Opus 5.5 a8eb147065 Pin leaflet.heat to 0.2.0 so the map loads again (#188)
leaflet.heat 1.0.0 was published to npm on 2026-11-03 and is ESM-only
(it starts with `import * as e from "leaflet"`). The pages loaded it
unversioned from unpkg as a classic <script>, so unpkg started serving
1.0.0. That throws "Cannot use import statement outside a module", which
leaves L.heatLayer undefined. map-view.html calls L.heatLayer at the top
level before the map gets a view, so the live map is a blank grey box,
and the heatmap page fails to load nodes.

Pin 0.2.0, the release these pages were written against, and add an
SRI hash so a future upstream change can't break or alter the pages.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:59:21 -08:00
pablorevilla-meshtastic 194658d303 more color changes 2026-10-30 14:13:34 -07:00
pablorevilla-meshtastic e4483ec02a change to color blue 2026-10-30 13:56:25 -07:00
pablorevilla-meshtastic e1e34c120e update 2026-10-29 16:05:47 -07:00
pablorevilla-meshtastic 63abac5448 added new map 2026-10-29 16:04:24 -07:00
pablorevilla-meshtastic 65fb307d07 fixed node inspector 2026-10-29 15:37:52 -07:00
pablorevilla-meshtastic f5d5dd6049 more updates 2026-10-29 13:10:17 -07:00
pablorevilla-meshtastic 8446ef8956 fixed colors in report 2026-10-29 10:47:51 -07:00
pablorevilla-meshtastic 7c8f5c42d0 fix one of the reports 2026-10-28 11:43:10 -07:00
pablorevilla-meshtastic 11b6a92f71 update 2026-10-28 11:26:11 -07:00
pablorevilla-meshtastic d022ec455e more updates 2026-10-28 11:21:57 -07:00
pablorevilla-meshtastic acba2284c5 more altitude 2026-10-28 11:10:52 -07:00
pablorevilla-meshtastic dddc8e0a13 more altitude details 2026-10-28 10:57:26 -07:00
pablorevilla-meshtastic 93fc31efd4 add altitude to table 2026-10-28 10:39:25 -07:00
pablorevilla-meshtastic 3e1d95ed2c fis 0 lat and long 2026-10-28 10:31:18 -07:00
pablorevilla-meshtastic ec9fafc362 change icon on light mode 2026-10-28 10:10:17 -07:00
pablorevilla-meshtastic 0e424da10a new stats report 2026-10-28 09:14:37 -07:00
pablorevilla-meshtastic 7ad590a83c update 2026-10-28 07:51:46 -07:00
pablorevilla-meshtastic b80a0b27df node page optimization 2026-10-28 07:49:04 -07:00
pablorevilla-meshtastic 330ee47932 updated version 2026-10-28 07:44:30 -07:00
pablorevilla-meshtastic 29860e8e7d fix report 2026-10-28 07:38:31 -07:00
pablorevilla-meshtastic ef52e96d7c fix date on neighour repor 2026-10-28 07:35:42 -07:00
pablorevilla-meshtastic acfd71bd2e fix 2nd key funtion 2026-10-27 19:50:39 -07:00
pablorevilla-meshtastic f570fdb65a Merge develop-4 UI updates into develop 2026-10-27 17:27:15 -07:00
11fb764fa8 Keep one malformed packet from breaking API endpoints or node updates (#176)
* Keep one malformed packet from breaking API endpoints or node updates

Values in a packet come from the mesh, so a forged packet or a newer firmware can carry
data the code did not expect:

- Routing.error_reason with a number the bundled protobufs do not define made
  Routing.Error.Name() raise, and /api/packets returned 500 for every list that included
  that packet.
- An unknown DeviceConfig.Role or HardwareModel in MAP_REPORT or NODEINFO raised the same
  way, and the whole node update was dropped.
- rx_snr of +inf was stored and served as "Infinity", which is not valid JSON, so the
  packet page's "seen by" list failed to load.
- A NEIGHBORINFO packet whose payload does not parse made /api/edges return 500 while it
  was among the 50 most recent.

Add decode_payload.enum_name(), which returns "unknown(N)" for undefined values, and use it
at all five enum lookups. Store non-finite SNR as NULL and serve existing ones as null.
Skip NEIGHBORINFO packets that do not decode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove a blank line that fails the ruff format check

`ruff format --check .` (the Ruff workflow, ruff 0.13.3) fails on develop because of this
line, so every pull request's lint job is red regardless of its content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:20:08 -07:00
c0c9a499d5 Escape node names and channels in chart tooltips and parsed telemetry (#177)
Three places still put mesh-provided strings into HTML unescaped:

- stats.html: the pie-chart tooltip formatter returned params.name raw. The charts cover
  channel, hardware model, role and gateway firmware, so a channel or firmware string ran
  as HTML when someone hovered its slice.
- traceroute.html: the node tooltip formatter returned the long name raw.
- packet.html: "PDOP", "sats_in_view", "ground_speed" and "altitude" values parsed from
  the payload text went into the card unescaped. A broadcast text message reading
  "PDOP: <img ...>" is enough.

Use the existing escapeHtml() from static/html-escape.js in all three.

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:19:16 -07:00
cfa71f9235 Keep one node's data from stopping the kiosk map or skewing the stats pies (#178)
/kiosk builds every marker in one forEach, so a single node with unexpected data stops
the whole page:

- node.role.toLowerCase() throws when role is null. Nodes first heard through a POSITION
  packet are created by ensure_node_exists() without a role, so on a fresh database the
  kiosk draws no nodes at all once one of them has a position.
- markers is a plain object keyed by channel name. A node on a channel called "__proto__",
  "constructor" or "toString" finds an existing non-array value there, and .push() throws.

Either error aborts the loop, and the channel filters and live packet blinking after it
never start. Treat a missing role as "not a router" and keep markers in a Map.

/stats counted values per field in a plain object too, so a channel called "constructor"
was counted as the string "function Object() { [native code] }1" and "__proto__" was lost.
Count in a Map there as well.

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:18:36 -07:00
5f3d37bf06 Build every CSV export through one helper that quotes cells and defuses formulas (#179)
The four "Export CSV" buttons each built CSV by hand, from values that come from the mesh:

- /nodelist used a data: URL through encodeURI(), which leaves "#" alone, so the file
  ended at the first "#" in any cell. Real node names contain one ("Big Rock Ridge #1").
- The node page's chart export (neighbors, telemetry) did not quote cells at all.
- /stats quoted cells but did not double quotes inside them, so a channel such as
  Mesh "West", Bay split into extra columns.
- None of them guarded against spreadsheet formulas. A node name, channel, firmware string
  or message that starts with = + - or @ opens as a live formula in Excel or LibreOffice.

Add static/csv.js (loaded from base.html, like html-escape.js) with csvCell(), which
quotes every cell, doubles inner quotes and prefixes formula-like text with ', leaving
plain numbers alone, and csvFromRows(). All four exports use it; /nodelist now downloads a
Blob like the others.

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:18:11 -07:00
1e9b1a3ce3 Answer 400, not 500, for malformed or out-of-range numeric parameters (#180)
* Answer 400, not 500, for malformed or out-of-range numeric parameters

Many endpoints turned query or path values into integers with a bare int(), so
?length=abc on /api/channels or /api/stats/top, /api/traceroute/abc and
/graph/traceroute/abc answered with an unhandled exception and a 500.

Values that do parse could still fail: node and packet ids are BIGINT and portnum is
INTEGER, and a longer number made the database driver raise, which also answered 500
(/api/packets?node_id=99999999999999999999999, /api/packets_seen/<same>, the node QR and
coverage routes, /api/stats and /api/stats/count filters). On PostgreSQL that includes
any portnum outside 32 bits.

length had no upper bound, so a huge value overflowed timedelta (500) or widened the
aggregate to the whole table. On SQLite a negative limit means "no limit", so
/api/stats/top?limit=-1 returned every node and skipped its 100-row cap.

Add parse_db_int(), which also rejects values outside the column's range, plus two small
helpers that answer 400 with a message. Clamp length to 1..3650, limit to 1..100 and
offset to >= 0. Endpoints that already ignored an unparseable filter (the /api/packets
node, portnum and time filters) treat an out-of-range one the same way. Valid requests are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove a blank line that fails the ruff format check

`ruff format --check .` (the Ruff workflow, ruff 0.13.3) fails on develop because of this
line, so every pull request's lint job is red regardless of its content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:16:50 -07:00
d66c5d21e9 Log request values and MQTT topics with %r so they cannot add log lines (#181)
* Log request values and MQTT topics with %r so they cannot add log lines

/api/packets logged an unparseable since, portnum or node id filter with an f-string, so
the decoded query value went into the log as is. ?since=1%0A<fake line> wrote a second,
forged log line, and escape sequences (ESC, BEL) reached whoever reads the log in a
terminal. The ingest error added in startdb.py logged the MQTT topic with %s.

Use %r for these values, as the gateway_id warning in mqtt_store.py already does, so CR,
LF, ESC and other control characters appear escaped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove a blank line that fails the ruff format check

`ruff format --check .` (the Ruff workflow, ruff 0.13.3) fails on develop because of this
line, so every pull request's lint job is red regardless of its content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:15:33 -07:00
a5f46d6360 Bound the traceroute data a request has to draw (#182)
* Bound the traceroute data a request has to draw

Every TRACEROUTE message is stored as a new row under its packet id, and its route can list
any number of hops. /graph/traceroute decodes every row, looks up every node on every route
and runs Graphviz over the result inside the request handler, so a publisher can make one
page view take a very long time and stall every other request meanwhile:

- 3,000 rows of 8 random hops for one packet id: the graph took about 43 s and other
  requests waited up to 24 s.
- 150 rows of 400 hops: about 40 s, then a 500, with other requests waiting about 10 s.

Real traceroutes are small. In 17 days of bay-area traffic no packet id had more than 72
rows, and none of 66,709 routes had more than 8 hops in either direction, which is the
firmware's limit.

get_traceroute() now returns at most the 200 earliest rows, and the TRACEROUTE decoder
treats a route longer than 8 hops as undecodable, which every caller already skips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove a blank line that fails the ruff format check

`ruff format --check .` (the Ruff workflow, ruff 0.13.3) fails on develop because of this
line, so every pull request's lint job is red regardless of its content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Nathan <201262769+nullrouten0@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:10:03 -07:00
Mike BennerandClaude Opus 5.5 ae7ba15263 Include the node's public key in the contact QR code (#185)
* Include the node's public key in the contact QR code #184

The contact QR code and import URL never set user.public_key. Current
Meshtastic apps refuse to import a keyless contact. On 2.7.x firmware,
an older app's add_contact also overwrites the key the radio already
holds, so direct messages to that node fail.

Read the key from the node's latest stored NODEINFO_APP packet. Share
the contact only when a 32-byte key is known. Otherwise the node page
hides the QR buttons and says why. The QR API response now also
returns public_key.

Also drop the stray blank line that fails ruff format --check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Do not share a contact when a node has reported more than one key #184

A forged NODEINFO uplinked over MQTT could otherwise become the newest
packet, and the shared contact would then replace the key a scanning
radio already trusts. The QR endpoint now reads every stored NODEINFO
from the node and refuses to share when their 32-byte keys disagree.
It reports that in public_key_conflict, and the node page says why.

Add an index on packet (from_node_id, portnum, import_time_us DESC) so
the lookup reads only the node's NODEINFO packets. Order with NULLS
LAST so PostgreSQL does not treat old rows without import_time_us as
newest. Carry is_licensed and is_unmessagable into the contact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Skip NODEINFO rows with no payload when building the contact #184

packet.payload is nullable, and MeshPacket.FromString(None) raises
TypeError, so one such row made the QR endpoint answer 500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:08:54 -07:00
pablorevilla-meshtastic b87e9b9917 mode changes 2026-10-26 21:55:56 -07:00
pablorevilla-meshtastic 0352ac5802 more cosmetic changes 2026-10-26 16:37:17 -07:00
pablorevilla-meshtastic daeed58042 latest updates 2026-10-26 15:17:14 -07:00