diff --git a/docs/pro-runtime-hardening-workboard.md b/docs/pro-runtime-hardening-workboard.md index f84e4db..c0e2653 100644 --- a/docs/pro-runtime-hardening-workboard.md +++ b/docs/pro-runtime-hardening-workboard.md @@ -279,6 +279,7 @@ Einführen eines klaren Betriebsmodells mit Fault-, Recovery- und Muted-Zuständ - `EngineStats` and `txBridge` now expose transition/fault counters plus `lastFault`, surfacing the new telemetry through `/runtime`. - Control-plane UI now renders those WS-02 transition counters, fault count, and last-fault summary so operators can watch runtime escalations without digging through logs. - Control-plane now exposes `POST /runtime/fault/reset` so operators can acknowledge `faulted` state; `TestRuntimeFaultReset*` covers the new HTTP path. +- Control-plane UI now also offers a Danger Zone `Reset Fault` button that calls the same endpoint so operators can acknowledge faults from the dashboard. ## Zielzustände laut Konzept @@ -329,6 +330,7 @@ Einführen eines klaren Betriebsmodells mit Fault-, Recovery- und Muted-Zuständ |---|---|---| | 2026-04-05 | Faulted escalation on persistent critical queue | `muted` now surfaces `RuntimeStateFaulted` when queue health stays critical and metrics capture every transition. | | 2026-04-05 | Manual fault reset endpoint | Added `POST /runtime/fault/reset` so operators can acknowledge `faulted` before the supervisor re-enters recovery. | +| 2026-04-05 | Fault-reset UI shortcut | Danger Zone now hosts a Reset Fault button wired to `/runtime/fault/reset` so operators get an in-app acknowledgement path without manual HTTP calls. | ## WS-02 Verifikation | Datum | Fokus | Ergebnis | diff --git a/internal/control/ui.html b/internal/control/ui.html index 59558e8..156d1e1 100644 --- a/internal/control/ui.html +++ b/internal/control/ui.html @@ -1121,6 +1121,7 @@ input.input-error {
+
@@ -1175,6 +1176,7 @@ const state = { dirty: new Set(), pendingRequests: 0, txBusy: false, + faultResetBusy: false, toggleBusy: {}, pollersStarted: false, mobilePanelsApplied: false, @@ -1489,6 +1491,26 @@ async function txAction(action) { } } +async function resetFaultAction() { + if (state.faultResetBusy) return; + state.faultResetBusy = true; + render(); + beginRequest(); + try { + await api('/runtime/fault/reset', { method: 'POST' }); + toast('Fault reset', 'ok'); + log('Fault reset request accepted', 'ok'); + await loadRuntime({ silent: true }); + } catch (error) { + toast(error.message, 'err'); + log('Fault reset failed: ' + error.message, 'err'); + } finally { + state.faultResetBusy = false; + endRequest(); + render(); + } +} + function fmt(n) { if (n == null) return '--'; if (n >= 1e9) return (n / 1e9).toFixed(2) + 'G'; @@ -1686,6 +1708,12 @@ function render() { $('btn-refresh').disabled = state.pendingRequests > 0; $('danger-stop').disabled = stopDisabled; $('danger-refresh').disabled = state.pendingRequests > 0; + const resetFaultBtn = $('danger-reset-fault'); + if (resetFaultBtn) { + const resetDisabled = state.faultResetBusy || !state.server.runtimeOk; + resetFaultBtn.disabled = resetDisabled; + resetFaultBtn.textContent = state.faultResetBusy ? 'Resetting…' : 'Reset Fault'; + } syncDirtyInput('freq-slider', 'frequencyMHz', (v) => typeof v === 'number' ? v.toFixed(1) : '100.0'); syncDirtyInput('freq-num', 'frequencyMHz', (v) => typeof v === 'number' ? v.toFixed(1) : '100.0'); @@ -1901,6 +1929,7 @@ function bindInputs() { $('danger-stop').addEventListener('click', () => txAction('stop')); $('btn-refresh').addEventListener('click', manualRefresh); $('danger-refresh').addEventListener('click', manualRefresh); + $('danger-reset-fault').addEventListener('click', () => resetFaultAction()); document.querySelectorAll('.toggle[data-toggle]').forEach((toggle) => { const key = toggle.dataset.toggle;