Files
App/www/index.html
HiepLM a6cf06d7eb
Some checks failed
Test / test (push) Has been cancelled
Add phần create map by upload
2026-06-19 11:52:21 +07:00

1656 lines
103 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Robot App</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body class="auth-logged-out">
<div id="loginScreen" class="loginScreen">
<div class="loginFrame">
<header class="loginHeader">
<div class="loginHeaderBrand" id="loginRobotLabel">RobotApp</div>
<div class="loginHeaderRight">
<span class="loginHeaderPrompt">Chọn cách đăng nhập:</span>
<div class="loginTabs" role="tablist">
<button id="loginTabPassword" type="button" class="loginTab active" role="tab" aria-selected="true" data-i18n="login.tab.password">
Tên đăng nhập và mật khẩu
</button>
<button id="loginTabPin" type="button" class="loginTab" role="tab" aria-selected="false" data-i18n="login.tab.pin">
Mã PIN
</button>
</div>
</div>
</header>
<div class="loginCard">
<div id="loginPanelPassword" class="loginPanel">
<div id="loginHelpPassword" class="loginHelp">
<h2 class="loginHelpTitle" data-i18n="login.password.title">Đăng nhập bằng tên và mật khẩu</h2>
<p data-i18n="login.password.help1">Nhập tên đăng nhập và mật khẩu để truy cập robot.</p>
<p data-i18n="login.password.help2">Tài khoản do quản trị viên cấp hoặc xem trong tài liệu hướng dẫn robot.</p>
<p data-i18n="login.password.help3">Nếu chưa có tài khoản, vui lòng liên hệ quản trị viên robot.</p>
</div>
<div class="loginForms">
<form id="loginForm" class="loginForm" action="#" method="post" novalidate>
<label class="loginField">
<span class="loginFieldLabel" data-i18n="login.field.username">Tên đăng nhập:</span>
<input id="loginUsername" name="username" type="text" autocomplete="username" placeholder="Admin" data-i18n-placeholder="login.placeholder.username" required />
</label>
<label class="loginField">
<span class="loginFieldLabel" data-i18n="login.field.password">Mật khẩu:</span>
<input id="loginPasswordInput" name="password" type="password" autocomplete="current-password" placeholder="" required />
</label>
<button type="submit" class="loginSubmit" data-mode="password">
<svg class="loginSubmitIcon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M12.65 10A5.99 5.99 0 0 0 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6a5.99 5.99 0 0 0 5.65-4H17v2h3v-2h1v-3h-3V9h-1.35zM7 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>
</svg>
<span class="loginSubmitLabel" data-i18n="login.submit">Đăng nhập</span>
</button>
</form>
<p id="loginError" class="loginError" hidden></p>
</div>
</div>
<div id="loginPanelPin" class="loginPanel loginPanel--pin" hidden>
<div class="loginPinLeft">
<div class="loginHelp">
<h2 class="loginHelpTitle" data-i18n="login.pin.title">Đăng nhập bằng mã PIN</h2>
<p data-i18n="login.pin.help1">Người dùng được kích hoạt PIN có thể đăng nhập tại đây.</p>
<p data-i18n="login.pin.help2">Nếu chưa có mã PIN 4 chữ số, vui lòng liên hệ quản trị viên robot.</p>
<p class="loginHelpNote" data-i18n="login.pin.helpNote">Không có mã PIN cấu hình sẵn — quản trị viên phải gán PIN trước.</p>
</div>
<div class="loginPinBoxes" id="loginPinBoxes" role="group" aria-label="Mã PIN 4 chữ số" data-i18n-aria="login.pin.aria.group">
<div class="loginPinCell" data-idx="0"></div>
<div class="loginPinCell" data-idx="1"></div>
<div class="loginPinCell" data-idx="2"></div>
<div class="loginPinCell" data-idx="3"></div>
</div>
<input id="loginPin" type="hidden" value="" autocomplete="off" />
<p id="loginPinError" class="loginError loginPinError" hidden></p>
</div>
<div class="loginKeypad" id="loginKeypad" aria-label="Bàn phím số" data-i18n-aria="login.pin.aria.keypad">
<button type="button" class="loginKey" data-key="1">1</button>
<button type="button" class="loginKey" data-key="2">2</button>
<button type="button" class="loginKey" data-key="3">3</button>
<button type="button" class="loginKey" data-key="4">4</button>
<button type="button" class="loginKey" data-key="5">5</button>
<button type="button" class="loginKey" data-key="6">6</button>
<button type="button" class="loginKey" data-key="7">7</button>
<button type="button" class="loginKey" data-key="8">8</button>
<button type="button" class="loginKey" data-key="9">9</button>
<button type="button" class="loginKey loginKey--wide" data-key="0">0</button>
<button type="button" class="loginKey loginKey--back" data-key="back" aria-label="Xóa" data-i18n-aria="login.pin.aria.backspace"></button>
</div>
</div>
</div>
</div>
</div>
<div class="shell auth-locked">
<aside class="mirNavShell" id="mirNavShell">
<nav class="mirNavRail" id="mirNavRail" aria-label="Main navigation" data-i18n-aria="nav.aria.main">
<button type="button" class="mirNavBackBtn" id="mirNavBackBtn" aria-label="Collapse menu" title="Collapse menu" data-i18n-aria="nav.collapse" data-i18n-title="nav.collapse">
<span aria-hidden="true">«</span>
</button>
<div class="mirNavRailItems">
<button type="button" class="mirNavRailItem" data-module="dashboards">
<svg class="mirNavRailIcon" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<path d="M4 4h7v9H4V4zm9 0h7v5h-7V4zM4 15h7v5H4v-5zm9 4h7v-5h-7v5z" fill="currentColor"/>
</svg>
<span class="mirNavRailLabel" data-i18n="nav.dashboards">Dashboards</span>
</button>
<button type="button" class="mirNavRailItem is-active" data-module="setup" aria-current="true">
<svg class="mirNavRailIcon" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="mirNavRailLabel" data-i18n="nav.setup">Setup</span>
</button>
<button type="button" class="mirNavRailItem" data-module="monitoring">
<svg class="mirNavRailIcon" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<path d="M3 3v18h18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
<path d="M7 14l4-4 3 3 5-6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="mirNavRailLabel" data-i18n="nav.monitoring">Monitoring</span>
</button>
<button type="button" class="mirNavRailItem" data-module="system">
<svg class="mirNavRailIcon" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-width="1.8"/>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
<span class="mirNavRailLabel" data-i18n="nav.system">System</span>
</button>
<button type="button" class="mirNavRailItem" data-module="help">
<svg class="mirNavRailIcon" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.8"/>
<path d="M9.5 9a2.5 2.5 0 1 1 4.2 1.8c-.8.6-1.2 1.2-1.2 2.2M12 17h.01" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
<span class="mirNavRailLabel" data-i18n="nav.help">Help</span>
</button>
</div>
<div class="mirNavRailFooter">
<button type="button" class="mirNavRailItem mirNavRailItem--logout" id="mirNavLogout">
<svg class="mirNavRailIcon" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="mirNavRailLabel" data-i18n="nav.logout">Log out</span>
</button>
</div>
</nav>
<aside class="mirNavFlyout" id="mirNavFlyout">
<div class="mirNavFlyoutHeader">
<h2 class="mirNavFlyoutTitle" id="mirNavFlyoutTitle">Setup</h2>
</div>
<nav class="mirNavFlyoutList" id="mirNavFlyoutList" aria-label="Submenu" data-i18n-aria="nav.aria.submenu"></nav>
<div class="mirNavFlyoutFooter">
<span class="mirNavStatusLed" aria-hidden="true"></span>
<span id="status" class="mirNavStatusText"></span>
</div>
</aside>
</aside>
<div class="body">
<header class="mirTopbar" id="mirTopbar">
<div class="mirTopbarInner">
<div class="mirTopbarLeft">
<div class="mirRobotId" id="mirRobotId" title="Robot" data-i18n-title="topbar.robotTitle">RobotApp</div>
<button type="button" class="mirPauseBtn" id="mirSegControl" aria-label="Start / Pause robot" title="Start / Pause robot" data-i18n-aria="topbar.controlAria" data-i18n-title="topbar.controlAria">
<svg class="mirPauseBtnIcon mirPauseBtnIcon--pause" id="mirControlIconPause" viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
<rect x="6" y="5" width="4.5" height="14" rx="1" fill="#f39c12"/>
<rect x="13.5" y="5" width="4.5" height="14" rx="1" fill="#f39c12"/>
</svg>
<svg class="mirPauseBtnIcon mirPauseBtnIcon--play" id="mirControlIconPlay" viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" hidden>
<path d="M9 6.5v11l9-5.5-9-5.5z" fill="#7bed9f"/>
</svg>
</button>
<div class="mirMissionStrip" id="mirMissionStrip">
<span class="mirMissionMsg" id="mirMissionMsg"></span>
<span class="mirStatePill" id="mirControlPill">PAUSED</span>
</div>
</div>
<div class="mirTopbarRight">
<button type="button" class="mirSegment mirSegment--status" id="mirSegStatus" aria-haspopup="true" aria-expanded="false">
<svg class="mirSvgIcon mirStatusSvg is-ok" id="mirStatusIcon" viewBox="0 0 20 20" width="18" height="18" aria-hidden="true">
<circle cx="10" cy="10" r="9" fill="none" stroke="currentColor" stroke-width="1.5"/>
<path d="M6 10.2l2.4 2.4L14 7.2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="mirSegmentLabel" id="mirStatusLabel" data-i18n="topbar.allOk">ALL OK</span>
<span class="mirCaret" aria-hidden="true"></span>
</button>
<div class="mirPanel mirPanel--status" id="mirStatusPanel" hidden>
<div class="mirPanelBody" id="mirStatusPanelBody"></div>
<div class="mirPanelFooter" id="mirStatusPanelFooter" hidden>
<button type="button" class="mirBtn mirBtn--reset" id="mirErrorResetBtn" data-i18n="topbar.reset">RESET</button>
</div>
</div>
<button type="button" class="mirSegment mirSegment--locale" id="mirSegLocale" aria-haspopup="true" aria-expanded="false">
<span class="mirFlag" id="mirLocaleFlag" aria-hidden="true">🇻🇳</span>
<span class="mirSegmentLabel" id="mirLocaleLabel">TIẾNG VIỆT</span>
<span class="mirCaret" aria-hidden="true"></span>
</button>
<div class="mirPanel mirPanel--locale" id="mirLocalePanel" hidden>
<button type="button" class="mirLocaleOption" data-locale="vi" data-i18n="topbar.localeOption.vi">🇻🇳 Tiếng Việt</button>
<button type="button" class="mirLocaleOption" data-locale="en" data-i18n="topbar.localeOption.en">🇺🇸 English</button>
</div>
<button type="button" class="mirSegment mirSegment--user" id="mirUserBtn" aria-haspopup="true" aria-expanded="false">
<svg class="mirSvgIcon mirUserSvg" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
<circle cx="12" cy="8" r="4" fill="currentColor"/>
<path d="M5 20c0-4 3.5-6 7-6s7 2 7 6" fill="currentColor"/>
</svg>
<span class="mirSegmentLabel" id="mirUserLabel">USER</span>
<span class="mirCaret" aria-hidden="true"></span>
</button>
<div class="mirPanel mirPanel--user" id="mirUserPanel" hidden>
<div class="mirUserPanelHeader">
<div class="mirUserPanelAvatar" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22" fill="#64748b"><circle cx="12" cy="8" r="4"/><path d="M5 20c0-4 3.5-6 7-6s7 2 7 6"/></svg>
</div>
<div>
<div class="mirUserPanelRole" id="mirUserPanelRole"></div>
<div class="mirUserPanelName mutedNote" id="mirUserPanelName"></div>
</div>
</div>
<label class="mirProfileField">
<span data-i18n="topbar.displayName">Tên hiển thị</span>
<input id="mirProfileDisplayName" type="text" autocomplete="name" />
</label>
<button type="button" class="mirBtn mirBtn--primary" id="mirProfileSaveBtn" data-i18n="topbar.changeUserData">Đổi thông tin</button>
<button type="button" class="mirBtn mirBtn--primary subtle" id="mirUserChangePasswordBtn" data-i18n="topbar.changePassword">Đổi mật khẩu</button>
<button type="button" class="mirBtn mirBtn--danger" id="mirUserSignOutBtn" data-i18n="topbar.logout">Đăng xuất</button>
</div>
<button type="button" class="mirSegment mirSegment--joystick" id="mirSegJoystick" title="Engage joystick" data-i18n-title="topbar.joystickAria" aria-label="Joystick">
<svg class="mirSvgIcon mirJoystickSvg" viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
<rect x="7" y="10" width="10" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/>
<line x1="12" y1="10" x2="12" y2="4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="12" cy="3" r="2.2" fill="currentColor"/>
</svg>
</button>
<div class="mirSegment mirSegment--battery" id="mirSegBattery" title="Battery" data-i18n-title="topbar.batteryTitle">
<span class="mirBatteryIcon" id="mirBatteryIcon" aria-hidden="true">
<span class="mirBatteryLevel" id="mirBatteryLevel"></span>
</span>
<span class="mirSegmentLabel mirBatteryPct" id="mirBatteryLabel">—%</span>
</div>
</div>
</div>
</header>
<main class="content">
<div class="page" id="pageOverview" data-page-content="dashboard" hidden>
<div class="dashboardShell">
<div class="dashboardContent">
<div id="dashboardListView" class="dashboardListView">
<header class="dashboardListHeader">
<div class="dashboardListHeaderIntro">
<h2 class="dashboardListTitle" data-i18n="dashboard.list.title">Dashboards</h2>
<p class="dashboardListSub">
<span data-i18n="dashboard.list.subtitle">Tạo và chỉnh sửa dashboard cho robot.</span>
<span class="dashboardListHelp" title="?" aria-hidden="true">?</span>
</p>
</div>
<div class="dashboardListHeaderActions">
<button id="dashboardCreateBtn" type="button" class="btn primary dashboardCreateBtn" data-i18n="dashboard.list.create">+ Tạo dashboard</button>
<button id="dashboardClearFiltersBtn" type="button" class="btn subtle dashboardClearFiltersBtn" data-i18n="dashboard.list.clearFilters">Xóa bộ lọc</button>
</div>
</header>
<div class="dashboardListBar">
<label class="dashboardFilter">
<span class="dashboardFilterLabel" data-i18n="dashboard.list.filterLabel">Lọc:</span>
<input id="dashboardFilterInput" type="search" class="dashboardFilterInput" data-i18n-placeholder="dashboard.list.filterPlaceholder" placeholder="Nhập tên để lọc…" autocomplete="off" />
</label>
<span id="dashboardListCount" class="dashboardListCount"></span>
<div class="dashboardPagination" id="dashboardPagination">
<button type="button" class="iconBtn dashboardPageBtn" data-page-action="first" title="First">«</button>
<button type="button" class="iconBtn dashboardPageBtn" data-page-action="prev" title="Previous"></button>
<span id="dashboardPageLabel" class="dashboardPageLabel"></span>
<button type="button" class="iconBtn dashboardPageBtn" data-page-action="next" title="Next"></button>
<button type="button" class="iconBtn dashboardPageBtn" data-page-action="last" title="Last">»</button>
</div>
</div>
<div class="dashboardTableWrap" id="dashboardTableWrap">
<table class="dashboardTable">
<thead>
<tr>
<th scope="col" class="dashboardTableStatusCol" aria-hidden="true"></th>
<th scope="col" data-i18n="dashboard.list.col.name">Tên</th>
<th scope="col" data-i18n="dashboard.list.col.createdBy">Tạo bởi</th>
<th scope="col" class="dashboardTableFuncCol" data-i18n="dashboard.list.col.functions">Chức năng</th>
</tr>
</thead>
<tbody id="dashboardTableBody"></tbody>
</table>
</div>
</div>
<div id="dashboardCreateView" class="dashboardCreateView" hidden>
<header class="dashboardCreateHeader">
<div class="dashboardCreateHeaderIntro">
<h2 class="dashboardCreateTitle" data-i18n="dashboard.create.title">Tạo dashboard</h2>
<p class="dashboardCreateSub">
<span data-i18n="dashboard.create.subtitle">Tạo dashboard mới trên robot.</span>
<span class="dashboardListHelp" title="?" aria-hidden="true">?</span>
</p>
</div>
<button id="dashboardCreateBackBtn" type="button" class="btn subtle dashboardCreateBackBtn" data-i18n="dashboard.create.backToList">← Quay lại danh sách</button>
</header>
<form id="dashboardCreateForm" class="dashboardCreateForm">
<div class="dashboardCreatePanel">
<div class="dashboardCreateField">
<label for="dashboardCreateName" data-i18n="dashboard.create.name">Tên</label>
<input id="dashboardCreateName" type="text" required autocomplete="off" data-i18n-placeholder="dashboard.create.namePlaceholder" placeholder="VD: John's Dashboard" />
</div>
<div class="dashboardCreateActions">
<button type="button" id="dashboardCreatePermissionsBtn" class="btn subtle dashboardMirBtn dashboardCreatePermissionsBtn" data-i18n="dashboard.create.permissionsBtn">Quyền</button>
<button type="submit" class="btn dashboardMirBtn dashboardCreateSubmitBtn">
<span class="dashboardCreateSubmitIcon" aria-hidden="true"></span>
<span data-i18n="dashboard.create.submit">Tạo dashboard</span>
</button>
<button type="button" id="dashboardCreateCancelBtn" class="btn subtle dashboardMirBtn dashboardCreateCancelBtn">
<span class="dashboardCreateCancelIcon" aria-hidden="true"></span>
<span data-i18n="dashboard.create.cancel">Hủy</span>
</button>
</div>
</div>
</form>
</div>
<div id="dashboardDesignerView" class="dashboardDesignerView" hidden>
<header class="dashboardMirDesignerBar">
<button id="dashboardBackToListBtn" type="button" class="dashboardMirBarBtn dashboardMirBarBtn--back" data-i18n="dashboard.list.back">← Danh sách</button>
<h2 id="dashboardDesignerTitle" class="dashboardMirDesignerTitle"></h2>
<div class="dashboardMirDesignerBarActions">
<button id="dashboardSaveBtn" type="button" class="dashboardMirSaveBtn" hidden data-i18n="dashboard.designer.save">Lưu</button>
<button id="dashboardEditModeBtn" type="button" class="dashboardMirBarBtn dashboardMirBarBtn--edit" hidden data-i18n="dashboard.editLayout">Sửa layout</button>
</div>
</header>
<nav id="dashboardDesignerToolbar" class="dashboardMirWidgetBar" hidden aria-label="Widget menus">
<div class="dashboardMirWidgetTabs" role="tablist">
<button type="button" class="dashboardMirWidgetTab" role="tab" data-widget-tab="maps" data-i18n="dashboard.menu.maps">Maps</button>
<button type="button" class="dashboardMirWidgetTab is-active" role="tab" data-widget-tab="missions" data-i18n="dashboard.menu.missions">Missions</button>
<button type="button" class="dashboardMirWidgetTab" role="tab" data-widget-tab="plc" data-i18n="dashboard.menu.plc">PLC Registers</button>
<button type="button" class="dashboardMirWidgetTab" role="tab" data-widget-tab="io" data-i18n="dashboard.menu.io">I/O</button>
<button type="button" class="dashboardMirWidgetTab" role="tab" data-widget-tab="misc" data-i18n="dashboard.menu.misc">Miscellaneous</button>
</div>
<div class="dashboardMirWidgetPanels">
<div class="dashboardMirWidgetPanel" data-panel="maps" hidden>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="map_locked" data-i18n="dashboard.widget.map_locked">Locked map</button>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="map" data-i18n="dashboard.widget.map">Map</button>
</div>
<div class="dashboardMirWidgetPanel" data-panel="missions">
<button type="button" class="dashboardMirWidgetPick" data-add-widget="mission_button" data-i18n="dashboard.widget.mission_button">Mission button</button>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="mission_group" data-i18n="dashboard.widget.mission_group">Mission group</button>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="mission_queue" data-i18n="dashboard.widget.mission_queue">Mission queue</button>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="pause_continue" data-i18n="dashboard.widget.pause_continue">Pause / Continue</button>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="mission_action_log" data-i18n="dashboard.widget.mission_action_log">Mission action log</button>
</div>
<div class="dashboardMirWidgetPanel" data-panel="plc" hidden>
<p class="dashboardMirWidgetPanelNote" data-i18n="dashboard.menu.comingSoon">Widget nhóm này sẽ có trong bản cập nhật sau.</p>
</div>
<div class="dashboardMirWidgetPanel" data-panel="io" hidden>
<p class="dashboardMirWidgetPanelNote" data-i18n="dashboard.menu.comingSoon">I/O widgets — sắp có.</p>
</div>
<div class="dashboardMirWidgetPanel" data-panel="misc" hidden>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="robot_summary" data-i18n="dashboard.widget.robot_summary">Robot summary</button>
<button type="button" class="dashboardMirWidgetPick" data-add-widget="logout_button" data-i18n="dashboard.widget.logout_button">Log-out button</button>
</div>
</div>
</nav>
<div class="dashboardMirCanvasWrap">
<div id="dashboardGrid" class="dashboardGrid dashboardMirGrid"></div>
<p id="dashboardDesignerEmpty" class="dashboardMirCanvasEmpty" data-i18n="dashboard.designer.empty">Chưa có widget trên dashboard này.</p>
</div>
</div>
</div>
</div>
</div>
<div class="page" id="pageConfig" data-page-content="config">
<div class="contentLeft">
<section class="card" id="layoutManagerCard">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="config.layout.title">Quản lý layout</div>
<div class="cardSub" data-i18n="config.layout.subtitle">Nhiều cấu hình robot — mỗi layout có LiDAR và model riêng.</div>
</div>
<div class="configPageActions">
<button id="refreshBtn" type="button" class="btn subtle" data-i18n="common.reload">Tải lại</button>
<button id="saveLayoutBtn" type="button" class="btn primary" data-i18n="config.layout.save">Lưu layout</button>
</div>
</div>
<div class="cardBody">
<div class="row rowWide">
<label data-i18n="config.layout.current">Layout hiện tại</label>
<select id="layoutSelect"></select>
</div>
<div class="row rowWide">
<label data-i18n="config.layout.newName">Tên layout mới</label>
<input id="layoutNewName" type="text" placeholder="VD: AGV kho A" data-i18n-placeholder="config.layout.newNamePlaceholder" />
</div>
<div class="checkRow">
<label>
<input id="layoutCloneCurrent" type="checkbox" />
<span data-i18n="config.layout.cloneCurrent">Sao chép từ layout đang mở</span>
</label>
</div>
<div class="layoutManagerActions">
<button id="layoutCreateBtn" type="button" class="btn subtle" data-i18n="config.layout.create">Tạo layout</button>
<button id="layoutDeleteBtn" type="button" class="btn subtle danger" data-i18n="common.delete">Xóa</button>
</div>
<p id="layoutActiveHint" class="mutedNote"></p>
</div>
</section>
<section class="card collapsible" id="lidarListCard">
<div
class="cardHeader cardHeaderToggle"
id="lidarListCardToggle"
role="button"
tabindex="0"
aria-expanded="true"
aria-controls="lidarListCardBody"
>
<div>
<div class="cardTitle" data-i18n="config.lidar.title">LiDARs</div>
<div class="cardSub" data-i18n="config.lidar.subtitle">Đăng ký tên, IP, port và chỉnh pose theo robot frame.</div>
</div>
<span class="cardChevron" aria-hidden="true"></span>
</div>
<div class="cardBody" id="lidarListCardBody">
<form id="lidarForm" class="form">
<div class="row">
<label data-i18n="config.lidar.field.name">Tên</label>
<input id="name" placeholder="Lidar trước" data-i18n-placeholder="config.lidar.placeholder.name" required />
</div>
<div class="row">
<label data-i18n="config.lidar.field.ip">IP</label>
<input id="ip" placeholder="192.168.0.10" data-i18n-placeholder="config.lidar.placeholder.ip" required />
</div>
<div class="row">
<label data-i18n="config.lidar.field.port">Port</label>
<input id="port" type="number" min="1" max="65535" value="2112" required />
</div>
<div class="actions">
<button id="addLidarBtn" class="btn primary" type="button" data-i18n="common.add">Thêm</button>
</div>
<p id="lidarFormHint" class="formHint" hidden></p>
</form>
<div id="lidarList" class="list"></div>
</div>
</section>
<section class="card collapsible" id="imuListCard">
<div
class="cardHeader cardHeaderToggle"
id="imuListCardToggle"
role="button"
tabindex="0"
aria-expanded="true"
aria-controls="imuListCardBody"
>
<div>
<div class="cardTitle" data-i18n="config.imu.title">IMU</div>
<div class="cardSub" data-i18n="config.imu.subtitle">Cảm biến quán tính — frame, topic và pose trên robot.</div>
</div>
<span class="cardChevron" aria-hidden="true"></span>
</div>
<div class="cardBody" id="imuListCardBody">
<form id="imuForm" class="form">
<div class="row">
<label data-i18n="config.imu.field.name">Tên</label>
<input id="imuName" placeholder="IMU chính" data-i18n-placeholder="config.imu.placeholder.name" required />
</div>
<div class="row">
<label data-i18n="config.imu.field.frame">Frame ID</label>
<input id="imuFrameId" placeholder="imu_link" data-i18n-placeholder="config.imu.placeholder.frame" required />
</div>
<div class="row">
<label data-i18n="config.imu.field.topic">Topic</label>
<input id="imuTopic" placeholder="imu/data" data-i18n-placeholder="config.imu.placeholder.topic" value="imu/data" required />
</div>
<div class="row rowWide">
<label data-i18n="config.imu.field.source">Nguồn</label>
<select id="imuSource">
<option value="external" data-i18n="config.imu.source.external">Ngoài (ROS topic)</option>
<option value="lidar_builtin" data-i18n="config.imu.source.lidarBuiltin">Tích hợp LiDAR</option>
<option value="onboard" data-i18n="config.imu.source.onboard">Onboard robot</option>
</select>
</div>
<div class="row">
<label data-i18n="config.imu.field.rate">Tần số (Hz)</label>
<input id="imuRateHz" type="number" min="1" max="1000" step="1" value="100" />
</div>
<div class="checkRow">
<label>
<input id="imuEnabled" type="checkbox" checked />
<span data-i18n="config.imu.enabled">Bật IMU</span>
</label>
</div>
<div class="actions">
<button id="addImuBtn" class="btn primary" type="button" data-i18n="config.imu.add">Thêm IMU</button>
</div>
<p id="imuFormHint" class="formHint" hidden></p>
</form>
<div id="imuList" class="list"></div>
</div>
</section>
<section class="card collapsible" id="robotModelCard">
<div
class="cardHeader cardHeaderToggle"
id="robotModelCardToggle"
role="button"
tabindex="0"
aria-expanded="true"
aria-controls="robotModelCardBody"
>
<div>
<div class="cardTitle" data-i18n="config.robot.title">Model robot</div>
<div class="cardSub">Kinematic differential — bánh, động cơ và giới hạn vận tốc.</div>
</div>
<span class="cardChevron" aria-hidden="true"></span>
</div>
<div class="cardBody" id="robotModelCardBody">
<div class="modelForm">
<div class="row rowWide">
<label>Model</label>
<select id="robotModel">
<option value="diff">Differential (2 bánh)</option>
<option value="bicycle">Bicycle</option>
</select>
</div>
<div id="diffParams" class="modelParams">
<details class="acc" open>
<summary>Hình học bánh</summary>
<div class="accBody">
<div class="row rowWide">
<label>Khoảng cách 2 bánh</label>
<div class="inputUnit">
<input id="wheelSeparationM" type="number" min="0.05" max="5" step="0.01" value="1.0" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Bán kính bánh</label>
<div class="inputUnit">
<input id="wheelRadiusM" type="number" min="0.02" max="1" step="0.01" value="0.3" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Tỷ lệ hiển thị</label>
<div class="inputUnit">
<input id="scaleMPerPx" type="number" min="0.001" max="0.1" step="0.001" value="0.005" />
<span class="unit">m/px</span>
</div>
</div>
<details class="acc accNested">
<summary>Hiệu chỉnh (nâng cao)</summary>
<div class="accBody">
<div class="row rowWide">
<label>b multiplier</label>
<input id="wheelSeparationMult" type="number" min="0.5" max="2" step="0.01" value="1.0" />
</div>
<div class="row rowWide">
<label>r multiplier</label>
<input id="wheelRadiusMult" type="number" min="0.5" max="2" step="0.01" value="1.0" />
</div>
</div>
</details>
</div>
</details>
<details class="acc">
<summary>Động cơ</summary>
<div class="accBody">
<p class="mutedNote">Mỗi bánh gán một động cơ — chọn hãng và model.</p>
<div id="motorWheels" class="motorWheels"></div>
</div>
</details>
<details class="acc">
<summary>Giới hạn vận tốc</summary>
<div class="accBody">
<div class="row rowWide">
<label>cmd_vel timeout</label>
<div class="inputUnit">
<input id="cmdVelTimeout" type="number" min="0.05" max="5" step="0.05" value="0.25" />
<span class="unit">s</span>
</div>
</div>
<div class="row rowWide">
<label>Linear max</label>
<div class="inputUnit">
<input id="linearMaxVel" type="number" min="0.01" max="5" step="0.05" value="1.0" />
<span class="unit">m/s</span>
</div>
</div>
<div class="row rowWide">
<label>Linear min</label>
<div class="inputUnit">
<input id="linearMinVel" type="number" min="-5" max="0" step="0.05" value="-0.5" />
<span class="unit">m/s</span>
</div>
</div>
<div class="row rowWide">
<label>Linear accel max</label>
<div class="inputUnit">
<input id="linearMaxAccel" type="number" min="0.01" max="10" step="0.05" value="0.8" />
<span class="unit">m/s²</span>
</div>
</div>
<div class="row rowWide">
<label>Angular max</label>
<div class="inputUnit">
<input id="angularMaxVel" type="number" min="0.01" max="10" step="0.05" value="1.7" />
<span class="unit">rad/s</span>
</div>
</div>
<div class="row rowWide">
<label>Angular accel max</label>
<div class="inputUnit">
<input id="angularMaxAccel" type="number" min="0.01" max="10" step="0.05" value="1.5" />
<span class="unit">rad/s²</span>
</div>
</div>
</div>
</details>
<p id="diffValidation" class="validation" hidden></p>
</div>
<div id="bicycleParams" class="modelParams" hidden>
<p class="mutedNote">
Kinematic bicycle — tham chiếu trục sau, quan hệ
<span class="mono">ω = v·tan(δ)/L</span>
(<a href="https://thomasfermi.github.io/Algorithms-for-Automated-Driving/Control/BicycleModel.html" target="_blank" rel="noopener">mô hình</a>).
</p>
<details class="acc" open>
<summary>Hình học (wheelbase)</summary>
<div class="accBody">
<div class="row rowWide">
<label>Wheelbase L</label>
<div class="inputUnit">
<input id="bicycleWheelbaseM" type="number" min="0.2" max="5" step="0.05" value="1.2" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Bán kính bánh</label>
<div class="inputUnit">
<input id="bicycleWheelRadiusM" type="number" min="0.02" max="1" step="0.01" value="0.15" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Tỷ lệ hiển thị</label>
<div class="inputUnit">
<input id="bicycleScaleMPerPx" type="number" min="0.001" max="0.1" step="0.001" value="0.005" />
<span class="unit">m/px</span>
</div>
</div>
<div class="row rowWide">
<label>Góc lái xem trước</label>
<div class="inputUnit">
<input id="bicycleSteerPreviewDeg" type="number" min="-45" max="45" step="1" value="15" />
<span class="unit">°</span>
</div>
</div>
</div>
</details>
<details class="acc">
<summary>Động cơ</summary>
<div class="accBody">
<p class="mutedNote">Bánh sau (drive) và bánh trước (steer).</p>
<div id="bicycleMotorWheels" class="motorWheels"></div>
</div>
</details>
<details class="acc">
<summary>Giới hạn</summary>
<div class="accBody">
<div class="row rowWide">
<label>δ max (lái)</label>
<div class="inputUnit">
<input id="bicycleSteerMaxDeg" type="number" min="5" max="60" step="1" value="35" />
<span class="unit">°</span>
</div>
</div>
<div class="row rowWide">
<label>cmd_vel timeout</label>
<div class="inputUnit">
<input id="bicycleCmdVelTimeout" type="number" min="0.05" max="5" step="0.05" value="0.25" />
<span class="unit">s</span>
</div>
</div>
<div class="row rowWide">
<label>Linear max</label>
<div class="inputUnit">
<input id="bicycleLinearMaxVel" type="number" min="0.01" max="5" step="0.05" value="1.0" />
<span class="unit">m/s</span>
</div>
</div>
<div class="row rowWide">
<label>Linear accel max</label>
<div class="inputUnit">
<input id="bicycleLinearMaxAccel" type="number" min="0.01" max="10" step="0.05" value="0.8" />
<span class="unit">m/s²</span>
</div>
</div>
</div>
</details>
<p id="bicycleValidation" class="validation" hidden></p>
</div>
<details class="acc">
<summary>Footprint</summary>
<div class="accBody">
<p class="mutedNote">Hình dạng robot (ROS polygon) — tọa độ theo robot frame.</p>
<div class="row rowWide">
<label>Hình dạng</label>
<select id="footprintShape">
<option value="rectangle">Hình chữ nhật</option>
<option value="circle">Hình tròn</option>
<option value="regular_polygon">Đa giác đều</option>
<option value="custom">Tùy chỉnh (đa giác)</option>
</select>
</div>
<div id="footprintPresetPanel" class="footprintPresetPanel">
<div id="fpRectParams" class="fpShapeParams">
<div class="row rowWide">
<label>Chiều dài</label>
<div class="inputUnit">
<input id="fpLengthM" type="number" min="0.1" max="10" step="0.05" value="1.4" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Chiều rộng</label>
<div class="inputUnit">
<input id="fpWidthM" type="number" min="0.1" max="10" step="0.05" value="1.1" />
<span class="unit">m</span>
</div>
</div>
</div>
<div id="fpCircleParams" class="fpShapeParams" hidden>
<div class="row rowWide">
<label>Bán kính</label>
<div class="inputUnit">
<input id="fpRadiusM" type="number" min="0.1" max="5" step="0.05" value="0.55" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Độ mịn (cạnh)</label>
<input id="fpCircleSegments" type="number" min="8" max="64" step="1" value="32" />
</div>
</div>
<div id="fpPolyParams" class="fpShapeParams" hidden>
<div class="row rowWide">
<label>Bán kính</label>
<div class="inputUnit">
<input id="fpPolyRadiusM" type="number" min="0.1" max="5" step="0.05" value="0.6" />
<span class="unit">m</span>
</div>
</div>
<div class="row rowWide">
<label>Số cạnh</label>
<input id="fpPolySides" type="number" min="3" max="32" step="1" value="6" />
</div>
</div>
<button id="applyFootprintPresetBtn" type="button" class="btn subtle btnBlock">Áp dụng hình dạng</button>
</div>
<div id="footprintCustomPanel" class="footprintCustomPanel hidden">
<div class="fpVertexRow">
<span class="fpVertexLabel">Số đỉnh: <strong id="fpVertexCount">0</strong></span>
<span id="fpSelectedVertexText" class="fpSelectedVertex mutedNote"></span>
</div>
<div class="fpVertexActions">
<button id="fpAddVertexBtn" type="button" class="btn subtle">Thêm đỉnh</button>
<button id="fpRemoveVertexBtn" type="button" class="btn subtle" disabled>Xóa đỉnh</button>
</div>
</div>
<button id="editFootprintBtn" type="button" class="btn subtle btnBlock">Sửa footprint</button>
<div id="footprintEditHint" class="fpHint" hidden></div>
</div>
</details>
</div>
</div>
</section>
</div>
</div>
<div class="page" id="pageMissions" data-page-content="missions" hidden>
<div id="missionsListView" class="missionsPage">
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="missions.title">Missions</div>
<div class="cardSub" data-i18n="missions.subtitle">Setup → Missions — danh sách nhiệm vụ robot.</div>
</div>
<button id="missionCreateOpenBtn" type="button" class="btn primary" data-i18n="missions.create">Create mission</button>
</div>
<div class="cardBody">
<div id="missionListEmpty" class="mutedNote" hidden data-i18n="missions.empty">Chưa có mission. Bấm Create mission để bắt đầu.</div>
<div id="missionList" class="missionList"></div>
</div>
</section>
<section class="card" id="missionQueueCard">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="missions.queue.title">Mission queue</div>
<div class="cardSub" data-i18n="missions.queue.subtitle">Thêm mission bằng biểu tượng queue — robot chạy theo thứ tự từ trên xuống.</div>
</div>
<div class="missionQueueCardActions">
<button id="missionQueueCancelBtn" type="button" class="btn subtle danger" data-i18n="missions.queue.cancel">Hủy chạy</button>
<button id="missionQueueClearBtn" type="button" class="btn subtle danger" data-i18n="missions.queue.clear">Xóa queue</button>
</div>
</div>
<div class="cardBody">
<div id="missionQueueRunner" class="missionQueueRunner mutedNote"></div>
<div id="missionQueueEmpty" class="mutedNote" data-i18n="missions.queue.empty">Queue trống. Bấm ▤ trên mission để thêm.</div>
<div id="missionQueueList" class="missionQueueList"></div>
</div>
</section>
</div>
<div id="missionEditorView" class="missionsPage" hidden>
<section class="card missionEditorCard">
<div class="missionEditorTop">
<div class="missionEditorTitleWrap">
<button id="missionEditorBackBtn" type="button" class="btn subtle missionBackBtn" data-i18n-aria="missions.editor.backAria"></button>
<div>
<div class="missionEditorKicker" data-i18n="missions.editor.kicker">Mission editor</div>
<div class="missionEditorTitleRow">
<h2 id="missionEditorTitle" class="missionEditorTitle"></h2>
<button id="missionSettingsBtn" type="button" class="iconBtn" data-i18n-aria="missions.editor.settingsAria" data-i18n-title="missions.editor.settingsAria"></button>
</div>
<div id="missionEditorMeta" class="missionEditorMeta"></div>
</div>
</div>
<div class="missionEditorTopActions">
<span id="missionEditorDirty" class="missionDirtyBadge" hidden data-i18n="missions.editor.unsaved">Chưa lưu</span>
<button id="missionSaveAsBtn" type="button" class="btn subtle" data-i18n="missions.editor.saveAs">Save as</button>
<button id="missionSaveBtn" type="button" class="btn primary" data-i18n="missions.editor.save">Save</button>
</div>
</div>
<div class="missionActionBar" id="missionActionBar" role="toolbar" data-i18n-aria="missions.editor.addActionAria">
<div class="missionGroupTabs" id="missionGroupTabs"></div>
</div>
<div class="missionEditorBody">
<p class="missionFlowHint" data-i18n="missions.editor.flowHint">Thực thi từ trên xuống dưới. Kéo biểu tượng ↔ để đổi thứ tự. Với Loop: kéo action vào vùng bên trong.</p>
<div id="missionActionList" class="missionActionList"></div>
<div id="missionActionListEmpty" class="missionActionListEmpty mutedNote" data-i18n="missions.editor.emptyActions">Chọn action từ menu phía trên để bắt đầu.</div>
</div>
</section>
</div>
</div>
<div class="page" id="pageMaps" data-page-content="maps" hidden>
<div id="mapsListView" class="mapsMirPage">
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="maps.title">Maps</h1>
<p class="mapsMirSubtitle">
<span data-i18n="maps.subtitle">Create and edit maps.</span>
<button type="button" class="mapsMirHelpBtn" id="mapsHelpBtn" data-i18n-title="maps.helpTitle" aria-label="Help">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.5"/><text x="8" y="11.5" text-anchor="middle" font-size="10" font-weight="700" fill="currentColor">?</text></svg>
</button>
</p>
</div>
<div class="mapsMirHeaderActions">
<button type="button" class="mapsMirBtn mapsMirBtn--green" id="mapsCreateOpenBtn">
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v12M1 7h12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span data-i18n="maps.create">Create map</span>
</button>
<button type="button" class="mapsMirBtn mapsMirBtn--green" id="mapsImportSiteBtn">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 2v8M5 7l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M3 12h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
<span data-i18n="maps.importSite">Import site</span>
</button>
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="mapsClearFiltersBtn">
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><circle cx="7" cy="7" r="5.5" fill="none" stroke="currentColor" stroke-width="1.3"/><path d="M4.5 4.5l5 5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
<span data-i18n="maps.clearFilters">Clear filters</span>
</button>
</div>
</header>
<div id="mapsActiveHint" class="mapsMirActiveHint" hidden></div>
<div class="mapsMirFilterBar">
<label class="mapsMirFilterLabel" for="mapsFilterInput" data-i18n="maps.filterLabel">Filter:</label>
<input type="search" id="mapsFilterInput" class="mapsMirFilterInput" data-i18n-placeholder="maps.filterPlaceholder" placeholder="Write name to filter by..." autocomplete="off" />
<span id="mapsFilterCount" class="mapsMirFilterCount">0 item(s) found</span>
<div class="mapsMirPager">
<button type="button" class="mapsMirPageBtn" id="mapsPageFirst" aria-label="First page">&laquo;</button>
<button type="button" class="mapsMirPageBtn" id="mapsPagePrev" aria-label="Previous page">&lsaquo;</button>
<span id="mapsPageLabel" class="mapsMirPageLabel">Page 1 of 1</span>
<button type="button" class="mapsMirPageBtn" id="mapsPageNext" aria-label="Next page">&rsaquo;</button>
<button type="button" class="mapsMirPageBtn" id="mapsPageLast" aria-label="Last page">&raquo;</button>
</div>
</div>
<div class="mapsMirTableWrap">
<table class="mapsMirTable" id="mapsTable">
<thead>
<tr>
<th data-i18n="maps.colName">Name</th>
<th data-i18n="maps.colCreatedBy">Created by</th>
<th class="mapsMirThFunctions" data-i18n="maps.colFunctions">Functions</th>
</tr>
</thead>
<tbody id="mapsList"></tbody>
</table>
<div id="mapsListEmpty" class="mapsMirEmpty" hidden></div>
</div>
</div>
<div id="mapsCreateView" class="mapsMirCreatePage" hidden>
<header class="mapsMirCreateHeader">
<div class="mapsMirCreateHeaderIntro">
<h1 class="mapsMirCreateTitle" data-i18n="maps.createPage.title">Create map</h1>
<p class="mapsMirCreateSubtitle">
<span data-i18n="maps.createPage.subtitle">Create a new map.</span>
<button type="button" class="mapsMirHelpBtn" id="mapsCreateHelpBtn" data-i18n-title="maps.helpTitle" aria-label="Help">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.5"/><text x="8" y="11.5" text-anchor="middle" font-size="10" font-weight="700" fill="currentColor">?</text></svg>
</button>
</p>
</div>
<button type="button" class="mapsMirGoBackBtn" id="mapsCreateGoBackBtn">
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M9 3L5 7l4 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span data-i18n="maps.createPage.goBack">Go back</span>
</button>
</header>
<form id="mapsCreateForm" class="mapsMirCreateForm">
<div class="mapsMirCreatePanel">
<label class="mapsMirCreateField" for="mapsCreateName">
<span class="mapsMirCreateFieldLabel">
<span data-i18n="maps.createPage.name">Name</span>
<button type="button" class="mapsMirFieldHelpBtn" tabindex="-1" data-i18n-title="maps.createPage.nameHelp" title="Help">?</button>
</span>
<input type="text" id="mapsCreateName" required autocomplete="off" data-i18n-placeholder="maps.createPage.namePlaceholder" placeholder="Enter the map's name..." />
</label>
<div class="mapsMirCreateField">
<span class="mapsMirCreateFieldLabel">
<span data-i18n="maps.createPage.site">Site</span>
<button type="button" class="mapsMirFieldHelpBtn" tabindex="-1" data-i18n-title="maps.createPage.siteHelp" title="Help">?</button>
</span>
<div class="mapsMirSitePicker">
<select id="mapsCreateSite" required></select>
<button type="button" class="mapsMirSiteManageBtn" id="mapsCreateSiteBtn" data-i18n="maps.createPage.siteManage">Create / Edit</button>
</div>
</div>
<div class="mapsMirCreateActions">
<button type="submit" class="mapsMirCreateSubmitBtn">
<span class="mapsMirCircleIcon mapsMirCircleIcon--ok" aria-hidden="true"></span>
<span data-i18n="maps.createPage.submit">Create map</span>
</button>
<button type="button" class="mapsMirCreateCancelBtn" id="mapsCreateCancelBtn">
<span class="mapsMirCircleIcon" aria-hidden="true"></span>
<span data-i18n="maps.createPage.cancel">Cancel</span>
</button>
</div>
</div>
</form>
</div>
<div id="mapEditorView" class="mapEditorPage" hidden>
<header class="mapEditorHeader">
<div class="mapEditorHeaderIntro">
<div class="mapEditorTitleRow">
<h1 class="mapEditorTitle" id="mapEditorTitle"></h1>
<button type="button" class="mapEditorGearBtn" id="mapEditorSettingsBtn" data-i18n-title="maps.editor.settings" aria-label="Settings">
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><circle cx="9" cy="9" r="2.5" fill="none" stroke="currentColor" stroke-width="1.3"/><path d="M9 1.5v2M9 14.5v2M1.5 9h2M14.5 9h2M3.4 3.4l1.4 1.4M13.2 13.2l1.4 1.4M3.4 14.6l1.4-1.4M13.2 4.8l1.4-1.4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</button>
<span id="mapEditorDirty" class="mapEditorDirtyBadge" hidden data-i18n="maps.editor.unsaved">Unsaved</span>
</div>
<p class="mapEditorSubtitle">
<span data-i18n="maps.editor.subtitle">Edit and draw the map.</span>
<button type="button" class="mapsMirHelpBtn" id="mapEditorHelpBtn" data-i18n-title="maps.editor.helpTitle" aria-label="Help">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.5"/><text x="8" y="11.5" text-anchor="middle" font-size="10" font-weight="700" fill="currentColor">?</text></svg>
</button>
</p>
</div>
<button type="button" class="mapEditorGoBackBtn" id="mapEditorBackBtn">
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M9 3L5 7l4 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span data-i18n="maps.editor.goBack">Go back</span>
</button>
</header>
<div class="mapEditorMappingBar" role="toolbar" data-i18n-aria="maps.editor.toolbarAria">
<button type="button" class="mapEditorMapTool" id="mapEditorSearchBtn" disabled data-i18n-title="maps.editor.tool.search" title="Search">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><circle cx="8.5" cy="8.5" r="5.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12.5 12.5L17 17" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorMenuBtn" data-i18n-title="maps.editor.menu" title="Menu">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><circle cx="4" cy="10" r="1.6" fill="currentColor"/><circle cx="10" cy="10" r="1.6" fill="currentColor"/><circle cx="16" cy="10" r="1.6" fill="currentColor"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorUndoBtn" disabled data-i18n-title="maps.editor.undo" title="Undo">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M7 6H4v3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 9a6.5 6.5 0 1 0 1.6 4.3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorSaveBtn" disabled data-i18n-title="maps.editor.tool.save" title="Save">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 3h9l3 3v11H4V3z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M6 3v5h7V3" fill="none" stroke="currentColor" stroke-width="1.2"/><rect x="6" y="12" width="8" height="5" rx="0.5" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>
</button>
<button type="button" class="mapEditorMapTool is-active" id="mapEditorPanBtn" data-tool="pan" data-i18n-title="maps.editor.tool.pan" title="Pan">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M10 2.5v15M2.5 10h15" stroke="currentColor" stroke-width="1.2"/><path d="M10 2.5L8 5.5h4L10 2.5zM10 17.5l-2-3h4l-2 3zM2.5 10l3-2v4l-3-2zM17.5 10l-3-2v4l3-2z" fill="currentColor"/></svg>
</button>
<div class="mapEditorMappingBarSpacer" aria-hidden="true"></div>
<select class="mapEditorObjectSelect" id="mapEditorObjectSelect" disabled>
<option value="" data-i18n="maps.editor.objectTypesNone">No object-type selected</option>
</select>
<button type="button" class="mapEditorMapTool" id="mapEditorCrosshairBtn" disabled data-i18n-title="maps.editor.tool.crosshair" title="Crosshair">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="6" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M10 4v12M4 10h12" stroke="currentColor" stroke-width="1.3"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorFitBtn" data-i18n-title="maps.editor.fit" title="Fit">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M3 7V3h4M13 3h4v4M17 13h-4v4M7 17H3v-4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorCenterBtn" data-i18n-title="maps.editor.tool.center" title="Center">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M8 3v3H3M12 3h5v5M17 12h-5v5M3 12h5v5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorLidarBtn" disabled data-i18n-title="maps.editor.tool.lidar" title="LiDAR">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M3.5 14a6.5 6.5 0 0 1 13 0" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M10 14V7M6.5 12.5L10 7l3.5 5.5M4.5 11l5.5-4 5.5 4" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorWaypointsBtn" disabled data-i18n-title="maps.editor.tool.waypoints" title="Positions">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M10 3v14" stroke="currentColor" stroke-width="1.3"/><path d="M10 4l4 2.5-1.2 2.4L10 8.2 7.2 8.9 6 6.5 10 4z" fill="currentColor"/><path d="M10 9l4 2.5-1.2 2.4L10 13.2 7.2 13.9 6 11.5 10 9z" fill="currentColor"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorZoomInBtn" data-i18n-title="maps.editor.zoomIn" title="Zoom in">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><circle cx="8.5" cy="8.5" r="5.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12.5 12.5L17 17M8.5 6v5M6 8.5h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
</button>
<button type="button" class="mapEditorMapTool" id="mapEditorZoomOutBtn" data-i18n-title="maps.editor.zoomOut" title="Zoom out">
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><circle cx="8.5" cy="8.5" r="5.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12.5 12.5L17 17M6 8.5h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
</button>
</div>
<div class="mapEditorCanvasWrap" id="mapEditorCanvasWrap">
<div class="mapEditorCanvasTip" id="mapEditorCanvasTip" role="status" data-i18n="maps.editor.canvasTip">Drag the map to move your view or use the zoom-in and -out buttons to zoom.</div>
<div class="mapEditorViewport">
<div class="mapEditorCanvasInner" id="mapEditorCanvasInner">
<div class="mapEditorSheet" id="mapEditorSheet">
<img id="mapEditorImage" class="mapEditorImage" alt="" hidden />
<div id="mapEditorEmpty" class="mapEditorEmpty" hidden data-i18n="maps.editor.noData">No map data — open ⋮ menu to upload a PNG.</div>
</div>
</div>
</div>
</div>
</div>
<dialog id="mapsSitesDialog" class="mapsMirSitesDialog">
<div class="mapsMirSitesDialogInner">
<header class="mapsMirSitesHeader">
<h2 class="mapsMirSitesTitle" data-i18n="maps.sitesDialog.title">Sites</h2>
<button type="button" class="mapsMirSitesCreateBtn" id="mapsSitesCreateBtn" data-i18n="maps.sitesDialog.createSite">Create site</button>
</header>
<p class="mapsMirSitesHelp" data-i18n="maps.sitesDialog.description">A site is a container for maps and other facility data on the robot.</p>
<div class="mapsMirSitesDivider" role="separator"></div>
<ul class="mapsMirSitesList" id="mapsSitesList" aria-label="Sites"></ul>
<div class="mapsMirSitesFooter">
<button type="button" class="mapsMirSitesOkBtn" id="mapsSitesOkBtn">
<span class="mapsMirCircleIcon mapsMirCircleIcon--ok" aria-hidden="true"></span>
<span data-i18n="common.ok">OK</span>
</button>
<button type="button" class="mapsMirSitesCancelBtn" id="mapsSitesCancelBtn">
<span data-i18n="common.cancel">Cancel</span>
</button>
</div>
</div>
</dialog>
<dialog id="mapsSiteFormDialog" class="mapsMirDialog">
<form id="mapsSiteForm" method="dialog">
<h2 class="mapsMirDialogTitle" id="mapsSiteFormTitle" data-i18n="maps.siteForm.create">Create site</h2>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.siteForm.name">Name *</span>
<input type="text" id="mapsSiteName" required autocomplete="off" />
</label>
<div class="mapsMirDialogFooter">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" data-close-dialog="mapsSiteFormDialog" data-i18n="common.cancel">Cancel</button>
<button type="submit" class="mapsMirBtn mapsMirBtn--green" data-i18n="common.save">Save</button>
</div>
</form>
</dialog>
<dialog id="mapEditorMenuDialog" class="mapsMirDialog mapsMirDialog--mapMenu">
<h2 class="mapsMirDialogTitle mapsMirMapMenuTitle" data-i18n="maps.menu.title">Upload, download and record maps</h2>
<div class="mapsMirMapMenuGrid">
<button type="button" class="mapsMirMapMenuAction" id="mapMenuUploadOverwrite">
<span class="mapsMirMapMenuIcon mapsMirMapMenuIcon--file" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32"><path fill="#5cae4c" d="M22.5 24H8.5a5.5 5.5 0 0 1-.9-10.9A6.5 6.5 0 0 1 23.5 14a4.5 4.5 0 0 1 .5 9.2V24z"/><path fill="#fff" d="M16 12v7.5M13.5 16.5 16 19l2.5-2.5"/><path stroke="#fff" stroke-width="2.2" stroke-linecap="round" d="M11.5 21.5 20.5 12.5"/></svg>
</span>
<span class="mapsMirMapMenuText">
<span class="mapsMirMapMenuLabel" data-i18n="maps.menu.uploadOverwrite">Upload and overwrite</span>
<span class="mapsMirMapMenuDesc" data-i18n="maps.menu.uploadOverwriteDesc">Replace existing map with uploaded map.</span>
</span>
</button>
<button type="button" class="mapsMirMapMenuAction" id="mapMenuRecordOverwrite" disabled>
<span class="mapsMirMapMenuIcon mapsMirMapMenuIcon--record" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="16" r="10" fill="#d9534f"/><path stroke="#fff" stroke-width="2.5" stroke-linecap="round" d="M10.5 21.5 21.5 10.5"/></svg>
</span>
<span class="mapsMirMapMenuText">
<span class="mapsMirMapMenuLabel" data-i18n="maps.menu.recordOverwrite">Record and overwrite</span>
<span class="mapsMirMapMenuDesc" data-i18n="maps.menu.recordOverwriteDesc">Replace existing map with new recording of map.</span>
</span>
</button>
<button type="button" class="mapsMirMapMenuAction" id="mapMenuUploadAppend" disabled>
<span class="mapsMirMapMenuIcon mapsMirMapMenuIcon--file" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32"><path fill="#5cae4c" d="M22.5 24H8.5a5.5 5.5 0 0 1-.9-10.9A6.5 6.5 0 0 1 23.5 14a4.5 4.5 0 0 1 .5 9.2V24z"/><path fill="#fff" d="M16 12v7.5M13.5 16.5 16 19l2.5-2.5"/></svg>
</span>
<span class="mapsMirMapMenuText">
<span class="mapsMirMapMenuLabel" data-i18n="maps.menu.uploadAppend">Upload and append</span>
<span class="mapsMirMapMenuDesc" data-i18n="maps.menu.uploadAppendDesc">Upload a new map and append it to current map.</span>
</span>
</button>
<button type="button" class="mapsMirMapMenuAction" id="mapMenuRecordAppend" disabled>
<span class="mapsMirMapMenuIcon mapsMirMapMenuIcon--record" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="16" r="10" fill="#d9534f"/></svg>
</span>
<span class="mapsMirMapMenuText">
<span class="mapsMirMapMenuLabel" data-i18n="maps.menu.recordAppend">Record and append</span>
<span class="mapsMirMapMenuDesc" data-i18n="maps.menu.recordAppendDesc">Record a new map and append it to current map.</span>
</span>
</button>
<button type="button" class="mapsMirMapMenuAction" id="mapMenuDownload" disabled>
<span class="mapsMirMapMenuIcon mapsMirMapMenuIcon--file" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32"><path fill="#5cae4c" d="M22.5 24H8.5a5.5 5.5 0 0 1-.9-10.9A6.5 6.5 0 0 1 23.5 14a4.5 4.5 0 0 1 .5 9.2V24z"/><path fill="#fff" d="M16 19V11.5M13.5 16.5 16 14l2.5 2.5"/></svg>
</span>
<span class="mapsMirMapMenuText">
<span class="mapsMirMapMenuLabel" data-i18n="maps.menu.download">Download map</span>
<span class="mapsMirMapMenuDesc" data-i18n="maps.menu.downloadDesc">Download the current map.</span>
</span>
</button>
</div>
<div class="mapsMirDialogFooter mapsMirMapMenuFooter">
<button type="button" class="mapsMirBtn mapsMirMapMenuCancelBtn" id="mapMenuCancelBtn" data-i18n="common.cancel">Cancel</button>
</div>
</dialog>
<dialog id="mapEditorSettingsDialog" class="mapsMirDialog">
<form id="mapEditorSettingsForm" method="dialog">
<h2 class="mapsMirDialogTitle" data-i18n="maps.settings.title">Map settings</h2>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.settings.name">Name</span>
<input type="text" id="mapSettingsName" required autocomplete="off" />
</label>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.settings.description">Description</span>
<textarea id="mapSettingsDesc" rows="2"></textarea>
</label>
<div class="mapsMirFieldRow">
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.settings.resolution">Resolution (m/px)</span>
<input type="number" id="mapSettingsResolution" step="0.001" min="0.001" />
</label>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originX">Origin X</span>
<input type="number" id="mapSettingsOriginX" step="0.01" />
</label>
</div>
<div class="mapsMirFieldRow">
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originY">Origin Y</span>
<input type="number" id="mapSettingsOriginY" step="0.01" />
</label>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originYaw">Origin yaw</span>
<input type="number" id="mapSettingsOriginYaw" step="0.01" />
</label>
</div>
<div class="mapsMirDialogFooter">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" data-close-dialog="mapEditorSettingsDialog" data-i18n="common.cancel">Cancel</button>
<button type="submit" class="mapsMirBtn mapsMirBtn--green" data-i18n="common.apply">Apply</button>
</div>
</form>
</dialog>
<dialog id="mapActivateDialog" class="mapsMirDialog">
<h2 class="mapsMirDialogTitle" data-i18n="maps.activateDialog.title">Activate map?</h2>
<p id="mapActivateDialogText" class="mapsMirDialogText"></p>
<div class="mapsMirDialogFooter">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="mapActivateNoBtn" data-i18n="common.no">No</button>
<button type="button" class="mapsMirBtn mapsMirBtn--green" id="mapActivateYesBtn" data-i18n="common.yes">Yes</button>
</div>
</dialog>
<input type="file" id="mapEditorUploadInput" accept="image/png,.png" hidden />
</div>
<div class="page" id="pageIntegrations" data-page-content="integrations" hidden>
<div class="integrationsPage">
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="integrations.modbus.title">Modbus trigger</div>
<div class="cardSub" data-i18n="integrations.modbus.subtitle">System → Triggers — coil 10012000 gắn mission_id. Thiết bị remote bật coil (Modbus TCP :5502) → mission vào queue.</div>
</div>
<button id="integrationAddTriggerBtn" type="button" class="btn primary" data-i18n="integrations.modbus.add">Thêm trigger</button>
</div>
<div class="cardBody">
<div id="integrationTriggerEmpty" class="mutedNote" data-i18n="integrations.modbus.empty">Chưa có trigger Modbus.</div>
<div id="integrationTriggerList" class="missionList"></div>
<div class="integrationCoilSection">
<div class="integrationSectionLabel" data-i18n="integrations.modbus.coilsLabel">Coil đã gán (bấm để mô phỏng rising edge)</div>
<div id="integrationCoilGrid" class="integrationCoilGrid"></div>
</div>
</div>
</section>
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="integrations.rest.title">REST API — MiR v2.0.0</div>
<div class="cardSub" data-i18n="integrations.rest.subtitle">Hệ thống bên ngoài POST mission vào queue qua REST.</div>
</div>
</div>
<div class="cardBody integrationApiBody">
<div class="row rowWide">
<label data-i18n="integrations.rest.baseUrl">Base URL</label>
<div id="integrationApiBaseUrl" class="mono integrationCode"></div>
</div>
<div class="integrationApiBlock">
<div class="integrationSectionLabel">POST /mission_queue</div>
<pre class="integrationPre">curl -X POST "<span class="integrationCurlHost">http://localhost:8080</span>/api/v2.0.0/mission_queue" \
-H "Content-Type: application/json" \
-d '{"mission_id":"&lt;mission_id&gt;","priority":0,"robot_id":"default"}'</pre>
</div>
<div class="integrationApiBlock">
<div class="integrationSectionLabel">GET /mission_queue · GET /missions · GET /status</div>
<pre class="integrationPre">GET /api/v2.0.0/mission_queue
GET /api/v2.0.0/missions
GET /api/v2.0.0/status</pre>
</div>
<div class="row rowWide integrationTestRow">
<label for="integrationRestMission" data-i18n="integrations.rest.quickTest">Thử nhanh</label>
<div class="integrationTestActions">
<select id="integrationRestMission"></select>
<button id="integrationRestTestBtn" type="button" class="btn subtle" data-i18n="integrations.rest.postQueue">POST queue</button>
</div>
</div>
</div>
</section>
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="integrations.fleet.title">MiRFleet — Lên lịch mission</div>
<div class="cardSub" data-i18n="integrations.fleet.subtitle">Ưu tiên, gán robot, chạy ASAP hoặc theo thời gian.</div>
</div>
<div class="integrationHeaderActions">
<button id="integrationRefreshBtn" type="button" class="btn subtle" data-i18n="common.reload">Tải lại</button>
<button id="integrationAddScheduleBtn" type="button" class="btn primary" data-i18n="integrations.fleet.addSchedule">Thêm lịch</button>
</div>
</div>
<div class="cardBody">
<div id="integrationScheduleEmpty" class="mutedNote" data-i18n="integrations.fleet.empty">Chưa có lịch fleet.</div>
<div id="integrationScheduleList" class="missionList"></div>
</div>
</section>
</div>
</div>
<div class="page" id="pageMonitoring" data-page-content="monitoring" hidden>
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="monitoring.log.title">System log</div>
<div class="cardSub" data-i18n="monitoring.log.subtitle">Monitoring → System log — nhật ký hệ thống (đang phát triển).</div>
</div>
</div>
<div class="cardBody">
<p class="mutedNote" data-i18n="monitoring.log.placeholder">Tính năng monitoring sẽ hiển thị log robot, cảnh báo và lịch sử mission tại đây.</p>
</div>
</section>
</div>
<div class="page" id="pageHelp" data-page-content="help" hidden>
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="help.api.title">API documentation</div>
<div class="cardSub" data-i18n="help.api.subtitle">Help → API — tham chiếu REST MiR v2.0.0 cho tích hợp bên ngoài.</div>
</div>
</div>
<div class="cardBody">
<p class="mutedNote" data-i18n="help.api.body1">Xem chi tiết endpoint tại System → Tích hợp hoặc tài liệu /api/v2.0.0/.</p>
<p class="mutedNote" data-i18n="help.api.body2">Reference Guide MiR rev 1.9: docs/Reference guide.pdf</p>
</div>
</section>
</div>
<div id="configSplitter" class="splitter" role="separator" aria-orientation="vertical" tabindex="0"></div>
<div class="contentRight" id="contentRight">
<section class="card">
<div class="cardHeader">
<div>
<div class="cardTitle" data-i18n="config.canvas.title">Bố trí trên robot</div>
</div>
</div>
<div class="cardBody">
<div class="canvasWrap" id="canvasWrap">
<canvas id="canvas"></canvas>
</div>
<div class="metaBar">
<div class="viewHint" data-i18n="config.canvas.viewHint">Cuộn chuột: zoom • Shift + kéo: di chuyển vùng nhìn</div>
<div id="robotDiffSummary" class="robotDiffSummary"></div>
<div><span data-i18n="config.canvas.robotCenter">Robot center:</span> <span id="robotCenterText"></span></div>
<div><span data-i18n="config.canvas.selected">Selected:</span> <span id="selectedText" data-i18n="common.none">none</span></div>
<div><span data-i18n="config.canvas.pose">Pose:</span> <span id="selectedRelText"></span></div>
</div>
</div>
</section>
</div>
</main>
</div>
</div>
<dialog id="missionCreateDialog" class="missionDialog">
<form id="missionCreateForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 data-i18n="missions.dialog.create.title">Create mission</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="missionCreateDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="missionCreateName" data-i18n="missions.dialog.create.name">Tên mission</label>
<input id="missionCreateName" type="text" required placeholder="VD: Go to charging station" data-i18n-placeholder="missions.dialog.create.namePlaceholder" />
</div>
<div class="row rowWide">
<label for="missionCreateGroup" data-i18n="missions.dialog.create.group">Nhóm mission</label>
<select id="missionCreateGroup"></select>
</div>
<div class="row rowWide">
<label for="missionCreateGroupNew" data-i18n="missions.dialog.create.groupNew">Hoặc nhóm mới</label>
<input id="missionCreateGroupNew" type="text" placeholder="Tùy chọn" data-i18n-placeholder="common.optional" />
</div>
<div class="row rowWide">
<label for="missionCreateDesc" data-i18n="missions.dialog.create.desc">Mô tả</label>
<textarea id="missionCreateDesc" rows="2" placeholder="Tùy chọn" data-i18n-placeholder="common.optional"></textarea>
</div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="missionCreateDialog" data-i18n="common.cancel">Hủy</button>
<button type="submit" class="btn primary" data-i18n="missions.create">Create mission</button>
</div>
</form>
</dialog>
<dialog id="missionSettingsDialog" class="missionDialog">
<form id="missionSettingsForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 data-i18n="missions.dialog.settings.title">Cài đặt mission</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="missionSettingsDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="missionSettingsName" data-i18n="missions.dialog.settings.name">Tên</label>
<input id="missionSettingsName" type="text" required />
</div>
<div class="row rowWide">
<label for="missionSettingsGroup" data-i18n="missions.dialog.settings.group">Nhóm</label>
<select id="missionSettingsGroup"></select>
</div>
<div class="row rowWide">
<label for="missionSettingsDesc" data-i18n="missions.dialog.settings.desc">Mô tả</label>
<textarea id="missionSettingsDesc" rows="2"></textarea>
</div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="missionSettingsDialog">Hủy</button>
<button type="submit" class="btn primary" data-i18n="common.apply">Áp dụng</button>
</div>
</form>
</dialog>
<dialog id="missionSaveAsDialog" class="missionDialog">
<form id="missionSaveAsForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 data-i18n="missions.dialog.saveAs.title">Save mission as</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="missionSaveAsDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="missionSaveAsName" data-i18n="missions.dialog.saveAs.name">Tên mission mới</label>
<input id="missionSaveAsName" type="text" required />
</div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="missionSaveAsDialog">Hủy</button>
<button type="submit" class="btn primary" data-i18n="missions.dialog.saveAs.submit">Lưu bản sao</button>
</div>
</form>
</dialog>
<dialog id="missionActionConfigDialog" class="missionDialog missionDialogWide">
<form id="missionActionConfigForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 id="missionActionConfigTitle" data-i18n="missions.dialog.actionConfig.title">Cấu hình action</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="missionActionConfigDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody" id="missionActionConfigBody"></div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="missionActionConfigDialog">Hủy</button>
<button type="submit" class="btn primary" data-i18n="common.apply">Áp dụng</button>
</div>
</form>
</dialog>
<dialog id="missionQueueDialog" class="missionDialog">
<form id="missionQueueForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3>Thêm vào mission queue</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="missionQueueDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<p id="missionQueueDialogMission" class="mutedNote"></p>
<div id="missionQueueVarFields" class="missionConfigGrid"></div>
<p id="missionQueueVarHint" class="mutedNote" hidden>Tham số đã chọn sẽ hiển thị màu xanh trong queue.</p>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="missionQueueDialog">Hủy</button>
<button type="submit" class="btn primary">Thêm vào queue</button>
</div>
</form>
</dialog>
</dialog>
<dialog id="dashboardPermissionsDialog" class="missionDialog dashboardPermissionsDialog">
<form id="dashboardPermissionsForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 data-i18n="dashboard.create.permissionsTitle">Quyền chỉnh sửa</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="dashboardPermissionsDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<p class="dashboardPermissionsIntro" data-i18n="dashboard.create.permissions">Chọn user groups được phép chỉnh sửa dashboard này.</p>
<div id="dashboardCreatePermissions" class="dashboardPermissionsList"></div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="dashboardPermissionsDialog" data-i18n="common.cancel">Hủy</button>
<button type="submit" class="btn dashboardMirBtn dashboardPermissionsApplyBtn" data-i18n="common.apply">Áp dụng</button>
</div>
</form>
</dialog>
<dialog id="dashboardEditDialog" class="missionDialog">
<form id="dashboardEditForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 data-i18n="dashboard.dialog.editDashboard.title">Sửa dashboard</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="dashboardEditDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<input type="hidden" id="dashboardEditId" />
<div class="row rowWide">
<label for="dashboardEditName" data-i18n="dashboard.create.name">Tên</label>
<input id="dashboardEditName" type="text" required />
</div>
<div class="row rowWide">
<label data-i18n="dashboard.create.permissions">Quyền chỉnh sửa</label>
<div id="dashboardEditPermissions" class="dashboardPermissionsList"></div>
</div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="dashboardEditDialog" data-i18n="common.cancel">Hủy</button>
<button type="submit" class="btn primary" data-i18n="common.save">Lưu</button>
</div>
</form>
</dialog>
<dialog id="dashboardAddWidgetDialog" class="missionDialog">
<form id="dashboardAddWidgetForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3>Thêm widget</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="dashboardAddWidgetDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="dashboardWidgetType">Loại widget</label>
<select id="dashboardWidgetType" required>
<option value="mission_button">Mission button</option>
<option value="mission_group">Mission group</option>
<option value="mission_queue">Mission queue</option>
<option value="pause_continue">Pause / Continue</option>
<option value="mission_action_log">Mission action log</option>
<option value="logout_button">Log-out button</option>
</select>
</div>
<div id="dashboardAddWidgetFields" class="missionConfigGrid"></div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="dashboardAddWidgetDialog">Hủy</button>
<button type="submit" class="btn primary">Thêm</button>
</div>
</form>
</dialog>
<dialog id="dashboardEditWidgetDialog" class="missionDialog">
<form id="dashboardEditWidgetForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3>Cấu hình widget</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="dashboardEditWidgetDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<input type="hidden" id="dashboardEditWidgetId" />
<div class="row rowWide">
<label for="dashboardEditWidgetType">Loại</label>
<input id="dashboardEditWidgetType" type="text" readonly />
</div>
<div id="dashboardEditWidgetFields" class="missionConfigGrid"></div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle danger" id="dashboardDeleteWidgetBtn">Xóa widget</button>
<button type="button" class="btn subtle" data-close-dialog="dashboardEditWidgetDialog">Hủy</button>
<button type="submit" class="btn primary">Lưu</button>
</div>
</form>
</dialog>
<dialog id="integrationAddTriggerDialog" class="missionDialog">
<form id="integrationAddTriggerForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3>Modbus trigger</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="integrationAddTriggerDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="integrationTriggerName">Tên trigger</label>
<input id="integrationTriggerName" type="text" required placeholder="VD: PLC line 1 start" />
</div>
<div class="row rowWide">
<label for="integrationTriggerCoil">Coil ID</label>
<input id="integrationTriggerCoil" type="number" min="1001" max="2000" value="1001" required />
</div>
<div class="row rowWide">
<label for="integrationTriggerMission">Mission</label>
<select id="integrationTriggerMission" required></select>
</div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="integrationAddTriggerDialog">Hủy</button>
<button type="submit" class="btn primary">Thêm</button>
</div>
</form>
</dialog>
<dialog id="integrationAddScheduleDialog" class="missionDialog">
<form id="integrationAddScheduleForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3>Lịch MiRFleet</h3>
<button type="button" class="iconBtn missionDialogClose" data-close-dialog="integrationAddScheduleDialog" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="integrationScheduleName">Tên lịch</label>
<input id="integrationScheduleName" type="text" required placeholder="VD: Ca sáng — đi dock" />
</div>
<div class="row rowWide">
<label for="integrationScheduleMission">Mission</label>
<select id="integrationScheduleMission" required></select>
</div>
<div class="row rowWide">
<label for="integrationScheduleRobot">Robot</label>
<select id="integrationScheduleRobot"></select>
</div>
<div class="row rowWide">
<label for="integrationSchedulePriority">Ưu tiên</label>
<input id="integrationSchedulePriority" type="number" value="0" />
</div>
<div class="row rowWide">
<label for="integrationScheduleMode">Chế độ</label>
<select id="integrationScheduleMode">
<option value="asap">ASAP — vào queue ngay</option>
<option value="scheduled">Scheduled — theo thời gian</option>
</select>
</div>
<div class="row rowWide" id="integrationScheduleStartAtRow" hidden>
<label for="integrationScheduleStartAt">Thời gian bắt đầu</label>
<input id="integrationScheduleStartAt" type="datetime-local" />
</div>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" data-close-dialog="integrationAddScheduleDialog">Hủy</button>
<button type="submit" class="btn primary">Thêm</button>
</div>
</form>
</dialog>
<dialog id="changePasswordDialog" class="missionDialog">
<form id="changePasswordForm" method="dialog" class="missionDialogForm">
<div class="missionDialogHeader">
<h3 data-i18n="auth.changePassword.title">Đổi mật khẩu</h3>
<button type="button" class="iconBtn missionDialogClose" onclick="document.getElementById('changePasswordDialog').close()" aria-label="Đóng" data-i18n-aria="common.close">×</button>
</div>
<div class="missionDialogBody">
<div class="row rowWide">
<label for="changePasswordCurrent" data-i18n="auth.changePassword.current">Mật khẩu hiện tại</label>
<input id="changePasswordCurrent" type="password" autocomplete="current-password" required />
</div>
<div class="row rowWide">
<label for="changePasswordNew" data-i18n="auth.changePassword.new">Mật khẩu mới</label>
<input id="changePasswordNew" type="password" autocomplete="new-password" required minlength="4" />
</div>
<div class="row rowWide">
<label for="changePasswordConfirm" data-i18n="auth.changePassword.confirm">Xác nhận mật khẩu mới</label>
<input id="changePasswordConfirm" type="password" autocomplete="new-password" required minlength="4" />
</div>
<p id="changePasswordError" class="loginError"></p>
</div>
<div class="missionDialogFooter">
<button type="button" class="btn subtle" onclick="document.getElementById('changePasswordDialog').close()" data-i18n="common.cancel">Hủy</button>
<button type="submit" class="btn primary">Lưu</button>
</div>
</form>
</dialog>
<div id="joystickOverlay" class="joystickOverlay" hidden>
<div class="joystickOverlayCard">
<div class="joystickOverlayHeader">
<strong data-i18n="topbar.joystickTitle">Điều khiển tay (Joystick)</strong>
<span class="mutedNote" id="joystickSpeedLabel">fast</span>
</div>
<div class="joystickPadWrap">
<div class="joystickPad" id="joystickPad">
<div class="joystickStick" id="joystickStick"></div>
</div>
</div>
<div class="joystickOverlayActions">
<label class="joystickSpeedSelect">
<span data-i18n="topbar.joystickSpeed">Tốc độ</span>
<select id="joystickSpeedSelect">
<option value="slow" data-i18n="topbar.joystickSpeed.slow">Slow</option>
<option value="medium" data-i18n="topbar.joystickSpeed.medium">Medium</option>
<option value="fast" data-i18n="topbar.joystickSpeed.fast" selected>Fast</option>
</select>
</label>
<button type="button" class="mirBtn mirBtn--danger" id="joystickDisengageBtn" data-i18n="topbar.joystickOff">Tắt joystick</button>
</div>
</div>
</div>
<script src="/i18n.js"></script>
<script src="/auth.js"></script>
<script src="/nav.js"></script>
<script src="/missions.js"></script>
<script src="/maps.js"></script>
<script src="/map-editor.js"></script>
<script src="/topbar.js"></script>
<script src="/dashboard.js"></script>
<script src="/integrations.js"></script>
<script src="/app.js"></script>
</body>
</html>