sl-webui b6c6dbd838 feat: WebUI main dashboard with panel launcher (Issue #630)
Replaces ui/index.html (old USB-serial HUD) with a full rosbridge
dashboard. Adds ui/dashboard.{css,js}.

Top bar:
- Robot name +  SALTYBOT logo
- Live battery % + voltage with fill bar (4S LiPo: 12.0V–16.8V)
- Safety state from /saltybot/safety_zone/status (GREEN/AMBER/RED)
- E-stop state display
- Drive mode display
- ROS uptime counter
- rosbridge WS input + CONNECT button

Panel grid (auto-fill responsive):
- MAP VIEW (#587) — /saltybot/pose/fused liveness dot
- GAMEPAD TELEOP (#598) — /cmd_vel activity indicator
- DIAGNOSTICS (#562) — /diagnostics liveness dot
- EVENT LOG (#576) — /rosout liveness dot
- SETTINGS (#614) — param service (config state, no topic)
- GIMBAL (#551) — /gimbal/state liveness dot

Each card shows: icon, title, issue #, description, topic chips,
and a LIVE/IDLE/OFFLINE status badge updated every second. Cards
open the linked standalone panel in the same tab.

Auto-detect rosbridge:
- Probes: page hostname:9090, localhost:9090, saltybot.local:9090
- Progress dots per candidate (trying/ok/fail)
- Falls back to manual URL entry
- Saves last successful URL to localStorage

Bottom bar:
-  E-STOP button (latches, publishes zero Twist to /cmd_vel)
  Space bar shortcut from dashboard
- RESUME button
- Drive mode switcher: MANUAL / AUTO / FOLLOW / DOCK
  (publishes to /saltybot/drive_mode std_msgs/String)
- Session timer (HH:MM:SS since page load)

Info strip: rosbridge URL · msg rate · latency (5s ping via
/rosapi/get_time) · robot IP

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 14:35:56 -04:00

271 lines
9.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>Saltybot — Dashboard</title>
<link rel="stylesheet" href="dashboard.css">
<script src="https://cdn.jsdelivr.net/npm/roslib@1.3.0/build/roslib.min.js"></script>
</head>
<body>
<!-- ══════════════════════════════════════════════════════ TOP BAR ══ -->
<div id="topbar">
<div id="topbar-left">
<div class="logo">⚡ SALTYBOT</div>
<div id="robot-name">ROBOT-01</div>
</div>
<div id="topbar-status">
<!-- Battery -->
<div class="stat-block" id="stat-battery">
<div class="stat-icon">🔋</div>
<div class="stat-body">
<div class="stat-label">BATTERY</div>
<div class="stat-val" id="val-battery"></div>
</div>
<div class="batt-bar"><div id="batt-fill"></div></div>
</div>
<div class="stat-sep"></div>
<!-- Safety -->
<div class="stat-block">
<div class="stat-icon">🛡️</div>
<div class="stat-body">
<div class="stat-label">SAFETY</div>
<div class="stat-val" id="val-safety"></div>
</div>
</div>
<div class="stat-sep"></div>
<!-- E-stop -->
<div class="stat-block">
<div class="stat-icon"></div>
<div class="stat-body">
<div class="stat-label">E-STOP</div>
<div class="stat-val" id="val-estop" style="color:#6b7280">OFF</div>
</div>
</div>
<div class="stat-sep"></div>
<!-- Drive mode -->
<div class="stat-block">
<div class="stat-icon">🚗</div>
<div class="stat-body">
<div class="stat-label">MODE</div>
<div class="stat-val" id="val-mode"></div>
</div>
</div>
<div class="stat-sep"></div>
<!-- Uptime -->
<div class="stat-block">
<div class="stat-icon"></div>
<div class="stat-body">
<div class="stat-label">UPTIME</div>
<div class="stat-val" id="val-uptime"></div>
</div>
</div>
</div>
<div id="topbar-right">
<div id="conn-dot"></div>
<input id="ws-input" type="text" value="ws://localhost:9090" placeholder="ws://robot-ip:9090" />
<button id="btn-connect" class="hbtn">CONNECT</button>
<span id="conn-label">Not connected</span>
</div>
</div>
<!-- ══════════════════════════════════════════════ PANEL GRID ══ -->
<div id="main">
<!-- Auto-detect bar -->
<div id="detect-bar">
<span id="detect-status">🔍 Auto-detecting rosbridge…</span>
<div id="detect-dots"></div>
</div>
<!-- Panel cards grid -->
<div id="grid">
<a class="panel-card" href="map_panel.html" data-panel="map">
<div class="card-header">
<div class="card-icon" style="color:#22c55e">🗺️</div>
<div>
<div class="card-title">MAP VIEW</div>
<div class="card-sub">#587</div>
</div>
<div class="card-dot" id="dot-map"></div>
</div>
<div class="card-desc">RPLIDAR scan overlay · robot position · UWB anchors · safety zones · breadcrumb trail</div>
<div class="card-topics">
<code>/saltybot/pose/fused</code>
<code>/scan</code>
<code>/saltybot/safety_zone/status</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-map">OFFLINE</span>
<span class="card-msg" id="msg-map">No data</span>
</div>
</a>
<a class="panel-card" href="gamepad_panel.html" data-panel="gamepad">
<div class="card-header">
<div class="card-icon" style="color:#06b6d4">🎮</div>
<div>
<div class="card-title">GAMEPAD TELEOP</div>
<div class="card-sub">#598</div>
</div>
<div class="card-dot" id="dot-gamepad"></div>
</div>
<div class="card-desc">Web Gamepad API · virtual dual sticks · WASD keyboard · speed limits · deadzone config</div>
<div class="card-topics">
<code>/cmd_vel</code>
<code>geometry_msgs/Twist</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-gamepad">OFFLINE</span>
<span class="card-msg" id="msg-gamepad">No data</span>
</div>
</a>
<a class="panel-card" href="diagnostics_panel.html" data-panel="diag">
<div class="card-header">
<div class="card-icon" style="color:#f59e0b">📊</div>
<div>
<div class="card-title">DIAGNOSTICS</div>
<div class="card-sub">#562</div>
</div>
<div class="card-dot" id="dot-diag"></div>
</div>
<div class="card-desc">Battery · CPU/GPU/motor temps · RAM/disk · WiFi signal · ROS2 node health</div>
<div class="card-topics">
<code>/diagnostics</code>
<code>diagnostic_msgs/DiagnosticArray</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-diag">OFFLINE</span>
<span class="card-msg" id="msg-diag">No data</span>
</div>
</a>
<a class="panel-card" href="event_log_panel.html" data-panel="events">
<div class="card-header">
<div class="card-icon" style="color:#a78bfa">📋</div>
<div>
<div class="card-title">EVENT LOG</div>
<div class="card-sub">#576</div>
</div>
<div class="card-dot" id="dot-events"></div>
</div>
<div class="card-desc">Filterable real-time feed · severity badges · node filter · text search · CSV export</div>
<div class="card-topics">
<code>/rosout</code>
<code>/saltybot/events</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-events">OFFLINE</span>
<span class="card-msg" id="msg-events">No data</span>
</div>
</a>
<a class="panel-card" href="settings_panel.html" data-panel="settings">
<div class="card-header">
<div class="card-icon" style="color:#9ca3af">⚙️</div>
<div>
<div class="card-title">SETTINGS</div>
<div class="card-sub">#614</div>
</div>
<div class="card-dot" id="dot-settings" style="background:#374151"></div>
</div>
<div class="card-desc">PID tuning · speed limits · safety thresholds · sensor toggles · named presets</div>
<div class="card-topics">
<code>rcl_interfaces/srv/GetParameters</code>
<code>SetParameters</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-settings" style="color:#6b7280">CONFIG</span>
<span class="card-msg" id="msg-settings">Parameter service</span>
</div>
</a>
<a class="panel-card" href="gimbal_panel.html" data-panel="gimbal">
<div class="card-header">
<div class="card-icon" style="color:#f97316">🎥</div>
<div>
<div class="card-title">GIMBAL</div>
<div class="card-sub">#551</div>
</div>
<div class="card-dot" id="dot-gimbal"></div>
</div>
<div class="card-desc">Pan/tilt joystick · live camera feed · preset positions · person-tracking toggle</div>
<div class="card-topics">
<code>/gimbal/cmd</code>
<code>/gimbal/state</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-gimbal">OFFLINE</span>
<span class="card-msg" id="msg-gimbal">No data</span>
</div>
</a>
</div><!-- /#grid -->
<!-- Connection info strip -->
<div id="info-strip">
<div class="info-item">
<span class="info-lbl">ROSBRIDGE</span>
<span class="info-val" id="info-ws"></span>
</div>
<div class="info-item">
<span class="info-lbl">MSG RATE</span>
<span class="info-val" id="info-rate"></span>
</div>
<div class="info-item">
<span class="info-lbl">LATENCY</span>
<span class="info-val" id="info-latency"></span>
</div>
<div class="info-item">
<span class="info-lbl">ROBOT IP</span>
<span class="info-val" id="info-ip"></span>
</div>
</div>
</div><!-- /#main -->
<!-- ══════════════════════════════════════════════ BOTTOM BAR ══ -->
<div id="bottombar">
<div id="bottombar-left">
<button id="btn-estop" class="estop-btn">⛔ E-STOP</button>
<button id="btn-resume" class="resume-btn" style="display:none">▶ RESUME</button>
</div>
<div id="bottombar-center">
<span class="bb-lbl">DRIVE MODE</span>
<div id="mode-btns">
<button class="mode-btn active" data-mode="MANUAL">MANUAL</button>
<button class="mode-btn" data-mode="AUTO">AUTO</button>
<button class="mode-btn" data-mode="FOLLOW">FOLLOW</button>
<button class="mode-btn" data-mode="DOCK">DOCK</button>
</div>
</div>
<div id="bottombar-right">
<div id="uptime-display">
<span class="bb-lbl">SESSION</span>
<span id="session-time">00:00:00</span>
</div>
<div style="color:#374151;font-size:9px">issue #630</div>
</div>
</div>
<script src="dashboard.js"></script>
</body>
</html>