diff --git a/frogpilot/system/the_pond/assets/components/sidebar.js b/frogpilot/system/the_pond/assets/components/sidebar.js index 06c22a6d4..1a497ba0c 100644 --- a/frogpilot/system/the_pond/assets/components/sidebar.js +++ b/frogpilot/system/the_pond/assets/components/sidebar.js @@ -15,7 +15,7 @@ const MenuItems = { { name: "Screen Recordings", link: "/screen_recordings", icon: "bi-record-circle" }, ], tools: [ - { name: "Device Settings", link: "/device_settings", icon: "bi-sliders" }, + { name: "Toggles", link: "/device_settings", icon: "bi-toggle-on" }, { name: "Download Speed Limits", link: "/download_speed_limits", icon: "bi-download" }, { name: "Error Logs", link: "/manage_error_logs", icon: "bi-exclamation-triangle" }, { name: "Galaxy", link: "/galaxy", icon: "bi-globe2" }, @@ -24,7 +24,7 @@ const MenuItems = { { name: "Testing Ground", link: "/testing_ground", icon: "bi-bezier2" }, { name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill" }, { name: "Tmux Log", link: "/manage_tmux", icon: "bi-terminal" }, - { name: "Toggles", link: "/manage_toggles", icon: "bi-toggle-on" }, + { name: "Backup and Restore", link: "/manage_toggles", icon: "bi-arrow-repeat" }, { name: "Software", link: "/manage_updates", icon: "bi-arrow-up-circle" }, { name: "Vehicle Features", link: "/vehicle_features", icon: "bi-car-front" }, ], diff --git a/frogpilot/system/the_pond/assets/components/tools/device_settings.js b/frogpilot/system/the_pond/assets/components/tools/device_settings.js index caa9d1dfe..34f023b69 100644 --- a/frogpilot/system/the_pond/assets/components/tools/device_settings.js +++ b/frogpilot/system/the_pond/assets/components/tools/device_settings.js @@ -617,7 +617,7 @@ export function DeviceSettings({ params }) { return html`
-

Device Settings

+

Toggles

-
🛰️
-

Tmux Log Unavailable via Galaxy

-

Live tmux streaming requires a direct connection.
Connect to your device's local network to use this feature.

-
- `; - } - const state = reactive({ paused: false, latest: '', log: '', selectorAction: null, + transport: "connecting", }); + const lifecycleVersion = ++tmuxLifecycleVersion; + let lifecycleTimer = null; - const event_source = new EventSource("/api/tmux_log/live"); - - event_source.onmessage = e => { - state.latest = e.data; + function applyIncomingLog(data) { + state.latest = data || ""; if (!state.paused) { state.log = state.latest; } } - event_source.onerror = err => { - console.error("Error receiving tmux log:", err); - event_source.close(); + function stopLiveTransport() { + if (liveEventSource) { + liveEventSource.close(); + liveEventSource = null; + } + if (livePollTimer) { + clearInterval(livePollTimer); + livePollTimer = null; + } + } + + async function fetchSnapshot() { + try { + const response = await fetch("/api/tmux_log/snapshot"); + if (!response.ok) throw new Error(await response.text()); + const payload = await response.json(); + applyIncomingLog(payload?.data || ""); + } catch (err) { + console.error("Error fetching tmux log snapshot:", err); + } + } + + function startPolling() { + if (livePollTimer) return; + if (liveEventSource) { + liveEventSource.close(); + liveEventSource = null; + } + state.transport = "polling"; + fetchSnapshot(); + livePollTimer = setInterval(() => { + if (!isTmuxRouteActive()) { + stopLiveTransport(); + return; + } + if (document.visibilityState !== "visible") { + return; + } + fetchSnapshot(); + }, REMOTE_POLL_INTERVAL_MS); + } + + function startEventStream() { + if (liveEventSource) return; + if (livePollTimer) { + clearInterval(livePollTimer); + livePollTimer = null; + } + state.transport = "streaming"; + liveEventSource = new EventSource("/api/tmux_log/live"); + let receivedAnyMessage = false; + + liveEventSource.onmessage = e => { + if (!isTmuxRouteActive() || document.visibilityState !== "visible") return; + receivedAnyMessage = true; + applyIncomingLog(e.data); + }; + + liveEventSource.onerror = err => { + console.error("Error receiving tmux log stream:", err); + if (!isTmuxRouteActive()) { + stopLiveTransport(); + return; + } + startPolling(); + }; + + // If SSE connects but no payload arrives quickly (common through tunnels), fallback. + setTimeout(() => { + if (!receivedAnyMessage && state.transport === "streaming" && isTmuxRouteActive()) { + startPolling(); + } + }, 3000); + } + + function updateTransportForLifecycle() { + if (lifecycleVersion !== tmuxLifecycleVersion) { + stopLiveTransport(); + if (lifecycleTimer) { + clearInterval(lifecycleTimer); + lifecycleTimer = null; + } + return; + } + + if (!isTmuxRouteActive()) { + stopLiveTransport(); + state.transport = "inactive"; + if (lifecycleTimer) { + clearInterval(lifecycleTimer); + lifecycleTimer = null; + } + return; + } + + if (document.visibilityState !== "visible") { + stopLiveTransport(); + state.transport = "paused"; + return; + } + + if (isGalaxyTunnel()) { + startPolling(); + return; + } + + if (!liveEventSource && !livePollTimer) { + startEventStream(); + } + } + + updateTransportForLifecycle(); + lifecycleTimer = setInterval(updateTransportForLifecycle, TRANSPORT_LIFECYCLE_INTERVAL_MS); + + function getStatusLabel() { + if (state.transport === "polling") { + return "Tmux Live Log (Remote Mode)"; + } + if (state.transport === "streaming") { + return "Tmux Live Log"; + } + return "Tmux Live Log"; } function togglePause() { @@ -258,7 +378,7 @@ export function TmuxLog() {
-
Tmux Live Log
+
${() => getStatusLabel()}
${() => state.log}
diff --git a/frogpilot/system/the_pond/assets/components/tools/toggles.js b/frogpilot/system/the_pond/assets/components/tools/toggles.js index db685061a..f2afcdd8a 100644 --- a/frogpilot/system/the_pond/assets/components/tools/toggles.js +++ b/frogpilot/system/the_pond/assets/components/tools/toggles.js @@ -88,9 +88,9 @@ export function ToggleControl() {
-
Reset Toggles to Default FrogPilot/Stock openpilot
+
Reset Toggles to Default StarPilot

- Reset all toggles to default FrogPilot/stock openpilot settings. + Reset all toggles to default StarPilot settings.