sfm: stored-event browser at /events

New standalone HTML page (sfm/event_browser.html, ~470 lines, Chart.js)
that lets you browse persisted events from the SeismoDb + WaveformStore.
Companion to the existing live-device viewer at /waveform:

  /waveform  — connect to a unit and pull events in real time
  /events    — browse events already stored in the DB

Flow:
  1. Page loads → GET /db/units → populate serial dropdown
  2. Select serial → GET /db/events?serial=X&limit=500 → event list
  3. Click event → GET /db/events/{id}/waveform.json → render

Layout is Instantel-printout-ready: channels stacked vertically in
Tran / Vert / Long / MicL order, trigger line at t=0, peak labels,
clean dark theme.  Frames the future PDF-export feature without
needing extra layout work.

Smoke-tested against the dev prod-snapshot — 4 channels render with
correct peaks for K558 events (L=0.3 in/s = the offset-fault peak
we've been chasing all week).

CHANGELOG entry added under [Unreleased] per the v0.20.0 release plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 06:53:48 +00:00
parent 8710b8f327
commit 460006e5cd
3 changed files with 603 additions and 1 deletions
+15 -1
View File
@@ -381,10 +381,24 @@ def webapp():
@app.get("/waveform", response_class=FileResponse)
def waveform_viewer():
"""Serve the standalone waveform viewer."""
"""Serve the standalone LIVE-device waveform viewer.
Talks to ``/device/*`` endpoints — for plotting events pulled from
a connected unit in real time. For the stored-event browser that
reads from the SeismoDb + WaveformStore, see ``/events``.
"""
return str(Path(__file__).parent / "waveform_viewer.html")
@app.get("/events", response_class=FileResponse)
def event_browser():
"""Serve the stored-event browser — pick a serial, list its events,
render any one's waveform from the persisted ``.h5`` via the
``/db/events/{id}/waveform.json`` endpoint. Standalone HTML +
Chart.js, no auth, no build step."""
return str(Path(__file__).parent / "event_browser.html")
@app.get("/device/info")
def device_info(
port: Optional[str] = Query(None, description="Serial port (e.g. COM5, /dev/ttyUSB0)"),