diverter-device-emulator/public/index.html
2026-09-22 22:26:58 +01:00

226 lines
8.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DVgo Device Emulator</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Outfit:wght@500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<div id="login-overlay" class="overlay hidden">
<form id="login-form" class="login-card" autocomplete="on">
<p class="eyebrow">Control portal</p>
<h1>Sign in</h1>
<p class="muted">Basic auth for the emulator API. Credentials stay in this browser session.</p>
<label>
Username
<input id="login-user" name="username" required />
</label>
<label>
Password
<input id="login-pass" name="password" type="password" required />
</label>
<p id="login-error" class="error hidden"></p>
<button type="submit">Continue</button>
</form>
</div>
<header class="topbar">
<div>
<h1>DVgo device emulator</h1>
</div>
<div class="topbar-meta">
<div id="tb-status" class="status-pill off">ThingsBoard</div>
<div id="mqtt-status" class="status-pill off">MQTT</div>
</div>
</header>
<main class="layout">
<aside class="sidebar">
<div class="panel-head">
<h2>Devices</h2>
<div class="device-menu">
<button
id="device-menu-toggle"
type="button"
class="device-menu-button"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="device-menu"
aria-label="Device options"
>
<span class="device-menu-icon" aria-hidden="true"></span>
</button>
<div id="device-menu" class="device-menu-panel hidden" role="menu">
<button id="add-open" type="button" class="primary" role="menuitem">Add</button>
<button id="select-all" type="button" role="menuitem">Select all</button>
<button id="deselect-all" type="button" role="menuitem">Deselect all</button>
<button id="bulk-disable" type="button" role="menuitem">Disable selected</button>
<button id="bulk-enable" type="button" role="menuitem">Enable selected</button>
<button id="bulk-delete" type="button" class="danger" role="menuitem">Delete selected</button>
</div>
</div>
</div>
<p id="bulk-status" class="muted"></p>
<div id="device-list" class="device-list"></div>
</aside>
<section class="workspace">
<div id="empty-state" class="empty-state">
<h2>No device selected</h2>
<p>Add a virtual DVgo device, then use the dashboard to publish live *_s telemetry to ThingsBoard.</p>
</div>
<div id="dashboard" class="dashboard hidden">
<div class="dash-head">
<div>
<p id="dash-type" class="eyebrow"></p>
<h2 id="dash-name"></h2>
<p id="dash-id" class="mono muted"></p>
</div>
<button id="delete-one" type="button" class="danger">Delete</button>
</div>
<div class="control-block">
<button id="toggle-mqtt" type="button" class="primary">Connect</button>
<span id="mqtt-spinner" class="spinner spinner-inline hidden" aria-hidden="true"></span>
<p class="connection-response">
connection response:
<span id="connection-response">—</span>
</p>
</div>
<div class="control-block">
<button id="toggle-telemetry" type="button" class="primary">Start telemetry</button>
</div>
<section class="group plant-panel">
<h3>Virtual plant</h3>
<p class="muted">Follows ThingsBoard shared attributes from the apps: one-shot boost (boostNowHw / boostNowAux) and HW/aux schedules. Schedule saves are polled every 5s while MQTT is connected.</p>
<section class="plant-card">
<h4>Hot water</h4>
<div class="lamps">
<div class="lamp-card">
<span id="hw-lamp" class="lamp" aria-hidden="true"></span>
<div>
<strong>Hot water boost</strong>
<small id="hw-lamp-detail">Off</small>
</div>
</div>
<div class="lamp-card">
<span id="tank-temp" class="plant-temp-value">—</span>
<div>
<strong>Hot Water Temp</strong>
<small class="key">heatsink_s</small>
</div>
</div>
</div>
<div class="meta-row plant-fields">
<label>
Heat rate (°C / min)
<input id="heat-rate" type="number" min="0" max="20" step="1" />
</label>
<label>
Thermostat max (°C)
<input id="thermostat" type="number" min="0" max="99" step="1" />
</label>
</div>
</section>
<section class="plant-card">
<h4>Aux</h4>
<div class="lamps">
<div class="lamp-card">
<span id="aux-lamp" class="lamp" aria-hidden="true"></span>
<div>
<strong>Aux boost / divert</strong>
<small id="aux-lamp-detail">Off</small>
</div>
</div>
</div>
</section>
</section>
<section class="group publish-panel">
<h3>Publish</h3>
<div class="meta-row publish-fields">
<label>
Publish interval (ms)
<input id="interval-input" type="number" min="500" step="500" />
</label>
<p id="last-published" class="muted"></p>
</div>
<div class="publish-actions">
<button id="publish-now" type="button">Publish now</button>
<button id="publish-select-all" type="button">Select all</button>
<button id="publish-deselect-all" type="button">Deselect all</button>
</div>
</section>
<div id="telemetry-groups"></div>
<details class="rpc-log">
<summary>Last RPC / shared attributes</summary>
<pre id="rpc-pre">No RPC yet.</pre>
</details>
</div>
</section>
</main>
<div id="add-modal" class="overlay hidden">
<form id="add-form" class="login-card">
<div id="add-busy" class="busy hidden" aria-live="polite" aria-busy="false">
<span class="spinner" aria-hidden="true"></span>
<p id="add-busy-text">Waiting for ThingsBoard…</p>
</div>
<p class="eyebrow">New virtual device</p>
<h2>Create in ThingsBoard</h2>
<label>
Name / serial
<input id="add-name" name="name" required placeholder="EMU-AABBCCDDEEFF" />
</label>
<label>
Label
<input id="add-label" name="label" placeholder="Kitchen emulator" />
</label>
<label>
Type
<input id="add-type" name="type" placeholder="DVgo" value="DVgo" />
</label>
<label>
Device profile
<select id="add-profile"></select>
</label>
<label>
Existing access token (optional)
<input id="add-token" name="accessToken" placeholder="Skip TB create and attach this MQTT token" />
</label>
<p id="add-error" class="error hidden"></p>
<div class="row-actions">
<button type="button" id="add-cancel">Cancel</button>
<button type="submit" class="primary">Create device</button>
</div>
</form>
</div>
<div id="confirm-modal" class="overlay hidden">
<div class="login-card">
<p class="eyebrow">ThingsBoard timeout</p>
<h2 id="confirm-title">Save device anyway?</h2>
<p id="confirm-message" class="muted"></p>
<div class="row-actions">
<button type="button" id="confirm-cancel">Don't save</button>
<button type="button" id="confirm-ok" class="primary">Save to emulator</button>
</div>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>