From 3e923609b8a43426ab24a942f657739926e7f2fc Mon Sep 17 00:00:00 2001 From: Grant Date: Sat, 11 Jul 2026 16:29:01 +0200 Subject: [PATCH] feat(w1): display failsafe cause text in status line for AMBER/RED --- widgets/w1_system_health_indicator.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgets/w1_system_health_indicator.json b/widgets/w1_system_health_indicator.json index 1bbef8f..6521a42 100644 --- a/widgets/w1_system_health_indicator.json +++ b/widgets/w1_system_health_indicator.json @@ -3,5 +3,5 @@ "css": ".w1-circle-wrap { display: inline-flex; flex-direction: column; align-items: center; gap: 4px; margin: 0 6px; } .w1-circle { width: 36px; height: 36px; border-radius: 50%; border: 2px solid #1e3050; background: #1a2d42; } .w1-circle.green { background: #00e09a; border-color: #00e09a; box-shadow: 0 0 12px #00e09a; } .w1-circle.amber { background: #ffb830; border-color: #ffb830; box-shadow: 0 0 12px #ffb830; } .w1-circle.red { background: #ff3a5a; border-color: #ff3a5a; box-shadow: 0 0 16px #ff3a5a; } .w1-circle-label { font-size: 9px; color: #6a9bbf; text-transform: uppercase; letter-spacing: 0.08em; font-family: monospace; } .w1-circle-label.active { color: #d8eeff; font-weight: bold; } #w1-circles { display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } #w1-message { font-size: 11px; color: #6a9bbf; text-align: center; max-width: 180px; line-height: 1.4; font-family: monospace; } #w1-message.green { color: #00e09a; } #w1-message.amber { color: #ffb830; } #w1-message.red { color: #ff3a5a; } #w1-footer { margin-top: 6px; font-size: 9px; color: #2a4060; font-family: monospace; }", - "js": "// W1 -- System Health Indicator\n// Reads external/rov-failsafe from Cockpit data lake.\n// Getter confirmed: window.cockpit.getDataLakeVariableData(id)\n// State: 0=GREEN 1=AMBER 2=RED null=waiting\nvar W1_VAR = 'external/rov-failsafe';\nvar W1_POLL_MS = 500;\n\nfunction w1ClearAll() {\n document.getElementById('w1-c-green').className = 'w1-circle';\n document.getElementById('w1-c-amber').className = 'w1-circle';\n document.getElementById('w1-c-red').className = 'w1-circle';\n document.getElementById('w1-l-green').className = 'w1-circle-label';\n document.getElementById('w1-l-amber').className = 'w1-circle-label';\n document.getElementById('w1-l-red').className = 'w1-circle-label';\n document.getElementById('w1-message').className = '';\n}\n\nfunction w1ApplyState(state) {\n w1ClearAll();\n var msgEl = document.getElementById('w1-message');\n if (state === 0) {\n document.getElementById('w1-c-green').className = 'w1-circle green';\n document.getElementById('w1-l-green').className = 'w1-circle-label active';\n msgEl.className = 'green';\n msgEl.textContent = 'Systems nominal';\n } else if (state === 1) {\n document.getElementById('w1-c-amber').className = 'w1-circle amber';\n document.getElementById('w1-l-amber').className = 'w1-circle-label active';\n msgEl.className = 'amber';\n msgEl.textContent = 'Parameter degraded';\n } else if (state === 2) {\n document.getElementById('w1-c-red').className = 'w1-circle red';\n document.getElementById('w1-l-red').className = 'w1-circle-label active';\n msgEl.className = 'red';\n msgEl.textContent = 'CRITICAL';\n } else {\n msgEl.textContent = 'Waiting for data...';\n }\n}\n\nfunction w1Poll() {\n var footEl = document.getElementById('w1-footer');\n try {\n if (typeof window.cockpit === 'undefined') {\n footEl.textContent = 'API not ready';\n return;\n }\n var raw = window.cockpit.getDataLakeVariableData(W1_VAR);\n if (raw === null || raw === undefined) {\n w1ApplyState(-1);\n footEl.textContent = 'Waiting for ' + W1_VAR;\n return;\n }\n w1ApplyState(parseInt(raw, 10));\n footEl.textContent = 'OK';\n } catch(err) {\n footEl.textContent = 'Error: ' + err.message;\n }\n}\n\nsetTimeout(w1Poll, 300);\nsetInterval(w1Poll, W1_POLL_MS);" + "js": "// W1 -- System Health Indicator\n// Reads external/rov-failsafe from Cockpit data lake.\n// Getter confirmed: window.cockpit.getDataLakeVariableData(id)\n// State: 0=GREEN 1=AMBER 2=RED null=waiting\n//\n// Two-variable split:\n// W1_VAR (external/rov-failsafe) -- integer 0/1/2, drives the\n// circle colour + label.\n// W1_CAUSE_VAR (external/rov-failsafe-cause) -- string, e.g.\n// \"Heartbeat Lost\", empty\n// when nominal. Drives the\n// #w1-message text for\n// AMBER/RED only -- GREEN\n// and the waiting state\n// ignore it and always show\n// fixed text.\nvar W1_VAR = 'external/rov-failsafe';\nvar W1_CAUSE_VAR = 'external/rov-failsafe-cause';\nvar W1_POLL_MS = 500;\n\nfunction w1ClearAll() {\n document.getElementById('w1-c-green').className = 'w1-circle';\n document.getElementById('w1-c-amber').className = 'w1-circle';\n document.getElementById('w1-c-red').className = 'w1-circle';\n document.getElementById('w1-l-green').className = 'w1-circle-label';\n document.getElementById('w1-l-amber').className = 'w1-circle-label';\n document.getElementById('w1-l-red').className = 'w1-circle-label';\n document.getElementById('w1-message').className = '';\n}\n\n// state: 0=GREEN 1=AMBER 2=RED -1=waiting (see w1Poll)\n// cause: string from W1_CAUSE_VAR, '' when there is no cause text\n// (nominal, or the cause variable has not published yet).\nfunction w1ApplyState(state, cause) {\n w1ClearAll();\n var msgEl = document.getElementById('w1-message');\n if (state === 0) {\n // GREEN -- always fixed text; cause is ignored even if non-empty.\n document.getElementById('w1-c-green').className = 'w1-circle green';\n document.getElementById('w1-l-green').className = 'w1-circle-label active';\n msgEl.className = 'green';\n msgEl.textContent = 'Systems nominal';\n } else if (state === 1) {\n // AMBER -- prefer the cause text (e.g. \"Heartbeat Lost\") when present,\n // fall back to the generic label if the cause variable is empty.\n document.getElementById('w1-c-amber').className = 'w1-circle amber';\n document.getElementById('w1-l-amber').className = 'w1-circle-label active';\n msgEl.className = 'amber';\n msgEl.textContent = cause ? cause : 'Parameter degraded';\n } else if (state === 2) {\n // RED -- same cause-text-first behaviour as AMBER.\n document.getElementById('w1-c-red').className = 'w1-circle red';\n document.getElementById('w1-l-red').className = 'w1-circle-label active';\n msgEl.className = 'red';\n msgEl.textContent = cause ? cause : 'CRITICAL';\n } else {\n msgEl.textContent = 'Waiting for data...';\n }\n}\n\nfunction w1Poll() {\n var footEl = document.getElementById('w1-footer');\n try {\n if (typeof window.cockpit === 'undefined') {\n footEl.textContent = 'API not ready';\n return;\n }\n var raw = window.cockpit.getDataLakeVariableData(W1_VAR);\n if (raw === null || raw === undefined) {\n // No colour data yet -- call with a consistent (-1, '') signature\n // rather than omitting the second argument.\n w1ApplyState(-1, '');\n footEl.textContent = 'Waiting for ' + W1_VAR;\n return;\n }\n // Cause text is read independently of the colour state above, every\n // poll (not just on AMBER/RED), so the fallback below always has a\n // definite '' rather than undefined if the cause variable has not\n // published yet.\n var causeRaw = window.cockpit.getDataLakeVariableData(W1_CAUSE_VAR);\n var cause = (causeRaw === null || causeRaw === undefined) ? '' : causeRaw;\n w1ApplyState(parseInt(raw, 10), cause);\n // Footer stays the widget's own poll-health line -- 'OK' on a\n // successful poll, independent of the failsafe cause text above.\n footEl.textContent = 'OK';\n } catch(err) {\n footEl.textContent = 'Error: ' + err.message;\n }\n}\n\nsetTimeout(w1Poll, 300);\nsetInterval(w1Poll, W1_POLL_MS);" }