feat(ingest): show capture port on recorder cards + growing indicator on schedule blocks
- Recorder cards now display a Port chip showing the bridge port (deltacast)
or SDI device index (blackmagic) so operators can see at a glance which
physical input each recorder is bound to.
- Schedule blocks render with a green accent + flame glyph when the backing
recorder has growing_enabled=true, so the EPG view distinguishes
edit-while-record slots from regular close-then-publish recordings.
🤖 Generated with Claude Code
This commit is contained in:
parent
8a675992c2
commit
6895fbc5af
1 changed files with 29 additions and 2 deletions
|
|
@ -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 }) {
|
|||
<StatusDot status={recorder.status} /> {recorder.status.toUpperCase()}
|
||||
</span>
|
||||
<span className="badge outline">{recorder.source}</span>
|
||||
{recorder.capturePort && (
|
||||
<span className="badge outline" title="Capture port" style={{ background: 'rgba(74,158,255,0.12)', borderColor: 'rgba(74,158,255,0.4)', color: 'var(--accent)' }}>
|
||||
<Icon name="signal" size={10} style={{ marginRight: 4, verticalAlign: -1 }} />{recorder.capturePort}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="recorder-sub mono">{recorder.url}</div>
|
||||
<div className="recorder-sub">
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
ref={blockRef}
|
||||
className={classes.join(' ')}
|
||||
style={{ left, width, '--epg-block-color': color || 'var(--text-3)' }}
|
||||
style={{ left, width, '--epg-block-color': blockColor }}
|
||||
onContextMenu={(ev) => { 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 : '')}>
|
||||
<span className="epg-block-bar" />
|
||||
{/* 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. */}
|
||||
<div className="epg-block-body" onPointerDown={(ev) => startDrag(ev, 'body')}>
|
||||
<span className="epg-block-name">{event.name}</span>
|
||||
<span className="epg-block-time mono">{_fmtTime(dispStart)}{drag && drag.moved ? ' → ' + _fmtTime(dispEnd) : ''}</span>
|
||||
{isGrowing && <span className="epg-block-glyph growing" title="growing file (edit-while-record)" style={{ color: '#2ecc71' }}>▶</span>}
|
||||
{isLive && <span className="epg-block-glyph live" title="on air">●</span>}
|
||||
{isFailed && <span className="epg-block-glyph failed" title={event.error_message || 'failed'}>!</span>}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue