Commit graph

315 commits

Author SHA1 Message Date
ede55a8a5f feat(plugin): add seq info bar, export panel, and 2-row action bar styles 2026-05-20 00:38:59 -04:00
9ba3bf6f83 feat(plugin): add seq info bar, hi-res button, export panel to panel UI
- Active sequence info bar shows current Premiere sequence name
- Import Proxy / Hi-Res split buttons replace single Import button
- Export panel (hidden) slides in with seq name, project picker, clip count
- Export Timeline button in second action row triggers panel
2026-05-20 00:38:09 -04:00
16888d62e2 feat(plugin): proxy URL fix, hi-res import, import path tracking, timeline export
- Fix: /stream returns relative URL — prepend serverUrl before Node.js download
- Add: importAssetHires() calls /assets/:id/hires for original file
- Add: saveImportMapping() stores tempPath→assetId in localStorage so
  timeline export can match Premiere clips back to MAM assets
- Add: startExportTimeline() reads active sequence via exportTimelineData(),
  shows export panel with seq name + clip count
- Add: confirmExportTimeline() resolves paths→assetIds, upserts sequence,
  PUT /sequences/:id/clips
- Add: refreshCurrentSequenceInfo() shows active sequence name in info bar
2026-05-20 00:37:34 -04:00
5bb22c17c8 feat(plugin): add exportTimelineData() and getProjectItems() to ExtendScript
exportTimelineData() walks all video tracks in the active sequence and
returns clip source/timeline frame positions + file paths so the panel JS
can map them back to MAM asset IDs for timeline export.

getProjectItems() enumerates all ProjectItems with paths — useful for
rebuilding the import mapping after a Premiere restart.
2026-05-20 00:35:18 -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
f7aedb1936 fix(ui): normalize sidebar — add Containers + Cluster to all 8 remaining pages 2026-05-20 00:22:57 -04:00
879c547e08 home: add Containers + Cluster cards, fix Editor link, extend loadStats 2026-05-20 00:02:48 -04:00
0c761d553c feat(ui): cluster node registry page — health, CPU, memory, deregister 2026-05-19 23:58:17 -04:00
e3cdf70883 feat(ui): Docker container management page — restart, stop, start 2026-05-19 23:57:23 -04:00
1e9710ce0c feat(editor): thumbnail images in media panel; Del=ripple, Shift+Del=lift 2026-05-19 23:56:23 -04:00
090452969c feat(api): register system + cluster routes; add self-heartbeat on startup 2026-05-19 23:50:19 -04:00
66844b93d3 feat(cluster): node registry API — heartbeat, list, deregister 2026-05-19 23:46:16 -04:00
bd8b492ff6 feat(db): cluster_nodes table for multi-server registry 2026-05-19 23:46:06 -04:00
910a906600 feat(system): Docker container management via Unix socket 2026-05-19 23:46:03 -04:00
89771a2380 feat(timeline): ripple delete on Del, extract/lift on Shift+Del 2026-05-19 23:45:41 -04:00
a5823effe9 feat(assets): add ?redirect=1 to thumbnail endpoint for img src use 2026-05-19 23:44:17 -04:00
36e668455f feat(editor): media-panel search, sequence duration badge, parseFloat guard
- Media panel gains a search input that filters the clip list in real time
  (case-insensitive match on display_name / filename)
- Timeline toolbar shows total sequence duration (e.g. 00:05:23;14) and
  frame rate, updated whenever clips change or a sequence is opened
- parseFloat() guard on state.seq.frame_rate so a NUMERIC string from
  Postgres never leaks into Timeline.render() / applyHistory()
2026-05-19 23:27:25 -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
bfc2649909 feat(editor): fps-aware render, FPS selector in new-seq dialog, keyboard help overlay
- openSequence() and applyHistory() now pass state.seq.frame_rate to
  Timeline.render() instead of hardcoded 59.94 — clips render on the
  correct frame grid for every sequence
- New-sequence panel gains a frame-rate selector (23.976 / 24 / 25 /
  29.97 / 30 / 50 / 59.94 / 60); createNewSequence() posts frame_rate
  to the API
- Press ? to open a keyboard shortcut help overlay; Escape to close
2026-05-19 23:20:10 -04:00
81c771a7be feat(jobs): replace polling with SSE EventSource for live job updates
- Drop setTimeout/scheduleRefresh loop in favour of EventSource on
  /api/v1/jobs/events (pushes every 2 s from the server)
- Refresh dot turns green on open, goes grey + "Reconnecting…" on error
  (EventSource auto-reconnects natively)
- Type-filter is now applied client-side against the full SSE payload so
  the dropdown change no longer triggers an HTTP round-trip
- killJob / retryJob / clearCompleted no longer call loadJobs(); the next
  SSE push (≤2 s) reflects the change automatically
2026-05-19 23:17:18 -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
d21c61a8b2 fix: addClip uses s.fps instead of hardcoded TC.secondsToFrames (59.94) 2026-05-19 23:08:13 -04:00
b175eaf54c fix: clean up temp segment directory after conform job finishes 2026-05-19 23:06:54 -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
5019563c38 fix: override user-select:none on draggable media items to fix drag initiation
EditorInterface root div has select-none (user-select:none) applied globally
to prevent text selection during editing. Chrome/Safari refuse to start HTML5
drag-and-drop on elements that inherit user-select:none, which is why no
ghost image appeared, cursor never changed, and no dragstart events fired.

Fix: add select-text (user-select:text) to both draggable divs in
MediaThumbnail (list view and grid view). This overrides the inherited none
specifically on the elements that need to be dragged, without changing the
global UX behavior of the editor.
2026-05-19 14:45:47 -04:00
fd6693ee17 fix: remove ContextMenuTrigger asChild from draggable elements to fix drag initiation
With asChild, Radix merges its pointer event handlers directly onto the
draggable div. This interferes with browser drag gesture initiation,
resulting in no ghost image and no drag events firing.

Fix: remove asChild so ContextMenuTrigger renders its own span (with
display:contents to preserve layout). Radix handlers now live on the
ancestor span, not the draggable div. Right-click still bubbles up to
trigger the context menu correctly.

Also add draggable={false} to <img> elements inside draggable divs
to prevent browser native image drag from competing with the parent.
2026-05-19 13:00:09 -04:00
18c4779f58 fix: add onDragEnd to AssetsPanel to clear isDragging state
- Import endDrag from useUIStore
- Add handleItemDragEnd callback that calls endDrag()
- Add onDragEnd? prop to MediaThumbnail interface
- Wire onDragEnd={onDragEnd} to both draggable divs (list & grid views)
- Pass onDragEnd={handleItemDragEnd} when rendering MediaThumbnail
- Without this, isDragging was permanently stuck at true after every drag
2026-05-19 11:20:29 -04:00
aec55fea83 fix: await onDropMedia, fix stale closure deps, surface errors in TrackLane
- Import ActionResult type from @openreel/core
- onDropMedia prop type now returns Promise<ActionResult> | void
- handleDrop now awaits onDropMedia so failures are visible
- Replace silent catch with console.error + toast.error on failure
- Add allTracks, playheadPosition, snapSettings to handleDrop useCallback deps
  to fix stale closure bug (calculateSnap was using stale snap/track state)
2026-05-19 11:12:09 -04:00
76e6568ac6 fix: await handleDropMedia and surface clip-add errors in Timeline
- handleDropMedia now returns the ActionResult from addClip/addClipToNewTrack
- The tracksRef onDrop handler now awaits handleDropMedia so errors aren't silently lost
- Replaces the swallowed catch block with a toast.error + console.error on failure
- This makes clip-add failures visible instead of silently doing nothing
2026-05-19 11:11:17 -04:00
43a17ecd14 feat(jobs): add Retry button for failed jobs with an associated asset 2026-05-19 00:54:47 -04:00
de4cb1b6a0 fix(tokens): add version cache-busters to api.js and topbar-strip.js 2026-05-19 00:51:47 -04:00
4407e8ce6d fix(edit): add version cache-busters to api.js and topbar-strip.js 2026-05-19 00:48:50 -04:00
36f165807a fix(topbar-strip): escape pageName() output before innerHTML insertion 2026-05-19 00:46:48 -04:00
76b0a5e05e fix(recorders): escape d.error in renderProbeResult to prevent XSS 2026-05-19 00:46:12 -04:00
9c83698b81 feat: inline rename on double-click in library asset cards
Double-clicking a clip name in the library shows an in-place text input.
Enter/blur commits the new display_name via PATCH; Escape cancels.
Clicking the card body or action buttons still work normally.
2026-05-19 00:41:43 -04:00
f39d086bc8 fix: add cache-buster version strings to api.js and topbar-strip.js in home.html 2026-05-19 00:39:24 -04:00
1e4fcb62f5 feat: add status filter chips and sort controls to library
Adds an "All / Ready / Processing / Error / Live" pill filter row and
a "Newest / Oldest / Name / Duration / Size" sort selector to the asset
toolbar. Both operate client-side on the loaded asset list so there is
no additional API overhead. State resets to "All / Newest" whenever a
different project or bin is selected.
2026-05-19 00:35:23 -04:00
08e8377309 fix: bump api.js cache-buster to v=6 in upload.html 2026-05-19 00:33:11 -04:00
280fc9dff2 fix: XSS in renderTags and stale api.js version in player.html
Tag values were inserted into innerHTML unsanitized — a tag containing
HTML would execute as markup. Switch to DOM-only construction for the
tag badges. Also bump api.js cache-buster to v=6.
2026-05-19 00:30:54 -04:00
f1e0453b0a fix: bump api.js cache-buster to v=6 in capture.html 2026-05-19 00:28:50 -04:00
9f7cb91cc2 fix: prevent JS injection via token name in confirmRevoke onclick
Token names containing single quotes (e.g. "O'Brien's key") broke the
onclick attribute string by closing the JS string literal early.
Apply JSON.stringify+esc pattern so name is safely embedded as a
JSON string literal instead of a raw single-quoted string.
2026-05-19 00:27:31 -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
d18fa2f761 feat(library): add Retry button for error-status assets in library grid
Error assets now show an amber circular-arrow action button on hover.
Clicking it calls POST /api/v1/assets/:id/retry, resets status to
'processing', and refreshes the grid — no manual DB intervention needed
when a proxy job fails.
2026-05-19 00:20:19 -04:00
130906ef42 feat(api.js): add retryAsset() helper for POST /assets/:id/retry 2026-05-19 00:17:39 -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
2bb731c7fc fix(users): prevent JS injection in delete onclick handlers for users/groups
confirmDeleteUser and confirmDeleteGroup were building onclick handlers
like onclick="confirmDeleteUser('id','NAME')" using esc() which doesn't
escape single quotes.  Usernames or group names containing ' would break
the JS string; a crafted value like `'; alert(1)//` is stored XSS.

Fix: use JSON.stringify(value) to produce a properly-escaped double-quoted
JS string literal, then esc() to HTML-encode the surrounding quotes for
safe embedding in the HTML attribute.  Same technique now used in both
renderUsers() and renderGroups().
2026-05-19 00:11:06 -04:00
1e8cde81be fix(projects): prevent JS injection via bin names in onclick handlers
binCard() was building onclick="renameBinPrompt('id', 'NAME')" by
calling esc() then .replace(/'/g, "\\'").  The problem: esc() converts
' to &#39;, so the replace never fires on raw single quotes.  When the
HTML parser evaluates the attribute it decodes &#39; back to ', breaking
the JS string — and for injected payloads like `'; alert(1)//` this is
stored XSS.

Fix: use JSON.stringify(b.name) to produce a properly-escaped double-
quoted JS string literal, then esc() to HTML-encode the surrounding
double-quotes for safe embedding in the HTML attribute.
2026-05-19 00:09:49 -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
0ea8d7ce33 fix(timeline): cap right-trim at source asset boundary
When duration_ms is known, dragging the right-trim handle past the end
of the source clip could push timeline_out_frames beyond what the source
material covers.  Cap the delta so neither timeline_out_frames nor
source_out_frames can extend past the available source frames.

Also changed assetFrames fallback from origSrcOut (prevents any extension
when duration is unknown) to null, so the guard is simply skipped when
we don't have duration metadata.
2026-05-19 00:02:34 -04:00
3c689ccddf fix(timecode): correct framesToTC for all frames beyond position 3
The previous algorithm used `if (rem >= DROP)` (i.e. rem >= 4) to decide
whether to advance to the next minute group.  This fired immediately at
frame 4, still inside minute 0 of the 10-minute non-drop group, producing
00:01:00;00 for what should be 00:00:00;04.  Every timecode display in
the editor was wrong for any position past the first four frames.

Each 10-minute block has one 3600-frame non-drop minute followed by nine
3596-frame drop minutes.  The fix checks `rem < FRAMES_FIRST_MIN` (3600)
to identify the non-drop minute, then subtracts it before dividing into
drop-minute slots.  Frame labels within drop minutes are shifted by DROP
(+4) so the first usable label is :00;04 as per SMPTE 12M.
2026-05-19 00:01:18 -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
0f37d01b2d fix(editor): keyboard tool shortcuts now actually switch the active tool
V/C/H key shortcuts called updateToolbarActive() which only updated button
visual state — Timeline.setTool() was never called so the cursor stayed on
the previous tool. Fix by calling Timeline.setTool() inside updateToolbarActive.

Also bump api.js reference to ?v=6 to match other pages.
2026-05-18 23:53:38 -04:00
fb3b998cfd fix(worker/thumbnail): mark asset ready even when thumbnail extraction fails
If the thumbnail job throws (network blip, ffmpeg error, short clip), the
asset was left stuck in status='processing' indefinitely. Since the proxy
already exists and the asset is playable, set status='ready' in the catch
block before re-throwing so BullMQ can still record the failure.
2026-05-18 23:51:04 -04:00
ff892a1ad5 fix(capture): use duration_ms field for recent captures duration display
The asset schema stores duration as duration_ms (milliseconds).
renderRecent() was checking c.duration (always undefined) so duration
always showed as '—'. Fix to use c.duration_ms / 1000.
2026-05-18 23:50:05 -04:00
08e5ba6298 fix(jobs): fetchJobs → loadJobs, add credentials to inline api helper
killJob() referenced fetchJobs() which is undefined — the correct name is
loadJobs(). Also the inline api() wrapper was missing credentials:'include'
so any API call on the jobs page would fail with a 401 in prod.
2026-05-18 23:48:56 -04:00
e472075087 fix(library): evict stale thumb URL on image load error, re-observe for retry
When a signed S3 URL expires the img fires onerror. Previously the stale URL
stayed in thumbCache so the broken image would persist. Now we delete the cache
entry, clear the loaded class, and re-add the element to the IntersectionObserver
so the next time it scrolls into view a fresh signed URL is fetched.
2026-05-18 23:46:12 -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
660afb94bb feat(editor): show fps/codec/resolution/duration in media panel asset list
- Add two-line layout to media panel items: name on top, metadata below
- fmtMs() converts duration_ms to MM:SS or HH:MM:SS for display
- Meta line shows resolution · codec · fps · duration, skipping null fields
- Assets with no extracted metadata (no proxy yet) show name only
- Active item meta line inherits accent color at reduced opacity
2026-05-18 23:37:56 -04:00
508cf8d41b feat(recorders): add Edit Recorder panel with PATCH support
- Edit (pencil) button appears on idle recorder cards; hidden while recording
- openEditPanel() pre-populates all form fields from existing recorder state
- openPanel() resets editingId and restores "New recorder" defaults
- closePanel() clears editingId and removes any stale probe result
- handleSaveRecorder() dispatches PATCH /recorders/:id in edit mode, POST otherwise
- Fix field name bugs in create path: codec→recording_codec, resolution→recording_resolution,
  proxy_config object→proxy_enabled/proxy_codec/proxy_resolution flat fields
- Badge in card now reads rec.recording_codec (correct DB field) instead of rec.codec
- Bump api.js cache-buster to v=6
2026-05-18 23:35:16 -04:00
79d44826fe feat(api.js): add patchRecorder() helper for PATCH /recorders/:id 2026-05-18 23:32:33 -04:00
7260b188c5 fix: remove dead DB UPDATE calls in conform worker
The jobs table row no longer exists for conform jobs (POST /jobs/conform
now goes directly to BullMQ). The UPDATE queries were no-ops (WHERE id = NULL)
so they're safe to remove. BullMQ tracks completed/failed status itself.
2026-05-18 23:28:13 -04:00
e895a2f2df fix: show duration overlay on asset cards using duration_ms
asset.duration is not a DB field — it's duration_ms (milliseconds).
Divide by 1000 before passing to formatDuration() which expects seconds.
2026-05-18 23:27:03 -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
29b5910fff feat: migrate editor sequences schema into auto-run migrations directory
Moved from schema_patch_editor.sql. All statements are idempotent
(IF NOT EXISTS / DO $$ BEGIN blocks) so safe to re-apply.
2026-05-18 23:23:33 -04:00
ffad0051f9 feat: migrate groups/tokens schema into auto-run migrations directory
Moved from schema_patch_groups_tokens.sql. All statements are idempotent
(IF NOT EXISTS / CREATE INDEX IF NOT EXISTS) so safe to re-apply.
2026-05-18 23:23:23 -04:00
717fdcd611 feat: extract and store fps/codec/resolution/duration_ms from source file
Uses getMediaInfo (ffprobe) on the downloaded original before transcoding.
Populates the asset record so the library can display accurate metadata.
2026-05-18 23:22:56 -04:00
817eaff8b1 feat: add getMediaInfo to executor.js using ffprobe JSON output
Exposes video stream fps/codec/resolution and container duration/size
so the proxy worker can populate asset metadata after transcoding.
2026-05-18 23:22:26 -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
596f755a6c fix: remove stray Wild Dragon brand remnant from editor.html 2026-05-18 23:14:14 -04:00
656c820638 feat: wire editor.html as primary editor, fix its sidebar/branding
- All pages: Editor nav link now points to editor.html (in-house NLE)
- Removes the :47435 OpenReel resolver script from all pages
- editor.html: canonical Z-AMPP sidebar (all 10 nav items, correct icons)
- editor.html: Z-AMPP brand logo, removes Wild Dragon SVG mark
- editor.html: removes Google Fonts import
- editor.html: adds auth-guard.js
2026-05-18 23:11:53 -04:00
910bbf8d3f merge: bring NLE editor pages (editor.html, timeline.js, timecode.js) from main 2026-05-18 23:02:51 -04:00
e8e26dd4d8 fix: remove Google Fonts, fix editor link to :47435, fix page titles
- Remove @import Google Fonts from common.css (was blocking CSS on LAN)
- Update Editor nav link on all pages to dynamically resolve to :47435
  (OpenReel SPA) using inline script so it works on any hostname
- Fix page titles from Wild Dragon -> Z-AMPP across all pages
- Resolver: <a href="#" id="editor-nav-link"> + IIFE sets href at load
2026-05-18 22:56:51 -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
8ab71239e3 feat(ui): add Open in Editor action to library cards 2026-05-18 20:14:29 -04:00
78a887a3e0 feat(ui): add NLE editor page (editor.html) 2026-05-18 20:10:25 -04:00
2fabc73299 fix(ui): prevent keydown listener accumulation on re-init 2026-05-18 20:05:34 -04:00
10152b5ad7 feat(ui): add DOM-based timeline engine (select, razor, playhead) 2026-05-18 20:02:41 -04:00
ad6e836345 feat(ui): add sequence API helpers to api.js 2026-05-18 19:58:35 -04:00
7d8ccc95e9 feat(ui): add 59.94 DF timecode utility module 2026-05-18 19:58:34 -04:00
7b8d8d4818 feat(api): register sequences route 2026-05-18 19:54:41 -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
eb248c690f fix(db): use DO blocks for idempotent ALTER TABLE (ADD CONSTRAINT IF NOT EXISTS is not valid PG syntax) 2026-05-18 19:48:11 -04:00
c662df94c4 fix(db): add CHECK constraints, UNIQUE, and asset_id index to sequences schema 2026-05-18 19:46:42 -04:00
b12d8c619a feat(db): add sequences and sequence_clips tables 2026-05-18 19:41:21 -04:00
0b94153518 Replace inline auth script with shared auth-guard.js in tokens.html 2026-05-18 13:46:10 -04:00
3203832aa9 Replace inline auth script with shared auth-guard.js in users.html 2026-05-18 13:44:35 -04:00
88c0781767 Replace inline auth script with shared auth-guard.js in jobs.html 2026-05-18 13:43:22 -04:00
81b832dc70 Replace inline auth script with shared auth-guard.js in recorders.html 2026-05-18 13:41:52 -04:00
936867c0c3 Replace inline auth script with shared auth-guard.js on recorders, jobs, users, tokens pages: tokens.html 2026-05-18 13:38:27 -04:00
de3920dd4a Replace inline auth script with shared auth-guard.js on recorders, jobs, users, tokens pages: users.html 2026-05-18 13:38:26 -04:00
9dfefc5731 Replace inline auth script with shared auth-guard.js on recorders, jobs, users, tokens pages: jobs.html 2026-05-18 13:38:25 -04:00
f3fbb027f6 Replace inline auth script with shared auth-guard.js on recorders, jobs, users, tokens pages: recorders.html 2026-05-18 13:38:24 -04:00
e3128acb15 fix: use shared auth-guard.js on capture.html 2026-05-18 13:27:24 -04:00
d8766f18cc fix: use shared auth-guard.js on upload.html 2026-05-18 13:26:19 -04:00
a40232e2b5 fix: use shared auth-guard.js on settings.html 2026-05-18 13:25:03 -04:00
ebe8b3be59 fix: use shared auth-guard.js on index.html 2026-05-18 13:24:25 -04:00