Add multi-screen dashboard simulator UI

This commit is contained in:
root
2026-06-03 00:02:31 -06:00
parent a7d007d1c5
commit f0878babd2
2 changed files with 232 additions and 101 deletions
+107 -19
View File
@@ -1,69 +1,157 @@
body { body {
margin: 0; margin: 0;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
background: #111; background: #101010;
color: #eee; color: #eee;
} }
main { main {
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;
padding: 24px; padding: 18px 18px 96px;
}
header {
text-align: center;
margin-bottom: 16px;
} }
h1 { h1 {
margin: 0 0 8px;
font-size: 1.8rem;
}
h2 {
margin-top: 0;
}
#statusLine {
color: #aaa;
font-size: 0.95rem;
}
.screen {
display: none;
}
.screen.active {
display: block;
}
.big-card {
background: #222;
border: 1px solid #333;
border-radius: 16px;
padding: 24px;
text-align: center; text-align: center;
margin-bottom: 16px;
}
.big-card p {
font-size: 4rem;
margin: 0;
} }
.grid { .grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 16px; gap: 14px;
} }
.card { .card {
background: #222; background: #222;
border: 1px solid #333; border: 1px solid #333;
border-radius: 12px; border-radius: 14px;
padding: 18px; padding: 16px;
text-align: center; text-align: center;
} }
.card h2 { .card h2 {
font-size: 1rem; font-size: 1rem;
margin: 0 0 12px;
color: #aaa; color: #aaa;
} }
.card p { .card p {
font-size: 2rem; font-size: 2.1rem;
margin: 0; margin: 0;
} }
.controls { .relay-row {
margin-top: 24px;
display: flex; display: flex;
gap: 12px; gap: 12px;
flex-wrap: wrap; margin-top: 16px;
justify-content: center; }
.relay-pill {
flex: 1;
background: #252525;
border: 1px solid #444;
border-radius: 999px;
padding: 14px;
text-align: center;
font-size: 1.2rem;
}
.detail-list {
background: #222;
border: 1px solid #333;
border-radius: 14px;
overflow: hidden;
margin-bottom: 18px;
}
.detail-list div {
display: flex;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #333;
font-size: 1.2rem;
}
.detail-list div:last-child {
border-bottom: none;
} }
button { button {
font-size: 1rem; font-size: 1.1rem;
padding: 14px 20px; padding: 16px 20px;
border: none; border: none;
border-radius: 10px; border-radius: 12px;
background: #444; background: #444;
color: white; color: white;
cursor: pointer; cursor: pointer;
min-width: 130px;
} }
button:hover { button:hover {
background: #666; background: #666;
} }
.status { #power button,
margin-top: 24px; #system button {
text-align: center; display: block;
color: #aaa; width: 100%;
margin-bottom: 14px;
font-size: 1.4rem;
padding: 22px;
}
nav {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #181818;
border-top: 1px solid #333;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 4px;
padding: 8px;
}
nav button {
min-width: 0;
padding: 14px 4px;
font-size: 0.9rem;
border-radius: 10px;
} }
+125 -82
View File
@@ -5,110 +5,153 @@
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
</head> </head>
<body> <body>
<main> <main>
<header>
<h1>Xterra Dashboard</h1> <h1>Xterra Dashboard</h1>
<div id="statusLine">RS-485: -- | WiFi: --</div>
</header>
<section class="grid"> <section id="dashboard" class="screen active">
<div class="card"> <div class="big-card">
<h2>Battery</h2> <h2>Battery</h2>
<p><span id="soc">--</span>%</p> <p><span id="dashSoc">--</span>%</p>
<small><span id="voltage">--</span>V / <span id="current">--</span>A</small> <small><span id="dashRuntime">--</span> hr runtime</small>
</div> </div>
<div class="card"> <div class="grid">
<h2>Runtime</h2> <div class="card"><h2>Fridge A</h2><p><span id="dashFridge1">--</span>°F</p></div>
<p><span id="runtime">--</span> hr</p> <div class="card"><h2>Fridge B</h2><p><span id="dashFridge2">--</span>°F</p></div>
<small><span id="remaining">--</span> Ah remaining</small> <div class="card"><h2>Rear Seat</h2><p><span id="dashRear">--</span>°F</p></div>
</div> <div class="card"><h2>Outside</h2><p><span id="dashOutside">--</span>°F</p></div>
</div>
<div class="card"> <div class="relay-row">
<h2>Fridge Zone 1</h2> <div id="dashStarlink" class="relay-pill">Starlink: --</div>
<p><span id="fridge1">--</span>°F</p> <div id="dashFridgeRelay" class="relay-pill">Fridge: --</div>
</div> </div>
</section>
<div class="card"> <section id="battery" class="screen">
<h2>Fridge Zone 2</h2> <h2>Battery Detail</h2>
<p><span id="fridge2">--</span>°F</p> <div class="detail-list">
</div> <div>SOC <strong><span id="batSoc">--</span>%</strong></div>
<div>Voltage <strong><span id="batVoltage">--</span> V</strong></div>
<div>Current <strong><span id="batCurrent">--</span> A</strong></div>
<div>Remaining <strong><span id="batRemaining">--</span> Ah</strong></div>
<div>Runtime <strong><span id="batRuntime">--</span> hr</strong></div>
<div>Battery Temp <strong><span id="batTemp">--</span>°F</strong></div>
</div>
</section>
<div class="card"> <section id="temps" class="screen">
<h2>Rear Seat</h2> <h2>Temperatures</h2>
<p><span id="rear">--</span>°F</p> <div class="grid">
</div> <div class="card"><h2>Fridge Zone 1</h2><p><span id="tempFridge1">--</span>°F</p></div>
<div class="card"><h2>Fridge Zone 2</h2><p><span id="tempFridge2">--</span>°F</p></div>
<div class="card"><h2>Rear Seat</h2><p><span id="tempRear">--</span>°F</p></div>
<div class="card"><h2>Outside</h2><p><span id="tempOutside">--</span>°F</p></div>
</div>
</section>
<div class="card"> <section id="power" class="screen">
<h2>Outside</h2> <h2>Power Control</h2>
<p><span id="outside">--</span>°F</p> <button onclick="toggleRelay('starlink')" id="starlinkBtn">Starlink</button>
</div> <button onclick="toggleRelay('fridge')" id="fridgeBtn">Fridge</button>
</section> </section>
<section class="controls"> <section id="system" class="screen">
<button onclick="toggleRelay('starlink')" id="starlinkBtn">Starlink</button> <h2>System</h2>
<button onclick="toggleRelay('fridge')" id="fridgeBtn">Fridge</button> <div class="detail-list">
<button onclick="enableWifi()">Enable WiFi 10 min</button> <div>RS-485 <strong id="sysRs485">--</strong></div>
</section> <div>WiFi <strong id="sysWifi">--</strong></div>
<div>WiFi Override <strong id="sysWifiOverride">--</strong></div>
</div>
<button onclick="enableWifi()">Enable WiFi 10 min</button>
</section>
</main>
<section class="status"> <nav>
<p>RS-485: <span id="rs485">--</span></p> <button onclick="showScreen('dashboard')">Dashboard</button>
<p>WiFi: <span id="wifi">--</span></p> <button onclick="showScreen('battery')">Battery</button>
</section> <button onclick="showScreen('temps')">Temps</button>
</main> <button onclick="showScreen('power')">Power</button>
<button onclick="showScreen('system')">System</button>
</nav>
<script> <script>
let relayState = {}; let relayState = {};
async function loadStatus() { function showScreen(id) {
const res = await fetch('/status'); document.querySelectorAll('.screen').forEach(screen => {
const data = await res.json(); screen.classList.remove('active');
});
document.getElementById(id).classList.add('active');
}
document.getElementById('soc').textContent = data.battery.soc; function onOff(value) {
document.getElementById('voltage').textContent = data.battery.voltage; return value ? 'ON' : 'OFF';
document.getElementById('current').textContent = data.battery.current; }
document.getElementById('runtime').textContent = data.battery.runtime_hours;
document.getElementById('remaining').textContent = data.battery.remaining_ah;
document.getElementById('fridge1').textContent = data.temps.fridge_zone_1; async function loadStatus() {
document.getElementById('fridge2').textContent = data.temps.fridge_zone_2; const res = await fetch('/status');
document.getElementById('rear').textContent = data.temps.rear_seat; const data = await res.json();
document.getElementById('outside').textContent = data.temps.outside;
relayState = data.relays; relayState = data.relays;
document.getElementById('starlinkBtn').textContent = document.getElementById('statusLine').textContent =
relayState.starlink ? 'Starlink: ON' : 'Starlink: OFF'; `RS-485: ${data.network.rs485_connected ? 'Connected' : 'Lost'} | WiFi: ${data.network.wifi_enabled ? 'Enabled' : 'Disabled'}`;
document.getElementById('fridgeBtn').textContent = document.getElementById('dashSoc').textContent = data.battery.soc;
relayState.fridge ? 'Fridge: ON' : 'Fridge: OFF'; document.getElementById('dashRuntime').textContent = data.battery.runtime_hours;
document.getElementById('dashFridge1').textContent = data.temps.fridge_zone_1;
document.getElementById('dashFridge2').textContent = data.temps.fridge_zone_2;
document.getElementById('dashRear').textContent = data.temps.rear_seat;
document.getElementById('dashOutside').textContent = data.temps.outside;
document.getElementById('dashStarlink').textContent = `Starlink: ${onOff(data.relays.starlink)}`;
document.getElementById('dashFridgeRelay').textContent = `Fridge: ${onOff(data.relays.fridge)}`;
document.getElementById('rs485').textContent = document.getElementById('batSoc').textContent = data.battery.soc;
data.network.rs485_connected ? 'Connected' : 'Disconnected'; document.getElementById('batVoltage').textContent = data.battery.voltage;
document.getElementById('batCurrent').textContent = data.battery.current;
document.getElementById('batRemaining').textContent = data.battery.remaining_ah;
document.getElementById('batRuntime').textContent = data.battery.runtime_hours;
document.getElementById('batTemp').textContent = data.battery.temperature_f;
document.getElementById('wifi').textContent = document.getElementById('tempFridge1').textContent = data.temps.fridge_zone_1;
data.network.wifi_enabled ? 'Enabled' : 'Disabled'; document.getElementById('tempFridge2').textContent = data.temps.fridge_zone_2;
} document.getElementById('tempRear').textContent = data.temps.rear_seat;
document.getElementById('tempOutside').textContent = data.temps.outside;
async function toggleRelay(name) { document.getElementById('starlinkBtn').textContent = `Starlink: ${onOff(data.relays.starlink)}`;
await fetch(`/relay/${name}`, { document.getElementById('fridgeBtn').textContent = `Fridge: ${onOff(data.relays.fridge)}`;
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({state: !relayState[name]})
});
loadStatus(); document.getElementById('sysRs485').textContent = data.network.rs485_connected ? 'Connected' : 'Disconnected';
} document.getElementById('sysWifi').textContent = data.network.wifi_enabled ? 'Enabled' : 'Disabled';
document.getElementById('sysWifiOverride').textContent = data.network.wifi_override_active ? 'Active' : 'Inactive';
}
async function enableWifi() { async function toggleRelay(name) {
await fetch('/network/wifi', { await fetch(`/relay/${name}`, {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({minutes: 10}) body: JSON.stringify({state: !relayState[name]})
}); });
loadStatus(); loadStatus();
} }
loadStatus(); async function enableWifi() {
setInterval(loadStatus, 2000); await fetch('/network/wifi', {
</script> method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({minutes: 10})
});
loadStatus();
}
loadStatus();
setInterval(loadStatus, 2000);
</script>
</body> </body>
</html> </html>