Просмотр исходного кода

control: move ingest configuration into dedicated tab

main
Jan 1 месяц назад
Родитель
Сommit
6a23b6c313
1 измененных файлов: 137 добавлений и 46 удалений
  1. +137
    -46
      internal/control/ui.html

+ 137
- 46
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 {
<div class="tab-bar">
<button class="tab-btn active" data-tab="overview" type="button">Overview</button>
<button class="tab-btn" data-tab="control" type="button">Transmission Control</button>
<button class="tab-btn" data-tab="ingest" type="button">Ingest</button>
<button class="tab-btn" data-tab="diagnostics" type="button">Diagnostics &amp; Health</button>
<button class="tab-btn" data-tab="activity" type="button">Activity &amp; Logs</button>
</div>
@@ -1282,6 +1292,76 @@ input.input-error {
</div>
</div>

</div>
<div class="stack">
<div class="card panel" data-panel-key="shortcuts">
<div class="panel-head" data-panel>
<h2>Shortcuts</h2>
<div class="meta">keyboard</div>
<span class="chevron">▼</span>
</div>
<div class="panel-body">
<div class="section-note">Fast control reference. Shortcuts stay out of the main operator path.</div>
<div class="shortcuts-grid">
<div>
<div class="shortcut-line"><span class="name">Start TX</span><span class="keys"><span class="kbd">t</span></span></div>
<div class="shortcut-line"><span class="name">Stop TX</span><span class="keys"><span class="kbd">Shift</span><span class="kbd">t</span></span></div>
<div class="shortcut-line"><span class="name">Refresh</span><span class="keys"><span class="kbd">r</span></span></div>
</div>
<div>
<div class="shortcut-line"><span class="name">Next Freq Preset</span><span class="keys"><span class="kbd">]</span></span></div>
<div class="shortcut-line"><span class="name">Prev Freq Preset</span><span class="keys"><span class="kbd">[</span></span></div>
<div class="shortcut-line"><span class="name">Apply Draft</span><span class="keys"><span class="kbd">Enter</span></span></div>
</div>
</div>
</div>
</div>


<div class="card panel" data-panel-key="danger">
<div class="panel-head" data-panel>
<h2>Danger Zone</h2>
<div class="meta">tx control</div>
<span class="chevron">▼</span>
</div>
<div class="panel-body">
<div class="section-note">Fast emergency controls. Nothing hidden here — just clearer separation from normal controls.</div>
<div class="actions-row" style="margin-top:0">
<button class="danger-btn" id="danger-stop" type="button">Emergency Stop TX</button>
<button class="danger-btn" id="danger-refresh" type="button">Hard Refresh Runtime</button>
<button class="danger-btn secondary" id="danger-reset-fault" type="button">Reset Fault</button>

</div>
<div class="section-note reset-hint" id="reset-hint">
Reset Fault moves the runtime back to DEGRADED while the queue settles before running again.
</div>
</div>
</div>


</div>
</div>
</section>


<section class="tab-panel" data-tab-panel="ingest">
<div class="tab-columns one">
<div class="stack">
<div class="card sidebar-card ingest-summary-card">
<div class="sidebar-section">
<div class="sidebar-title">Active Ingest Summary</div>
<div class="section-note">Runtime snapshot of active ingest state and source. Deep runtime metrics stay in Diagnostics.</div>
<div class="kv ingest-summary-kv">
<div class="k">State</div><div class="v" id="ingest-summary-state">--</div>
<div class="k">Source</div><div class="v" id="ingest-summary-source">--</div>
<div class="k">Signal</div><div class="v" id="ingest-summary-signal">--</div>
<div class="k">Detail</div><div class="v" id="ingest-summary-detail">--</div>
<div class="k">Origin</div><div class="v" id="ingest-summary-origin">--</div>
<div class="k">Last Chunk</div><div class="v" id="ingest-summary-last">--</div>
</div>
</div>
</div>

<div class="card panel" data-panel-key="ingest">
<div class="panel-head" data-panel>
<div class="led on-blue" style="width:6px;height:6px"></div>
@@ -1506,52 +1586,6 @@ input.input-error {
</div>
</div>

</div>
<div class="stack">
<div class="card panel" data-panel-key="shortcuts">
<div class="panel-head" data-panel>
<h2>Shortcuts</h2>
<div class="meta">keyboard</div>
<span class="chevron">▼</span>
</div>
<div class="panel-body">
<div class="section-note">Fast control reference. Shortcuts stay out of the main operator path.</div>
<div class="shortcuts-grid">
<div>
<div class="shortcut-line"><span class="name">Start TX</span><span class="keys"><span class="kbd">t</span></span></div>
<div class="shortcut-line"><span class="name">Stop TX</span><span class="keys"><span class="kbd">Shift</span><span class="kbd">t</span></span></div>
<div class="shortcut-line"><span class="name">Refresh</span><span class="keys"><span class="kbd">r</span></span></div>
</div>
<div>
<div class="shortcut-line"><span class="name">Next Freq Preset</span><span class="keys"><span class="kbd">]</span></span></div>
<div class="shortcut-line"><span class="name">Prev Freq Preset</span><span class="keys"><span class="kbd">[</span></span></div>
<div class="shortcut-line"><span class="name">Apply Draft</span><span class="keys"><span class="kbd">Enter</span></span></div>
</div>
</div>
</div>
</div>


<div class="card panel" data-panel-key="danger">
<div class="panel-head" data-panel>
<h2>Danger Zone</h2>
<div class="meta">tx control</div>
<span class="chevron">▼</span>
</div>
<div class="panel-body">
<div class="section-note">Fast emergency controls. Nothing hidden here — just clearer separation from normal controls.</div>
<div class="actions-row" style="margin-top:0">
<button class="danger-btn" id="danger-stop" type="button">Emergency Stop TX</button>
<button class="danger-btn" id="danger-refresh" type="button">Hard Refresh Runtime</button>
<button class="danger-btn secondary" id="danger-reset-fault" type="button">Reset Fault</button>

</div>
<div class="section-note reset-hint" id="reset-hint">
Reset Fault moves the runtime back to DEGRADED while the queue settles before running again.
</div>
</div>
</div>


</div>
</div>
@@ -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();
</script>
</body>
</html>


Загрузка…
Отмена
Сохранить