| @@ -1077,6 +1077,8 @@ input.input-error { | |||
| <div class="sidebar-title">Health</div> | |||
| <div class="health-line"><div class="name">HTTP</div><div class="val" id="health-http">--</div></div> | |||
| <div class="health-line"><div class="name">Runtime</div><div class="val" id="health-runtime">--</div></div> | |||
| <div class="health-line"><div class="name">Runtime Signal</div><div class="val" id="health-indicator">--</div></div> | |||
| <div class="health-line"><div class="name">Runtime Alert</div><div class="val" id="health-alert">--</div></div> | |||
| <div class="health-line"><div class="name">Audio Buffer</div><div class="val" id="health-audio">--</div></div> | |||
| <div class="health-line"><div class="name">Last Update</div><div class="val" id="health-last">--</div></div> | |||
| </div> | |||
| @@ -1714,7 +1716,7 @@ function render() { | |||
| updateText('info-fmmod', fmtBool(cfg.fm?.fmModulationEnabled)); | |||
| updateText('info-live', engine.state ? `${String(engine.state).toUpperCase()} / ${state.server.runtimeOk ? 'runtime ok' : 'runtime pending'}` : (state.server.configOk ? 'config only' : '--')); | |||
| updateHealth(audioStream); | |||
| updateHealth(engine, audioStream); | |||
| updateMeters(engine, driver, audioStream); | |||
| drawSparkline('spark-audio', state.charts.audio, 'good', 1); | |||
| drawSparkline('spark-underruns', state.charts.underruns, underruns > 0 ? 'err' : 'warn'); | |||
| @@ -1731,7 +1733,8 @@ function renderToggle(key, toggleId, labelId) { | |||
| updateText(labelId, busy ? '...' : (on ? 'ON' : 'OFF')); | |||
| } | |||
| function updateHealth(audioStream) { | |||
| function updateHealth(engine, audioStream) { | |||
| engine = engine || {}; | |||
| updateText('health-http', state.server.configOk ? 'OK' : 'OFFLINE'); | |||
| $('health-http').className = 'val ' + (state.server.configOk ? 'good' : 'err'); | |||
| @@ -1739,6 +1742,31 @@ function updateHealth(audioStream) { | |||
| updateText('health-runtime', runtimeState); | |||
| $('health-runtime').className = 'val ' + (state.server.runtimeOk ? 'good' : 'warn'); | |||
| const runtimeIndicator = engine.runtimeIndicator; | |||
| const indicatorLabels = { | |||
| normal: 'Normal', | |||
| degraded: 'Degraded', | |||
| queueCritical: 'Queue critical', | |||
| }; | |||
| const indicatorText = indicatorLabels[runtimeIndicator] || (runtimeIndicator ? runtimeIndicator : '--'); | |||
| let indicatorSeverity = ''; | |||
| if (runtimeIndicator === 'queueCritical') indicatorSeverity = 'err'; | |||
| else if (runtimeIndicator === 'degraded') indicatorSeverity = 'warn'; | |||
| else if (runtimeIndicator === 'normal') indicatorSeverity = 'good'; | |||
| const indicatorEl = $('health-indicator'); | |||
| if (indicatorEl) { | |||
| indicatorEl.className = 'val' + (indicatorSeverity ? ' ' + indicatorSeverity : ''); | |||
| } | |||
| updateText('health-indicator', indicatorText); | |||
| const runtimeAlertRaw = (engine.runtimeAlert || '').trim(); | |||
| const hasAlert = !!runtimeAlertRaw; | |||
| const alertEl = $('health-alert'); | |||
| if (alertEl) { | |||
| alertEl.className = 'val ' + (hasAlert ? 'warn' : 'good'); | |||
| } | |||
| updateText('health-alert', hasAlert ? runtimeAlertRaw : 'None'); | |||
| let audioLabel = 'N/A'; | |||
| let audioClass = 'val'; | |||
| if (audioStream) { | |||