diff --git a/internal/control/ui.html b/internal/control/ui.html index f6f5784..f63de10 100644 --- a/internal/control/ui.html +++ b/internal/control/ui.html @@ -774,6 +774,15 @@ input.input-error { text-transform: uppercase; letter-spacing: .08em; } +.ingest-summary-card .sidebar-section { + margin: 0; + padding: 0; + border: none; +} +.ingest-summary-kv .v { + font-family: var(--mono); + font-size: 11px; +} .apply-btn { background: var(--accent); border-color: transparent; @@ -1046,6 +1055,7 @@ input.input-error {
+
@@ -1282,6 +1292,76 @@ input.input-error { + +
+
+
+

Shortcuts

+
keyboard
+ +
+
+
Fast control reference. Shortcuts stay out of the main operator path.
+
+
+
Start TXt
+
Stop TXShiftt
+
Refreshr
+
+
+
Next Freq Preset]
+
Prev Freq Preset[
+
Apply DraftEnter
+
+
+
+
+ + +
+
+

Danger Zone

+
tx control
+ +
+
+
Fast emergency controls. Nothing hidden here — just clearer separation from normal controls.
+
+ + + + +
+
+ Reset Fault moves the runtime back to DEGRADED while the queue settles before running again. +
+
+
+ + +
+ + + + +
+
+
+ +
@@ -1506,52 +1586,6 @@ input.input-error {
-
-
-
-
-

Shortcuts

-
keyboard
- -
-
-
Fast control reference. Shortcuts stay out of the main operator path.
-
-
-
Start TXt
-
Stop TXShiftt
-
Refreshr
-
-
-
Next Freq Preset]
-
Prev Freq Preset[
-
Apply DraftEnter
-
-
-
-
- - -
-
-

Danger Zone

-
tx control
- -
-
-
Fast emergency controls. Nothing hidden here — just clearer separation from normal controls.
-
- - - - -
-
- Reset Fault moves the runtime back to DEGRADED while the queue settles before running again. -
-
-
-
@@ -2271,6 +2305,18 @@ function ageString(ts) { return h + 'h ago'; } +function ageFromTimestamp(value) { + if (!value) return '--'; + if (typeof value === 'number') return ageString(value); + const ts = Date.parse(String(value)); + if (Number.isNaN(ts)) return '--'; + return ageString(ts); +} + +function joinSummaryParts(parts) { + return parts.filter((part) => String(part || '').trim() !== '').join(' · '); +} + function updateText(id, text) { const el = $(id); if (el && el.textContent !== String(text)) el.textContent = text; @@ -2409,6 +2455,11 @@ function render() { const engine = runtime.engine || {}; const driver = runtime.driver || {}; const audioStream = runtime.audioStream || null; + const hasIngestRuntime = !!runtime.ingest; + const ingest = runtime.ingest || {}; + const ingestActive = ingest.active || {}; + const ingestSource = ingest.source || {}; + const ingestRuntime = ingest.runtime || {}; const appliedRaw = engine.appliedFrequencyMHz; const appliedFreq = Number.isFinite(Number(appliedRaw)) ? Number(appliedRaw) : null; @@ -2533,6 +2584,45 @@ function render() { updateText('rds-meta', sectionHasErrors('rds') ? 'Validation error' : (rdsDirty ? `${Object.keys(getSectionPatch('rds')).length} unsaved` : 'PS + RT')); updateText('ingest-meta', state.ingestSaving ? 'Saving' : (state.ingestDirty ? 'Unsaved changes' : 'Saved config')); + const configuredKind = String(cfg.ingest?.kind || ingestFieldValue('kind') || 'none').toLowerCase(); + const activeKind = String(ingestActive.kind || configuredKind || 'none').toLowerCase(); + const runtimeStateLabel = String(ingestRuntime.state || '').toLowerCase(); + const sourceStateLabel = String(ingestSource.state || '').toLowerCase(); + const stateSummary = hasIngestRuntime ? (joinSummaryParts([ + runtimeStateLabel ? runtimeStateLabel.toUpperCase() : '', + sourceStateLabel ? `source ${sourceStateLabel.toUpperCase()}` : '', + ingestRuntime.prebuffering ? 'PREBUFFERING' : '', + ingestRuntime.writeBlocked ? 'WRITE-BLOCKED' : '', + ]) || '--') : '--'; + const sourceSummary = joinSummaryParts([ + activeKind || 'none', + ingestActive.transport || '', + ingestActive.codec || '', + Number.isFinite(Number(ingestActive.sampleRateHz)) ? `${Number(ingestActive.sampleRateHz)} Hz` : '', + Number.isFinite(Number(ingestActive.channels)) ? `${Number(ingestActive.channels)} ch` : '', + ]) || '--'; + const signalSummary = hasIngestRuntime ? (joinSummaryParts([ + ingestSource.connected ? 'connected' : 'disconnected', + Number.isFinite(Number(ingestSource.bufferedSeconds)) ? `${Number(ingestSource.bufferedSeconds).toFixed(2)}s buffered` : '', + Number.isFinite(Number(ingestSource.reconnects)) ? `${Number(ingestSource.reconnects)} reconnects` : '', + ]) || '--') : '--'; + const detailSummary = hasIngestRuntime ? (ingestSource.streamTitle || ingestActive.detail || ingestSource.lastError || '--') : '--'; + const origin = ingestActive.origin || {}; + const originSummary = hasIngestRuntime ? (joinSummaryParts([ + origin.kind || '', + origin.endpoint || '', + origin.streamName || '', + origin.mode || '', + origin.sdpPath || '', + ]) || '--') : '--'; + const lastChunkSummary = hasIngestRuntime ? ageFromTimestamp(ingestRuntime.lastChunkAt || ingestSource.lastChunkAt) : '--'; + updateText('ingest-summary-state', stateSummary); + updateText('ingest-summary-source', sourceSummary); + updateText('ingest-summary-signal', signalSummary); + updateText('ingest-summary-detail', detailSummary); + updateText('ingest-summary-origin', originSummary); + updateText('ingest-summary-last', lastChunkSummary); + updateText('info-backend', cfg.backend?.kind || cfg.backend || '--'); updateText('info-freq', fmtFreq(cfg.fm?.frequencyMHz)); updateText('info-preemph', cfg.fm?.preEmphasisTauUS ? `${cfg.fm.preEmphasisTauUS} µs` : 'Off'); @@ -3147,3 +3237,4 @@ init(); +