|
|
|
@@ -821,7 +821,29 @@ async function sendPatch(patch,{ok='Applied',clearKeys=[]}={}){beginReq();try{co |
|
|
|
async function applySection(sec){if(secErrors(sec)){toast('Fix validation errors first','warn');return;}const patch=secPatch(sec),keys=Object.keys(patch);if(!keys.length){toast('No changes','info');return;}const msg=sec==='freq'?'Frequency updated':sec==='rds'?'RDS text updated':'Applied';await sendPatch(patch,{ok:msg,clearKeys:keys});} |
|
|
|
function resetSection(sec){clearDirty(Object.keys(FIELDS).filter(k=>FIELDS[k].section===sec));toast('Draft reset','info');} |
|
|
|
async function applyCfgSection(sec){if(sec==='rds-id'&&S.cfgErrors?.pi){toast('Fix validation errors first','warn');return;}const patch=cfgPatch(sec);if(!Object.keys(patch).length){toast('No changes','info');return;}const hasR=cfgHasRestart(sec);beginReq();try{const res=await api('/config',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(patch)});Object.keys(CFG).filter(k=>CFG[k].sec===sec).forEach(k=>delete S.cfgDraft[k]);S.cfgDirty[sec]=false;if(sec==='rds-id'&&S.cfgErrors)delete S.cfgErrors.pi;toast(hasR?'Saved (restart required)':'Applied live','ok');log('CFG '+sec+' '+JSON.stringify(patch)+(hasR?' [restart]':' [live]'),'ok');await Promise.allSettled([loadConfig({silent:true}),loadRuntime({silent:true})]);return res;}catch(e){toast(e.message,'err');log('CFG failed: '+e.message,'err');throw e;}finally{endReq();}} |
|
|
|
async function txAction(action){if(S.txBusy)return;S.txBusy=true;render();beginReq();try{await api(`/tx/${action}`,{method:'POST'});toast(action==='start'?'TX started':'TX stopped','ok');log('TX '+action,'ok');await Promise.allSettled([loadRuntime({silent:true}),loadConfig({silent:true})]);}catch(e){toast(e.message,'err');log('TX '+action+' failed: '+e.message,'err');}finally{S.txBusy=false;endReq();render();}} |
|
|
|
async function txAction(action){ |
|
|
|
if(S.txBusy)return; |
|
|
|
S.txBusy=true; |
|
|
|
if(action==='stop'&&S.server.runtime){ |
|
|
|
S.server.runtime.engine={...(S.server.runtime.engine||{}),state:'stopping'}; |
|
|
|
} |
|
|
|
render(); |
|
|
|
beginReq(); |
|
|
|
try{ |
|
|
|
log('TX '+action+' requested','info'); |
|
|
|
await api(`/tx/${action}`,{method:'POST'}); |
|
|
|
toast(action==='start'?'TX started':'TX stop requested','ok'); |
|
|
|
log('TX '+action+' accepted','ok'); |
|
|
|
await Promise.allSettled([loadRuntime({silent:true}),loadConfig({silent:true})]); |
|
|
|
}catch(e){ |
|
|
|
toast(e.message,'err'); |
|
|
|
log('TX '+action+' failed: '+e.message,'err'); |
|
|
|
}finally{ |
|
|
|
S.txBusy=false; |
|
|
|
endReq(); |
|
|
|
render(); |
|
|
|
} |
|
|
|
} |
|
|
|
async function resetFault(){if(S.faultBusy)return;S.faultBusy=true;render();beginReq();try{await api('/runtime/fault/reset',{method:'POST'});toast('Fault reset','ok');log('Fault reset','ok');await loadRuntime({silent:true});}catch(e){toast(e.message,'err');log('Fault reset failed: '+e.message,'err');}finally{S.faultBusy=false;endReq();render();}} |
|
|
|
async function setToggle(key,val){if(S.toggleBusy[key])return;S.toggleBusy[key]=true;render();try{await sendPatch({[key]:val},{ok:key.replace(/Enabled$/,'')+' '+(val?'enabled':'disabled')});}finally{S.toggleBusy[key]=false;render();}} |
|
|
|
async function saveIngest(){if(S.ingestSaving)return;if(!S.ingestDirty){toast('No changes','info');return;}S.ingestSaving=true;S.ingestError='';beginReq();render();try{const res=await api('/config/ingest/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ingest:S.ingestDraft})});S.ingestDirty=false;toast(res.reloadScheduled?'Saved, reloading…':'Saved','ok');log('Ingest saved'+(res.reloadScheduled?' [reload]':''),'ok');if(res.reloadScheduled)setTimeout(()=>location.reload(),1500);}catch(e){S.ingestError=e.message;toast(e.message,'err');log('Ingest save failed: '+e.message,'err');}finally{S.ingestSaving=false;endReq();render();}} |
|
|
|
@@ -856,7 +878,9 @@ function _render(){ |
|
|
|
$('tx-state').textContent=S.txBusy?'WORKING':txSt.toUpperCase(); |
|
|
|
$('tx-state').className='tx-state '+(S.txBusy?'working':txSt); |
|
|
|
setText('tx-hint',eng.lastError?`Last error: ${eng.lastError}`:S.txBusy?'Command in progress':'Runtime polled every 1s'); |
|
|
|
const startDis=S.txBusy||txSt==='running',stopDis=S.txBusy||['idle','stopped',''].includes(txSt); |
|
|
|
const canStopStates=['running','arming','prebuffering','degraded','muted','faulted','stopping']; |
|
|
|
const startDis=S.txBusy||txSt==='running'; |
|
|
|
const stopDis=S.txBusy||!canStopStates.includes(txSt); |
|
|
|
$('btn-start').disabled=startDis;$('btn-stop').disabled=stopDis;$('btn-refresh').disabled=S.pending>0; |
|
|
|
|
|
|
|
// ── Overview: meters + sparklines |
|
|
|
@@ -908,7 +932,7 @@ function _render(){ |
|
|
|
setText('compliance-meta',S.cfgDirty['compliance']?'Draft pending':'Saved + Restart Required'); |
|
|
|
$('compliance-apply').disabled=!S.cfgDirty['compliance'];$('compliance-reset').disabled=!S.cfgDirty['compliance']; |
|
|
|
// Danger |
|
|
|
$('danger-stop').disabled=stopDis;const rfl=$('danger-reset-fault');if(rfl){rfl.disabled=S.faultBusy||!S.server.runtimeOk;rfl.textContent=S.faultBusy?'Resetting...':'Reset Fault';} |
|
|
|
$('danger-stop').disabled=S.txBusy;const rfl=$('danger-reset-fault');if(rfl){rfl.disabled=S.faultBusy||!S.server.runtimeOk;rfl.textContent=S.faultBusy?'Resetting...':'Reset Fault';} |
|
|
|
const rh=$('reset-hint');if(rh){const sn=normState(eng.state);rh.textContent=sn==='faulted'?'Faulted: reset moves runtime back to DEGRADED.':sn==='muted'||sn==='degraded'?'Reset Fault holds at DEGRADED until queue recovers.':'Manual fault reset drops to DEGRADED while queue recovers.';} |
|
|
|
|
|
|
|
// ── RDS tab |
|
|
|
@@ -934,8 +958,10 @@ function _render(){ |
|
|
|
syncSlider('rdsinj-slider','rdsinj-val','rdsInjection',v=>v==null?'--':(Number(v)*100).toFixed(1)+'%'); |
|
|
|
const lvlDirty=!!S.cfgDirty['rds-lvl'];$('rds-levels-apply').disabled=!lvlDirty;$('rds-levels-reset').disabled=!lvlDirty; |
|
|
|
// Status card |
|
|
|
const activePS=String(eng.activePS||cfg.rds?.ps||'').trim(); |
|
|
|
const activeRT=String(eng.activeRadioText||cfg.rds?.radioText||'').trim(); |
|
|
|
setText('rds-stat-enabled',cfg.rds?.enabled?'ON':'OFF');setText('rds-stat-pi',fmtPI(cfg.rds?.pi)); |
|
|
|
setText('rds-stat-pty',fmtPTY(cfg.rds?.pty));setText('rds-stat-ps',cfg.rds?.ps||'--');setText('rds-stat-rt',cfg.rds?.radioText||'--'); |
|
|
|
setText('rds-stat-pty',fmtPTY(cfg.rds?.pty));setText('rds-stat-ps',activePS||'--');setText('rds-stat-rt',activeRT||'--'); |
|
|
|
setText('rds-stat-pilot',fmtPilot(cfg.fm?.pilotLevel));setText('rds-stat-inj',fmtPilot(cfg.fm?.rdsInjection)); |
|
|
|
|
|
|
|
// ── Ingest tab |
|
|
|
|