177 lines
9.5 KiB
HTML
177 lines
9.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="color-scheme" content="dark light">
|
|
<title>AGIBOT X2 · Control Dashboard</title>
|
|
<link rel="stylesheet" href="/css/app.css">
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='8' fill='%233987e5'/><path d='M10 12h12v9a3 3 0 0 1-3 3h-6a3 3 0 0 1-3-3z' fill='%23fff'/><circle cx='13.5' cy='16.5' r='1.5' fill='%233987e5'/><circle cx='18.5' cy='16.5' r='1.5' fill='%233987e5'/><path d='M16 6v5' stroke='%23fff' stroke-width='2' stroke-linecap='round'/></svg>">
|
|
</head>
|
|
<body>
|
|
|
|
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>
|
|
<symbol id="i-overview" viewBox="0 0 24 24"><path d="M4 13h6V4H4v9Zm0 7h6v-5H4v5Zm10 0h6v-9h-6v9Zm0-16v5h6V4h-6Z"/></symbol>
|
|
<symbol id="i-control" viewBox="0 0 24 24"><path d="M12 2 9 6h6l-3-4Zm0 20 3-4H9l3 4ZM2 12l4-3v6l-4-3Zm20 0-4 3V9l4 3Zm-10-4a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z"/></symbol>
|
|
<symbol id="i-motion" viewBox="0 0 24 24"><path d="M13 2a2 2 0 1 1 0 4 2 2 0 0 1 0-4ZM8.4 7.6 5 9.2a1 1 0 0 0 .8 1.8l3.6-1.6 1.5 2.8-3.2 3.1a1 1 0 0 0-.3.9l.6 4.3a1 1 0 0 0 2-.3l-.5-3.7 3.2-3 2.1 3.9a1 1 0 0 0 1.8-1l-3.1-5.7 1.1-2.9 2.2 1.5a1 1 0 0 0 1.1-1.6l-3.3-2.3a2 2 0 0 0-2.4.1L8.4 7.6Z"/></symbol>
|
|
<symbol id="i-sensors" viewBox="0 0 24 24"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0 2a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm-5.7-4.3a1 1 0 0 1 0 1.4 6 6 0 0 0 0 8.5 1 1 0 1 1-1.4 1.4 8 8 0 0 1 0-11.3 1 1 0 0 1 1.4 0Zm12.8 0a8 8 0 0 1 0 11.3 1 1 0 1 1-1.4-1.4 6 6 0 0 0 0-8.5 1 1 0 0 1 1.4-1.4Z"/></symbol>
|
|
<symbol id="i-vision" viewBox="0 0 24 24"><path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 3a4 4 0 1 1 0 8 4 4 0 0 1 0-8Zm0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z"/></symbol>
|
|
<symbol id="i-interaction" viewBox="0 0 24 24"><path d="M12 3a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V6a3 3 0 0 1 3-3Zm-6 8a1 1 0 0 1 1 1 5 5 0 0 0 10 0 1 1 0 1 1 2 0 7 7 0 0 1-6 6.9V21a1 1 0 1 1-2 0v-2.1A7 7 0 0 1 5 12a1 1 0 0 1 1-1Z"/></symbol>
|
|
<symbol id="i-power" viewBox="0 0 24 24"><path d="M4 8a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v1h1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8Zm2 0v8h11V8H6Zm1.5 1.5h5v5h-5v-5Z"/></symbol>
|
|
<symbol id="i-nav" viewBox="0 0 24 24"><path d="M12 2 3 21l9-4 9 4L12 2Zm0 5.6 4.6 9.7L12 15.2l-4.6 2.1L12 7.6Z"/></symbol>
|
|
<symbol id="i-extensions" viewBox="0 0 24 24"><path d="M10 3a2 2 0 0 1 2 2v1h3a1 1 0 0 1 1 1v3h1a2 2 0 1 1 0 4h-1v3a1 1 0 0 1-1 1h-3v-1a2 2 0 1 0-4 0v1H5a1 1 0 0 1-1-1v-3H3a2 2 0 1 1 0-4h1V7a1 1 0 0 1 1-1h3V5a2 2 0 0 1 2-2Z"/></symbol>
|
|
<symbol id="i-console" viewBox="0 0 24 24"><path d="M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm2 0v14h14V5H5Zm2.7 3.3 3 3a1 1 0 0 1 0 1.4l-3 3a1 1 0 1 1-1.4-1.4L8.6 12 6.3 9.7a1 1 0 0 1 1.4-1.4ZM12 14h5v2h-5v-2Z"/></symbol>
|
|
<symbol id="i-settings" viewBox="0 0 24 24"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0 2a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm-1.4-8h2.8l.4 2.4a8 8 0 0 1 1.7 1l2.3-.9 1.4 2.4-1.9 1.5a8 8 0 0 1 0 2l1.9 1.5-1.4 2.4-2.3-.9a8 8 0 0 1-1.7 1l-.4 2.4h-2.8l-.4-2.4a8 8 0 0 1-1.7-1l-2.3.9-1.4-2.4 1.9-1.5a8 8 0 0 1 0-2L3.2 6.9l1.4-2.4 2.3.9a8 8 0 0 1 1.7-1L10.6 2Z"/></symbol>
|
|
<symbol id="i-stop" viewBox="0 0 24 24"><path d="M8 3h8l5 5v8l-5 5H8l-5-5V8l5-5Zm1 6v6h6V9H9Z"/></symbol>
|
|
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M12 5V2L8 6l4 4V7a5 5 0 1 1-5 5H5a7 7 0 1 0 7-7Z"/></symbol>
|
|
<symbol id="i-scan" viewBox="0 0 24 24"><path d="M3 7V4a1 1 0 0 1 1-1h3v2H5v2H3Zm14-4h3a1 1 0 0 1 1 1v3h-2V5h-2V3ZM3 17h2v2h2v2H4a1 1 0 0 1-1-1v-3Zm18 0v3a1 1 0 0 1-1 1h-3v-2h2v-2h2ZM3 11h18v2H3v-2Z"/></symbol>
|
|
</defs></svg>
|
|
|
|
<div class="app" id="app">
|
|
|
|
<header class="topbar">
|
|
<div class="brand">
|
|
<div class="brand-mark" aria-hidden="true">
|
|
<span class="brand-dot"></span>
|
|
</div>
|
|
<div class="brand-text">
|
|
<strong id="brand-name">AGIBOT X2</strong>
|
|
<span id="brand-sub">Control dashboard</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="topbar-status" id="topbar-status">
|
|
<div class="pill" id="pill-connection" title="Bridge transport">
|
|
<span class="dot" data-state="offline"></span>
|
|
<span class="pill-label">Connecting</span>
|
|
</div>
|
|
<div class="pill" id="pill-mode" title="Current motion mode">
|
|
<span class="pill-key">Mode</span>
|
|
<span class="pill-label">—</span>
|
|
</div>
|
|
<div class="pill" id="pill-battery" title="Battery level">
|
|
<span class="pill-key">Batt</span>
|
|
<span class="pill-label">—</span>
|
|
</div>
|
|
<div class="pill" id="pill-host" title="Robot host">
|
|
<span class="pill-key">Host</span>
|
|
<span class="pill-label">—</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="topbar-actions">
|
|
<button class="icon-btn" id="btn-theme" title="Toggle light / dark" aria-label="Toggle theme">
|
|
<svg class="ico" aria-hidden="true"><use href="#i-vision"></use></svg>
|
|
</button>
|
|
<button class="btn-stop" id="btn-stop" title="Zero velocity immediately (Space)">
|
|
<svg class="ico" aria-hidden="true"><use href="#i-stop"></use></svg>
|
|
<span>STOP</span>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="rail" id="rail" aria-label="Sections"></nav>
|
|
|
|
<main class="content" id="content" tabindex="-1"></main>
|
|
|
|
</div>
|
|
|
|
<!-- Shown whenever the robot agent is unreachable. Covers the whole app so no
|
|
control is clickable while the robot is off. -->
|
|
<div class="gate" id="gate" hidden>
|
|
<div class="gate-card">
|
|
<div class="gate-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 64 64" width="72" height="72">
|
|
<circle cx="32" cy="32" r="27" fill="none" stroke="currentColor" stroke-width="3" opacity=".22"/>
|
|
<path d="M32 14v20" stroke="currentColor" stroke-width="4.5" stroke-linecap="round"/>
|
|
<path d="M45.5 20.5a19 19 0 1 1-27 0" fill="none" stroke="currentColor"
|
|
stroke-width="4.5" stroke-linecap="round"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<h1 id="gate-title">Robot is powered off</h1>
|
|
<p id="gate-message">
|
|
The dashboard cannot reach the X2. Switch the robot on and this page will
|
|
connect by itself — no need to reload.
|
|
</p>
|
|
|
|
<div class="gate-status">
|
|
<span class="gate-spinner" aria-hidden="true"></span>
|
|
<span id="gate-status-text">Waiting for the robot…</span>
|
|
</div>
|
|
|
|
<dl class="gate-facts" id="gate-facts"></dl>
|
|
|
|
<div class="gate-actions">
|
|
<button class="btn btn-primary" id="gate-find">Find my robot</button>
|
|
<button class="btn" id="gate-retry">Retry now</button>
|
|
<button class="btn btn-ghost" id="gate-toggle-address">Set address manually</button>
|
|
</div>
|
|
|
|
<!-- Editing the address lives inside the gate. Sending the operator off to
|
|
the Settings tab meant leaving the very screen that is blocking the
|
|
app, and the gate reasserted itself the moment the next state arrived. -->
|
|
<div class="gate-address" id="gate-address" hidden>
|
|
<label for="gate-host">Robot address</label>
|
|
<div class="gate-address-row">
|
|
<input class="input" id="gate-host" type="text"
|
|
placeholder="e.g. 10.255.254.84" autocomplete="off" spellcheck="false">
|
|
<button class="btn btn-primary" id="gate-save">Connect</button>
|
|
</div>
|
|
<p class="gate-hint">The dashboard remembers this and reconnects automatically from now on.</p>
|
|
</div>
|
|
|
|
<div class="gate-results" id="gate-results" hidden></div>
|
|
|
|
<p class="gate-hint" id="gate-hint"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toasts" id="toasts" role="status" aria-live="polite"></div>
|
|
|
|
<div class="modal-backdrop" id="modal-backdrop" hidden>
|
|
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
|
<h2 id="modal-title">Confirm</h2>
|
|
<p id="modal-body"></p>
|
|
<div class="modal-actions">
|
|
<button class="btn" id="modal-cancel">Cancel</button>
|
|
<button class="btn btn-danger" id="modal-confirm">Confirm</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Stamped by the server on every page load. index.html is served no-store, so
|
|
// this value is always current even when everything else came from cache.
|
|
window.__BUILD__ = "{{BUILD}}";
|
|
</script>
|
|
<script type="module" src="/js/main.js"></script>
|
|
<script>
|
|
// Safety net. If the browser somehow still ran a cached bundle, main.js will
|
|
// not have reported a matching build, and the page would fail in confusing
|
|
// ways (a tab throwing "Cannot convert undefined or null to object" because
|
|
// it is reading a shape the API no longer sends). Say so plainly instead.
|
|
setTimeout(function () {
|
|
if (window.__MAIN_BUILD__ === window.__BUILD__) return;
|
|
var bar = document.createElement('div');
|
|
bar.style.cssText =
|
|
'position:fixed;left:0;right:0;bottom:0;z-index:400;padding:14px 18px;' +
|
|
'background:#fab219;color:#1a1a19;font:500 13px system-ui,sans-serif;' +
|
|
'display:flex;gap:14px;align-items:center;justify-content:center';
|
|
bar.innerHTML =
|
|
'<span>This page is running an outdated copy of the dashboard.</span>';
|
|
var btn = document.createElement('button');
|
|
btn.textContent = 'Load the current version';
|
|
btn.style.cssText =
|
|
'padding:7px 14px;border:0;border-radius:7px;background:#1a1a19;color:#fff;' +
|
|
'font:600 13px system-ui,sans-serif;cursor:pointer';
|
|
btn.onclick = function () {
|
|
location.replace(location.pathname + '?r=' + Date.now());
|
|
};
|
|
bar.appendChild(btn);
|
|
document.body.appendChild(bar);
|
|
}, 5000);
|
|
</script>
|
|
</body>
|
|
</html>
|