generated from rmcguire/go-server-with-otel
add ui, new config opts
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
{{define "card"}}
|
||||
<article id="card-{{.Device.SerialNumber}}" class="device-card">
|
||||
<div class="card-head">
|
||||
<img class="device-icon" src="{{.Icon}}" alt="" width="56" height="56">
|
||||
<div class="card-title">
|
||||
<strong>{{.Device.FriendlyName}}</strong>
|
||||
<small>{{.Device.SerialNumber}}</small>
|
||||
</div>
|
||||
<button class="bug-btn" title="Show raw device JSON" aria-label="Show raw device JSON"
|
||||
hx-get="/ui/devices/{{.Device.SerialNumber}}/debug"
|
||||
hx-target="#debug-modal-body"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.getElementById('debug-modal').showModal()">🐛</button>
|
||||
<span class="dot {{if .Device.Connected}}dot-ok{{else}}dot-off{{end}}"
|
||||
title="{{if .Device.Connected}}Connected{{else}}Disconnected{{end}}"></span>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div><span>Status</span>
|
||||
<b>{{if .Device.Running}}🔥 {{prettyMode .Device.RunningState}}{{else}}Idle{{end}}</b></div>
|
||||
<div><span>Setpoint</span><b>{{.Device.Setpoint}}°F</b></div>
|
||||
<div><span>Alerts</span>
|
||||
<b>{{if gt .Device.AlertCount 0}}⚠ {{.Device.AlertCount}}{{else}}0{{end}}</b></div>
|
||||
</div>
|
||||
|
||||
{{if ge .Device.HotWaterAvailability 0}}
|
||||
<div class="fill">
|
||||
<div class="fill-head"><span>Hot water</span><b>{{.Device.HotWaterAvailability}}%</b></div>
|
||||
<div class="fill-meter">
|
||||
<div class="fill-bar {{fillClass .Device.HotWaterAvailability}}" style="{{fillWidth .Device.HotWaterAvailability}}"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="card-foot">
|
||||
{{if .Modes}}
|
||||
<label class="mode-label">
|
||||
Mode
|
||||
<select name="mode"
|
||||
hx-post="/ui/devices/{{.Device.SerialNumber}}/mode"
|
||||
hx-target="#card-{{.Device.SerialNumber}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="change"
|
||||
{{if .Pending}}disabled aria-busy="true"{{end}}>
|
||||
{{range .Modes}}
|
||||
<option value="{{.Value}}" {{if .Selected}}selected{{end}}>{{.Label}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</label>
|
||||
{{if .Pending}}<span class="badge badge-pending" aria-busy="true">Updating → {{.Pending}}…</span>{{end}}
|
||||
{{else}}
|
||||
<small class="mode-fixed">Mode: {{prettyMode .Device.Mode}} (not settable)</small>
|
||||
{{end}}
|
||||
{{if .Error}}<span class="badge badge-error" role="alert">{{.Error}}</span>{{end}}
|
||||
</div>
|
||||
</article>
|
||||
{{end}}
|
||||
@@ -0,0 +1 @@
|
||||
{{define "debug"}}{{.}}{{end}}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{define "devices"}}
|
||||
{{if .Devices}}
|
||||
{{range .Devices}}{{template "card" .}}{{end}}
|
||||
{{else}}
|
||||
<article class="empty-state">
|
||||
<p>No EcoNet devices loaded yet.</p>
|
||||
<small>Device state populates after the first successful cloud poll.</small>
|
||||
</article>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,57 @@
|
||||
{{define "layout"}}<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>EcoNet Admin</title>
|
||||
<link rel="stylesheet" href="/static/pico.classless.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
<script>
|
||||
// Set the theme before first paint to avoid a flash.
|
||||
(function () {
|
||||
var saved = localStorage.getItem("theme");
|
||||
var theme = saved || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="app-header">
|
||||
<hgroup>
|
||||
<h1>EcoNet Admin</h1>
|
||||
<p>Rheem water heater status & control</p>
|
||||
</hgroup>
|
||||
<button id="theme-toggle" class="secondary theme-toggle" aria-label="Toggle dark mode" onclick="toggleTheme()"></button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div id="devices" class="device-grid" hx-get="/ui/devices" hx-trigger="every 30s" hx-swap="innerHTML">
|
||||
{{template "devices" .}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<dialog id="debug-modal" class="debug-modal">
|
||||
<article>
|
||||
<header>
|
||||
<button aria-label="Close" rel="prev" onclick="document.getElementById('debug-modal').close()"></button>
|
||||
<strong>Raw device JSON</strong>
|
||||
</header>
|
||||
<pre><code id="debug-modal-body">Loading…</code></pre>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
function themeIcon(t) { return t === "dark" ? "☀️" : "🌙"; } // sun / moon
|
||||
function toggleTheme() {
|
||||
var cur = document.documentElement.getAttribute("data-theme");
|
||||
var next = cur === "dark" ? "light" : "dark";
|
||||
document.documentElement.setAttribute("data-theme", next);
|
||||
localStorage.setItem("theme", next);
|
||||
document.getElementById("theme-toggle").textContent = themeIcon(next);
|
||||
}
|
||||
document.getElementById("theme-toggle").textContent =
|
||||
themeIcon(document.documentElement.getAttribute("data-theme"));
|
||||
</script>
|
||||
</body>
|
||||
</html>{{end}}
|
||||
Reference in New Issue
Block a user