const $ = (sel, root = document) => root.querySelector(sel); const $$ = (sel, root = document) => [...root.querySelectorAll(sel)]; async function api(path, body) { const opts = { method: body ? "POST" : "GET" }; if (body) { opts.headers = { "Content-Type": "application/json" }; opts.body = JSON.stringify(body); } const res = await fetch(path, opts); return res.json(); } function formatLog(entries) { return entries .map((e) => { const ts = new Date(e.t).toLocaleTimeString(); const arrow = e.direction === "out" ? "โ†’" : e.direction === "in" ? "โ†" : e.direction === "err" ? "โœ–" : "ยท"; return `${ts} ${arrow} ${e.text}`; }) .join("\n"); } let lastKnownButtons = null; async function refreshStatus() { try { const s = await api("/api/status"); $("#status").textContent = s.connected ? "online" : "offline"; $("#status").className = "status " + (s.connected ? "online" : "offline"); if (!lastKnownButtons) { $("#brokerUrl").value = s.brokerUrl; $("#hbEnabled").checked = s.heartbeatEnabled; $("#hbInterval").value = s.heartbeatIntervalMs; for (const key of ["start1", "stop1", "start2", "stop2"]) { const card = $(`.button-card[data-button="${key}"]`); card.querySelector(".mac").value = s.buttons[key].mac; card.querySelector(".mv").value = s.buttons[key].voltage; card.querySelector(".mv-value").textContent = s.buttons[key].voltage; } lastKnownButtons = s.buttons; } $("#log").textContent = formatLog(s.log); $("#log").scrollTop = $("#log").scrollHeight; } catch (err) { console.error(err); } } $("#btnConnect").addEventListener("click", async () => { await api("/api/connect", { brokerUrl: $("#brokerUrl").value.trim() }); refreshStatus(); }); $("#btnDisconnect").addEventListener("click", async () => { await api("/api/disconnect", {}); refreshStatus(); }); $("#btnHbApply").addEventListener("click", async () => { await api("/api/heartbeat", { enabled: $("#hbEnabled").checked, intervalMs: parseInt($("#hbInterval").value, 10), }); refreshStatus(); }); $$(".button-card").forEach((card) => { const button = card.dataset.button; const macInput = card.querySelector(".mac"); const mvInput = card.querySelector(".mv"); const mvValue = card.querySelector(".mv-value"); const pressBtn = card.querySelector(".press"); macInput.addEventListener("change", async () => { await api("/api/config", { button, mac: macInput.value.trim() }); }); mvInput.addEventListener("input", () => { mvValue.textContent = mvInput.value; }); mvInput.addEventListener("change", async () => { await api("/api/battery", { button, voltage: parseInt(mvInput.value, 10) }); }); pressBtn.addEventListener("click", async () => { await api("/api/press", { button }); refreshStatus(); }); }); refreshStatus(); setInterval(refreshStatus, 1500);