1735 lines
109 KiB
HTML
1735 lines
109 KiB
HTML
<!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">«</button>
|
||
<button type="button" class="mapsMirPageBtn" id="mapsPagePrev" aria-label="Previous page">‹</button>
|
||
<span id="mapsPageLabel" class="mapsMirPageLabel">Page 1 of 1</span>
|
||
<button type="button" class="mapsMirPageBtn" id="mapsPageNext" aria-label="Next page">›</button>
|
||
<button type="button" class="mapsMirPageBtn" id="mapsPageLast" aria-label="Last page">»</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>
|
||
<!-- Layer 1: View space — pan/zoom on canvasInner only -->
|
||
<div class="mapEditorViewport" id="mapEditorViewport">
|
||
<div class="mapEditorCanvasInner" id="mapEditorCanvasInner">
|
||
<!-- Layer 2: Image space — floor plan pixels 1:1 (20 px/m at res 0.05) -->
|
||
<div class="mapEditorImageLayer" id="mapEditorImageLayer">
|
||
<div class="mapEditorSheet" id="mapEditorSheet">
|
||
<div class="mapEditorSheetGrid" id="mapEditorSheetGrid" aria-hidden="true"></div>
|
||
<img id="mapEditorImage" class="mapEditorImage" alt="" draggable="false" hidden />
|
||
<div id="mapEditorOrigin" class="mapEditorOrigin" hidden aria-hidden="true">
|
||
<span class="mapEditorOriginAxis mapEditorOriginAxis--x" aria-hidden="true"></span>
|
||
<span class="mapEditorOriginAxis mapEditorOriginAxis--y" aria-hidden="true"></span>
|
||
<span class="mapEditorOriginDot" aria-hidden="true"></span>
|
||
<span class="mapEditorOriginLabel" id="mapEditorOriginLabel"></span>
|
||
</div>
|
||
<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>
|
||
<!-- Layer readout: view | image px | world m -->
|
||
<div class="mapEditorStatusBar" id="mapEditorStatusBar" aria-live="polite">
|
||
<span class="mapEditorStatusItem mapEditorStatusItem--view" id="mapEditorStatusView">View —</span>
|
||
<span class="mapEditorStatusItem mapEditorStatusItem--image" id="mapEditorStatusImage">Image —</span>
|
||
<span class="mapEditorStatusItem mapEditorStatusItem--world" id="mapEditorStatusWorld">World —</span>
|
||
</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="any" min="0.001" />
|
||
</label>
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originX">Origin X</span>
|
||
<input type="number" id="mapSettingsOriginX" step="any" />
|
||
</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="any" />
|
||
</label>
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originYaw">Origin yaw</span>
|
||
<input type="number" id="mapSettingsOriginYaw" step="any" />
|
||
</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="mapUploadConfirmDialog" class="mapsMirDialog">
|
||
<div class="mapsMirDialogPanel">
|
||
<h2 class="mapsMirDialogTitle" data-i18n="maps.uploadConfirm.title">Overwrite map?</h2>
|
||
<p id="mapUploadConfirmText" class="mapsMirDialogText"></p>
|
||
<div class="mapsMirDialogFooter">
|
||
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="mapUploadConfirmNoBtn" data-i18n="common.no">No</button>
|
||
<button type="button" class="mapsMirBtn mapsMirBtn--green" id="mapUploadConfirmYesBtn" data-i18n="maps.uploadConfirm.yes">Overwrite</button>
|
||
</div>
|
||
</div>
|
||
</dialog>
|
||
|
||
<dialog id="mapUploadMetaDialog" class="mapsMirDialog">
|
||
<form id="mapUploadMetaForm" method="dialog">
|
||
<h2 class="mapsMirDialogTitle" data-i18n="maps.uploadMeta.title">Map metadata</h2>
|
||
<p class="mapsMirDialogText" data-i18n="maps.uploadMeta.hint">Enter ROS map_server parameters or import a .yaml file.</p>
|
||
<button type="button" class="mapsMirLinkBtn" id="mapUploadImportYamlBtn" data-i18n="maps.uploadMeta.importYaml">Import YAML file…</button>
|
||
<input type="file" id="mapUploadYamlInput" accept=".yaml,.yml,text/yaml" hidden />
|
||
<div class="mapsMirFieldRow">
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.settings.resolution">Resolution (m/px)</span>
|
||
<input type="number" id="mapUploadResolution" step="any" min="0.001" required />
|
||
</label>
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.uploadMeta.negate">Negate</span>
|
||
<input type="number" id="mapUploadNegate" step="1" min="0" max="1" />
|
||
</label>
|
||
</div>
|
||
<div class="mapsMirFieldRow">
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originX">Origin X</span>
|
||
<input type="number" id="mapUploadOriginX" step="any" required />
|
||
</label>
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originY">Origin Y</span>
|
||
<input type="number" id="mapUploadOriginY" step="any" required />
|
||
</label>
|
||
</div>
|
||
<div class="mapsMirFieldRow">
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.settings.originYaw">Origin yaw</span>
|
||
<input type="number" id="mapUploadOriginYaw" step="any" />
|
||
</label>
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.uploadMeta.occupiedThresh">Occupied thresh</span>
|
||
<input type="number" id="mapUploadOccupiedThresh" step="any" min="0" max="1" />
|
||
</label>
|
||
</div>
|
||
<label class="mapsMirField">
|
||
<span class="mapsMirFieldLabel" data-i18n="maps.uploadMeta.freeThresh">Free thresh</span>
|
||
<input type="number" id="mapUploadFreeThresh" step="any" min="0" max="1" />
|
||
</label>
|
||
<div class="mapsMirDialogFooter">
|
||
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="mapUploadMetaCancelBtn" data-i18n="common.cancel">Cancel</button>
|
||
<button type="submit" class="mapsMirBtn mapsMirBtn--green" data-i18n="maps.uploadMeta.continue">Continue</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="mapActivateDialog" class="mapsMirDialog">
|
||
<div class="mapsMirDialogPanel">
|
||
<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>
|
||
</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 1001–2000 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":"<mission_id>","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="/map-geo.js"></script>
|
||
<script src="/map-yaml.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>
|