From c210c563fd7a53ba7a66c43a0af5e26f4d825cbe Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 12 Jun 2026 01:14:52 -0600 Subject: [PATCH] webui: highlight temperature cards on alerts --- .../overland-controller.ino | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index 94d925c..52624c4 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -391,6 +391,17 @@ button[onclick^="scan"]{ } } + +.temp-alert { + border-color: #f97066 !important; + background: linear-gradient(180deg, rgba(122,39,26,0.95), rgba(67,24,24,0.95)) !important; + box-shadow: 0 0 0 1px rgba(249,112,102,0.35), 0 0 18px rgba(249,112,102,0.25) !important; +} +.temp-alert .muted, +.temp-alert .inputHelp { + color: #ffd7d3 !important; +} + @@ -1015,6 +1026,7 @@ function render(data){ $("fw").textContent=(s.firmware_name||"--")+" "+(s.firmware_version||""); $("uptime").textContent=formatUptime(s.uptime_seconds||0); renderConfigControls(data); + applyTempAlertStyles(data); populateSetupFields(data); } @@ -1143,6 +1155,65 @@ async function factoryReset(){ alert("Factory reset requested. Reconnect to the controller AP if needed."); } + +function tempHighAlertActive(t){ + const temp = Number(t.temperature_f); + const threshold = Number(t.high_alert_f); + return !!( + t.high_alert || + ( + t.high_alert_enabled && + Number.isFinite(temp) && + Number.isFinite(threshold) && + temp > threshold + ) + ); +} + +function tempDisplayGroup(t){ + const group = String(t.group || "").trim().toLowerCase(); + if (t.weather || group === "outside" || group === "weather") return "outside"; + if (group) return group; + return String(t.name || "").trim().toLowerCase(); +} + +function applyTempAlertStyles(data){ + const temps = data && Array.isArray(data.temps) ? data.temps : []; + const active = temps.filter(tempHighAlertActive); + + document.querySelectorAll(".temp-alert").forEach(el => el.classList.remove("temp-alert")); + if (!active.length) return; + + const alertKeys = new Set(); + active.forEach(t => { + const name = String(t.name || "").trim().toLowerCase(); + const group = tempDisplayGroup(t); + if (name) alertKeys.add(name); + if (group) alertKeys.add(group); + if (group === "fridge") alertKeys.add("fridge"); + if (group === "cabin") alertKeys.add("cabin"); + if (group === "outside") alertKeys.add("outside"); + }); + + const candidates = document.querySelectorAll( + ".card, .tile, .tempCard, .tempTile, .tempConfigCard, .reading, .sensor, .stat" + ); + + candidates.forEach(el => { + const text = (el.textContent || "").toLowerCase(); + if (!text.includes("°") && !text.includes("temp") && !text.includes("fridge") && !text.includes("cabin")) { + return; + } + + for (const key of alertKeys) { + if (key && text.includes(key)) { + el.classList.add("temp-alert"); + return; + } + } + }); +} + function renderConfigControls(data){ const cfg=data.config||{}; const relays=cfg.relays||[];