Recorders are now physical capture ports, not user-created rows: - migration 036: label, enabled, auto_provisioned + UNIQUE(node_id,device_index) (the structural fix that makes two recorders sharing a port impossible) - mam-api: auto-provision one recorder row per port from heartbeat capabilities (reconcileRecordersForNode); create-once, never overwrites operator config - mam-api: POST /:id/enable + /:id/disable (provision/teardown standby sidecar); PATCH accepts label; config persists across enable/disable - node-agent: freeCapturePort() force-removes any container on a capture port before standby/start — eliminates the EADDRINUSE collisions - web-ui: recorder menu grouped by node (online/offline), Enable/Disable toggle, per-recorder config modal (codec/bitrate/growing/label/project), friendly label over hardware name, no destructive delete Fixes the delete/recreate churn that orphaned standby sidecars and collided on capture ports during this session's outage.
38 lines
2 KiB
SQL
38 lines
2 KiB
SQL
-- Migration 036: Recorders become physical hardware, not user-created rows.
|
|
--
|
|
-- A recorder now maps 1:1 to a physical capture port: (node_id, device_index).
|
|
-- mam-api auto-provisions one row per port from each node-agent heartbeat's
|
|
-- capabilities (deltacast/blackmagic arrays). Rows are NEVER deleted by the
|
|
-- operator — they're discovered, enabled/disabled, and configured in place.
|
|
-- This removes the delete/create churn that orphaned standby sidecars and
|
|
-- caused capture-port (EADDRINUSE) collisions.
|
|
--
|
|
-- New columns:
|
|
-- label : optional friendly name overlaid on the hardware identity
|
|
-- (e.g. "Aurora" for zampp3-dc0). NULL → UI shows node+port name.
|
|
-- enabled : operator opt-in. false (default) = no standby sidecar, port idle.
|
|
-- true = persistent standby sidecar kept up (idle-preview), ready
|
|
-- to record. Toggled by the Enable/Disable button.
|
|
-- auto_provisioned : true when the row was created by heartbeat discovery
|
|
-- (vs a legacy manually-created recorder). Informational.
|
|
--
|
|
-- Identity:
|
|
-- UNIQUE(node_id, device_index) is the structural guarantee that two
|
|
-- recorders can never share a capture port — the root-cause fix for the
|
|
-- collisions. Partial unique index (WHERE both are non-null) so any legacy
|
|
-- rows without a node/device don't violate it.
|
|
|
|
ALTER TABLE recorders
|
|
ADD COLUMN IF NOT EXISTS label TEXT DEFAULT NULL,
|
|
ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS auto_provisioned BOOLEAN NOT NULL DEFAULT false;
|
|
|
|
-- One recorder per physical port. Partial so pre-existing rows lacking a
|
|
-- node_id/device_index (e.g. network sources) are unaffected.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS recorders_node_device_uniq
|
|
ON recorders (node_id, device_index)
|
|
WHERE node_id IS NOT NULL AND device_index IS NOT NULL;
|
|
|
|
-- Fast lookup of a node's ports during heartbeat reconciliation.
|
|
CREATE INDEX IF NOT EXISTS recorders_node_id_idx
|
|
ON recorders (node_id);
|