webui: add relay state badges and feedback
This commit is contained in:
@@ -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%}
|
.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{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}
|
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}
|
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}
|
.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}
|
.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";
|
const API_BASE="/api/v1";
|
||||||
function api(path){return API_BASE+path;}
|
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";
|
const STATUS_FIELDS_OVERVIEW="battery,temps,relays,vehicle,network,alarms,system";
|
||||||
|
|
||||||
function isConfigTabActive(){
|
function isConfigTabActive(){
|
||||||
@@ -344,8 +386,42 @@ async function setBmsEnabled(enabled){
|
|||||||
await load();
|
await load();
|
||||||
}
|
}
|
||||||
async function relay(id,on){
|
async function relay(id,on){
|
||||||
await fetch(api("/relay/set"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id,state:on})});
|
relayPending[id]=on;
|
||||||
await load();
|
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){
|
function render(data){
|
||||||
data=mergeStatus(window.lastStatus,data);
|
data=mergeStatus(window.lastStatus,data);
|
||||||
@@ -389,10 +465,26 @@ function render(data){
|
|||||||
|
|
||||||
$("relays").innerHTML=(data.relays||[]).map(r=>`
|
$("relays").innerHTML=(data.relays||[]).map(r=>`
|
||||||
<div class="item">
|
<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">
|
<span class="row" style="gap:6px">
|
||||||
<button class="on" onclick="relay('${r.id}',true)">ON</button>
|
<button class="on"
|
||||||
<button class="off" onclick="relay('${r.id}',false)">OFF</button>
|
${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>
|
</span>
|
||||||
</div>`).join("");
|
</div>`).join("");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user