35 Commits
Author SHA1 Message Date
agessaman 332a61eb35 feat(web_viewer): fetch a message row's packet by hash when it's opened on Real-time
The replay on load is the newest 50 messages and the newest 50 packets, and packets reach back far less, so older message and command rows had a hash and no packet. Opening such a row now asks GET /api/packets/by-hash/<hash>?near=<epoch> for that packet's stored receptions and fills in route, path, receptions and Inspect bytes. The near time bounds the scan to the timestamp index, since the hash is only inside the row's JSON (about 6 ms against a full scan's 250 ms on 90k rows).

The fetched receptions fill the row only: they stay out of the spark and the replay's coverage, since they aren't new hearings. The detail says Loading while it waits, that the packet isn't in the bot's log when nothing comes back, or that it couldn't load and reopening retries.
2026-10-24 16:11:16 -07:00
agessaman b057ad9622 fix(web_viewer): explain a message row's missing packet in its detail instead of on the row
"packet not on the tape" didn't say why. On load the page replays the newest 50 messages and the newest 50 packets, and packets reach back far less, so older messages have no packet to join. The row no longer carries the fact; the expanded detail says the packet is older than the ones loaded, wasn't seen on the page (live rows), or was never recorded by the bot.
2026-10-24 16:01:21 -07:00
agessaman bcebeadcab style(web_viewer): polish the dashboard and Real-time after the critique
- Real-time rows put SNR before the hop count. On a phone the end of the line is cut off, and SNR is the reading a quick check wants; it used to show as "SNR …".
- A repeat heard again only lights its "heard ×N" count instead of re-running the whole row's 1.6 s flash, so a busy tape no longer shimmers. In 20 s of live traffic: 26 count bumps, 8 flashes for genuinely new rows. Reduced motion turns both off.
- The health strip says "1 client", not "1 clients".
- American spelling throughout the user-visible text: "One-hop neighbors" and its tooltip and empty message.
- "Multibyte share of packets" in sentence case like the other card titles.
- The hops chart's y-axis reads "% of nodes or packets" instead of chart-library "% of series".
- The route bar's label rounds to one decimal like its legend, so it no longer shows 98% beside 98.4%.
- The note that bot statistics need collect_stats is configuration guidance, not a degraded state, so it drops the warning color.

The full suite passes (5878), and axe-core still finds nothing on any page in either theme.
2026-10-24 14:59:35 -07:00
agessaman 3657bea8ec style(web_viewer): one color per packet type and role, and 20 channel colors that aren't status lamps
The dashboard's payload mix colored its bars by rank, so a packet type's color changed whenever the traffic order did, and disagreed with the multibyte chart further down, which colors the same types by a fixed slot. The sixth rank was also #198754, the status green. Payload types now take their fixed slot from the chart's SERIES_PALETTE, per theme, so each type has one color everywhere on the page. The role mix gives each role a fixed color matching the Mesh page's node fills: repeaters Signal Blue, room servers Route Teal, then companion orange, sensor violet, bot pink, gateway cyan. Unknown and Other stay neutral, any other role takes a spare, and none of these colors is a lamp.

Real-time hashed channel names onto the whole hue wheel, so a channel could come out status green or alarm red; Public was green. Channel dots now draw from 20 colors per theme, ten hues kept clear of the lamp red, amber and green at two lightness steps, each holding 3:1 on the card (4.2:1 light, 5.2:1 dark). A channel starts at its hashed slot, so its color stays the same between visits. If another channel on the page already holds that slot it steps 7 slots on, a clearly different hue, so up to 20 channels never share a color; with a plain hash, two of three channels on the test mesh collided.

DESIGN.md's data-palette section describes both.
2026-10-24 14:52:30 -07:00
agessaman 12672f432f feat(web_viewer): an RX lamp on Real-time and the newest node by name
Real-time's receiver strip gains an RX LED beside the station state, borrowed from the radio's own front panel. It lights for 140 ms on each live reception, and a burst keeps it lit instead of strobing. It is dark when nothing is arriving, which honestly answers "is anything coming in?" on a quiet station. It uses the connected lamp's green because it means receiving, sits behind aria-hidden next to "Last heard", which carries the same information in words, and stays dark under prefers-reduced-motion. Replayed history doesn't light it; it's one class toggle per packet, with no polling.

The dashboard's New nodes tile names the newest arrival from the last day under its count, e.g. "newest: N7JMV-BEACON-RP · 7 hops · 1h ago", so the number has a neighbor behind it. Long names truncate so the tile keeps its height. The snapshot computes it with one indexed query alongside the other contact counts, so nothing new is polled.
2026-10-24 14:48:49 -07:00
agessaman 732ac72b76 feat(web_viewer): a first-run panel and a listening clock for a station that hasn't heard anything
On a fresh install the dashboard was about fifteen identical "No data." boxes and empty donut rings, which read as broken at exactly the moment an operator wants to know the new bot is working. Real-time said "Nothing heard yet" forever, with no sense of whether the silence was normal.

Until the snapshot has seen any node or packet, the dashboard now shows one panel in place of the grid:
- What the station is doing, from the same verdict as the strip: listening, starting, not running (start the bot), or the radio problem in the way.
- What fills in first: nodes from adverts, then paths and hop counts, then one-hop neighbors.
- That timing depends on the mesh.
- For admins, buttons to watch Real-time and check the radio settings; both pages are admin-only, so anonymous visitors get the panel without them.
The grid comes back on its own once anything is heard.

Real-time's empty tape now says "Listening since 2:28 PM (12 min) · nothing heard yet", counting each second from the bot's start time, which /api/banner-status now reports while the bot runs. After ten minutes with nothing, it adds one line of diagnosis: that can be normal on a quiet mesh, otherwise check the frequency and preset in Radio settings, or the Logs. When the bot isn't running or the radio can't receive, it says that instead.
2026-10-24 14:45:16 -07:00
agessaman b42f3c39b2 fix(web_viewer): say when the radio comes back and where the tape has gaps
Recoveries passed in silence. When the banner poll saw the radio come back, the offline or zombie banner simply vanished; clearing the flag by hand replaced the banner's markup with "Offline flag cleared … Refresh to confirm", so a refresh was the only confirmation. Worse, that replacement was permanent: if the radio dropped again on the same page, the banner came back showing the stale success message (and its Since time was gone). The zombie banner had the same bug.

The banner poll now notices when the bot starts running again, the radio answers again, reconnects, or comes back online. It shows one notice for the most basic of these, e.g. "Radio back online at 2:23 PM · offline for 42 min, since 1:41 PM". The notice dismisses itself after 20 seconds or on close. The server keeps no start time for a dropped radio link, so for that case the page notes when it first saw the link down. The clear and restart buttons no longer touch their banner: they wait with a spinner, poll again sooner, and reset once the flag clears, so the banner is intact if the radio drops again.

Real-time records both kinds of event on the tape as small dividers in time order:
- A recovery gets the same line with a green dot.
- A dropped socket gets a line when it reconnects. The reconnect replays the newest 50 rows; if the oldest of them reaches back past the drop, the line says "Link lost 2:28–2:31 PM · nothing missed" (and drops under 15 seconds aren't mentioned). Otherwise the line says which span isn't shown, with an amber dot, and the 10-minute spark draws that span as unknown instead of quiet.
Clear tape removes the dividers, and at most 20 are kept.
2026-10-24 14:33:08 -07:00
agessaman 87aaac3006 fix(web_viewer): stop claiming the station is up when it isn't
The dashboard's health strip and Real-time's receiver strip both said all was well whenever the viewer itself answered. On a database the bot had never run against, the strip showed a green "Online · radio ok" beside "bot down?", and Real-time a green "Listening": /api/health reported "healthy" with no bot at all, and "radio ok" only meant the zombie flag was unset.

/api/banner-status, which every page already polls every 10 seconds, now also says whether the bot is running and whether its radio is connected. The stored start time survives the bot stopping, so "running" comes from the system_health heartbeat the bot rewrites every 30 seconds: older than two minutes, or missing, and the bot isn't there. radio_connected is only reported while it is, since it's the bot's last word on the link. The poll also runs once on load instead of waiting 10 seconds.

base.html turns that into one verdict for both pages: Bot starting, Bot not running (with when it was last seen), Radio not responding, Radio disconnected, Radio can't transmit, or Listening. The lamp colors follow DESIGN.md: red for not working, amber for degraded (sends failing while receiving may still work), green only when the radio is listening. Real-time says "Checking the bot…" until the first answer instead of assuming. Uptime shows a dash while the bot isn't running, in place of "bot down?".

The change chips compared the last two complete days even when one of them had recorded nothing, so a day the bot was down showed as a 100% collapse on every tile. When either day has no packets, adverts or messages, the chips stay neutral and the note under the tiles names the day: "No change shown: nothing was recorded on Oct 23." A fresh install, where both days are empty, gets the neutral chips without the note.
2026-10-24 14:14:43 -07:00
agessaman 49b369025b fix(web_viewer): rebuild the browser packet decoder from meshcore-decoder 0.4.0
My fork of meshcore-decoder is now at 0.4.0, which brings it in line with current MeshCore firmware: encrypted PATH payloads, GRP_DATA and MULTIPART, region transport codes, the new request and text types, and the firmware's packet hash. 0.4.0 isn't published to npm, so build.sh now fetches the fork at a pinned commit (60733c0) and installs its runtime dependencies from the lockfile, still with install scripts disabled. Two builds produce byte-identical bundles.

The browser entry exports the same additions as the fork's lite entry (AnonRequestType, TextType and their name functions, PathPayloadDecoder) plus the region transport-code helpers.

The byte breakdown's hash fallback now uses the decoder's packetHash, which is the firmware hash the server sends. The old fallback hashed the hex text and never matched.

All 72,828 packets in the bot's database decode to the same payload type and path as the bot's own decoder, and packetHash matches the stored packet_hash on every one. Advert names and Public decryption are unchanged from 0.3.0. In the browser, Real-time loads without console errors, and 2-byte-hop packets, including a decrypted Public message, show the right header, path and payload in the byte breakdown.
2026-10-17 22:31:03 -07:00
agessaman 2fa8641645 style(web_viewer): put color back in Real-time's controls, the byte breakdown and Contacts' tiles
The polish passes had turned these gray in the name of keeping lamp colors for state. They now carry color that means something:
- Real-time's filter lenses take the colors of what they show (Everything in the link blue, Messages in the message blue, Commands in the bot's violet), filling when chosen; Pause fills while the tape is paused, and the 10-minute activity bars are blue
- the byte breakdown's segments are fuller tints (34% instead of 20%) with darker or lighter ink, 5.4:1 and up in light and 11.9:1 in dark, and each segment card's swatch is solid
- Contacts' Active Contacts counts read in the link blue and its Advertisements counts in teal, with the card icons matching
2026-10-17 20:43:25 -07:00
agessaman c5ea5b8aa1 fix(web_viewer): rebuild the browser packet decoder from meshcore-decoder 0.3.0
The web viewer's static/js/meshcore-browser.mjs was an esbuild bundle of @michaelhart/meshcore-decoder at 0.2.7 or older, added without a record of its version or how it was built. Those versions read the path-length byte as a plain byte count, so every packet with 2- or 3-byte hops was misparsed from the path onward: wrong header and type, a path split into single bytes, garbage payload fields, garbled advert names and group text that never decrypted.

The bundle is rebuilt from 0.3.0, which takes the hop count from bits 0-5 and the hash size from bits 6-7. Twelve real packets from the bot's database (adverts, group text, direct messages and returned paths at 1, 2 and 3 bytes per hop) decode to the same hop count, path and payload as the bot's own decoder; multibyte adverts get their real names and a 2-byte-hop Public message decrypts, on the tape and in the byte breakdown.

scripts/meshcore-decoder-browser/ holds the browser entry the old bundle was built from (recovered from the bundle: it re-exports the library and routes Ed25519's SHA-512 through crypto-js so signatures verify on a plain-HTTP page) and build.sh, which fetches the published package, installs its runtime dependencies with install scripts disabled and bundles it with a pinned esbuild in a temporary directory. It reproduces the committed bundle byte for byte. The unused meshcore-browser.min.mjs is removed, and the multibyte workarounds from the byte breakdown polish (skipping the browser decode and saying so) are taken out again.
2026-10-17 20:13:15 -07:00
agessaman 84f06f8ebf fix(web_viewer): polish the packet byte breakdown and stop trusting the old decoder on multibyte packets
The bundled browser decoder reads the path-length byte as a plain byte count, so on a packet with 2- or 3-byte hops it misplaces everything after it. The modal took its header fields, path and payload from that decode first: a flood group-text packet (0x15) showed as 0x3D Custom, the path came out as dozens of single bytes, and the advert and decrypted sections read garbage. The header now comes from the byte on the wire, path and payload from the bot's own decode, and multibyte packets skip the browser decode with a note saying why. The tape skips browser decoding for them too ("multibyte path, not decoded here") rather than risk a garbled advert name or a false "no key here".

The rest is the polish pass:
- the modal shows the 16-character packet hash the tape, search and Copy hash use, not the decoder's 8-character one
- byte ranges account for transport codes ("Byte 4", not a fixed "Bytes 0-0"), paths are grouped by hop and payloads by byte so the hex wraps between bytes, and the path-length byte's bits are explained (hop count in bits 0-5, hash size in bits 6-7)
- colors come from the base.html theme tokens, light first with dark overrides, replacing a separate bluish dark palette and its light-mode patches; byte segments use the dashboard's categorical data colors as tints (5.3:1 and up in light, 8.9:1 in dark) instead of lamp green, amber and red, the hash is no longer amber and the binary column no longer red
- one level of cards: the raw bytes are a single well instead of a card holding a box with a 4px amber stripe, the segment cards carry a swatch that ties them to the bytes, and labels follow the viewer's small-caps style
- Copy raw hex is the primary button, and copying falls back to execCommand when the Clipboard API is missing (plain-HTTP LAN viewers), saying so if the browser still refuses
2026-10-17 19:57:53 -07:00
agessaman 79e6dfe96d fix(web_viewer): keep Real-time's phone strip from overlapping
On a phone the strip was forced onto one line with items allowed to shrink below their text, so once the tape was held and the "N new · show" button joined Pause, "heard 10 s ago" ran into the rate and the button's label spilled out of it. The readings now keep their width, the controls drop to their own line at the right when there isn't room, and the button reads just "N new" on a phone (its accessible name keeps the full wording).
2026-10-17 19:47:28 -07:00
agessaman 2a1c2f86d8 feat(web_viewer): rebuild Real-time as one tape of what the radio heard
Real-time was three equal panels (commands, packets, messages) that each scrolled on their own, couldn't be held still, and showed the same transmission up to three times. It's now one tape with one row per transmission: a packet, the channel message decrypted from it and a command it triggered share a row, joined by packet hash, and the bot's reply hangs under the command with how many times a repeater was heard echoing it. Those echoes fold into the reply instead of getting rows of their own, and a packet heard several times is one row with a count and every reception (SNR, RSSI, hops, path) when opened.

A receiver strip at the top says whether the bot is listening or the radio is offline or not responding (from the banner poll base.html already runs, now published as a banner-status event), when it last heard a packet, the packet rate and a 10-minute activity bar. The tape holds still while you read it: Pause (P), scrolling down, a moving mouse pointer or keyboard focus queue new rows behind an "N new" button. Rows can be kept (safe from the 300-row limit and from Clear), filtered to messages or commands, by packet type or channel, and searched (/). Replayed history sits under a "Heard before you opened this page" divider, and adverts and keyed group text are decoded in the browser and labeled as such. Each row's glyph chip carries its packet type's icon in the dashboard chart's color for that type; decoded messages get a stronger tint, and the bot's commands and replies are violet.

Server side, command rows carry trigger_packet_hash (from the message's routing info) and, once the transmission tracker matches a repeat, reply_packet_hash. Replayed rows carry stream_ts and replayed, and live rows stream_ts. Commands stored before this pair with their message only on an exact sender, channel and text match within 20 seconds.

Real-time is admin-only now: its socket already was, so anonymous visitors got three "Admin login required" badges over spinners that never filled. They're redirected to login and the nav link is hidden from them.

The byte breakdown sliced multibyte paths by hop count, so 2- and 3-byte hops spilled into the payload; it uses the path's byte count and shows the raw path-length byte. Route types 0 and 2 were swapped, and the channel key store was often built before the decoder had loaded, so it never decrypted anything. js-sha256 is vendored under static/vendor instead of loading from cdnjs.
2026-10-17 19:40:23 -07:00
agessaman e0b7eca4fd style(web_viewer): retire legacy blue and green, quiet the packet stream
The pre-Bootstrap-5 #007bff and #28a745 (and their rgba forms) still sat on the navbar brand, status lamps, dark form focus, the conversation rail, the Contacts focus ring, the login page's brand tokens, and Realtime's hex path and newest-row glow. They now use Signal Blue (#0d6efd) and Lamp Green (#198754), which also brings the hex path's white text from 3.1:1 to 4.5:1. The brand stays above 3:1 on the rail as large bold text. The unused .log-entry, .command-entry and .packet-entry rules in base.html are removed.

Realtime packet rows carried an 11-color stripe down the left of each rounded card, repeating what the colored type badge in the row already says. The stripes are gone, so the stream reads like the dashboard's cards.

DESIGN.md adds the 2px (hair) and 5px (meter) radii that legend swatches, focus rings and meters already use, drops the drift notes these changes resolve, and records the touch-target sizing from the adapt pass.
2026-10-17 18:14:04 -07:00
agessaman 4d9f758eff perf(web_viewer): load page libraries where used and vendor the map stack
Every page loaded Chart.js (~200KB), Moment and js-sha256 from a CDN whether it used them or not. Chart.js now loads only on the dashboard, Contacts and Multibyte, and js-sha256 only on Realtime, through a page_libs block in base.html. Moment is gone: its one call wrote to a #last-update element that no page renders, and that formatting is now done natively. The remaining base scripts stay at the end of <body>, so they don't block first paint.

The Mesh page loaded Leaflet 1.9.4, MapLibre GL 5.6.0, maplibre-gl-leaflet 0.1.4 and vis-network 9.1.9 from unpkg, and the login page loaded DM Sans and Outfit from Google Fonts, so neither worked on a LAN without internet. They are now vendored under static/vendor/ and static/fonts/ with their licenses and a README recording versions and sources (source-map comments stripped, since the maps aren't vendored). With nothing left on unpkg or Google Fonts, the CSP drops those hosts; the characterization golden is regenerated for that. package-data gains a fourth directory level for Leaflet's images.

Bootstrap, Font Awesome, Socket.IO, Chart.js and js-sha256 still come from CDNs; PRODUCT.md records that as the remaining offline gap.
2026-10-17 18:10:02 -07:00
agessaman c514dd5361 fix(web_viewer): hold text at the 0.7rem floor and use one monospace stack
Realtime message times were 9.3px (0.58rem), Realtime meta chips and phone Mesh stat labels 9.9px, Mesh, Plugins and Contacts badges about 10.5px, and the Contacts path count compounded to 10.5px as a <small> inside a badge. All now sit at the 0.7rem floor DESIGN.md sets, and the path count inherits the badge size in a lighter weight. Phone layouts still fit at 390px.

Monospace was five different stacks, three of them ending in 'Courier New'. base.html now defines --font-mono (the ui-monospace stack from DESIGN.md) and points Bootstrap's --bs-font-monospace at it, and logs, realtime, mesh, contacts, greeter, config and the legacy .log-entry all read it.
2026-10-17 17:51:23 -07:00
agessaman ebdc44cd90 fix(web_viewer): status and accent text passes contrast in both themes
Bootstrap's text-success, text-warning, text-info and text-danger are fill tones, so used as text they failed contrast (amber on white was 1.6:1, info 2.0:1), and nothing adjusted them for dark mode. base.html now defines per-theme status text tokens that pass 4.5:1 on every surface tier, and the text utilities, text-muted, form-text, code, links and the outline buttons resolve through them. Info and warning badges carry dark text. Ink Muted moves a step darker so it holds on paper-sunk.

The dark message bubbles on Realtime were solid navy, forest green and purple, which overpowered the page and dropped the sender names to 1.7:1. They are now low-alpha tints of the signal and lamp hues over the card, like the dashboard delta chips, and the error bubble is red in both themes instead of purple in dark.

Also: the light log levels and level toggles move to tones that pass on the log well, the dark ERROR toggle uses dark text, Realtime drops its local orange text-warning and its opacity on message times, and the four copies of the dark .table td * rule no longer force body text color onto badges and buttons. DESIGN.md records the tokens and the rule that a fill hue never goes on text directly.
2026-10-17 17:46:51 -07:00
agessaman 15a6096430 fix(web_viewer): name every icon-only button
Icon-only buttons were named only by title, or not at all: the Realtime scroll buttons, the Mesh toolbar, contact row actions, the Plugins remove and reveal buttons, the Feeds and Radio row actions, every Bootstrap close button and the navbar toggler. Each now has an aria-label (scoped where it helps, such as "Scroll packets to newest" or "Remove Webhook 2"), with its icon hidden from assistive tech. Toggles that hold state expose it: the Mesh hot-connections button and the password reveal buttons set aria-pressed. The Plugins filter box and the dynamic key/value fields also get names, and repeating blocks name their enable switch after the block.
2026-10-17 17:04:39 -07:00
agessaman 1371d6d537 fix(web_viewer): open realtime packet details from the keyboard
Packet rows with raw hex only opened their detail modal on a mouse click. They now act as buttons (role, tabindex, Enter and Space), show the hover treatment plus a focus ring when focused, and closing the modal sends focus back to the row that opened it, since Bootstrap doesn't do that for a modal opened from script.
2026-10-17 16:58:38 -07:00
Adam GessamanandGerard Hickey dd546a12ed feat(web_viewer): split anonymous read access from an admin session (#300)
* feat(web_viewer): split anonymous read access from an admin session

When web_viewer_password is set, Dashboard, Contacts, and Mesh Graph are readable without logging in, along with the read-only APIs they need. Radio, Config, Logs, every mutating API, /api/channels, and the live Socket.IO streams require an admin session, since those can expose channel keys and decrypted traffic. The Realtime page still renders publicly but shows "Admin login required" instead of connecting. Anything not on the public allowlist fails closed.

Login now compares HMAC digests with hmac.compare_digest and clears the session before setting authenticated_admin. Logout clears the whole session and returns to the dashboard.

Co-authored-by: Gerard Hickey <hickey@kinetic-compute.com>

* fix(web_viewer): stop Contacts redeclaring IS_ADMIN and hide banner admin buttons

contacts.html declared its own top-level const IS_ADMIN, but base.html already declares one in an earlier script on the same page. Classic scripts share one global lexical scope, so the redeclaration threw a SyntaxError and the whole Contacts script never ran, leaving the table on its loading placeholder in every mode. Contacts now uses the global from base.html.

The zombie-recover and clear-offline banner buttons were still rendered for anonymous viewers even though their /api/admin/* endpoints now return 401. They are gated on the same auth_enabled/is_admin check as the other admin controls.

* fix(web_viewer): drop sockets on logout, block off-site login redirects, allow HEAD on public paths

Logging out cleared the HTTP session but left that login's Socket.IO connections open, and a socket keeps the session it connected with, so a /realtime tab kept streaming packets, decrypted messages, and logs after logout. Each login now gets an admin_login_id, sockets record it on connect, and logout disconnects the sockets opened under that login. Other admin logins stay connected.

The login next check passed ///host and /\host because urlparse sees no netloc in either, but browsers resolve both as another origin. Next targets are now rejected if they start with //, contain a backslash, or contain a tab or newline. This check predates the admin split.

Public pages and GET APIs only accepted literal GET, so HEAD (and OPTIONS) on them returned 401 and made uptime checks report the viewer as down. They now accept HEAD and OPTIONS too; admin paths are unchanged.

* docs(changelog): note the login redirect fix

* fix(web_viewer): pass IS_ADMIN through a body data attribute so ESLint can parse base.html

The frontend lint job failed with "Parsing error: Unexpected token {" because base.html's inline script assigned IS_ADMIN from a raw Jinja expression, which isn't valid JS until rendered. The flag now lives in data-is-admin on <body> and the script reads it from document.body.dataset. I also dropped the unused AUTH_ENABLED constant and declared IS_ADMIN as a readonly ESLint global, since contacts.html reads it from base.html.

---------

Co-authored-by: Gerard Hickey <hickey@kinetic-compute.com>
2026-09-23 20:47:58 -07:00
agessaman 1fb0dbb72b chore(web_viewer): drop debug console logging from templates
Removes 26 console.log calls, including contacts.html dumping whole data
structures and sample contact records on every render. Variables and
callback parameters that existed only to feed those logs go with them
(deviceTypes, anyNewDevices, an availableEdges debug block, and a
realtime status handler whose body was nothing but a log), so no new
no-unused-vars warnings are introduced — the count drops 60 to 59.

console.warn and console.error are kept. One console.log survives in
realtime.html: the decoder key-count line is a one-time startup message
with real diagnostic value, and removing it would mean deleting two
counters and their increments inside a loop.

Also normalizes the mesh page's user-visible "Neighbours" strings to
match the project's American spelling. The option value stays
"neighbors", so evidence-mode filtering is untouched.
2026-08-07 13:44:31 -07:00
agessaman 9ae9ebb327 Harden P0 security and release boundaries 2026-07-16 10:21:50 -07:00
Stacy Olivas 973d1fc947 fix: BUG-028 byte_data init; BUG-LOG-1/4 SocketIO and cleanup_database handler fixes 2026-04-14 10:02:46 -07:00
agessaman ae52be4d2b Enhance logging and UI elements in web viewer
- Added a function to strip ANSI color codes from log lines for better display in SocketIO web clients, improving log readability.
- Implemented dark mode styles for dropdown menus and other UI components to enhance user experience in dark theme.
- Updated the contacts template to include a new overflow menu for additional actions, improving accessibility and usability.
- Enhanced the login page with a more visually appealing layout and improved theme handling to prevent flash of unstyled content.
- Refined log level toggles in the logs template for better user interaction and visibility of log levels.

These changes improve the overall functionality and aesthetics of the web viewer.
2026-03-29 16:56:01 -07:00
agessaman 6246a81ab3 Enhance real-time message display and layout in web viewer
- Added new CSS styles for live message metadata badges and layout adjustments for better visual organization.
- Reorganized the HTML structure of the real-time message and packet stream sections for improved responsiveness and clarity.
- Updated JavaScript functions to support new channel key normalization and hashing for enhanced message handling.
- Modified test cases to reflect changes in message entry templates and ensure proper functionality of new features.
2026-03-21 22:07:43 -07:00
Stacy Olivas ad77d7b00d fix: BUG-025/026/027/028/029 implementations and ruff/mypy refinements
BUG-025: send_channel_message retry logic on no_event_received
BUG-026: split_text_into_chunks and chunked dispatch in message_handler
BUG-027: test_weekly_on_wrong_day_does_not_run patch uses fake_now
BUG-028: byte_data = b"" initialised before try in decode_meshcore_packet
BUG-029: app.py db_path via self._config_base; realtime.html socket race
  fixed; base.html forceNew removed; ping_timeout 5 to 20s

Additional: ruff and mypy refinements across all modules; discord bridge,
telegram bridge, rate limiter, and service plugin updates
2026-03-17 18:07:19 -07:00
Stacy Olivas 93f73a15a2 feat: web viewer — auth, contact management, live streaming, config, maintenance, and backup
Auth (BUG-001):
- Optional password via web_viewer_password in [Web_Viewer]; /login and
  /logout; Flask session guard on all routes and SocketIO handlers

Contact management and export:
- Star contacts of any type; purge-preview + purge inactive contacts
- GET /api/export/contacts and /api/export/paths: CSV/JSON with time-range

Config tab and maintenance:
- /config page: SMTP, log rotation, DB backup settings in bot_metadata
- Nightly email digest (uptime, contacts, DB size, log errors); SMTP
  timeout=30s; pre-rotation log attachment hook
- GET /api/maintenance/status: Maintenance Status card

DB backup, restore, and purge:
- POST /api/maintenance/backup_now; GET /api/maintenance/list_backups;
  POST /api/maintenance/restore (SQLite magic-byte validation)
- POST /api/maintenance/purge: remove rows older than threshold
- Scheduled backups: daily/weekly/manual with retention pruning
- Config save validates db_backup_dir exists; 400 on missing path

Live streaming and realtime monitoring:
- Live Activity panel: colour-coded SocketIO feed with pause/clear
- capture_channel_message() feeds packet_stream; message_data event
- /realtime page: three independent stream panels; [#channel] prefix
- /logs page: subscribe_logs/log_line; log-tail thread; level colouring
- History replay: last 50/50/200 items on connect
- Werkzeug 3.1 WebSocket fix: _apply_werkzeug_websocket_fix()
- BUG-029: db_path resolved via config_base = Path(config_path).parent;
  stored as self._config_base; dead _get_db_path() removed

Scroll/filter controls and connected agents:
- Scroll-to-top/bottom on Live Activity and all realtime panels
- Type-filter checkboxes (Packets/Commands/Messages) with applyFilters()
- GET /api/connected_clients: agent count clickable; Bootstrap modal
2026-03-17 18:07:18 -07:00
agessaman c3a196e319 Update mesh and realtime templates for clarity
- Shortened the title in the mesh graph visualization from "Mesh Graph Visualization" to "Mesh Graph" for conciseness.
- Removed the subtitle from the real-time monitoring header to streamline the display.
2026-02-16 16:20:41 -08:00
agessaman 7984fa3af6 feat: Enhance command messaging with repeat tracking and transmission monitoring
- Updated `send_dm` and `send_channel_message` methods to include an optional `command_id` for tracking message repeats.
- Integrated transmission tracking to record and manage repeat messages, improving message handling and response accuracy.
- Enhanced `capture_command` method in `BotIntegration` to store repeat information for better analysis in the web viewer.
- Added favicon support and improved web viewer templates to display repeat information effectively.
- Implemented JavaScript updates in the web viewer to handle command updates and display repeat counts dynamically.
2026-01-11 20:30:02 -08:00
agessaman 106e7efca8 feat: Add light mode support for modal and UI elements in web viewer
- Implemented comprehensive light mode styles for various components in the web viewer, including modals, segment cards, and tables.
- Enhanced visual consistency by applying theme-based background and text colors across different UI elements.
- Updated styles for header and message content to ensure readability and aesthetic appeal in light mode.
2026-01-10 19:44:24 -08:00
agessaman ca80924e38 feat: Enhance web viewer functionality and configuration options
- Updated `config.ini.example` to include a new option for additional hashtag channels to decode in the packet stream.
- Modified `BotDataViewer` to retrieve and display additional decode-only channels from the configuration.
- Improved packet handling in `message_handler.py` to capture full packet data for web viewer integration.
- Enhanced the web viewer's JavaScript to support detailed packet analysis and display, including color-coded hex breakdowns and improved user interface elements.
- Added new styles and scripts to the web viewer templates for better visual representation of packet data and improved user experience.
2026-01-10 16:39:07 -08:00
agessaman 183976e899 Add dark mode support across web viewer templates. 2025-12-06 12:35:58 -08:00
agessaman f162336d44 updated documentation about placeholders in responses, improvements to path data in webviewer 2025-11-11 20:18:01 -08:00
agessaman 7bb51f219b Web Viewer Integration
- Add Flask + Flask-SocketIO web viewer with dashboard (modules/web_viewer/app.py and related)
- Add web viewer templates: index, realtime, tracking (contacts), cache, purging, stats (modules/web_viewer/templates/)
- Add integration hooks and utility functions for web viewer (modules/web_viewer/integration.py, modules/utils.py)
- Add command to launch web viewer from bot CLI (modules/commands/webviewer_command.py)
- Update .gitignore: ignore db/log files, test scripts, and web viewer artifacts
- Add restart_viewer.sh helper script for standalone web viewer restart/troubleshooting
- Add guidance and documentation for modern viewer in WEB_VIEWER.md and docs/
- Various code structure and import improvements to core bot and command modules to support integration
- Add ACL support for sensitive commands
- Example config updates

Benefits:
- Decouples monitoring/UI from bot core process
- Enables real-time browser dashboard and unified contact/repeater tracking
- Easier integration, dev, and troubleshooting
2025-10-21 21:57:00 -07:00