mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-01 05:52:32 +07:00
Dash updates
This commit is contained in:
@@ -162,6 +162,17 @@ static struct mg_field_set set_graph2 = {
|
||||
"graph2", fields_graph2, read_graph2, NULL, 0, 0, NULL,
|
||||
};
|
||||
|
||||
static int authenticate(const char *user, const char *pass) {
|
||||
int level = 0; // Authentication failure
|
||||
if (strcmp(pass, "admin") == 0) {
|
||||
level = 7; // Administrator
|
||||
} else if (strcmp(pass, "user") == 0) {
|
||||
level = 3; // Ordinary dude
|
||||
}
|
||||
(void) user;
|
||||
return level;
|
||||
}
|
||||
|
||||
void mg_dash_init(struct mg_mgr *mgr) {
|
||||
static struct mg_dash dash; // Important: keep it static!
|
||||
|
||||
@@ -176,6 +187,9 @@ void mg_dash_init(struct mg_mgr *mgr) {
|
||||
mg_dash_file_add(mg_str("device-config.json"), 1234);
|
||||
mg_dash_file_add(mg_str("device-log-2026-04-25.txt"), 1327854);
|
||||
|
||||
// Require authentication
|
||||
dash.authenticate = authenticate;
|
||||
|
||||
mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &dash);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
<title>My Dashboard</title>
|
||||
|
||||
<!-- Mongoose device dashboard -->
|
||||
<!--
|
||||
<script src="https://cdn.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js"></script>
|
||||
-->
|
||||
<script src="dashboard.js"></script>
|
||||
<script>
|
||||
Dashboard.init({
|
||||
debug: true,
|
||||
auth: true,
|
||||
data: {
|
||||
graph1: {data: []},
|
||||
graph2: {data: [], report: true},
|
||||
@@ -22,6 +26,8 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Tab switching
|
||||
const tabs = Array.from(document.querySelectorAll(".header-left .nav-link[href^='#']"));
|
||||
const panels = Array.from(document.querySelectorAll(".tab-panel"));
|
||||
|
||||
@@ -61,6 +67,54 @@
|
||||
const initialId = window.location.hash.slice(1) || "overview";
|
||||
activateTab(initialId);
|
||||
|
||||
// Auto logout on inactivity
|
||||
function restartAutoLogout() {
|
||||
const autologoutTimeoutSeconds = 300;
|
||||
if (window.autoLogoutTimerId) clearTimeout(window.autoLogoutTimerId);
|
||||
window.autoLogoutTimerId = setTimeout(() => {
|
||||
Dashboard.logout().then(() => location.reload());
|
||||
}, autologoutTimeoutSeconds * 1000);
|
||||
};
|
||||
function setupAutoLogout() {
|
||||
['mousemove', 'keydown', 'scroll', 'touchstart'].forEach(ev => {
|
||||
document.addEventListener(ev, restartAutoLogout, true);
|
||||
});
|
||||
restartAutoLogout();
|
||||
};
|
||||
|
||||
// Login page
|
||||
const passwordEl = document.getElementById('login-password');
|
||||
const loginButtonEl = document.getElementById('login-submit');
|
||||
function showLogin(yes) {
|
||||
document.getElementById('auth').style.display = yes ? '' : 'none';
|
||||
document.getElementById('app').style.display = yes ? 'none' : '';
|
||||
if (yes) {
|
||||
document.body.style.visibility = 'visible';
|
||||
passwordEl.focus();
|
||||
passwordEl.onkeydown = function(ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
loginButtonEl.click();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
loginButtonEl.onclick = function(ev) {
|
||||
ev.preventDefault();
|
||||
const username = (document.getElementById('login-username')?.value || '').trim();
|
||||
const password = passwordEl.value || '';
|
||||
passwordEl.value = '';
|
||||
Dashboard.login({username, password}).then(r => showLogin(false));
|
||||
};
|
||||
|
||||
Dashboard.on('logout', function() { showLogin(true); });
|
||||
Dashboard.on('ready', function() {
|
||||
setupAutoLogout();
|
||||
document.body.style.visibility = 'visible';
|
||||
});
|
||||
Dashboard.login().catch(err => showLogin(true));
|
||||
|
||||
// GRAPHS
|
||||
// {stroke: '#14B8A6FF', fill: '#14B8A61A'}, // teal
|
||||
// {stroke: '#F59E0BFF', fill: '#F59E0B1A'}, // amber
|
||||
@@ -216,7 +270,8 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
.toggle:checked::after { transform: translateX(calc(100% - 3px)); }
|
||||
.toggle:disabled, .toggle.disabled { background: #d1d5db; cursor: not-allowed; }
|
||||
.toggle.edited { border-color: #0fe; }
|
||||
.u-over { top: 0 !important; }
|
||||
.u-under { top: 0 !important; }
|
||||
.hidden { display: none; }
|
||||
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } }
|
||||
@media (max-width: 900px) { .metrics-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 480px) { .metrics-grid { grid-template-columns: 1fr; } .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } .file-row { gap: 0.65rem; } .file-size { text-align: left; } }
|
||||
@@ -229,129 +284,155 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-container">
|
||||
<div class="header-left">
|
||||
<nav class="nav-tabs" role="tablist" aria-label="Primary tabs">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600" width="1200" height="600">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#2dd4bf"/>
|
||||
<stop offset="100%" stop-color="#0f766e"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" stroke="url(#g)" stroke-width="30" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="160" y="60" width="300" height="300" rx="70"/>
|
||||
<rect x="20" y="200" width="300" height="300" rx="70"/>
|
||||
</g>
|
||||
<text x="560" y="360"
|
||||
font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
|
||||
font-size="180"
|
||||
font-weight="600"
|
||||
fill="#6b7280">
|
||||
MyLogo
|
||||
</text>
|
||||
</svg>
|
||||
<a href="#overview" id="tab-overview" class="nav-link nav-link-active" role="tab" aria-controls="overview" aria-selected="true">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 11.5 12 4l9 7.5" />
|
||||
<path d="M5 10.5V20h14v-9.5" />
|
||||
</svg>
|
||||
Overview
|
||||
</a>
|
||||
<a href="#settings" id="tab-settings" class="nav-link" role="tab" aria-controls="settings" aria-selected="false" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 .6 1.7 1.7 0 0 0-.4 1.1V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-.4-1.1 1.7 1.7 0 0 0-1-.6 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-.6-1 1.7 1.7 0 0 0-1.1-.4H2.8a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.1-.4 1.7 1.7 0 0 0 .6-1 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6c.38-.16.72-.4 1-.7.26-.3.4-.69.4-1.1V2.8a2 2 0 1 1 4 0v.1c0 .41.14.8.4 1.1.28.3.62.54 1 .7a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.16.38.4.72.7 1 .3.26.69.4 1.1.4h.1a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.1.4 1.7 1.7 0 0 0-.7 1z" />
|
||||
</svg>
|
||||
Settings
|
||||
</a>
|
||||
<a href="#firmware" id="tab-firmware" class="nav-link" role="tab" aria-controls="firmware" aria-selected="false" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 3v10" />
|
||||
<path d="m8 9 4 4 4-4" />
|
||||
<rect x="4" y="17" width="16" height="4" rx="2" />
|
||||
</svg>
|
||||
Firmware Update
|
||||
</a>
|
||||
<a href="#files" id="tab-files" class="nav-link" role="tab" aria-controls="files" aria-selected="false" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 7a2 2 0 0 1 2-2h4l2 2h6a2 2 0 0 1 2 2v2H4Z" />
|
||||
<path d="M4 11h16v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z" />
|
||||
<path d="M8 15h8" />
|
||||
</svg>
|
||||
File Manager
|
||||
</a>
|
||||
</nav>
|
||||
<div class="connection-badge connection-badge-${__status.online ? 'online' : 'offline'}" aria-label="connection active">
|
||||
<span class="connection-label">${__status.online ? 'live' : 'offline'}</span>
|
||||
<span class="connection-dot connection-dot-${__status.online ? 'online' : 'offline'}" aria-hidden="true"></span>
|
||||
<body style="visibility: hidden;">
|
||||
<section id="auth" style="display:none; min-height: 100vh; background: radial-gradient(900px 520px at 15% 0%, rgba(56, 189, 248, 0.18), rgba(56, 189, 248, 0)), radial-gradient(760px 460px at 85% 10%, rgba(45, 212, 191, 0.14), rgba(45, 212, 191, 0)), linear-gradient(180deg, #f8fafc, #def2f7);">
|
||||
<div class="tab-stage" style="min-height: 100vh; display: flex; align-items: center; justify-content: center;">
|
||||
<div class="panel" style="width: 100%; max-width: 28rem;">
|
||||
<div class="panel-title">Dashboard Sign In</div>
|
||||
<div class="panel-body" style="gap: 0.75rem;">
|
||||
<form id="login-form" autocomplete="on" style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
<div class="field">
|
||||
<label class="field-label" for="login-password">Password</label>
|
||||
<input id="login-password" class="field-input" type="password" name="password" autocomplete="current-password" placeholder="••••••" />
|
||||
</div>
|
||||
<div id="login-error" class="note" aria-live="polite" style="display:none; background: #fee2e2; border-color: #fca5a5; color: #991b1b;"></div>
|
||||
<div style="margin: 0; color: #999;">
|
||||
Use "admin" or "user" to sign in.
|
||||
</div>
|
||||
<div style="display:flex; justify-content:flex-end;">
|
||||
<button class="btn btn-primary" id="login-submit" type="submit">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<nav class="nav-tabs">
|
||||
<div class="nav-link">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
<span>admin</span>
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||
<path d="M16 17l5-5-5-5" />
|
||||
<path d="M21 12H9" />
|
||||
</svg>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="tab-stage">
|
||||
<section id="overview" class="tab-panel tab-panel-active" role="tabpanel" aria-labelledby="tab-overview">
|
||||
<div class="metrics-grid" aria-label="Device metrics overview">
|
||||
<article class="metric-tile">
|
||||
<div class="metric-label">Temperature</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number" data-bind="metrics.temperature">24.8</span>
|
||||
<span class="metric-unit">°C</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-good">ok</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile metric-tile-warning">
|
||||
<div class="metric-label">CPU Load</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number" data-bind="metrics.cpu">37</span>
|
||||
<span class="metric-unit">%</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-warn">moderate</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile metric-tile-alert">
|
||||
<div class="metric-label">Free Memory</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number">${metrics.ram}</span>
|
||||
<span class="metric-unit">KB</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-alert" style="${metrics.ram <= 30 ? '': 'display:none;'}">low</span>
|
||||
<span class="metric-status metric-status-warn" style="${metrics.ram <= 50 && metrics.ram > 30 ? '': 'display:none;'}">ok-ish</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile">
|
||||
<div class="metric-label">Volume</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number" data-bind="settings.volume">18.7</span>
|
||||
<span class="metric-unit">dB</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-good">ok</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<div id="app">
|
||||
<div class="header">
|
||||
<div class="header-container">
|
||||
<div class="header-left">
|
||||
<nav class="nav-tabs" role="tablist" aria-label="Primary tabs">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600" width="1200" height="600">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#2dd4bf"/>
|
||||
<stop offset="100%" stop-color="#0f766e"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" stroke="url(#g)" stroke-width="30" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="160" y="60" width="300" height="300" rx="70"/>
|
||||
<rect x="20" y="200" width="300" height="300" rx="70"/>
|
||||
</g>
|
||||
<text x="560" y="360"
|
||||
font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
|
||||
font-size="180"
|
||||
font-weight="600"
|
||||
fill="#6b7280">
|
||||
MyLogo
|
||||
</text>
|
||||
</svg>
|
||||
<a href="#overview" id="tab-overview" class="nav-link nav-link-active" role="tab" aria-controls="overview" aria-selected="true">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 11.5 12 4l9 7.5" />
|
||||
<path d="M5 10.5V20h14v-9.5" />
|
||||
</svg>
|
||||
Overview
|
||||
</a>
|
||||
<a href="#settings" id="tab-settings" class="nav-link" role="tab" aria-controls="settings" aria-selected="false" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 .6 1.7 1.7 0 0 0-.4 1.1V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-.4-1.1 1.7 1.7 0 0 0-1-.6 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-.6-1 1.7 1.7 0 0 0-1.1-.4H2.8a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.1-.4 1.7 1.7 0 0 0 .6-1 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6c.38-.16.72-.4 1-.7.26-.3.4-.69.4-1.1V2.8a2 2 0 1 1 4 0v.1c0 .41.14.8.4 1.1.28.3.62.54 1 .7a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.16.38.4.72.7 1 .3.26.69.4 1.1.4h.1a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.1.4 1.7 1.7 0 0 0-.7 1z" />
|
||||
</svg>
|
||||
Settings
|
||||
</a>
|
||||
<a href="#firmware" id="tab-firmware" class="nav-link" role="tab" aria-controls="firmware" aria-selected="false" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 3v10" />
|
||||
<path d="m8 9 4 4 4-4" />
|
||||
<rect x="4" y="17" width="16" height="4" rx="2" />
|
||||
</svg>
|
||||
Firmware Update
|
||||
</a>
|
||||
<a href="#files" id="tab-files" class="nav-link" role="tab" aria-controls="files" aria-selected="false" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 7a2 2 0 0 1 2-2h4l2 2h6a2 2 0 0 1 2 2v2H4Z" />
|
||||
<path d="M4 11h16v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z" />
|
||||
<path d="M8 15h8" />
|
||||
</svg>
|
||||
File Manager
|
||||
</a>
|
||||
</nav>
|
||||
<div class="connection-badge connection-badge-${__status.online ? 'online' : 'offline'}" aria-label="connection active">
|
||||
<span class="connection-label">${__status.online ? 'live' : 'offline'}</span>
|
||||
<span class="connection-dot connection-dot-${__status.online ? 'online' : 'offline'}" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<nav class="nav-tabs">
|
||||
<div class="nav-link">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
<span>${__status.username || 'admin'}</span>
|
||||
<svg viewBox="0 0 24 24"
|
||||
onclick="Dashboard.logout()"
|
||||
style="cursor: pointer;"
|
||||
class="nav-icon" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||
<path d="M16 17l5-5-5-5" />
|
||||
<path d="M21 12H9" />
|
||||
</svg>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="tab-stage">
|
||||
<section id="overview" class="tab-panel tab-panel-active" role="tabpanel" aria-labelledby="tab-overview">
|
||||
<div class="metrics-grid" aria-label="Device metrics overview">
|
||||
<article class="metric-tile">
|
||||
<div class="metric-label">Temperature</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number" data-bind="metrics.temperature">24.8</span>
|
||||
<span class="metric-unit">°C</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-good">ok</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile metric-tile-warning">
|
||||
<div class="metric-label">CPU Load</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number" data-bind="metrics.cpu">37</span>
|
||||
<span class="metric-unit">%</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-warn">moderate</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile metric-tile-alert">
|
||||
<div class="metric-label">Free Memory</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number">${metrics.ram}</span>
|
||||
<span class="metric-unit">KB</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-alert" style="${metrics.ram <= 30 ? '': 'display:none;'}">low</span>
|
||||
<span class="metric-status metric-status-warn" style="${metrics.ram <= 50 && metrics.ram > 30 ? '': 'display:none;'}">ok-ish</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile">
|
||||
<div class="metric-label">Volume</div>
|
||||
<div class="metric-value-row">
|
||||
<div class="metric-value">
|
||||
<span class="metric-number" data-bind="settings.volume">18.7</span>
|
||||
<span class="metric-unit">dB</span>
|
||||
</div>
|
||||
<span class="metric-status metric-status-good">ok</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr;">
|
||||
|
||||
@@ -596,5 +677,6 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
debug: true,
|
||||
data: { status: { led1: false, version: '1.0.0' } },
|
||||
});
|
||||
|
||||
// Show dashboard only when all device data is reported
|
||||
Dashboard.on('ready', () => document.body.style.visibility = 'visible');
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -27,8 +30,8 @@
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main style="margin: 2rem; color: #64748b;">
|
||||
<body style="visibility: hidden;">
|
||||
<main id="main" style="margin: 2rem; color: #64748b;">
|
||||
<h1>LED control version ${status.version}</h1>
|
||||
|
||||
<div style="display: flex; gap: 1rem; ">
|
||||
|
||||
Reference in New Issue
Block a user