Add full-screen alarm acknowledgement overlay
This commit is contained in:
@@ -155,3 +155,54 @@ nav button {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.alarm-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: #200;
|
||||||
|
color: white;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-overlay.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-box {
|
||||||
|
width: min(720px, 100%);
|
||||||
|
background: #3a0000;
|
||||||
|
border: 4px solid #ffdddd;
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 36px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 0 40px rgba(0,0,0,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-icon {
|
||||||
|
font-size: 5rem;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-box h1 {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-box p {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-box button {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 24px;
|
||||||
|
background: white;
|
||||||
|
color: #300;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="alarmOverlay" class="alarm-overlay hidden">
|
||||||
|
<div class="alarm-box">
|
||||||
|
<div class="alarm-icon">⚠</div>
|
||||||
|
<h1 id="alarmTitle">ALARM</h1>
|
||||||
|
<p id="alarmMessage">--</p>
|
||||||
|
<button onclick="acknowledgeAlarm()">Acknowledge</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
<h1>Xterra Dashboard</h1>
|
<h1>Xterra Dashboard</h1>
|
||||||
@@ -80,6 +89,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
let relayState = {};
|
let relayState = {};
|
||||||
|
let activeAlarmKey = null;
|
||||||
|
let acknowledgedAlarms = new Set();
|
||||||
|
|
||||||
function showScreen(id) {
|
function showScreen(id) {
|
||||||
document.querySelectorAll('.screen').forEach(screen => {
|
document.querySelectorAll('.screen').forEach(screen => {
|
||||||
@@ -92,6 +103,70 @@ function onOff(value) {
|
|||||||
return value ? 'ON' : 'OFF';
|
return value ? 'ON' : 'OFF';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkAlarms(data) {
|
||||||
|
const alarms = [];
|
||||||
|
|
||||||
|
if (data.temps.rear_seat >= 95) {
|
||||||
|
alarms.push({
|
||||||
|
key: 'rear_seat_critical',
|
||||||
|
title: 'REAR SEAT TEMP CRITICAL',
|
||||||
|
message: `${data.temps.rear_seat}°F detected near car seat area`
|
||||||
|
});
|
||||||
|
} else if (data.temps.rear_seat >= 85) {
|
||||||
|
alarms.push({
|
||||||
|
key: 'rear_seat_warning',
|
||||||
|
title: 'REAR SEAT TEMP HIGH',
|
||||||
|
message: `${data.temps.rear_seat}°F detected near car seat area`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.temps.fridge_zone_1 >= 45) {
|
||||||
|
alarms.push({
|
||||||
|
key: 'fridge_zone_1_warm',
|
||||||
|
title: 'FRIDGE ZONE 1 WARM',
|
||||||
|
message: `${data.temps.fridge_zone_1}°F`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.temps.fridge_zone_2 >= 45) {
|
||||||
|
alarms.push({
|
||||||
|
key: 'fridge_zone_2_warm',
|
||||||
|
title: 'FRIDGE ZONE 2 WARM',
|
||||||
|
message: `${data.temps.fridge_zone_2}°F`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.battery.soc <= 20) {
|
||||||
|
alarms.push({
|
||||||
|
key: 'battery_low',
|
||||||
|
title: 'BATTERY LOW',
|
||||||
|
message: `${data.battery.soc}% remaining`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const unacked = alarms.find(alarm => !acknowledgedAlarms.has(alarm.key));
|
||||||
|
|
||||||
|
if (unacked) {
|
||||||
|
showAlarm(unacked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlarm(alarm) {
|
||||||
|
activeAlarmKey = alarm.key;
|
||||||
|
document.getElementById('alarmTitle').textContent = alarm.title;
|
||||||
|
document.getElementById('alarmMessage').textContent = alarm.message;
|
||||||
|
document.getElementById('alarmOverlay').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function acknowledgeAlarm() {
|
||||||
|
if (activeAlarmKey) {
|
||||||
|
acknowledgedAlarms.add(activeAlarmKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
activeAlarmKey = null;
|
||||||
|
document.getElementById('alarmOverlay').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
async function loadStatus() {
|
async function loadStatus() {
|
||||||
const res = await fetch('/status');
|
const res = await fetch('/status');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -128,6 +203,8 @@ async function loadStatus() {
|
|||||||
document.getElementById('sysRs485').textContent = data.network.rs485_connected ? 'Connected' : 'Disconnected';
|
document.getElementById('sysRs485').textContent = data.network.rs485_connected ? 'Connected' : 'Disconnected';
|
||||||
document.getElementById('sysWifi').textContent = data.network.wifi_enabled ? 'Enabled' : 'Disabled';
|
document.getElementById('sysWifi').textContent = data.network.wifi_enabled ? 'Enabled' : 'Disabled';
|
||||||
document.getElementById('sysWifiOverride').textContent = data.network.wifi_override_active ? 'Active' : 'Inactive';
|
document.getElementById('sysWifiOverride').textContent = data.network.wifi_override_active ? 'Active' : 'Inactive';
|
||||||
|
|
||||||
|
checkAlarms(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleRelay(name) {
|
async function toggleRelay(name) {
|
||||||
|
|||||||
Reference in New Issue
Block a user