Files
julian 39d4bac419 Setup: manage multiple device instances per category (add/remove)
The data model was already multi-instance (lane_devices = one row per
instance; assign always inserts) -- the limitation was UI-only. Make the
whole flow support more than one of every category:

- Backend: add DELETE /api/setup/assign/:id (unassign by id). /state now
  redacts secrets (pushPassword/webPassword/relayPassword) via a shared
  redactSecrets() also used by /assign -- it was returning raw config rows.
- Web: SetupWizard reworked from one fixed slot per category into a list of
  assigned instances (driver/role/host + Remove) plus an "Add another" form.
  select-type config fields (e.g. printer role) now render as dropdowns.
- api.ts: add fetchState(), unassignDevice(), Assignment/SetupState types.

Verified via Fastify inject: two printers assigned to one lane both list,
no secret leak, delete -> 204, delete unknown -> 404, count drops to 1.
Full repo typechecks.

Wiki: first-run-setup documents multi-instance + delete + redaction.
2026-06-14 20:39:39 +02:00

3.4 KiB

type, tags, sources, updated
type tags sources updated
concept
parking
architecture
devices
admin
parking-system-architecture
2026-06-15

First-Run Setup (device selection)

The admin install flow that makes the system device-agnostic in practice: on first run, an admin assigns devices per lane by choosing from the device-registry catalog and entering each device's connection config.

Implementation-derived (from apps/server + apps/web), not the source doc.

Flow

  1. Read the catalog — GET /api/setup/catalog returns supported drivers per category (no secrets, just schema) plus a discoverable list. The web SetupWizard renders a picker + the driver's config fields, and a Scan button for discoverable drivers (device-discovery).
  2. Test (optional, no save) — POST /api/setup/test (admin-only). Validates the config, probes reachability (healthCheck), and reports preconditions (e.g. input_link_relay) — without saving or changing the device. The wizard's Test connection button shows a health badge + any precondition warnings.
  3. Save & configure — POST /api/setup/assign (admin-only). Validates, then configures the device: fixes preconditions (e.g. disables input_link_relay) and sets up the Digest- authenticated input push (device-input-flow) — the admin never touches the device's own web UI. Fails the save (no DB row) if the device can't be configured, so there are no orphan/half-configured rows. On success persists to lane_devices.
  4. Remove — DELETE /api/setup/assign/:id (admin-only) drops one instance's row. Only our row is removed; the device itself is not un-hardened/un-configured (a stale push from an unknown device id is already rejected, and re-assigning reconfigures it).
  5. Complete — POST /api/setup/complete marks the single-row setup_state.

Config granularity — multi-instance per category

The data model is multi-instance: lane_devices holds one row per instance, keyed by a generated id, with no one-per-(lane, category) constraint. So a lane can have more than one of every category — e.g. two printers (an entry dispenser + a booth printer; see printer-roles-failover), multiple readers, multiple cameras. assign always inserts a new row (never an upsert), and state returns the full list.

The SetupWizard reflects this: each category shows the list of assigned instances for the current lane (with Remove) plus an Add another form — not a single fixed slot. select-type config fields (e.g. a printer's role) render as dropdowns.

Organized per lane — each lane gets its access controller(s), reader(s), camera(s), and printer(s), each with its own connection settings. Matches the architecture's "mixable per lane" reality (a lane can serve permit holders via wiegand and casual via host-side reads on one relay — see entry-exit-readers).

Security notes

  • The assign/state/delete/complete endpoints require the admin role (local-jwt-auth).
  • Device credentials are stored in lane_devices.config — protect at rest (disk-os-hardening); device hosts belong on the isolated VLAN (network-isolation).
  • Secrets are stripped on the way out: assign and state both redact pushPassword, webPassword, and relayPassword from the returned config (the UI lists devices; it never needs the stored secrets).