Commit graph

128 commits

Author SHA1 Message Date
6a1d271576 feat(ui): polish round 2 — live refresh, schedule calendar, jobs times, real sidebar user
- recorders: dispatch df:recorders-changed on create/start/stop/delete so the
  list updates immediately instead of waiting for the 10s poll tick
- library: poll every 4s while any asset is live/processing (15s otherwise) and
  listen for df:assets-changed so a stopped recorder's LIVE badge drops and
  the thumbnail appears without a manual refresh
- auth: synthetic /auth/me (AUTH_ENABLED=false) now uses LOCAL_OPERATOR / USER /
  USERNAME instead of hardcoding "Admin", and flags synthetic:true
- shell: Sidebar takes `me` as a prop, drops the misleading "Admin" fallback,
  and surfaces an "auth off" hint when the response is synthetic
- jobs: replace the always-empty ETA column with a Time column that shows
  queued/started/done/failed N ago (full timestamp on hover); widen column
- schedule: new month-calendar view (default) with events plotted on day cells
  by status; clicking a day pre-fills the new-schedule modal with a 30-min
  window on that day; List view kept behind a toggle

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 14:52:04 -04:00
1afb150237 feat(assets): cleanup-live-orphans + retry handles non-error states
Two changes for issue #7 (HLS cleanup + orphan reaper) and the user's
"SRT clips ingest but won't play" complaint:

1) New POST /assets/cleanup-live-orphans — lists every directory under
   /live/<uuid>/ and deletes the ones whose UUIDs don't match an asset
   row. These accumulate when a recorder crashes mid-capture: the live
   HLS dir is created but no asset is ever finalized in the DB, so the
   files just sit on disk forever.

2) POST /assets/:id/retry now also works for assets that are 'ready'
   or 'archived' but have no proxy_s3_key. The original behavior (only
   re-queue when status='error') made it impossible to re-generate a
   proxy for older recorder captures that landed without one — the
   user could see a thumbnail in the library but the player would just
   show "Preview not yet available" with no retry path.
2026-05-23 10:28:42 -04:00
claude
90a9e4361a feat(comments): persistent frame-anchored comments on asset detail
- migration 010: asset_comments table (id, asset_id, user_id, body,
  frame_ms, resolved, timestamps) with index on asset_id+created_at
- new routes mounted at /api/v1/assets/:assetId/comments — GET/POST/
  PATCH/DELETE with author join (display_name + initials), nullable
  user_id so comments still attach when AUTH_ENABLED is off
- Asset detail loads comments from the API on mount instead of the
  empty ZAMPP_DATA.COMMENTS seed; addComment POSTs and merges the
  returned row; resolved-toggle and delete are wired
- CommentsList: new trash-icon delete action per comment, helpful
  empty-state copy ('Add one below to mark a frame'), tooltips on
  the timestamp and resolved buttons

Now editor comments survive page reload, are visible to other users
via the same API, and pin reliably to frame_ms (integer) instead of
a parsed HH:MM:SS:FF string.
2026-05-23 04:21:11 +00:00
claude
24820e921e polish: schedule past-time confirm, recorder name sanitization, asset detail player controls
- Schedule: if start_at is more than a minute in the past, confirm()
  before submitting (operator may want to fire immediately, but
  shouldn't do it accidentally)
- Recorders: generateClipName now sanitizes the recorder name so the
  S3 key / SMB path / ffmpeg arg stays clean — spaces become
  underscores, anything outside [A-Za-z0-9._-] is dropped, capped at 40
- Asset detail: audio mute + fullscreen buttons now key off streamUrl
  state (rather than videoRef.current which is null on first render)
  so they reliably appear when a stream is available
2026-05-23 04:12:42 +00:00
claude
47ad01d0b2 polish(projects,jobs,bins): row menus, real status bars, bulk retry
Projects:
- Per-row 3-dot menu in list view: Open / Rename / Delete (PATCH + DELETE)
- ProjectCard's bottom bar now shows real ready/in-flight/error counts
  for the project's assets instead of fake 70/20 segments
- After mutations, project list refreshes from /projects + recomputes
  asset counts client-side

Bins:
- GET /api/v1/bins now returns every bin across every project when
  no project_id is supplied; result rows include project_name + asset_count
- Asset right-click 'Move to bin' filters to bins in the same project as
  the asset and surfaces project_name as a tooltip

Jobs:
- 'Retry all failed' button in the header appears when there are
  failed jobs and POSTs /retry for each one in parallel
- Failed-row error message now clips with title= tooltip so 3KB
  ffmpeg stderr doesn't blow out the row layout

window.PROJECT_COLORS exposed for cross-screen access.
2026-05-23 04:09:13 +00:00
claude
992fbdfa20 fix(recorders,library): empty-capture handling + right-click context menu
Proxy failures ("moov atom not found"):
- root cause: failed/aborted SRT/RTMP recordings still uploaded 0-byte
  (or ftyp-only ~1KB) objects to S3, which ffmpeg can't probe
- worker proxy.js now bails on inputs < 4 KiB with a clear message
  before handing the file to ffmpeg
- capture-manager.stop() returns framesReceived + empty flag
- capture shutdown handler skips POST /assets entirely on empty
  sessions, instead calls new POST /assets/:id/mark-empty to flip
  the pre-created live asset to 'error' with a note

Library asset right-click menu:
- new AssetContextMenu component on screens-library.jsx; right-click
  any asset in grid or list view to open
- actions: Open, Rename, Move to bin (lists up to 10 bins), Remove
  from bin, Copy asset ID, Delete permanently (hard=true)
- viewport-aware positioning (won't clip past window edges)
- dismisses on outside click / contextmenu / scroll
- Library now refreshes via /assets after mutations; normalizeAsset
  exposed on window so the re-fetch shape matches boot
- ctx-menu styles in styles-rest.css
2026-05-23 03:52:30 +00:00
claude
9877ed351f fix(recorders): queue proxy on finalize + custom clip names
- POST /api/v1/assets: when transitioning from 'live' to 'processing'
  with a hi-res key but no proxy, queue a proxy job instead of just
  flipping status='ready'. Recorder-captured clips now get a proxy
  + thumbnail like upload-path assets do
- POST /api/v1/recorders/:id/start now accepts { clipName } in the body;
  operator-supplied name (sanitized to [A-Za-z0-9 ._-], capped at 80)
  overrides the auto-generated <recorder>_<timestamp> fallback
- RecorderRow gets a 'Clip name (optional)' input visible when stopped;
  Enter triggers Record, value sent on POST start, cleared on stop
- New POST /api/v1/assets/:id/generate-proxy and
  POST /api/v1/assets/backfill-proxies for one-shot cleanup of pre-fix
  clips that have a hi-res master but no proxy
2026-05-23 03:41:03 +00:00
claude
b128c9f5a9 fix(metrics): use real job_status enum values (queued/processing/complete) 2026-05-23 03:31:14 +00:00
claude
ef4c301149 feat(home,users): real metrics, working Users row actions + Groups CRUD
- Home: new /api/v1/metrics/home endpoint buckets last 24h of assets,
  jobs done/failed into hourly counts; sparklines now render real
  time-series instead of decorative sine waves
- Home stat cards are now clickable (route to relevant page) and the
  delta lines show real activity ("+N added in last 24h", "N completed")
- Home live-feed tiles use HlsPreview for recorders with a live_asset_id
- Users: row 3-dot menu is now a real popover with Rename / Reset
  password / Delete actions wired to PATCH /users/:id and DELETE
- Users: role is now an inline <select> that PATCHes immediately
- Users: Created column replaces fake 'last active' (no last_login
  tracking yet); group count is real
- Groups tab is now functional — list groups, create, expand to
  show + manage members (add/remove), delete; backed by existing
  /api/v1/groups CRUD
- Policies tab is now an honest 'coming soon' stub
- New icons: key, lock, edit; new .row-menu popover styles
2026-05-23 03:30:10 +00:00
claude
53196d38ce feat(scheduler): recorder scheduling — UI, CRUD, tick loop, recurrence
- New Ingest → Schedule page: upcoming/past/all tabs, status badges
  (pending / recording / completed / cancelled / failed), 10s
  auto-refresh, cancel/delete actions
- New Schedule modal: name, recorder dropdown, datetime-local start/end,
  recurrence (one-shot / daily / weekly), sensible defaults (+5min / +35min)
- Backend: migration 009 (recorder_schedules), routes/schedules.js
  (list/create/edit/cancel/delete), scheduler.js tick loop polling every
  15s; transitions trigger /recorders/:id/start and /stop via in-process
  HTTP so we reuse the full container orchestration path
- Recurring schedules: tick loop auto-queues the next occurrence on
  completion (daily = +24h, weekly = +7d)
- Sidebar + app.jsx route wired in, schedule-row table style added
2026-05-23 03:19:24 +00:00
claude
6398879b56 feat: SDK deployment UI, proxy encoding global settings, S3 env fallback
- Settings: drop AMPP tab, rename GPU/Transcoding → Proxy encoding
  with explicit 'applied to every ingested file' wording, expose
  CPU codec/preset options when GPU is off
- New Capture SDKs tab (Settings): upload Blackmagic / AJA / Deltacast
  SDK archives (.zip / .tar.gz) staged to /sdk/<vendor>/ inside mam-api;
  BMD is fully wired into the FFmpeg build pipeline, AJA + Deltacast
  staging-only pending FFmpeg patches
- mam-api: new /api/v1/sdk routes (multer upload, extract, list, delete);
  Dockerfile gets unzip+tar; docker-compose mounts /mnt/NVME/MAM/sdk:/sdk
- proxy worker now reads proxy-encoding settings from DB on every job,
  builds args for libx264 / NVENC / VAAPI, falls back to libx264 on
  hardware-encode failure
- settings GET /s3 falls back to S3_* env vars when DB is empty so the
  UI reflects what's actually wired (fixes 'not configured' false alarm)
2026-05-23 02:58:32 +00:00
328f7b4f31 feat: live HLS preview, proxy worker fixes, Settings tabs, growing-files + Premier panel
- worker/proxy: scale-to-even filter, analyzeduration 100M, skip images, hasAudio
- worker/promotion: SMB landing zone -> S3 on idle, queues proxy job, status='ready'
- web-ui screens-ingest: HlsPreview component replaces fake LiveStrip/FauxFrame
- web-ui screens-admin: functional Settings tabs (S3, GPU, Growing, SDI, AMPP)
- mam-api /settings/growing: GET/PUT growing-files config
- mam-api /assets/:id/live-path: SMB UNC/POSIX path for live growing assets
- capture-manager: GROWING_ENABLED -> write hires to /growing instead of S3 stream
- recorders.js: pass GROWING_ENABLED to capture container, bind /growing mount
- docker-compose: mount /mnt/NVME/MAM/wild-dragon-growing on mam-api + worker
- premiere-plugin: Mount Live button, Relink-to-HiRes, live->ready status poll
2026-05-22 19:12:53 -04:00
ceceedf201 probe fallback: basic TCP/UDP connectivity when capture service is offline 2026-05-22 17:26:26 -04:00
4864db03f3 probe: fallback to basic TCP/UDP connectivity check when capture service is offline 2026-05-22 17:22:30 -04:00
4afd0c7b21 feat: add /cluster/containers endpoint via Docker socket
Lists all containers on the local host; supports POST /containers/:id/restart.
Falls back to [] gracefully if Docker socket is unavailable.
2026-05-22 16:57:33 -04:00
ddb4cf0c51 feat: add POST /jobs/:id/retry endpoint for re-queuing failed BullMQ jobs 2026-05-22 12:18:53 -04:00
994fd799d0 fix: stop endpoint handles missing/dead containers gracefully 2026-05-22 11:32:44 -04:00
37767f9939 fix(cluster): pickIp() only treats 172.17.x as docker bridge, not all of RFC1918 172.16/12 2026-05-21 21:27:15 -04:00
bbed2a7059 fix(decklink): mount /dev/blackmagic in sidecar + remote node routing via node-agent
Two bugs fixed:
1. SDI capture sidecar never had /dev/blackmagic bound — ffmpeg opened the
   decklink input inside a container with no device nodes, so frame=0.
   Fix: local spawns now push '/dev/blackmagic:/dev/blackmagic' onto Binds
   when source_type='sdi'.

2. recorders.js always spawned sidecars against the local Docker socket
   (zampp1), even when a recorder's node_id pointed at zampp2 (where the
   card is). Fix: resolveNodeTarget() looks up the recorder's cluster node;
   if it's a different hostname the sidecar is spawned via a new
   POST /sidecar/start endpoint on the remote node-agent.

node-agent gains three new routes (all talk to the local Docker socket):
  POST   /sidecar/start         — create + start container (host network,
                                   privileged, /dev/blackmagic bind for sdi)
  DELETE /sidecar/:id           — stop + remove
  GET    /sidecar/:id/status    — inspect + poll capture service

docker-compose.worker.yml: add /var/run/docker.sock and LIVE_DIR to
node-agent so it can spawn sidecars, and document build-capture prerequisite.: recorders.js
2026-05-21 18:51:10 -04:00
4c65753358 recorders route: accept full codec field set + node/port pinning
POST/PATCH now persist all new codec columns via a whitelist. /start
forwards every codec setting to the capture container as an env var. The
live-asset created during /start now uses the recorder's container ext
(.mov vs .mp4 etc.) instead of always assuming .mov.
2026-05-21 00:17:45 -04:00
0efef0d81b cluster route: fallback IP from request + /devices/blackmagic endpoint
Heartbeat handler now overrides 172.x docker bridge IPs with the
request's source address when the request itself came from a real LAN.
Adds GET /devices/blackmagic that flattens every node's capabilities so
the recorder UI can show a card picker spanning the whole cluster.
2026-05-21 00:16:36 -04:00
066b9b17d3 feat: expand GPU transcoding settings (extension, framerate, rc mode, audio) 2026-05-20 23:41:42 -04:00
02cfa68b92 fix(assets): replace static S3_BUCKET with getS3Bucket() for dynamic config 2026-05-20 15:49:40 -04:00
737e69d72f fix(upload): replace static S3_BUCKET with getS3Bucket() for dynamic config 2026-05-20 15:48:48 -04:00
ab504841c3 feat(settings): add S3 / object-storage settings routes (GET, PUT, test) 2026-05-20 15:48:14 -04:00
1725ec1de9 feat: settings routes for hardware inventory, GPU transcoding, capture service URL 2026-05-20 14:18:43 -04:00
dd3c2894f6 feat: cluster heartbeat stores capabilities (GPU/BMD hardware detection) 2026-05-20 14:18:22 -04:00
5161644205 fix(capture): handle non-JSON responses from capture service gracefully 2026-05-20 13:55:06 -04:00
4dd377e28d feat(cluster): add GET /:id/ping to probe node agent reachability and latency 2026-05-20 13:49:56 -04:00
a855ea7885 feat(api): add GET /assets/:id/hires endpoint for original file download
Returns presigned S3 URL + filename/ext/file_size for the original
hi-res source so the Premiere plugin can download and import it.
2026-05-20 00:34:18 -04:00
66844b93d3 feat(cluster): node registry API — heartbeat, list, deregister 2026-05-19 23:46:16 -04:00
910a906600 feat(system): Docker container management via Unix socket 2026-05-19 23:46:03 -04:00
a5823effe9 feat(assets): add ?redirect=1 to thumbnail endpoint for img src use 2026-05-19 23:44:17 -04:00
4d0e715982 fix(sequences): coerce NUMERIC frame_rate to float in all API responses
node-postgres returns NUMERIC columns as strings by default.  Add a
mapSeq() helper that parses frame_rate to a JS float before any response
is sent.  Affected routes: GET /, POST /, PUT /:id, GET /:id.
2026-05-19 23:24:16 -04:00
16b8530d43 fix: include filename in search; add POST /cleanup-live to recover stuck live assets 2026-05-19 23:10:51 -04:00
8a2ef38326 fix: bulk-fetch jobs by state (no N+1 getState()); add GET /events SSE stream 2026-05-19 23:09:47 -04:00
d382c6b559 fix: EDL export uses sequence frame_rate for timecode (29.97/59.94 DF, others non-drop) 2026-05-19 23:09:17 -04:00
07ded22f8e feat: video proxy streaming endpoint + editor drag-and-drop to timeline
- mam-api: add GET /api/v1/assets/:id/video streaming proxy that fetches
  from RustFS/S3 and pipes to browser with range-request support, bypassing
  direct S3 access from Chrome
- mam-api: fix /stream route to return /video proxy URL for both proxy and
  original-mp4 assets; return null cleanly for non-playable sources
- s3/client: set requestChecksumCalculation/responseChecksumValidation to
  WHEN_REQUIRED to suppress x-amz-checksum-mode header on signed URLs
- editor: fix loadSourceAsset to set state.sourceAsset even when no proxy
  exists (info toast instead of bail-out) so Insert/Overwrite still work
- editor: add drag-and-drop from media panel to timeline — items are now
  draggable, timeline container accepts drops and calls Timeline.addClip
  with the asset at playhead position
- editor: add tl-drag-over CSS highlight on timeline during drag
2026-05-19 22:47:33 -04:00
f8e42b886d fix(sequences): apply correct 59.94 DF framesToTC to EDL export
sequences.js had the same `if (rem >= DROP)` bug as timecode.js — any
frame ≥ 4 in the first non-drop minute of each 10-minute group would
produce a timecode offset by one minute. EDL files exported from the
editor would have wrong in/out points for nearly every event.

Applies the FRAMES_FIRST_MIN (3600) boundary check fix, matching the
correction already made to services/web-ui/public/js/timecode.js.
2026-05-19 00:22:17 -04:00
d3e12deb18 feat(assets): add POST /:id/retry to re-queue errored assets
Assets stuck in status='error' had no recovery path without manual DB
edits. Adds a retry endpoint that re-dispatches the proxy job, which
chains into thumbnail generation automatically and restores the asset
to 'processing' → 'ready' without operator intervention.
2026-05-19 00:17:00 -04:00
58e2e539f8 fix(upload): scope original S3 keys under assetId to prevent collisions
Both /init and /simple were keying originals as
`originals/${projectId}/${filename}`.  Two uploads of the same filename
into the same project would share a key — the second upload would silently
overwrite the first file in S3 while both assets remained in the DB with
the same original_s3_key.

Changed to `originals/${assetId}/${filename}` (matching the proxies/
convention) so every asset has its own unique S3 prefix.
2026-05-19 00:08:13 -04:00
4f8964e807 fix(tokens): add requireAuth middleware to token routes
Token CRUD endpoints had no authentication guard.  Without it,
unauthenticated requests could reach the handler — GET would return
empty results silently, and POST could attempt to insert a token with
user_id = NULL.  All other route files in this codebase apply
requireAuth explicitly; tokens.js was simply missing it.
2026-05-19 00:07:41 -04:00
b23700f30a fix(recorders): use already-imported uuidv4 instead of dynamic import
Dynamic `(await import('uuid')).v4()` inside the /start route handler
re-imports the module every call (though Node caches it). uuidv4 is
already imported at the top of the file.
2026-05-18 23:56:00 -04:00
e6314be92d fix(assets): strip internal full_count column from list response
The window function COUNT(*) OVER() leaks `full_count` on every row.
Strip it before sending so callers only see actual asset fields.
2026-05-18 23:44:14 -04:00
a9ca7be1d5 feat: add PATCH /recorders/:id endpoint to edit recorder settings
Allows updating name, source_type, source_config, recording_codec,
recording_resolution, proxy_enabled, proxy_codec, proxy_resolution,
and project_id. Blocked while the recorder is actively recording.
2026-05-18 23:24:27 -04:00
48b69879cb fix: conform route broken SQL — remove dead DB insert, use BullMQ directly
The POST /conform route was inserting into the jobs table with non-existent
columns (project_id, metadata) and an invalid enum value ('pending'). Since
GET /jobs reads entirely from BullMQ, the DB insert was both incorrect and
redundant. Now we just enqueue the BullMQ job and return its ID.
2026-05-18 23:22:14 -04:00
1f31d1037d merge: bring sequences/auth/admin backend + auth-guard frontend into fix/library-and-signal-indicator 2026-05-18 21:25:36 -04:00
1816c7fa1e fix(api): guard DELETE 404, verify sequence before clip wipe, add clip validation 2026-05-18 19:53:14 -04:00
05d49b7199 feat(api): add sequences route with CRUD, clip sync, and EDL export 2026-05-18 19:50:29 -04:00
cb63e4743d fix: /me returns guest user when AUTH_ENABLED is false so auth-guard never fires on dev 2026-05-18 13:21:37 -04:00
1e4c92c2df feat(mam-api): add personal API token routes 2026-05-18 12:50:58 -04:00
d23ca9be73 feat(mam-api): add groups admin CRUD routes with member management 2026-05-18 12:50:49 -04:00
5ed604136c feat(mam-api): add users admin CRUD routes 2026-05-18 12:50:33 -04:00
57116dde42 feat(recorders): stable elapsed timer + live HLS preview on the card; optimistic signal default 2026-05-18 09:40:42 -04:00
7d76f9c549 feat(growing-files): Phase 1 - live HLS preview during recording
While a recorder is running, the capture container tees an HLS
stream into /live/<assetId>/ alongside the ProRes master upload.
The asset row is pre-created at recorder start with status='live'
so the clip appears in the library immediately. /api/v1/assets/:id/stream
returns the HLS playlist URL until recording stops, then proxy.

* docker-compose: shared wild-dragon-live mount on api/capture/web-ui
* migration 001-add-live-status: idempotent ALTER TYPE for asset_status
* mam-api: runMigrations() on boot; recorders.js pre-creates live asset
  + passes ASSET_ID; assets.js POST upserts on existing live row instead
  of inserting a duplicate, and stream route returns HLS for live assets
* capture: parallel HLS ffmpeg into /live/<assetId>/; ASSET_ID env
* web-ui: nginx serves /live/, preview.js loads hls.js, LIVE badge added
2026-05-18 07:29:50 -04:00
Zac
bab24e156a feat(recorders): probe sources + reflect real signal in main status
Two things that together stop bogus URLs from masquerading as a recording:

PROBE BUTTON in the New Recorder panel. Before you commit to record, hit Probe Source - the capture container runs ffprobe with a 10s timeout against the URL and returns the parsed streams. UI shows green Signal Detected with codec/resolution/fps/audio, or red No Signal Detected with the actual ffprobe error message. For SDI it lists DeckLink devices. Listener-mode sources cannot be probed standalone (would block waiting for a publisher) and the UI says so.

MAIN STATUS LABEL ON THE RECORDING CARD now mirrors the live signal instead of hardcoding Recording. So a recorder pointed at a dead URL goes Connecting... -> Connection error (red) instead of looking like everything is fine. When frames actually start arriving the label flips to Recording (blue) and the dot turns blue. If a previously-good stream drops the label switches to Signal lost (red).

API:
* capture: POST /capture/probe runs ffprobe and returns { ok, streams, format, error? }
* mam-api: POST /api/v1/recorders/probe proxies through to the capture sidecar with a 15s outer timeout
2026-05-17 18:39:21 -04:00
Zac
349bc5a41d feat: multi-select + bulk move/copy/delete, brand blue, hardhat loader
* Library cards now show a checkbox on hover (and persistent when selected). Click checkbox = toggle, shift-click = range. Plain click on a card with an active selection extends/shrinks the selection instead of opening preview. Floating pill at the bottom shows count + Move / Copy / Delete / Clear. Move + Copy open a tiny bin picker (current project, default to current bin).

* mam-api/routes/assets.js: PATCH /:id now also accepts bin_id (null = move out of bin). New POST /:id/copy makes a reference-copy of the asset row (same S3 keys, new id) into the target bin/project.

* api.js: moveAsset(id, binId) and copyAsset(id, {binId, projectId}) helpers.

* All accent tokens swapped from the amber oklch(76% 0.178 52) to the Wild Dragon signature blue oklch(55% 0.20 266) = #1f3ad0 ish. Login splash + first-load splash + signal-receiving + button primary all picked it up automatically through common.css.

* Loading indicator across the app uses the AMPP Safe hardhat photo gently pulsing with a tiny blue dot underneath. .ampp-loading component lives in common.css with --sm / --xs / --inline variants. Replaces the plain "Loading assets…" empty state in index.html.
2026-05-17 14:48:34 -04:00
Zac
72545126c4 fix: delete asset actually deletes
Trash icon in the library was firing PATCH /assets/:id with {status:"deleted"}. The PATCH route only accepts display_name/tags/notes so it returned "No fields to update" and the asset stayed put.

* api.js: add deleteAsset(id, {hard}) helper hitting the real DELETE route.
* index.html: deleteAssetPrompt now calls deleteAsset (soft archive). Confirm dialog reworded to match.
* mam-api/routes/assets.js: list endpoint hides status=archived by default. Pass ?include_archived=true to see them in a future restore-from-trash view. Filtering by ?status=archived still works for power users.
* All HTML: bump api.js cache-buster v=4 -> v=5 so the new helper is fetched.
2026-05-17 12:55:55 -04:00
Zac
ac1878452f fix: library + caller-only recorders + live signal indicator
Three problems blocked the end-to-end flow:

1) Library always rendered empty because /assets returns {assets,total} but
   index.html (and capture.html) assumed r.data was an array. Fixed in
   api.js by unwrapping r.data.assets centrally; total is kept on r.total.

2) SRT/RTMP caller mode pulled audio only. ffmpeg opened the network input
   before the H264 SPS arrived, marked the video stream as pix_fmt=none,
   and silently dropped it from the stream map. Added -probesize 32M
   -analyzeduration 10M -fflags +genpts and explicit -map 0✌️0?/0🅰️0? so
   each track survives independently of when it appears.

3) Hitting Record gave no feedback about whether a stream was actually
   arriving. capture-manager now parses ffmpeg progress lines (frame=...
   fps=...) and tracks framesReceived, currentFps, lastFrameAt, lastError.
   getStatus() returns a derived signal enum (connecting | receiving |
   lost | error | stopped). The recorder controller gives each spawned
   container a stable network alias `recorder-<id>` and the GET
   /recorders/:id/status endpoint proxies the live capture status through.
   recorders.html polls that every 2s and renders the badge under each
   active card with the running frame/fps counter or the ffmpeg error.

Also:
* recorders.html: dropped the listener-mode UI entirely. All new recorders
  are caller-mode (pull). The MAM is no longer offered as an RTMP/SRT
  server. Legacy listener records still render but read-only.
2026-05-17 07:39:58 -04:00
3154cce37c fix: ETag case mismatch in multipart upload complete route
api.js sends parts as { partNumber, ETag } (uppercase) but upload.js
was reading p.etag (lowercase), resulting in undefined ETag passed to
S3 CompleteMultipartUpload → InvalidPart error on all large file uploads.

Also handle both casings defensively.
2026-05-16 18:56:38 -04:00
17646c1155 fix(jobs): read from BullMQ queues instead of empty DB table
GET /api/v1/jobs now queries the proxy, thumbnail, and conform BullMQ
queues directly and returns normalized job objects with id, type,
status, progress, asset_id, timestamps, and error fields.

Also adds DELETE /:id to remove completed/failed jobs from the queue,
supporting the clearCompleted action in jobs.html.

The PostgreSQL jobs table is still used only for conform job creation
(POST /conform) to preserve that workflow.
2026-05-16 17:38:53 -04:00
78b1f3482f feat(recorders): add PortBindings for SRT/RTMP listener mode containers
When source_config.mode === 'listener':
- SRT: bind UDP listen_port (default 9000) on container host
- RTMP: bind TCP listen_port (default 1935) on container host
Add ExposedPorts to container config alongside HostConfig.PortBindings.
Also pass LISTEN, LISTEN_PORT, STREAM_KEY env vars to container.
2026-05-16 08:21:03 -04:00
44759391e5 Fix jobs.js: send camelCase fields to conform worker (projectId/outputFormat) 2026-05-16 00:46:45 -04:00
a9cc8caf42 fix(recorders): add S3_REGION to container env, accept 304/404 on stop/remove 2026-05-16 00:31:10 -04:00
e796a0d15f fix(routes+ui): capture route bin optional, jobs Redis URL, recorders S3_REGION+stop codes, api.js full rewrite, upload.html multipart fix, capture.html bin guard: jobs.js 2026-05-16 00:30:26 -04:00
ada5597f79 fix(auth+bugs): optional auth bypass, login routes, conform column name, panel metadata fields, login page: auth.js 2026-05-15 23:40:11 -04:00
4ba898f6a3 fix: remove premature thumbnail dispatch from upload route (proxy worker now handles it) 2026-05-15 21:26:57 -04:00
db73235149 fix: add POST /assets handler for capture registration + thumbnail job dispatch 2026-05-15 21:24:02 -04:00
0e36ca9972 feat: AMPP folder sync integration — pre-create folder hierarchy on upload, expose lookup endpoint for Script Task: upload.js 2026-04-18 13:42:09 -04:00
e25e63b3f0 feat: AMPP folder sync integration — pre-create folder hierarchy on upload, expose lookup endpoint for Script Task: ampp.js 2026-04-18 13:42:08 -04:00
56e2a97506 feat: AMPP folder sync integration — pre-create folder hierarchy on upload, expose lookup endpoint for Script Task: settings.js 2026-04-18 13:42:08 -04:00
8ab7ea4d8d Phase 2: services/mam-api/src/routes/recorders.js 2026-04-07 22:05:41 -04:00
6994e2d697 Phase 2: services/mam-api/src/routes/upload.js 2026-04-07 22:05:40 -04:00
a463b67bce add services/mam-api/src/routes/bins.js 2026-04-07 21:58:28 -04:00
b1e9b4a9ca add services/mam-api/src/routes/projects.js 2026-04-07 21:58:28 -04:00
31927f9742 add services/mam-api/src/routes/capture.js 2026-04-07 21:58:27 -04:00
55b765698c add services/mam-api/src/routes/jobs.js 2026-04-07 21:58:27 -04:00
5cb9ccaefc add services/mam-api/src/routes/assets.js 2026-04-07 21:58:27 -04:00