Add multi-screen dashboard simulator UI
This commit is contained in:
+125
-82
@@ -5,110 +5,153 @@
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Xterra Dashboard</h1>
|
||||
<div id="statusLine">RS-485: -- | WiFi: --</div>
|
||||
</header>
|
||||
|
||||
<section class="grid">
|
||||
<div class="card">
|
||||
<h2>Battery</h2>
|
||||
<p><span id="soc">--</span>%</p>
|
||||
<small><span id="voltage">--</span>V / <span id="current">--</span>A</small>
|
||||
</div>
|
||||
<section id="dashboard" class="screen active">
|
||||
<div class="big-card">
|
||||
<h2>Battery</h2>
|
||||
<p><span id="dashSoc">--</span>%</p>
|
||||
<small><span id="dashRuntime">--</span> hr runtime</small>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Runtime</h2>
|
||||
<p><span id="runtime">--</span> hr</p>
|
||||
<small><span id="remaining">--</span> Ah remaining</small>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="card"><h2>Fridge A</h2><p><span id="dashFridge1">--</span>°F</p></div>
|
||||
<div class="card"><h2>Fridge B</h2><p><span id="dashFridge2">--</span>°F</p></div>
|
||||
<div class="card"><h2>Rear Seat</h2><p><span id="dashRear">--</span>°F</p></div>
|
||||
<div class="card"><h2>Outside</h2><p><span id="dashOutside">--</span>°F</p></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Fridge Zone 1</h2>
|
||||
<p><span id="fridge1">--</span>°F</p>
|
||||
</div>
|
||||
<div class="relay-row">
|
||||
<div id="dashStarlink" class="relay-pill">Starlink: --</div>
|
||||
<div id="dashFridgeRelay" class="relay-pill">Fridge: --</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="card">
|
||||
<h2>Fridge Zone 2</h2>
|
||||
<p><span id="fridge2">--</span>°F</p>
|
||||
</div>
|
||||
<section id="battery" class="screen">
|
||||
<h2>Battery Detail</h2>
|
||||
<div class="detail-list">
|
||||
<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">
|
||||
<h2>Rear Seat</h2>
|
||||
<p><span id="rear">--</span>°F</p>
|
||||
</div>
|
||||
<section id="temps" class="screen">
|
||||
<h2>Temperatures</h2>
|
||||
<div class="grid">
|
||||
<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">
|
||||
<h2>Outside</h2>
|
||||
<p><span id="outside">--</span>°F</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="power" class="screen">
|
||||
<h2>Power Control</h2>
|
||||
<button onclick="toggleRelay('starlink')" id="starlinkBtn">Starlink</button>
|
||||
<button onclick="toggleRelay('fridge')" id="fridgeBtn">Fridge</button>
|
||||
</section>
|
||||
|
||||
<section class="controls">
|
||||
<button onclick="toggleRelay('starlink')" id="starlinkBtn">Starlink</button>
|
||||
<button onclick="toggleRelay('fridge')" id="fridgeBtn">Fridge</button>
|
||||
<button onclick="enableWifi()">Enable WiFi 10 min</button>
|
||||
</section>
|
||||
<section id="system" class="screen">
|
||||
<h2>System</h2>
|
||||
<div class="detail-list">
|
||||
<div>RS-485 <strong id="sysRs485">--</strong></div>
|
||||
<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">
|
||||
<p>RS-485: <span id="rs485">--</span></p>
|
||||
<p>WiFi: <span id="wifi">--</span></p>
|
||||
</section>
|
||||
</main>
|
||||
<nav>
|
||||
<button onclick="showScreen('dashboard')">Dashboard</button>
|
||||
<button onclick="showScreen('battery')">Battery</button>
|
||||
<button onclick="showScreen('temps')">Temps</button>
|
||||
<button onclick="showScreen('power')">Power</button>
|
||||
<button onclick="showScreen('system')">System</button>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
let relayState = {};
|
||||
<script>
|
||||
let relayState = {};
|
||||
|
||||
async function loadStatus() {
|
||||
const res = await fetch('/status');
|
||||
const data = await res.json();
|
||||
function showScreen(id) {
|
||||
document.querySelectorAll('.screen').forEach(screen => {
|
||||
screen.classList.remove('active');
|
||||
});
|
||||
document.getElementById(id).classList.add('active');
|
||||
}
|
||||
|
||||
document.getElementById('soc').textContent = data.battery.soc;
|
||||
document.getElementById('voltage').textContent = data.battery.voltage;
|
||||
document.getElementById('current').textContent = data.battery.current;
|
||||
document.getElementById('runtime').textContent = data.battery.runtime_hours;
|
||||
document.getElementById('remaining').textContent = data.battery.remaining_ah;
|
||||
function onOff(value) {
|
||||
return value ? 'ON' : 'OFF';
|
||||
}
|
||||
|
||||
document.getElementById('fridge1').textContent = data.temps.fridge_zone_1;
|
||||
document.getElementById('fridge2').textContent = data.temps.fridge_zone_2;
|
||||
document.getElementById('rear').textContent = data.temps.rear_seat;
|
||||
document.getElementById('outside').textContent = data.temps.outside;
|
||||
async function loadStatus() {
|
||||
const res = await fetch('/status');
|
||||
const data = await res.json();
|
||||
|
||||
relayState = data.relays;
|
||||
relayState = data.relays;
|
||||
|
||||
document.getElementById('starlinkBtn').textContent =
|
||||
relayState.starlink ? 'Starlink: ON' : 'Starlink: OFF';
|
||||
document.getElementById('statusLine').textContent =
|
||||
`RS-485: ${data.network.rs485_connected ? 'Connected' : 'Lost'} | WiFi: ${data.network.wifi_enabled ? 'Enabled' : 'Disabled'}`;
|
||||
|
||||
document.getElementById('fridgeBtn').textContent =
|
||||
relayState.fridge ? 'Fridge: ON' : 'Fridge: OFF';
|
||||
document.getElementById('dashSoc').textContent = data.battery.soc;
|
||||
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 =
|
||||
data.network.rs485_connected ? 'Connected' : 'Disconnected';
|
||||
document.getElementById('batSoc').textContent = data.battery.soc;
|
||||
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 =
|
||||
data.network.wifi_enabled ? 'Enabled' : 'Disabled';
|
||||
}
|
||||
document.getElementById('tempFridge1').textContent = data.temps.fridge_zone_1;
|
||||
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) {
|
||||
await fetch(`/relay/${name}`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({state: !relayState[name]})
|
||||
});
|
||||
document.getElementById('starlinkBtn').textContent = `Starlink: ${onOff(data.relays.starlink)}`;
|
||||
document.getElementById('fridgeBtn').textContent = `Fridge: ${onOff(data.relays.fridge)}`;
|
||||
|
||||
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() {
|
||||
await fetch('/network/wifi', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({minutes: 10})
|
||||
});
|
||||
async function toggleRelay(name) {
|
||||
await fetch(`/relay/${name}`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({state: !relayState[name]})
|
||||
});
|
||||
|
||||
loadStatus();
|
||||
}
|
||||
loadStatus();
|
||||
}
|
||||
|
||||
loadStatus();
|
||||
setInterval(loadStatus, 2000);
|
||||
</script>
|
||||
async function enableWifi() {
|
||||
await fetch('/network/wifi', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({minutes: 10})
|
||||
});
|
||||
|
||||
loadStatus();
|
||||
}
|
||||
|
||||
loadStatus();
|
||||
setInterval(loadStatus, 2000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user