diff --git a/services/web-ui/public/screens-ingest.jsx b/services/web-ui/public/screens-ingest.jsx index 08354fb..ce29443 100644 --- a/services/web-ui/public/screens-ingest.jsx +++ b/services/web-ui/public/screens-ingest.jsx @@ -492,6 +492,18 @@ function HlsPreview({ assetId, recorderId, muted = true, controls = false, class /* ===== Recorders ===== */ function _normRecorder(r) { const cfg = r.source_config || {}; + // Surface the capture port for SDI / Deltacast recorders so the recorder card + // can show which physical input the recorder is bound to. For Deltacast, + // cfg.port is the bridge port index (0-7). For Blackmagic SDI, cfg.device + // is something like /dev/blackmagic/dv0 — we slice off the trailing index. + let capturePort = null; + if (r.source_type === 'deltacast') { + capturePort = cfg.port != null ? `Port ${cfg.port}` : null; + } else if (r.source_type === 'sdi') { + const dev = cfg.device || ''; + const m = dev.match(/(\d+)$/); + if (m) capturePort = `SDI ${m[1]}`; + } return { ...r, source: r.source_type || '·', @@ -500,6 +512,7 @@ function _normRecorder(r) { res: r.recording_resolution || '·', framerate: r.recording_framerate || 'native', node: r.node_id ? r.node_id.slice(0, 8) : 'primary', + capturePort, elapsed: '·', bitrate: '·', health: 100, @@ -708,6 +721,11 @@ function RecorderRow({ recorder: initialRecorder, onRefresh }) { {recorder.status.toUpperCase()} {recorder.source} + {recorder.capturePort && ( + + {recorder.capturePort} + + )}
{recorder.url}
@@ -1471,29 +1489,38 @@ function _EventBlock({ event, recorder, dayStart, dayEnd, pph, now, projects, on const left = (startMin / 60) * pph; const width = Math.max(40, ((endMin - startMin) / 60) * pph); + // Schedules backed by a recorder configured to write a growing file get a + // green accent so operators can tell at a glance which slots will produce + // an edit-while-recording deliverable (vs. close-then-publish). + const isGrowing = !!(recorder && recorder.growing_enabled); + const classes = ['epg-block']; if (isLive) classes.push('live'); if (isFailed) classes.push('failed'); else if (isPast) classes.push('past'); if (drag && drag.moved) classes.push('dragging'); if (canDrag) classes.push('resizable'); + if (isGrowing) classes.push('growing'); + + const blockColor = isGrowing ? '#2ecc71' : (color || 'var(--text-3)'); return (
{ ev.preventDefault(); ev.stopPropagation(); onContextMenu(event, ev); }} onPointerMove={onPointerMove} onPointerUp={endDrag} onPointerCancel={endDrag} - title={event.name + ' · ' + _fmtTime(dispStart) + ' → ' + _fmtTime(dispEnd) + (event.error_message ? ' · ' + event.error_message : '')}> + title={event.name + (isGrowing ? ' · GROWING' : '') + ' · ' + _fmtTime(dispStart) + ' → ' + _fmtTime(dispEnd) + (event.error_message ? ' · ' + event.error_message : '')}> {/* Body click → edit, body drag → move. We hang the click on pointerup so the threshold check above can demote a drag back to a click. */}
startDrag(ev, 'body')}> {event.name} {_fmtTime(dispStart)}{drag && drag.moved ? ' → ' + _fmtTime(dispEnd) : ''} + {isGrowing && } {isLive && } {isFailed && !}