Fine tune the admin UI.

This commit is contained in:
2026-06-28 12:34:55 -05:00
parent 54ae1e66da
commit 941ce78bfd
30 changed files with 1166 additions and 261 deletions

View File

@@ -0,0 +1,26 @@
class AddCard extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<style>
:host{display:block}section{height:100%;box-sizing:border-box}.row{display:flex;gap:8px;align-items:end}.row>*{flex:1}
</style>
<section>
<h3>Add API</h3>
<div class="row">
<div><label>A</label><input id="a" type="number" value="1"></div>
<div><label>B</label><input id="b" type="number" value="2"></div>
</div>
<button id="add">Add</button>
<pre id="out"></pre>
</section>`;
this.querySelector('#add').addEventListener('click', () => this.addNums());
}
async addNums() {
const a = +this.querySelector('#a').value;
const b = +this.querySelector('#b').value;
this.querySelector('#out').textContent = await window.App.req('POST', '/api/add', {a, b});
}
}
customElements.define('add-card', AddCard);

View File

@@ -0,0 +1,32 @@
class LedCard extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<style>
:host{display:block}section{height:100%;box-sizing:border-box}.rangeRow{display:flex;gap:12px;align-items:center}.rangeRow input{flex:1}.rangeRow output{min-width:3ch;text-align:right;font-weight:650}
</style>
<section>
<h3>LED</h3>
<label>Brightness</label>
<div class="rangeRow"><input id="brightness" type="range" min="0" max="100" value="0"><output id="value">0</output></div>
<button id="apply">Apply</button>
<pre id="out"></pre>
</section>`;
this.brightness = this.querySelector('#brightness');
this.value = this.querySelector('#value');
this.brightness.addEventListener('input', () => this.value.value = this.brightness.value);
this.querySelector('#apply').addEventListener('click', () => this.setLed());
window.addEventListener('app:settings-loaded', event => this.applySettings(event.detail.settings));
}
applySettings(settings) {
if (!settings || settings.ledBrightness === undefined) return;
this.brightness.value = settings.ledBrightness;
this.value.value = settings.ledBrightness;
}
async setLed() {
this.querySelector('#out').textContent = await window.App.req('POST', '/api/led', {brightness: +this.brightness.value});
}
}
customElements.define('led-card', LedCard);

View File

@@ -0,0 +1,26 @@
class StatusCard extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<style>
:host{display:block}section{height:100%;box-sizing:border-box}button+button{margin-left:6px}
</style>
<section>
<h3>Status</h3>
<button id="ping">Ping</button>
<button id="me" class="secondary">Me</button>
<pre id="out"></pre>
</section>`;
this.querySelector('#ping').addEventListener('click', () => this.call('GET', '/api/ping'));
this.querySelector('#me').addEventListener('click', () => this.loadMe());
}
async call(method, url) {
this.querySelector('#out').textContent = await window.App.req(method, url);
}
async loadMe() {
await this.call('GET', '/api/me');
}
}
customElements.define('status-card', StatusCard);

View File

@@ -0,0 +1,59 @@
class UptimeCard extends HTMLElement {
constructor() {
super();
this.source = null;
}
connectedCallback() {
this.innerHTML = `
<style>
:host{display:block}section{height:100%;box-sizing:border-box}.liveValue{font-size:34px;font-weight:700;line-height:1.15;margin-top:10px}.state{display:inline-flex;align-items:center;gap:6px;margin-top:6px}.dot{width:9px;height:9px;border-radius:50%;background:#b3261e}.dot.on{background:#188038}
</style>
<section>
<h3>Live uptime</h3>
<div class="muted">Server-Sent Events</div>
<div class="liveValue"><span id="seconds">--</span>s</div>
<div class="muted state"><span id="dot" class="dot"></span><span id="state">Disconnected</span></div>
</section>`;
window.addEventListener('app:login', event => this.start(event.detail.token));
window.addEventListener('beforeunload', () => this.stop());
if (window.App && window.App.token()) this.start(window.App.token());
}
disconnectedCallback() {
this.stop();
}
setState(text, connected) {
this.querySelector('#state').textContent = text;
this.querySelector('#dot').classList.toggle('on', !!connected);
}
start(token) {
this.stop();
if (!token || !window.EventSource) {
this.setState('Unavailable', false);
return;
}
this.setState('Connecting', false);
this.source = new EventSource('/api/uptime/events?token=' + encodeURIComponent(token));
this.source.addEventListener('open', () => this.setState('Connected', true));
this.source.addEventListener('uptime', event => {
try {
const json = JSON.parse(event.data);
this.querySelector('#seconds').textContent = json.uptimeSeconds;
this.setState('Connected', true);
} catch (error) {
this.setState('Invalid event', false);
}
});
this.source.addEventListener('error', () => this.setState('Reconnecting', false));
}
stop() {
if (this.source) this.source.close();
this.source = null;
}
}
customElements.define('uptime-card', UptimeCard);