Repository navigation
Closed
Labels
enhancementNew feature or requestNew feature or requestjavascriptPull requests that update javascript codePull requests that update javascript code
Description
Problem and evidence
ONVIF license-plate recognition reads are stored and fully exposed by the API (added in 0e091c6c), but the web UI has no screen for them. Nothing in web/js calls /api/lpr.
Backend (src/web/api_handlers_lpr.c, routes in src/web/libuv_api_handlers.c:289-291):
POST /api/lpr/search: JSON body{camera_uuid (required), start_at, end_at (ms epoch, range ≤ 366 days), limit (default 50), match?: "exact"|"partial", plate?}. Returns reads withid, camera_uuid, stream_name, observed_at, received_at, source, plate, confidence, attributes{country, region, plate_type, direction, lane, vehicle_type, vehicle_color, ...}, recording_id?.POST /api/lpr/export: same body (limit default 1000). Responds with a JSON download (lightnvr-lpr-export.json).DELETE /api/lpr/reads/{uuid}.- Authorization is per camera:
AUTHZ_LPR_READ,AUTHZ_LPR_SEARCH,AUTHZ_LPR_EXPORT,AUTHZ_LPR_DELETE(include/core/authorization.h). Every query is audit-logged, and plate strings aresecure_zero'd server side, so treat plates as sensitive data.
Expected behavior and scope
- Add a "Plates" (LPR) view. The Investigation workspace (
web/js/components/preact/investigation/) is a natural home, either as a tab or as a sibling page linked from the Recordings/Timeline sub-nav. Choose whichever fits the existing nav, and explain the choice in the PR. - Filters: camera picker (cameras the user can see, which supply
camera_uuid), time range, plate text with an exact/partial toggle, and a limit. - Results table: plate, time (local), camera, confidence, direction, vehicle type and color, and a "View recording" link using
recording_id/observed_atthat deep-links to the timeline at that time. - Export button, shown only when export is permitted (a 403 should hide or disable it, not show an error toast). Delete per row, with confirmation, shown only when permitted.
- Empty state that explains LPR reads come from ONVIF cameras with plate analytics.
- All strings go through
t()with newlpr.*keys inweb/public/locales/en.json. - Plates must not go into
localStorageor URLs. Keep filter state in memory.
Acceptance criteria
- A user with search permission can find reads by partial plate within a time range and open the matching recording.
- A user without export or delete permission doesn't see those actions, or sees them disabled.
- 400 errors (bad range, etc.) show readable validation messages.
- Keyboard accessible and labelled (the repo runs jsx-a11y lint in CI).
Validation
cd web && npx vite build,npm run lint,npm test.- Jest tests for the request body built from the filters and for permission-driven rendering, with
fetchmocked. - Optional: an e2e spec that seeds reads through the DB test helpers, if any exist.
Constraints and rollback
- UI only. No backend changes needed. If you find an API gap (for example, listing cameras with LPR data), open a separate issue rather than changing the API here.
- Rollback: revert the frontend PR.
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestjavascriptPull requests that update javascript codePull requests that update javascript code
Activity
Add license plate search page and fix multi-row LPR search