diff --git a/internal/control/ui.html b/internal/control/ui.html index b54efcc..a7fa3c3 100644 --- a/internal/control/ui.html +++ b/internal/control/ui.html @@ -1000,10 +1000,10 @@ function flowNodeData(){const cfg=S.server.config||{},rt=S.server.runtime||{},en source:{state:sourceKind==='none'?'idle':(sourceState==='reconnecting'?'warn':(src.connected===false?'err':'good')),sub:sourceKind.toUpperCase(),detail:(sourceKind==='none'?'No source':(src.connected===false?'Disconnected':'Connected')),lines:[`Kind: ${sourceKind}`,`Origin: ${active.origin?.endpoint||active.origin?.streamName||'--'}`,`State: ${src.state||'idle'}`,`Reconnects: ${src.reconnects??0}`],applyMode:'reload'}, ingest:{state:ir.writeBlocked?'err':(String(ir.state||'').toLowerCase()==='degraded'||sourceState==='reconnecting'?'warn':(sourceKind==='none'?'idle':'good')),sub:(ir.state||src.state||'idle').toUpperCase(),detail:joinParts([isFinite(Number(src.bufferedSeconds))?`${Number(src.bufferedSeconds).toFixed(1)} s buffer`:'',ir.prebuffering?'prebuffer':'']).trim()||'Stable',lines:[`Runtime: ${ir.state||'--'}`,`Buffered: ${isFinite(Number(src.bufferedSeconds))?Number(src.bufferedSeconds).toFixed(2)+'s':'--'}`,`Last chunk: ${ageFrom(ir.lastChunkAt||src.lastChunkAt)}`,`Write blocked: ${ir.writeBlocked?'yes':'no'}`],applyMode:'reload'}, audio:{state:audio.ToneAmplitude>0&&sourceKind==='none'?'warn':(sourceKind==='none'?'idle':'good'),sub:`Gain ${Number(audio.gain??0).toFixed(2)}`,detail:sourceKind!=='none'?'External program feed':(audio.toneAmplitude>0?'Tone generator armed':'No input source'),lines:[`Gain: ${Number(audio.gain??0).toFixed(2)}`,`Tone L: ${audio.toneLeftHz??'--'} Hz`,`Tone R: ${audio.toneRightHz??'--'} Hz`,`Tone Amp: ${Number(audio.toneAmplitude??0).toFixed(2)}`,`Source kind: ${sourceKind}`],applyMode:'mixed'}, - processing:{state:'good',sub:fm.limiterEnabled?'Limiter ON':'Limiter OFF',detail:fm.bs412Enabled?`BS.412 active · ${Number(fm.bs412ThresholdDBr??0).toFixed(1)} dBr`:`Pre-emphasis ${fm.preEmphasisTauUS||0} µs`,lines:[`Limiter: ${fm.limiterEnabled?'on':'off'}`,`Ceiling: ${Number(fm.limiterCeiling??0).toFixed(2)}`,`Pre-emphasis: ${fm.preEmphasisTauUS||0} µs`,`BS.412: ${fm.bs412Enabled?'on':'off'}`,`Mode: ${fm.bs412Enabled?'compliance shaping active':'nominal processing path'}`],applyMode:'mixed'}, + processing:{state:'good',sub:fm.limiterEnabled?'Limiter ON':'Limiter OFF',detail:`Pre-emphasis ${fm.preEmphasisTauUS||0} µs`,lines:[`Limiter: ${fm.limiterEnabled?'on':'off'}`,`Ceiling: ${Number(fm.limiterCeiling??0).toFixed(2)}`,`Pre-emphasis: ${fm.preEmphasisTauUS||0} µs`,`Mode: nominal audio processing path`],applyMode:'mixed'}, stereo:{state:fm.stereoEnabled?'good':'idle',sub:fm.stereoEnabled?(fm.stereoMode||'DSB'):'MONO',detail:`Pilot ${(Number(fm.pilotLevel??0)*100).toFixed(1)} %`,lines:[`Enabled: ${fm.stereoEnabled?'yes':'no'}`,`Mode: ${fm.stereoMode||'DSB'}`,`Pilot: ${(Number(fm.pilotLevel??0)*100).toFixed(1)}%`],applyMode:'live'}, rds:{state:rds.enabled?'good':'idle',sub:rds.enabled?String(eng.activePS||rds.ps||'--'):'DISABLED',detail:rds.enabled?(eng.activeRadioText?String(eng.activeRadioText).slice(0,28):'Radiotext active'):'No subcarrier data',lines:[`Enabled: ${rds.enabled?'yes':'no'}`,`PI: ${rds.pi||'--'}`,`PTY: ${fmtPTY(rds.pty)}`,`PS: ${eng.activePS||rds.ps||'--'}`,`RT: ${eng.activeRadioText||rds.radioText||'--'}`],applyMode:'mixed'}, - mpx:{state:queueHealth==='critical'?'err':(queueHealth==='low'?'warn':'good'),sub:`Pilot ${(Number(fm.pilotLevel??0)*100).toFixed(1)}% / RDS ${(Number(fm.rdsInjection??0)*100).toFixed(1)}%`,detail:queueHealth==='critical'?'Queue critical':(queueHealth==='low'?'Queue low':(fm.compositeClipper?.enabled?'Composite clipper enabled':(fm.bs412Enabled?'BS.412 shaping active':`MPX gain ${Number(fm.mpxGain??1).toFixed(2)}`))),lines:[`Pilot: ${(Number(fm.pilotLevel??0)*100).toFixed(1)}%`,`RDS inj: ${(Number(fm.rdsInjection??0)*100).toFixed(1)}%`,`MPX gain: ${Number(fm.mpxGain??1).toFixed(2)}`,`Clipper: ${fm.compositeClipper?.enabled?'on':'off'}`,`Mode: ${queueHealth==='critical'?'queue health critical':(queueHealth==='low'?'queue health low':(fm.compositeClipper?.enabled?'clipper engaged':'nominal multiplex path'))}`],applyMode:'mixed'}, + mpx:{state:queueHealth==='critical'?'err':(queueHealth==='low'?'warn':'good'),sub:`Pilot ${(Number(fm.pilotLevel??0)*100).toFixed(1)}% / RDS ${(Number(fm.rdsInjection??0)*100).toFixed(1)}%`,detail:queueHealth==='critical'?'Queue critical':(queueHealth==='low'?'Queue low':(fm.bs412Enabled?`BS.412 active · ${Number(fm.bs412ThresholdDBr??0).toFixed(1)} dBr`:(fm.compositeClipper?.enabled?'Composite clipper enabled':`MPX gain ${Number(fm.mpxGain??1).toFixed(2)}`))),lines:[`Pilot: ${(Number(fm.pilotLevel??0)*100).toFixed(1)}%`,`RDS inj: ${(Number(fm.rdsInjection??0)*100).toFixed(1)}%`,`MPX gain: ${Number(fm.mpxGain??1).toFixed(2)}`,`Clipper: ${fm.compositeClipper?.enabled?'on':'off'}`,`BS.412: ${fm.bs412Enabled?'on':'off'}`,`Mode: ${queueHealth==='critical'?'queue health critical':(queueHealth==='low'?'queue health low':(fm.bs412Enabled?'compliance shaping active':(fm.compositeClipper?.enabled?'clipper engaged':'nominal multiplex path')))}`],applyMode:'mixed'}, tx:{state:txState==='running'?'good':(txState==='faulted'?'err':(txState==='idle'?'idle':'warn')),sub:isFinite(Number(eng.appliedFrequencyMHz))?`${Number(eng.appliedFrequencyMHz).toFixed(1)} MHz`:(isFinite(Number(fm.frequencyMHz))?`${Number(fm.frequencyMHz).toFixed(1)} MHz`:'--'),detail:String(eng.state||'idle').toUpperCase(),lines:[`State: ${String(eng.state||'idle').toUpperCase()}`,`Applied: ${isFinite(Number(eng.appliedFrequencyMHz))?Number(eng.appliedFrequencyMHz).toFixed(1)+' MHz':'--'}`,`Target: ${isFinite(Number(fm.frequencyMHz))?Number(fm.frequencyMHz).toFixed(1)+' MHz':'--'}`,`Queue: ${eng.queue?.health||'--'}`,`Underruns: ${eng.underruns??'--'}`],applyMode:'live'}, }; } @@ -1019,7 +1019,7 @@ function hideFlowTooltip(){const tip=$('flow-tooltip');if(tip)tip.classList.remo function openFlowPopover(key,anchor){S.flowSelected=S.flowSelected===key?null:key;S.flowAnchor=S.flowSelected?anchor:null;hideFlowTooltip();render();} function closeFlowPopover(){S.flowSelected=null;S.flowAnchor=null;render();} function flowJump(tab){document.querySelectorAll('.tab-btn[data-tab]').forEach(b=>b.classList.toggle('active',b.dataset.tab===tab));document.querySelectorAll('.tab-panel[data-tab-panel]').forEach(p=>p.classList.toggle('active',p.dataset.tabPanel===tab));closeFlowPopover();} -function renderFlowPopover(){const pop=$('flow-popover');if(!pop)return;if(!S.flowSelected||!S.flowAnchor){pop.classList.remove('show');return;}const key=S.flowSelected;const d=flowNodeData()[key];if(!d){pop.classList.remove('show');return;}const cfg=S.server.config||{};let fields='';let actions='';let modeNote='';if(key==='tx'){modeNote='Applies immediately.';fields=`
`;actions=``;}else if(key==='rds'){modeNote='Mixed runtime impact: enable/PS/RT apply now; PI/PTY belong in detailed controls.';fields=`
`;actions=``;}else if(key==='source'||key==='ingest'){modeNote='Requires hard reload via detailed ingest configuration.';fields=`
`;actions=``;}else if(key==='processing'){modeNote='Mixed runtime impact: limiter and ceiling apply now; pre-emphasis/BS.412 stay in detailed controls.';fields=`
`;actions=``;}else if(key==='mpx'){modeNote='Mixed runtime impact: pilot/RDS injection apply now; MPX gain and structural clipper settings stay in detailed controls.';fields=`
`;actions=``;}else if(key==='audio'){modeNote='Mixed runtime impact: tone controls apply now; input gain remains in detailed controls.';fields=`
`;actions=``;}else if(key==='stereo'){modeNote='Applies immediately.';fields=`
`;actions=``;} +function renderFlowPopover(){const pop=$('flow-popover');if(!pop)return;if(!S.flowSelected||!S.flowAnchor){pop.classList.remove('show');return;}const key=S.flowSelected;const d=flowNodeData()[key];if(!d){pop.classList.remove('show');return;}const cfg=S.server.config||{};let fields='';let actions='';let modeNote='';if(key==='tx'){modeNote='Applies immediately.';fields=`
`;actions=``;}else if(key==='rds'){modeNote='Mixed runtime impact: enable/PS/RT apply now; PI/PTY belong in detailed controls.';fields=`
`;actions=``;}else if(key==='source'||key==='ingest'){modeNote='Requires hard reload via detailed ingest configuration.';fields=`
`;actions=``;}else if(key==='processing'){modeNote='Mixed runtime impact: limiter and ceiling apply now; pre-emphasis stays in detailed controls.';fields=`
`;actions=``;}else if(key==='mpx'){modeNote='Mixed runtime impact: pilot/RDS injection and clipper enable apply now; BS.412, MPX gain and structural clipper settings stay in detailed controls.';fields=`
`;actions=``;}else if(key==='audio'){modeNote='Mixed runtime impact: tone controls apply now; input gain remains in detailed controls.';fields=`
`;actions=``;}else if(key==='stereo'){modeNote='Applies immediately.';fields=`
`;actions=``;} pop.innerHTML=`
${FLOW_NODES.find(n=>n.key===key)?.label||key}
${String(d.state||'idle').toUpperCase()} · ${d.sub||''}
${(d.lines||[]).map(line=>`
${line}
`).join('')}
${modeNote}
${fields}
${actions}
`; const r=S.flowAnchor.getBoundingClientRect();pop.style.left=`${Math.min(window.innerWidth-380,Math.max(12,r.left + window.scrollX))}px`;pop.style.top=`${r.bottom + window.scrollY + 10}px`;pop.classList.add('show');$('flow-popover-close')?.addEventListener('click',closeFlowPopover); $('flow-action-start')?.addEventListener('click',()=>{closeFlowPopover();txAction('start');});$('flow-action-stop')?.addEventListener('click',()=>{closeFlowPopover();txAction('stop');});$('flow-action-reset-fault')?.addEventListener('click',()=>{closeFlowPopover();resetFault();});$('flow-open-control')?.addEventListener('click',()=>flowJump('tx'));$('flow-open-rds')?.addEventListener('click',()=>flowJump('rds'));$('flow-open-ingest')?.addEventListener('click',()=>flowJump('ingest'));$('flow-open-diagnostics')?.addEventListener('click',()=>flowJump('diagnostics'));$('flow-open-processing')?.addEventListener('click',()=>flowJump('tx'));$('flow-open-audio')?.addEventListener('click',()=>flowJump('tx'));$('flow-open-stereo')?.addEventListener('click',()=>flowJump('tx'));