Developer Guide
Widget & Settings UI Development Guide
Widget & Settings UI Development Guide
1. Dashboard Grid
UI Core builds the dashboard as CSS grid. Each module with RUNNING status and ui_profile != HEADLESS gets a cell. Size from manifest.json:
| size | Width | Height | Use case |
|---|---|---|---|
| 1x1 | 1 col | 1 row | Simple indicator |
| 2x1 | 2 col | 1 row | Compact status |
| 2x2 | 2 col | 2 row | Full widget with chart |
| 4x1 | full width | 1 row | Horizontal panel |
2. Key Rule: Widget Lives in iframe
- No
100vh— use width/height: 100% - No scroll —
scrolling="no"set by parent - No parent DOM access — sandbox blocks it
- No alert/confirm/prompt — blocked
- No localStorage reliance — data from API only
3. Required CSS Template
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; background: transparent; }
.root { width: 100%; height: 100%; display: flex; flex-direction: column; padding: 14px 16px; gap: 10px; overflow: hidden; }
Forbidden:
body { min-height: 100vh; } /* breaks layout */
body { overflow: auto; } /* causes scroll */
.widget { position: fixed; } /* escapes cell */
4. BASE URL
const BASE = window.location.pathname
.replace(/\/(widget|settings)(\.html)?(\?.*)?$/, '');
// Use: fetch(BASE + '/status')
5. Adaptive Layouts
function checkLayout() {
const root = document.getElementById('root');
root.classList.toggle('compact', root.offsetHeight < 160);
root.classList.toggle('wide', root.offsetWidth > 600);
}
window.addEventListener('resize', checkLayout);
6. Data from Module
async function load() {
const data = await fetch(BASE + '/status').then(r => r.json());
render(data);
}
load();
setInterval(load, 30_000);
7. Core API from Widget
const uiToken = new URLSearchParams(window.location.search).get('ui_token');
const devices = await fetch('http://localhost:7070/api/v1/devices', {
headers: { 'Authorization': `Bearer ${uiToken}` }
}).then(r => r.json());
UI token: read-only (device.read, events.subscribe), TTL 1 hour.
8. Realtime: SSE
const es = new EventSource(BASE + '/events/stream');
es.addEventListener('state_changed', (e) => updateUI(JSON.parse(e.data)));
9. settings.html Rules
Displayed in a scrollable modal. overflow-y: auto allowed. Save via module API, not localStorage.
Checklist
- html, body: width/height 100%, overflow hidden
- No 100vh, no position: fixed
- BASE computed from pathname
- Error handling on all fetch calls
- Auto-refresh with setInterval or SSE
- Compact mode for height < 160px