webui: add relay state badges and feedback

This commit is contained in:
2026-06-07 19:37:01 -06:00
parent 4474b6d11a
commit 594edc3dd9
@@ -48,6 +48,35 @@ h1{font-size:21px;margin:0}.sub{color:var(--muted);font-size:13px;margin-top:4px
.fill{height:100%;background:linear-gradient(90deg,var(--good),#9be15d);width:0%}
button{width:100%;border:0;border-radius:12px;padding:12px;background:var(--btn);color:var(--text);font-weight:800;font-size:15px}
button.on{background:#17462a;color:#dfffea}button.off{background:#46202a;color:#ffe2e8}
.relayBadge{
padding:4px 8px;
border-radius:999px;
font-size:11px;
font-weight:700;
border:1px solid var(--line);
}
.relayBadge.on{
background:#173c22;
color:#dfffea;
}
.relayBadge.off{
background:#3f1820;
color:#ffe2e8;
}
.relayBadge.pending{
background:#4d3f12;
color:#fff0b3;
}
.relayFeedback{
font-size:11px;
color:var(--muted);
margin-top:4px;
}
input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;background:#0f151d;color:var(--text);font-size:15px}
.list{display:grid;gap:8px;margin-top:12px}
.item{display:flex;justify-content:space-between;gap:8px;background:#0f151d;border:1px solid var(--line);border-radius:12px;padding:10px}
@@ -241,6 +270,19 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
const API_BASE="/api/v1";
function api(path){return API_BASE+path;}
const relayPending={};
const relayFeedback={};
function relayStateLabel(r){
if(relayPending[r.id]!==undefined) return "SENDING";
return r.state ? "ON" : "OFF";
}
function relayStateClass(r){
if(relayPending[r.id]!==undefined) return "pending";
return r.state ? "on" : "off";
}
const STATUS_FIELDS_OVERVIEW="battery,temps,relays,vehicle,network,alarms,system";
function isConfigTabActive(){
@@ -344,8 +386,42 @@ async function setBmsEnabled(enabled){
await load();
}
async function relay(id,on){
await fetch(api("/relay/set"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id,state:on})});
await load();
relayPending[id]=on;
relayFeedback[id]=on ? "Turning on..." : "Turning off...";
if(window.lastStatus) render(window.lastStatus);
try{
const r=await fetch(api("/relay/set"),{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({id,state:on})
});
if(!r.ok){
throw new Error("HTTP "+r.status);
}
relayFeedback[id]=on ? "Relay ON" : "Relay OFF";
await load();
}catch(err){
console.error(err);
relayFeedback[id]="Command failed";
if(window.lastStatus) render(window.lastStatus);
}finally{
delete relayPending[id];
setTimeout(()=>{
delete relayFeedback[id];
if(window.lastStatus) render(window.lastStatus);
},1500);
}
}
function render(data){
data=mergeStatus(window.lastStatus,data);
@@ -389,10 +465,26 @@ function render(data){
$("relays").innerHTML=(data.relays||[]).map(r=>`
<div class="item">
<span>${r.name||r.id}</span>
<div>
<div class="row" style="gap:8px">
<span>${r.name||r.id}</span>
<span class="relayBadge ${relayStateClass(r)}">${relayStateLabel(r)}</span>
</div>
<div class="relayFeedback">${relayFeedback[r.id]||""}</div>
</div>
<span class="row" style="gap:6px">
<button class="on" onclick="relay('${r.id}',true)">ON</button>
<button class="off" onclick="relay('${r.id}',false)">OFF</button>
<button class="on"
${relayPending[r.id]!==undefined?"disabled":""}
onclick="relay('${r.id}',true)">
ON
</button>
<button class="off"
${relayPending[r.id]!==undefined?"disabled":""}
onclick="relay('${r.id}',false)">
OFF
</button>
</span>
</div>`).join("");