Commit Graph

29 Commits

Author SHA1 Message Date
b576bb44a7 vrek: advanced image settings checked in the browser, issue closed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:34:34 -05:00
daabcf74ab vrek: both cameras' existing accounts do reach ISAPI
Corrects the claim that one camera needed a separate web account; that
came from a single failed request while accounts were being changed on
that camera. The advanced settings work with the logins already stored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:33:55 -05:00
d49be1b933 vrek: correct the lock theory, the web account was deleted
The later 401s are explained by the account no longer existing, so the
illegal-login-lock hypothesis is withdrawn. The HTTPS refusal observed
while the account worked over HTTP still stands unexplained.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:32:29 -05:00
c0e987d06e vrek: camera traffic is plain HTTP; TLS investigation deferred to tomorrow
- Record that ONVIF, snapshots and ISAPI all use plain HTTP and that
  MediaMTX pulls RTSP with the login in the URL; no password crosses the
  wire in the clear, but everything else does.
- Both cameras answer HTTPS with a self-signed certificate, yet digest
  that works on port 80 is refused on 443; repeated attempts tripped the
  camera's login lock, so testing resumes tomorrow, one attempt at a time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:31:09 -05:00
12d0604dfd vrek: defer the forum survey and the dashboard marker
Probing the cameras directly answered what the desk research was for, and
the camera page already says whether advanced settings are available.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:23:49 -05:00
a74e737d64 vrek: ISAPI stays read-only
An Operator account can read ISAPI imaging but not write it (403,
lowPrivilege, proven with a no-op PUT that changed nothing). Rather than
give the app an Administrator camera login, the ISAPI-only settings stay
view-only and changes keep going through ONVIF; editing them is deferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:22:04 -05:00
8e87fd9d3d Show the camera's own advanced image settings over ISAPI
- Read /ISAPI/Image/channels/1 and its capabilities with digest auth and
  the camera's stored login, behind a vendor module.
- New "Advanced image" panel: shutter and its limits, gain limit, WDR,
  highlight and backlight compensation, noise reduction, white balance,
  day/night switching, lens distortion correction, digital zoom, the
  camera's lights and power line frequency.
- Availability comes from the camera's own capabilities, so only settings
  it advertises are shown; a refused login says a camera web account is
  needed, anything else says the camera doesn't offer more. Answers are
  cached per camera and path for five minutes.
- Parser and options are tested against real documents from a camera,
  kept as fixtures (imaging data only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 18:18:35 -05:00
e319b0198f vrek: goal to research the cameras' own web interface
- New goal: understand the Hikvision/Annke web UI and decide between
  embedding it and rebuilding its features natively.
- Issue for surveying forums and open-source clients, explicitly
  excluding firmware flashing, backdoors and auth bypasses.
- Finding: an activated Operator account reaches most of ISAPI; the
  alert stream needs more rights, main-stream stills are refused on both
  cameras, and the two cameras' account setups differ.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 17:36:13 -05:00
817310569d vrek: live video and recording goal met (6/6) and retired
- Both cameras watched live on main and sub, and motion clips recorded
  and played back from both; audio plays too, correcting the earlier
  assumption that browsers can't decode G711 in MP4.
- Retention check deferred to 2026-09-27, when today's clips age out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 17:19:37 -05:00
4fc68cacf1 Add a Recordings page to browse, play and delete motion clips
- /recordings lists clips newest first with camera, time and size, plays
  them in the browser and deletes after a confirmation.
- /api/recordings lists clips; /api/recordings/clip serves one with Range
  support so seeking works, and deletes it. A clip is named by folder and
  file name, both matched against fixed patterns and resolved inside the
  recordings folder, so no request can reach another file.
- Each camera's page links to its own clips.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 17:16:14 -05:00
4a0790565f Record motion clips from ONVIF events
- Watch each camera's ONVIF VideoSource/MotionAlarm; record while it
  reports movement and stop after a 10s post-roll, through a per-stream
  MediaMTX path so viewers are never interrupted.
- Clips go to RECORDINGS_DIR (default .data/recordings), one folder per
  recorded stream; RECORD_STREAM, RECORD_KEEP_DAYS and MOTION_RECORDING
  configure the rest. An hourly sweep deletes clips past retention.
- Add allCameraRecords(): listCameras() calls connection(), which never
  resolves outside a request and silently stalled startup. Background
  work is no longer awaited in instrumentation either.
- Document the settings in the README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 17:09:13 -05:00
2edafdeeaf vrek: live video slice done; recording design and motion event findings
- First live-video slice closed: both cameras watched live on main and
  sub (4 of the goal's 6 checks), leftover-MediaMTX cleanup confirmed.
- Recording decided: motion-triggered clips, no pre-roll, 7-day
  retention, stored under .data with an env override, stream by config.
- Motion source decided: ONVIF VideoSource/MotionAlarm; Hikvision's
  alert stream refuses the ONVIF login (403).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 16:51:18 -05:00
41021a62ad Add live video: WHEP relay route and WebRTC player
- POST/DELETE /api/cameras/[id]/live/whep check the session, resolve the
  camera from the registry, and relay WebRTC signaling to MediaMTX on
  localhost. The browser never sees MediaMTX's address, its error text or
  a camera login; video flows browser↔MediaMTX, not through Next.
- LivePlayer negotiates with the browser's own RTCPeerConnection and
  shows connecting, reconnecting and failed states with Retry.
- The camera page gains a Live section; the pop-out plays live video and
  falls back to snapshot polling if it can't. Both offer Main/Sub.
- Stop a leftover MediaMTX from a server that didn't exit cleanly: record
  its pid and, on startup, stop it only if that pid is still our binary
  with our config.
- Export vrek log: both cameras watched live on main and sub (4 of the
  goal's 6 checks).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 16:40:21 -05:00
5824db8ea9 Add MediaMTX video bridge: pinned installer and supervised localhost process
- npm run video:install downloads the pinned MediaMTX v1.21.0 for this
  platform, verifies its SHA-256 against checksums committed in
  src/lib/mediamtx-install.ts, and installs it into gitignored bin/.
- On server start, instrumentation launches MediaMTX with a generated
  owner-only config: API and WebRTC signaling on 127.0.0.1, only the ICE
  port (UDP 8189) on the LAN, a hashed per-boot API password, no
  anonymous users, unused protocols off. Restarts on crash with backoff,
  stops with the server; VIDEO_BRIDGE=off skips it.
- Camera streams are added to MediaMTX at runtime, on demand, with the
  stored login; nothing with a password is written to disk or echoed.
- rtspSourceWithLogin() reads a profile's RTSP URI over ONVIF.
- Export vrek log (live video goal, decisions, codec finding).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 15:33:55 -05:00
1c9270c7c0 vrek: measure gol-2nxgqjn as met on one user observation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 15:10:48 -05:00
2aac38146d vrek: ISAPI snapshot findings, retire met goals
- Record that camera …af54 has no full-resolution still over ONVIF or
  ISAPI (fnd-dw1pqcw, fnd-k1jyt83); close iss-3vck3js.
- Retire gol-2nxgqjn, and gol-wqf95dq and gol-8v1wqvp as met on one
  user observation each.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 15:08:50 -05:00
ebd488b672 Add camera pop-out window
- "Pop out" on each dashboard card opens /cameras/[id]/live in a named
  popup sized to the main stream (a plain link if popups are blocked).
- The live page shows only the frame, never stretched past its own size,
  with a fading name/time/refresh-rate overlay and error handling.
- The card pauses its own polling while its pop-out is open; the two
  coordinate over a BroadcastChannel.
- Share frame rendering (FrameImage) between card and pop-out.
- Ignore a stray build cache folder; export vrek log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 15:02:39 -05:00
f4d40dbea6 Add image and stream editing with audit log, manual exposure controls
- Per-section Edit/Save for image settings and streams (Server Actions),
  validated against the camera's own ranges; stream saves ask to confirm
  the restart. Results report applied or adjusted values.
- Every write is appended to a local audit log (.data/audit.jsonl).
- In Manual exposure, edit exposure time (with approximate shutter speed)
  and gain; the read-only page shows them, and a hint explains that
  Brightness mainly works in Auto.
- Pin onvif to the mikemainguy fork for complete imaging/encoder setters;
  set turbopack.root automatically when onvif is a linked checkout.
- Export vrek log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 14:44:00 -05:00
0ec434ca26 Add read-only camera settings page and dashboard stream summary
Read-only settings and stream URLs (vrek gol-2nxgqjn), shaped by a
read-only probe of the real cameras (findings fnd-*) and decisions
dec-s205dvs and dec-gtw493s.

- src/lib/camera-settings.ts: reads streams, image, time/NTP and
  network over ONVIF behind a CameraSettingsSource interface, so an
  ISAPI source can be added later. Sections are read independently
  and fail on their own with fixed text. RTSP URLs have credentials
  stripped and use the registry host. camera.ts exports its cached
  connect() and the profile helpers it shares with it (iss-7eaywtf).
- /cameras/[id]: server-rendered page with an access check, 404 for
  unknown ids or non-private hosts, and a streamed Suspense section.
  Shows streams (with copyable RTSP URLs and an ONVIF-login note),
  image, time/NTP with a drift warning, and network, plus Refresh
  and back. Copy falls back to execCommand over plain LAN HTTP,
  where the clipboard API is unavailable (iss-s935a4n).
- Dashboard card: the name links to the camera page; a main-stream
  summary line ("Main 4096×1860 · H.264 · 20 fps"); a manufacturer
  of "ONVIF" is hidden (iss-pv2bvzj).

522 tests, 99.9% line coverage. Checked by the user in a browser
against both cameras. Refreshes the vrek export (the icon principle
now covers the page's new icons).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 13:00:37 -05:00
b05047d8e5 vrek: retire icons goal, add icon principle
Retires gol-m8je1wc (icons done for now) and records principle
pri-8ev5m89, derived from dec-1vnz5fw. Icons come from lucide-react,
always accompany text, are aria-hidden, use standard Tailwind sizes,
map one concept to one icon, and are tested by lucide-<name> class.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 12:35:54 -05:00
190191df92 Add Lucide icons: camera status badge, actions and warnings
Adds lucide-react (vrek dec-1vnz5fw, chosen over Heroicons and React
Icons) for the icons goal gol-m8je1wc.

- Camera status badge on each card (iss-scj42jq): Live, Connecting,
  Needs login, Not activated, Offline, Session ended. Each is an icon
  plus a visible label, derived from existing query state with no
  extra device calls. Logic in src/app/camera-status.tsx.
- Action icons (iss-chrdd0v): scan (radar, then spinner), refresh
  rate, change or forget login, retry, the camera web page link
  (replaces ↗), save, sign in and sign out.
- Warning and error icons (iss-egy082x): unsecured banner, setup
  panel, inline errors, session-ended notice, login and setup
  headings.

Icons are decorative (aria-hidden), spinners respect reduced motion,
and every accessible name is unchanged (all 403 prior tests pass
untouched). 416 tests, 99.9% line coverage. The vitest threshold
comment now points to principle pri-be2smzk. Refreshes the vrek
export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 12:33:16 -05:00
5d181f3b3b Enforce admin login: login, first-run setup, proxy and access checks
Completes securing the web front end (vrek gol-wqf95dq, iss-r5vrjx7).

- /login: Server Action with a generic error, same-site-only redirect
  back to ?next=, and throttling of failed logins (10 per address and
  100 overall per 15 min). The header shows "Signed in as" with
  Sign out (iss-nj9wmwp).
- First run with no admin: instrumentation prints a one-time setup
  code, shared with the app through globalThis. /setup requires it,
  and 5 wrong codes rotate it. "Skip for now" runs unsecured for the
  browser session behind a red warning banner on every page
  (iss-9nxdndr).
- src/proxy.ts: optimistic redirects to /login or /setup, 401 for
  the API, and the 12 h sliding session refresh. requirePageAccess()
  and apiAccessDenied() re-check in the page and all 6 route handlers
  (iss-76d5wrb).
- An expired session now shows "Your session has ended" instead of
  the camera-login form.
- README documents in-app setup, skipping and signing in.

Verified with unit tests (383, 99.9% line coverage), end to end
against `next start`, and manually in a browser by the user.
Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:58:20 -05:00
462141aa35 Add admin login foundations: scrypt admin file, CLI, sessions
Groundwork for securing the web front end (vrek gol-wqf95dq). The app
does not enforce login yet.

- src/lib/admin-file.ts: the admin file at ADMIN_AUTH_FILE (default
  .data/admin.json). scrypt hashing (N=2^16, random salt, bounded
  parameters, constant-time compare), zod-validated reads where a
  malformed file is an error, and atomic 0600 writes that won't
  replace an existing admin without overwrite. Plain Node, so the
  CLI can share it (iss-mffqscg).
- src/lib/admin-auth.ts: server-only app layer; failed logins always
  cost one hash.
- scripts/create-admin.mts + `npm run admin:create`: create or reset
  the admin outside the app, interactive (hidden, confirmed) or piped
  (iss-7xmka20). The README documents it, a no-npm Node one-liner,
  the file format, and password reset.
- src/lib/session-token.ts and session.ts: stateless HMAC-signed
  session cookie, keyed from the password hash so a password change
  ends every session, with a 12 h sliding window (iss-e27nb70,
  dec-f0xar8r).

281 tests, 99.8% line coverage. Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:46:40 -05:00
a174d7a2d6 Keep runtime data out of Next's output file tracing
next build warned that the env-dependent registry and credential-store
paths made Turbopack trace the whole project (vrek iss-jvxcd1n).
Marking only the readFile calls cleared the warning, but it still
traced .data/cameras.json and the encrypted .data/credentials.json,
which a standalone deployment would copy from the build machine.

A /* turbopackIgnore: true */ on each default path.join(process.cwd(),
".data", ...) fixes both. The traced files for every route now include
nothing from src/, public/ or .data/. The env-var overrides the tests
use still work. Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:23:34 -05:00
4931c3af83 Reach 99.8% test coverage and enforce a 95% floor
- Route tests for camera info, snapshot and credentials (vrek
  iss-nc2tj7c): input validation, store-only-after-verify, reconnects
  after login changes, and fixed error messages with no upstream text.
- Providers and layout tests (iss-nc5w0j8).
- Discovery tests for onvif.ts (iss-3bg4r6e): multicast parsing,
  unicast sweep batching and subnet limits, de-duplication and
  merging. They run on a fake network, replacing onvif Discovery,
  node:dgram and node:os, per new vrek principle pri-e14bahk
  (replaceable transport; tests never touch the real network).
- coverage.thresholds.lines = 95, so `npm run coverage` fails below
  the goal (iss-zjpc22k).

200 tests, 99.77% line coverage. Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:17:55 -05:00
31026559b7 Test camera.ts: connection handling, errors and snapshot auth
Adds 47 tests for camera.ts (vrek iss-qak2mz8), bringing it to 100%
line coverage. A scripted stand-in for the onvif Cam class drives the
connect outcomes: profiles, warnings, inactive devices, and login
errors. Snapshots run over real HTTP against a local server with real
Digest and Basic 401 challenges. Also covers isAllowedHost edge cases.

Overall line coverage is 72.4% (143 tests). Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:07:53 -05:00
33c12ea70c Stop camera routes echoing raw error messages
cameraErrorResponse now returns fixed text for each error class and
logs the original server-side. Upstream ONVIF/SOAP responses, socket
errors and credential-file paths no longer reach the browser (vrek
iss-tz5s098). CameraAuthError gains a missingLogin flag so the UI can
still tell "no login saved" apart from "login rejected".

Tests throw errors carrying a fake password and file path, and check
neither appears in any response. Closes the dashboard migration (vrek
iss-qbh3541). Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:03:46 -05:00
870b4cd2af Move dashboard client onto React Query hooks
Adds src/app/camera-queries.ts: React Query hooks over our own routes,
with each camera's queries keyed ['camera', id]. The camera card,
snapshot polling, login form and network scan use these hooks instead
of hand-rolled fetch/useEffect state (vrek iss-2fm6x2y, iss-ksxmctm,
iss-m032zwq, iss-8hfq2y2).

- Snapshot polling pauses in hidden tabs, never overlaps a slow
  frame, and stops after a failure until Retry. Each frame's object
  URL is created and revoked in one effect, so none leak under Strict
  Mode.
- Saving or forgetting a login resets only that camera's queries.
- New "Forget saved login" action, shown for stored logins.
- Fix: a scan timeout typed below 1 s now clamps to 1 s instead of
  falling back to 5 s.

Adds jsdom component tests (test/dom.tsx helpers): 93 tests, line
coverage 54.3%. Refreshes the vrek export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:01:34 -05:00
97a6b59cfe Track vrek export, MCP config and IDE VCS mapping
Commits .vrek/log.ndjson, the vrek export of goals, principles,
decisions, issues and evidence (restore with vrek_import), plus
.mcp.json, which wires the vrek MCP server, and .idea/vcs.xml. The
SQLite database and its -wal/-shm files are local state and are
gitignored; the export is refreshed and committed periodically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 08:52:49 -05:00