浏览代码

ui: show runtime state in control health

tags/v0.9.0
Jan Svabenik 1 个月前
父节点
当前提交
9fbe4e5bf9
共有 3 个文件被更改,包括 52 次插入5 次删除
  1. +3
    -0
      internal/control/control.go
  2. +19
    -2
      internal/control/control_test.go
  3. +30
    -3
      internal/control/ui.html

+ 3
- 0
internal/control/control.go 查看文件

@@ -145,6 +145,9 @@ func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) {
if queue, ok := stats["queue"]; ok {
status["queue"] = queue
}
if runtimeState, ok := stats["state"]; ok {
status["runtimeState"] = runtimeState
}
}
}



+ 19
- 2
internal/control/control_test.go 查看文件

@@ -8,8 +8,8 @@ import (
"net/http/httptest"
"testing"

cfgpkg "github.com/jan/fm-rds-tx/internal/config"
"github.com/jan/fm-rds-tx/internal/audio"
cfgpkg "github.com/jan/fm-rds-tx/internal/config"
"github.com/jan/fm-rds-tx/internal/output"
)

@@ -92,6 +92,23 @@ func TestStatusReportsQueueStats(t *testing.T) {
}
}

func TestStatusReportsRuntimeState(t *testing.T) {
srv := NewServer(cfgpkg.Default())
srv.SetTXController(&fakeTXController{stats: map[string]any{"state": "faulted"}})
rec := httptest.NewRecorder()
srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/status", nil))
if rec.Code != 200 {
t.Fatalf("status: %d", rec.Code)
}
var body map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal runtime state: %v", err)
}
if body["runtimeState"] != "faulted" {
t.Fatalf("expected runtimeState faulted, got %v", body["runtimeState"])
}
}

func TestDryRunEndpoint(t *testing.T) {
srv := NewServer(cfgpkg.Default())
rec := httptest.NewRecorder()
@@ -301,4 +318,4 @@ func (f *fakeTXController) TXStats() map[string]any {
return map[string]any{}
}
func (f *fakeTXController) UpdateConfig(_ LivePatch) error { return f.updateErr }
func (f *fakeTXController) ResetFault() error { return f.resetErr }
func (f *fakeTXController) ResetFault() error { return f.resetErr }

+ 30
- 3
internal/control/ui.html 查看文件

@@ -1764,14 +1764,41 @@ function renderToggle(key, toggleId, labelId) {
updateText(labelId, busy ? '...' : (on ? 'ON' : 'OFF'));
}

function runtimeStateClass(engineState) {
const normalized = String(engineState || '').toLowerCase();
if (!normalized) {
return 'warn';
}
switch (normalized) {
case 'faulted':
return 'err';
case 'muted':
case 'degraded':
case 'prebuffering':
case 'arming':
case 'stopping':
case 'idle':
case 'unknown':
return 'warn';
default:
return 'good';
}
}

function updateHealth(engine, audioStream) {
engine = engine || {};
updateText('health-http', state.server.configOk ? 'OK' : 'OFFLINE');
$('health-http').className = 'val ' + (state.server.configOk ? 'good' : 'err');

const runtimeState = state.server.runtimeOk ? 'OK' : 'WAITING';
updateText('health-runtime', runtimeState);
$('health-runtime').className = 'val ' + (state.server.runtimeOk ? 'good' : 'warn');
let runtimeLabel = 'WAITING';
let runtimeClass = 'warn';
if (state.server.runtimeOk) {
const engineStateName = String(engine.state || 'unknown');
runtimeLabel = engineStateName.toUpperCase();
runtimeClass = runtimeStateClass(engineStateName);
}
updateText('health-runtime', runtimeLabel);
$('health-runtime').className = 'val ' + runtimeClass;

const runtimeIndicator = engine.runtimeIndicator;
const indicatorLabels = {


正在加载...
取消
保存