Skip to content

[Agent] Add a license plate (LPR) search UI #653

Open

Description

@matteius

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 with id, 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 are secure_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_at that 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 new lpr.* keys in web/public/locales/en.json.
  • Plates must not go into localStorage or 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 fetch mocked.
  • 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestjavascriptPull requests that update javascript code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions