Add multi-user web server variant (browser UI behind a reverse proxy)

New web/ subfolder: a Node http server that serves the SAME tooling as the
Electron app to any browser, with per-session MCMS credentials so several
operators can use it at once behind a TLS reverse proxy. Mobile-responsive.

Not a fork — it reuses the core and the UI:
- serves renderer/app.js + app.css verbatim and rewrites index.html on the
  fly (mobile viewport + browser window.api shim + responsive overlay)
- web/public/api-web.js: a drop-in window.api over fetch + NDJSON streaming;
  Electron file dialogs -> <input type=file>, CSV-to-Downloads -> Blob
- web/server.js: per-session McmsClient (cookie pfw_sid), idle expiry,
  routes mirroring the IPC handlers, NDJSON for the 6 streaming ops
- pure Node http, no new deps (borrows ../node_modules tough-cookie)

Shared improvements (benefit both apps):
- src/dashboard.js: extracted, pure dashboard aggregation (main.js now uses
  it); adds abnormal-Tx detection alongside abnormal-Rx
- renderer: dashboard health stats are now clickable — abnormal Rx / Tx /
  lasers-off pop a list of the offending devices (like the iOS app), via a
  new showListModal

Docs: web/README.md (run + reverse-proxy + security), CLAUDE.md §16.

Verified: node --check all; started the server and confirmed transformed
index, shared-asset serving, 401 auth gate, login (per-session client), and
NDJSON wiring; rendered login + dashboard drilldown + mobile over HTTP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon Vanvik 2026-06-23 14:07:23 +02:00
parent 9106e5a071
commit 96b0264179
10 changed files with 1096 additions and 109 deletions

View file

@ -556,3 +556,28 @@ button.danger:hover:not(:disabled) {
font-weight: 700;
letter-spacing: 0.5px;
}
.dash-chevron { color: var(--neon); margin-left: 8px; font-weight: 700; }
/* List/menu popped from a clickable dashboard stat. */
.list-modal { width: 540px; }
.list-modal-items {
max-height: 52vh;
overflow-y: auto;
display: flex;
flex-direction: column;
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: inset 0 0 22px rgba(33, 200, 255, 0.04);
}
.list-modal-item {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 14px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
}
.list-modal-item:last-child { border-bottom: none; }
.list-modal-item:hover { background: rgba(33, 200, 255, 0.06); }
.lm-primary { font-weight: 600; }
.lm-secondary { color: var(--fg-dim); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }