2026-08-13 16:23:18 +04:00

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>