- A scan no longer looks hung: the button counts seconds against the
chosen timeout, the page says in words that it is listening for
cameras and asking every address in turn, the refresh afterwards has
its own state, and the result reports how many cameras were found, how
long it took and how many addresses were checked. scanNetwork() returns
that report; discoverCameras() still exists on top of it.
- The config pointer now remembers the binary folder beside the data
folder, so install-video and start can't disagree about where MediaMTX
lives, whichever directory each is run from.
Checked through an installed tarball against the real network: 2 cameras
found, 4,093 addresses probed, 3.0 s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
onvif-dashboard install-video put MediaMTX in ./bin relative to where it
was run, but start launches the server from inside the package, so the
app looked for it there and reported it missing. serverEnvironment() now
settles CAMERAS_DATA_DIR and CAMERAS_BIN_DIR while still in the user's
directory, and start passes them on. Checked from an installed tarball:
MediaMTX in ./bin is found and the video bridge comes up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Installing 0.1.0 failed twice, both my mistakes.
- Node refuses to strip types under node_modules, so shipping .ts for the
CLI could never work from an installed package. tsconfig.cli.json now
compiles those modules to dist/ as ES modules, and the helper scripts
are plain .mjs.
- serverExternalPackages made Turbopack emit require("onvif-<hash>"),
a name that resolves nowhere, because onvif comes from a git URL.
Bundling it fixes that; checked from an installed tarball, where
/api/discover answered 200 and a real camera's info and snapshot came
back through the packaged server.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- next.config.ts builds standalone output with image optimization off,
and scripts/bundle-standalone.mjs puts the static files beside the
server, then drops what tracing swept in but the app never runs (the
MediaMTX binary and sharp's platform binaries, 79 MB). The build is
27 MB and npm pack is 4.9 MB.
- The CLI gained start, which runs that server from any directory with
the remembered data folder and stored key in its environment, and
admin, so the messages in the UI can name a command that exists.
- Messages that said "npm run …" now say "onvif-dashboard …".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- src/lib/first-run.ts settles where data lives (asked once, remembered
in a pointer under the user's config dir), creates the owner-only key
that protects stored camera logins, and reports whether MediaMTX is
installed. Without a terminal nothing prompts: defaults are taken and
logged, so a service still starts. Running it again changes nothing.
- cli/onvif-dashboard.mjs is the published command: setup, install-video
and help. Node runs the TypeScript in src/lib directly, so the CLI
needs no build of its own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Rename the package to onvif-dashboard, drop private, add the MIT
LICENSE and the metadata npm needs; a files list keeps the tarball to
473 kB instead of sweeping in the MediaMTX binary.
- src/lib/paths.ts now decides where everything lives, so the app can run
from any directory. CAMERAS_DATA_DIR moves the data folder;
CAMERAS_BIN_DIR moves the helper binaries; every existing per-file
override still wins, and the defaults are exactly what running from the
repo meant before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 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>
- 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>
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>
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>
Sets up Vitest (jsdom, Testing Library, v8 coverage) per the Next 16
testing guide, using Vite's native tsconfig path resolution instead of
vite-tsconfig-paths. A server-only stub and a temp-data helper let
server modules run in isolation. @types/node moves to ^24 to match the
Node 24 runtime (a vitest 5 peer requirement).
First 68 tests cover the discover route (including iss-dbwgww8: no raw
errors in responses), the credential store, the camera registry,
camera-route helpers and the refresh-rate schema. Line coverage is
31.2%, toward the 95% goal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Discovers ONVIF cameras (Hikvision/Annke) over WS-Discovery, keeps a
server-side registry and an encrypted credential store, and serves
info, snapshot and credentials routes for each camera.
The home page is now a Server Component that lists known cameras from
the registry on load, without a scan (vrek iss-a0hz0py). The snapshot
refresh rate lives in the URL (?refresh=), and a scan refreshes the
server-rendered list.
Route input is validated with zod (iss-rjqy3hy), and /api/discover no
longer returns raw error messages (iss-dbwgww8). Adds
@tanstack/react-query and zod as dependencies, with a QueryClient
provider in the root layout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>