917 Commits
Author SHA1 Message Date
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
pablorevilla-meshtastic f936b32a1c add node list to map 2026-10-26 10:03:14 -07:00
pablorevilla-meshtastic 92f5b16073 more updates 2026-10-26 09:51:41 -07:00
pablorevilla-meshtastic 03ca33143b move visual changes 2026-10-26 09:45:28 -07:00
pablorevilla-meshtastic de81155e9b more cosmetic changes 2026-10-26 09:27:03 -07:00
pablorevilla-meshtastic ec67254472 quick map fixes 2026-10-26 09:21:10 -07:00
pablorevilla-meshtastic ef926798fe chat colors changes 2026-10-25 22:25:15 -07:00
pablorevilla-meshtastic 278d3b7e94 First push lots of changes 2026-10-25 22:24:48 -07:00
pablorevilla-meshtastic aabb099468 update protobufs 2026-10-24 11:15:56 -07:00
pablorevilla-meshtastic 92900362dc change readme 2026-10-23 22:27:35 -07:00
pablorevilla-meshtastic df7766e783 change reporing on Snapshot 2026-10-19 17:37:30 -07:00
pablorevilla-meshtastic fb3a0154dd some updates 2026-10-15 22:49:55 -07:00
pablorevilla-meshtastic 012150cba9 update big 2026-10-14 10:12:11 -07:00
pablorevilla-meshtastic ef37a910b9 change packet reach activity 2026-10-14 10:00:17 -07:00
pablorevilla-meshtastic f4fbf16e4f uncheck packets reach 2026-10-14 08:46:00 -07:00
pablorevilla-meshtastic f9b684ee44 fix template 2026-10-14 07:36:27 -07:00
cd78c392e9 Bound /api/coverage parameters and run the ITM model off the event loop (#172)
* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Bound /api/coverage parameters and run the ITM model off the event loop

The coverage endpoint took radius_km, step_km and the other model inputs
straight from the query string with only a float() conversion. The ITM
loop advances by step_km until it passes radius_km, so step_km=0 (or a
negative value) never ends and radius_km=inf never ends either; a very
small step with a large radius means millions of model calls. All of this
ran synchronously inside the request handler, so one such request stalls
every other request the server is handling.

- Reject non-finite values and anything outside a sane range for each
  parameter (400 with a message saying the allowed range).
- Cap radius_km / step_km at 4000 steps.
- Run compute_perimeter / compute_coverage in a worker thread so the event
  loop keeps serving other requests while the model runs.
- Make the loops themselves refuse a non-positive step or non-finite radius,
  so a future caller cannot reintroduce the hang, and shrink the result
  cache (64 entries) since bounded inputs make large results possible.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Pablo Revilla <pablorevilla@gmail.com>
Co-authored-by: Nathan <nathan@stonekitty.net>
Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com>
2026-10-14 07:26:18 -07:00