Thêm phần 5. Monitoring và 6. System
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-25 16:00:18 +07:00
parent e0e864ea13
commit 77ea3cc361
27 changed files with 3564 additions and 31 deletions

View File

@@ -2378,18 +2378,367 @@ GET /api/v2.0.0/status</pre>
</div>
</div>
<div class="page" id="pageSettings" data-page-content="settings" hidden>
<div class="mapsMirPage">
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="settings.title">Settings</h1>
<p class="mapsMirSubtitle" data-i18n="settings.subtitle">System → Settings — global robot configuration.</p>
</div>
<div class="mapsMirHeaderActions">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="settingsRefreshBtn">
<span data-i18n="monitoring.refresh">Refresh</span>
</button>
</div>
</header>
<section class="settingsMirGridWrap" id="settingsGridView">
<div class="settingsMirGrid" id="settingsTileGrid"></div>
</section>
<section class="mapsMirCard" id="settingsDetailView" hidden>
<header class="mapsMirCardHeader">
<div>
<h2 class="mapsMirCardTitle" data-i18n="settings.loggingRetention.title">Logging & retention</h2>
<p class="mapsMirCardSub" data-i18n="settings.loggingRetention.subtitle">Tune history limits and log level.</p>
</div>
<div class="mapsMirCardActions">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="settingsBackBtn">
<span data-i18n="settings.back">Back</span>
</button>
<button type="button" class="mapsMirBtn mapsMirBtn--primary" id="settingsApplyBtn">
<span data-i18n="settings.apply">Apply</span>
</button>
</div>
</header>
<div class="mapsMirCardBody">
<div class="mapsMirFormGrid">
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="settings.retention.missionRuns">Mission history (runs)</span>
<input type="number" min="10" max="20000" step="10" id="settingsRetentionMissionRuns" class="mapsMirInput" />
<span class="mapsMirFieldHint" data-i18n="settings.retention.missionRunsHint">Max persisted mission runs (bounded history).</span>
</label>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="settings.retention.systemLog">System log entries</span>
<input type="number" min="100" max="20000" step="100" id="settingsRetentionSystemLog" class="mapsMirInput" />
<span class="mapsMirFieldHint" data-i18n="settings.retention.systemLogHint">Max number of system log rows stored.</span>
</label>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="settings.retention.errorLogs">Error log entries</span>
<input type="number" min="10" max="5000" step="10" id="settingsRetentionErrorLogs" class="mapsMirInput" />
<span class="mapsMirFieldHint" data-i18n="settings.retention.errorLogsHint">Max number of error log rows stored.</span>
</label>
<label class="mapsMirField">
<span class="mapsMirFieldLabel" data-i18n="settings.logLevel">Log level</span>
<select id="settingsLogLevel" class="mapsMirSelect">
<option value="error">error</option>
<option value="warn">warn</option>
<option value="info">info</option>
<option value="debug">debug</option>
</select>
<span class="mapsMirFieldHint" data-i18n="settings.logLevelHint">Controls verbosity of system logs.</span>
</label>
</div>
<div class="mapsMirNote" id="settingsStatusText" aria-live="polite"></div>
</div>
</section>
<section class="mapsMirCard">
<header class="mapsMirCardHeader">
<div>
<h2 class="mapsMirCardTitle" data-i18n="settings.comingSoon.title">More settings</h2>
<p class="mapsMirCardSub" data-i18n="settings.comingSoon.subtitle">Time, network, security and backups will appear here.</p>
</div>
</header>
<div class="mapsMirCardBody">
<p class="mutedNote" data-i18n="monitoring.comingSoon">Feature coming soon.</p>
</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 id="monitoringAnalyticsView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="monitoring.analytics.title">Analytics</h1>
<p class="mapsMirSubtitle">
<span data-i18n="monitoring.analytics.subtitle">Driven distance over time.</span>
<button type="button" class="mapsMirHelpBtn" id="analyticsHelpBtn" data-i18n-title="monitoring.analytics.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--outline" id="analyticsRefreshBtn">
<span data-i18n="monitoring.refresh">Refresh</span>
</button>
</div>
</header>
<div class="monAnalyticsToolbar">
<div class="monAnalyticsDates">
<label class="monAnalyticsLabel" for="analyticsStartDate" data-i18n="monitoring.analytics.startDate">Start date</label>
<input type="date" id="analyticsStartDate" class="monAnalyticsInput" />
<label class="monAnalyticsLabel" for="analyticsEndDate" data-i18n="monitoring.analytics.endDate">End date</label>
<input type="date" id="analyticsEndDate" class="monAnalyticsInput" />
</div>
<div class="monAnalyticsPresets" id="analyticsPresets"></div>
<div class="monAnalyticsOptions">
<label class="monAnalyticsLabel" for="analyticsGrouping" data-i18n="monitoring.analytics.grouping">Grouping</label>
<select id="analyticsGrouping" class="monAnalyticsSelect">
<option value="day" data-i18n="monitoring.analytics.perDay">Per day</option>
<option value="month" data-i18n="monitoring.analytics.perMonth">Per month</option>
</select>
<label class="monAnalyticsLabel" for="analyticsChartMode" data-i18n="monitoring.analytics.chartType">Chart</label>
<select id="analyticsChartMode" class="monAnalyticsSelect">
<option value="bar" data-i18n="monitoring.analytics.barChart">Bar graph</option>
<option value="accumulated" data-i18n="monitoring.analytics.accumulated">Accumulated</option>
</select>
</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 class="monAnalyticsSummary">
<span id="analyticsTotalLabel" class="monAnalyticsTotal"></span>
</div>
</section>
<div class="monAnalyticsChartWrap">
<svg id="analyticsChart" class="monAnalyticsChart" viewBox="0 0 800 280" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Driven distance chart"></svg>
<div id="analyticsChartEmpty" class="mapsMirEmpty" hidden data-i18n="monitoring.analytics.empty">No distance data for this period.</div>
</div>
</div>
<div id="monitoringSystemLogView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="monitoring.systemLog.title">System log</h1>
<p class="mapsMirSubtitle" data-i18n="monitoring.systemLog.subtitle">Events from operating system components.</p>
</div>
<div class="mapsMirHeaderActions">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="systemLogRefreshBtn"><span data-i18n="monitoring.refresh">Refresh</span></button>
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="systemLogClearFiltersBtn"><span data-i18n="monitoring.clearFilters">Clear filters</span></button>
</div>
</header>
<div class="mapsMirFilterBar">
<label class="mapsMirFilterLabel" for="systemLogFilterInput" data-i18n="monitoring.filterLabel">Filter:</label>
<input type="search" id="systemLogFilterInput" class="mapsMirFilterInput" data-i18n-placeholder="monitoring.systemLog.filterPlaceholder" autocomplete="off" />
<span id="systemLogFilterCount" class="mapsMirFilterCount">0</span>
<div class="mapsMirPager">
<button type="button" class="mapsMirPageBtn" id="systemLogPageFirst">&laquo;</button>
<button type="button" class="mapsMirPageBtn" id="systemLogPagePrev">&lsaquo;</button>
<span id="systemLogPageLabel" class="mapsMirPageLabel">Page 1 of 1</span>
<button type="button" class="mapsMirPageBtn" id="systemLogPageNext">&rsaquo;</button>
<button type="button" class="mapsMirPageBtn" id="systemLogPageLast">&raquo;</button>
</div>
</div>
<div class="mapsMirTableWrap">
<table class="mapsMirTable mapsMirTable--systemLog" id="systemLogTable">
<thead>
<tr>
<th class="monMirThState" data-i18n="monitoring.systemLog.colState">State</th>
<th data-i18n="monitoring.systemLog.colModule">Module</th>
<th data-i18n="monitoring.systemLog.colMessage">Message</th>
<th data-i18n="monitoring.systemLog.colTime">Time</th>
</tr>
</thead>
<tbody id="systemLogList"></tbody>
</table>
<div id="systemLogListEmpty" class="mapsMirEmpty" hidden data-i18n="monitoring.systemLog.empty">No system log entries.</div>
</div>
</div>
<div id="monitoringErrorLogsView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="monitoring.errorLogs.title">Error logs</h1>
<p class="mapsMirSubtitle" data-i18n="monitoring.errorLogs.subtitle">Detected system errors.</p>
</div>
<div class="mapsMirHeaderActions">
<button type="button" class="mapsMirBtn mapsMirBtn--primary" id="errorLogsGenerateBtn"><span data-i18n="monitoring.errorLogs.generate">Generate log</span></button>
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="errorLogsDeleteAllBtn"><span data-i18n="monitoring.errorLogs.deleteAll">Delete all</span></button>
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="errorLogsRefreshBtn"><span data-i18n="monitoring.refresh">Refresh</span></button>
</div>
</header>
<div class="mapsMirFilterBar">
<label class="mapsMirFilterLabel" for="errorLogsFilterInput" data-i18n="monitoring.filterLabel">Filter:</label>
<input type="search" id="errorLogsFilterInput" class="mapsMirFilterInput" data-i18n-placeholder="monitoring.errorLogs.filterPlaceholder" autocomplete="off" />
<span id="errorLogsFilterCount" class="mapsMirFilterCount">0</span>
</div>
<div class="mapsMirTableWrap">
<table class="mapsMirTable mapsMirTable--errorLogs" id="errorLogsTable">
<thead>
<tr>
<th data-i18n="monitoring.errorLogs.colDescription">Description</th>
<th data-i18n="monitoring.errorLogs.colModule">Module</th>
<th data-i18n="monitoring.errorLogs.colTime">Time</th>
<th class="mapsMirThFunctions" data-i18n="monitoring.errorLogs.colFunctions">Functions</th>
</tr>
</thead>
<tbody id="errorLogsList"></tbody>
</table>
<div id="errorLogsListEmpty" class="mapsMirEmpty" hidden data-i18n="monitoring.errorLogs.empty">No errors logged.</div>
</div>
</div>
<div id="monitoringHardwareView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="monitoring.hardware.title">Hardware health</h1>
<p class="mapsMirSubtitle" data-i18n="monitoring.hardware.subtitle">Robot hardware component status.</p>
</div>
<div class="mapsMirHeaderActions">
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="hardwareRefreshBtn"><span data-i18n="monitoring.refresh">Refresh</span></button>
</div>
</header>
<div id="hardwareGroupsList" class="monHwList"></div>
<div id="hardwareListEmpty" class="mapsMirEmpty" hidden data-i18n="monitoring.hardware.empty">No hardware data.</div>
</div>
<div id="monitoringSafetyView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="monitoring.safety.title">Safety system</h1>
<p class="mapsMirSubtitle" data-i18n="monitoring.safety.subtitle">Live emergency stop and laser scanner status.</p>
</div>
</header>
<div class="monSafetyGrid" id="safetyGrid">
<div class="monSafetyCard" id="safetyEstopCard">
<h2 class="monSafetyCardTitle" data-i18n="monitoring.safety.estop">Emergency stop</h2>
<div class="monSafetyStatus" id="safetyEstopStatus"></div>
</div>
<div class="monSafetyCard" id="safetyFrontCard">
<h2 class="monSafetyCardTitle" data-i18n="monitoring.safety.frontScanner">Front scanner</h2>
<div class="monSafetyStatus" id="safetyFrontStatus"></div>
</div>
<div class="monSafetyCard" id="safetyRearCard">
<h2 class="monSafetyCardTitle" data-i18n="monitoring.safety.rearScanner">Rear scanner</h2>
<div class="monSafetyStatus" id="safetyRearStatus"></div>
</div>
</div>
</div>
<div id="monitoringPlaceholderView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" id="monitoringPlaceholderTitle"></h1>
<p class="mapsMirSubtitle">
<span id="monitoringPlaceholderSubtitle"></span>
</p>
</div>
</header>
<div class="mapsMirTableWrap">
<p class="mapsMirEmpty" id="monitoringPlaceholderBody" data-i18n="monitoring.comingSoon">Tính năng đang phát triển.</p>
</div>
</div>
<div id="monitoringMissionLogView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<h1 class="mapsMirTitle" data-i18n="monitoring.missionLog.title">Mission log</h1>
<p class="mapsMirSubtitle">
<span data-i18n="monitoring.missionLog.subtitle">Missions executed and currently running.</span>
<button type="button" class="mapsMirHelpBtn" id="missionLogHelpBtn" data-i18n-title="monitoring.missionLog.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--outline" id="missionLogRefreshBtn">
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M11 2v3H8M3 12V9h3" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><path d="M2.5 5.5A5 5 0 0 1 11 3.5l1.5 1.5M12.5 8.5A5 5 0 0 1 3.5 10.5L2 9" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
<span data-i18n="monitoring.refresh">Refresh</span>
</button>
<button type="button" class="mapsMirBtn mapsMirBtn--outline mapsMirBtn--danger" id="missionLogClearHistoryBtn">
<span data-i18n="monitoring.missionLog.clearHistory">Clear history</span>
</button>
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="missionLogClearFiltersBtn">
<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="monitoring.clearFilters">Clear filters</span>
</button>
</div>
</header>
<div class="mapsMirFilterBar">
<label class="mapsMirFilterLabel" for="missionLogFilterInput" data-i18n="monitoring.filterLabel">Filter:</label>
<input type="search" id="missionLogFilterInput" class="mapsMirFilterInput" data-i18n-placeholder="monitoring.missionLog.filterPlaceholder" placeholder="Filter by mission or state..." autocomplete="off" />
<span id="missionLogFilterCount" class="mapsMirFilterCount">0 item(s) found</span>
<div class="mapsMirPager">
<button type="button" class="mapsMirPageBtn" id="missionLogPageFirst" aria-label="First page">&laquo;</button>
<button type="button" class="mapsMirPageBtn" id="missionLogPagePrev" aria-label="Previous page">&lsaquo;</button>
<span id="missionLogPageLabel" class="mapsMirPageLabel">Page 1 of 1</span>
<button type="button" class="mapsMirPageBtn" id="missionLogPageNext" aria-label="Next page">&rsaquo;</button>
<button type="button" class="mapsMirPageBtn" id="missionLogPageLast" aria-label="Last page">&raquo;</button>
</div>
</div>
<div class="mapsMirTableWrap">
<table class="mapsMirTable mapsMirTable--missionLog" id="missionLogTable">
<thead>
<tr>
<th class="monMirThIcon" aria-hidden="true"></th>
<th data-i18n="monitoring.missionLog.colMission">Mission</th>
<th data-i18n="monitoring.missionLog.colState">State</th>
<th data-i18n="monitoring.missionLog.colMessage">Message</th>
<th data-i18n="monitoring.missionLog.colStart">Start time</th>
<th data-i18n="monitoring.missionLog.colRanFor">Ran for</th>
<th data-i18n="monitoring.missionLog.colStartedBy">Started by</th>
<th class="mapsMirThFunctions" data-i18n="monitoring.missionLog.colFunctions">Functions</th>
</tr>
</thead>
<tbody id="missionLogList"></tbody>
</table>
<div id="missionLogListEmpty" class="mapsMirEmpty" hidden data-i18n="monitoring.missionLog.empty">No missions in the log yet.</div>
</div>
</div>
<div id="monitoringActionLogView" class="mapsMirPage" hidden>
<header class="mapsMirHeader">
<div class="mapsMirHeaderText">
<button type="button" class="mapsMirBtn mapsMirBtn--outline monitoringActionLogBack" id="actionLogBackBtn">
<span data-i18n="monitoring.actionLog.back">← Mission log</span>
</button>
<h1 class="mapsMirTitle" id="actionLogTitle" data-i18n="monitoring.actionLog.title">Mission action log</h1>
<p class="mapsMirSubtitle">
<span id="actionLogSubtitle"></span>
</p>
</div>
<div class="mapsMirHeaderActions">
<a class="mapsMirBtn mapsMirBtn--outline" id="actionLogDownloadBtn" href="#" download>
<span data-i18n="monitoring.actionLog.download">Download</span>
</a>
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="actionLogClearFiltersBtn">
<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="monitoring.clearFilters">Clear filters</span>
</button>
</div>
</header>
<div class="mapsMirFilterBar">
<label class="mapsMirFilterLabel" for="actionLogFilterInput" data-i18n="monitoring.filterLabel">Filter:</label>
<input type="search" id="actionLogFilterInput" class="mapsMirFilterInput" data-i18n-placeholder="monitoring.actionLog.filterPlaceholder" placeholder="Filter by action, state or message..." autocomplete="off" />
<span id="actionLogFilterCount" class="mapsMirFilterCount">0 item(s) found</span>
<div class="mapsMirPager">
<button type="button" class="mapsMirPageBtn" id="actionLogPageFirst" aria-label="First page">&laquo;</button>
<button type="button" class="mapsMirPageBtn" id="actionLogPagePrev" aria-label="Previous page">&lsaquo;</button>
<span id="actionLogPageLabel" class="mapsMirPageLabel">Page 1 of 1</span>
<button type="button" class="mapsMirPageBtn" id="actionLogPageNext" aria-label="Next page">&rsaquo;</button>
<button type="button" class="mapsMirPageBtn" id="actionLogPageLast" aria-label="Last page">&raquo;</button>
</div>
</div>
<div class="mapsMirTableWrap">
<table class="mapsMirTable mapsMirTable--actionLog" id="actionLogTable">
<thead>
<tr>
<th class="monMirThIcon" aria-hidden="true"></th>
<th data-i18n="monitoring.actionLog.colAction">Action</th>
<th data-i18n="monitoring.actionLog.colState">State</th>
<th data-i18n="monitoring.actionLog.colMessage">Message</th>
<th data-i18n="monitoring.actionLog.colStart">Start time</th>
<th data-i18n="monitoring.actionLog.colRanFor">Ran for</th>
</tr>
</thead>
<tbody id="actionLogList"></tbody>
</table>
<div id="actionLogListEmpty" class="mapsMirEmpty" hidden data-i18n="monitoring.actionLog.empty">No actions logged for this mission.</div>
</div>
</div>
</div>
<div class="page" id="pageHelp" data-page-content="help" hidden>
@@ -2783,6 +3132,8 @@ GET /api/v2.0.0/status</pre>
<script src="/topbar.js"></script>
<script src="/dashboard.js"></script>
<script src="/integrations.js"></script>
<script src="/settings.js"></script>
<script src="/monitoring.js"></script>
<script src="/app.js"></script>
</body>
</html>