From e8bef1ac7c37aa89cddf59ae1dde6f959c2f3ef7 Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Mon, 13 Apr 2026 22:34:28 -0400 Subject: [PATCH] feat: add waveform viewer endpoint and enhance UI with new tabs for history, units, monitor log, and sessions --- sfm/server.py | 6 + sfm/sfm_webapp.html | 562 ++++++++++++++++++++++++++++++++++++++- sfm/waveform_viewer.html | 5 +- 3 files changed, 570 insertions(+), 3 deletions(-) diff --git a/sfm/server.py b/sfm/server.py index 58e49a9..0ed47ad 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -402,6 +402,12 @@ def webapp(): return str(Path(__file__).parent / "sfm_webapp.html") +@app.get("/waveform", response_class=FileResponse) +def waveform_viewer(): + """Serve the standalone waveform viewer.""" + return str(Path(__file__).parent / "waveform_viewer.html") + + @app.get("/device/info") def device_info( port: Optional[str] = Query(None, description="Serial port (e.g. COM5, /dev/ttyUSB0)"), diff --git a/sfm/sfm_webapp.html b/sfm/sfm_webapp.html index 904842c..5db6746 100644 --- a/sfm/sfm_webapp.html +++ b/sfm/sfm_webapp.html @@ -448,6 +448,143 @@ font-size: 14px; margin-top: 40px; } + + /* ── DB tabs (History / Units / Monitor Log / Sessions) ── */ + .db-tab-pane { padding: 0; display: flex; flex-direction: column; overflow: hidden; } + .db-tab-pane.active { display: flex; } + + .db-toolbar { + background: var(--surface); + border-bottom: 1px solid var(--border2); + padding: 8px 18px; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + flex-shrink: 0; + } + .db-toolbar label { color: var(--text-dim); font-size: 11px; white-space: nowrap; } + .db-toolbar input[type="text"], + .db-toolbar input[type="date"], + .db-toolbar select { font-size: 12px; padding: 4px 8px; } + .db-toolbar select#db-serial-filter { width: 120px; } + .db-toolbar input.date-input { width: 130px; } + .db-toolbar-spacer { flex: 1; } + .db-count-badge { + color: var(--text-mute); + font-size: 11px; + white-space: nowrap; + } + + .db-scroll { flex: 1; overflow-y: auto; padding: 14px 18px; } + + .db-table-wrap { + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; + max-width: 100%; + } + table.db-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; + } + table.db-table thead th { + background: var(--surface2); + color: var(--text-dim); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + padding: 7px 12px; + text-align: left; + border-bottom: 1px solid var(--border); + white-space: nowrap; + } + table.db-table tbody tr { border-bottom: 1px solid var(--border2); } + table.db-table tbody tr:last-child { border-bottom: none; } + table.db-table tbody tr:nth-child(even) { background: var(--surface); } + table.db-table tbody tr:hover { background: var(--surface2); } + table.db-table tbody td { + padding: 7px 12px; + color: var(--text); + white-space: nowrap; + font-family: monospace; + font-size: 12px; + } + table.db-table tbody td.td-text { + font-family: inherit; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + } + table.db-table tbody td.td-dim { color: var(--text-mute); } + table.db-table tbody td.td-key { color: var(--blue-lt); } + + /* PPV color tiers: green < 0.5, amber < 2.0, red ≥ 2.0 in/s */ + .ppv-ok { color: var(--green-lt); font-weight: 600; } + .ppv-warn { color: var(--yellow); font-weight: 600; } + .ppv-high { color: var(--red); font-weight: 600; } + + .ft-badge { + background: rgba(248,81,73,0.15); + border: 1px solid rgba(248,81,73,0.4); + border-radius: 4px; + color: var(--red); + font-family: inherit; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.05em; + padding: 1px 6px; + } + .ft-toggle-btn { + background: none; + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-dim); + cursor: pointer; + font-size: 11px; + padding: 2px 8px; + } + .ft-toggle-btn:hover { border-color: var(--red); color: var(--red); } + .ft-toggle-btn.flagged { border-color: var(--red); color: var(--red); background: rgba(248,81,73,0.1); } + + .db-empty { + color: var(--text-mute); + font-size: 13px; + padding: 40px 0; + text-align: center; + } + + /* Units tab cards */ + .units-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + max-width: 900px; + } + .unit-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 14px 16px; + cursor: pointer; + } + .unit-card:hover { border-color: var(--blue-lt); } + .unit-card .uc-serial { + font-size: 16px; + font-weight: 700; + font-family: monospace; + color: var(--blue-lt); + margin-bottom: 8px; + } + .unit-card .uc-stat { + display: flex; + justify-content: space-between; + margin-bottom: 4px; + } + .unit-card .uc-label { font-size: 11px; color: var(--text-mute); } + .unit-card .uc-val { font-size: 12px; color: var(--text); font-family: monospace; } @@ -538,6 +675,10 @@ + + + + +
+
+ + + + + + + +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+ + + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ + +
+
+