mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-02 12:02:09 +08:00
rename sidebar and tmux remote
This commit is contained in:
@@ -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" },
|
||||
],
|
||||
|
||||
@@ -617,7 +617,7 @@ export function DeviceSettings({ params }) {
|
||||
|
||||
return html`
|
||||
<div class="ds-wrapper">
|
||||
<h2>Device Settings</h2>
|
||||
<h2>Toggles</h2>
|
||||
|
||||
<input
|
||||
class="ds-search"
|
||||
|
||||
@@ -12,6 +12,16 @@ const logSelectorState = reactive({
|
||||
newName: ""
|
||||
});
|
||||
|
||||
let liveEventSource = null;
|
||||
let livePollTimer = null;
|
||||
let tmuxLifecycleVersion = 0;
|
||||
const REMOTE_POLL_INTERVAL_MS = 2000;
|
||||
const TRANSPORT_LIFECYCLE_INTERVAL_MS = 500;
|
||||
|
||||
function isTmuxRouteActive() {
|
||||
return window.location.pathname === "/manage_tmux";
|
||||
}
|
||||
|
||||
async function loadTmuxLogs() {
|
||||
if (logSelectorState.loading || logSelectorState.logsLoadedOnce) return;
|
||||
|
||||
@@ -177,35 +187,145 @@ function TmuxLogSelector({ action, closeFn }) {
|
||||
}
|
||||
|
||||
export function TmuxLog() {
|
||||
if (isGalaxyTunnel()) {
|
||||
return html`
|
||||
<div class="tunnel-notice">
|
||||
<div class="tunnel-notice-icon">🛰️</div>
|
||||
<h3 class="tunnel-notice-title">Tmux Log Unavailable via Galaxy</h3>
|
||||
<p class="tunnel-notice-body">Live tmux streaming requires a direct connection.<br>Connect to your device's local network to use this feature.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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() {
|
||||
<div class="tmux-block">
|
||||
<div class="tmux-wrapper">
|
||||
<div class="tmuxContainer">
|
||||
<div class="tmuxHeader">Tmux Live Log</div>
|
||||
<div class="tmuxHeader">${() => getStatusLabel()}</div>
|
||||
<pre class="tmuxLog">${() => state.log}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,9 +88,9 @@ export function ToggleControl() {
|
||||
</section>
|
||||
|
||||
<section class="toggle-control-widget" style="margin-left: 1.5rem">
|
||||
<div class="toggle-control-title">Reset Toggles to Default FrogPilot/Stock openpilot</div>
|
||||
<div class="toggle-control-title">Reset Toggles to Default StarPilot</div>
|
||||
<p class="toggle-control-text">
|
||||
Reset all toggles to default FrogPilot/stock openpilot settings.
|
||||
Reset all toggles to default StarPilot settings.
|
||||
</p>
|
||||
<button class="toggle-control-button" @click="${confirmResetDefault}">
|
||||
Reset Toggles to Default
|
||||
|
||||
@@ -3385,15 +3385,40 @@ def setup(app):
|
||||
|
||||
def generate():
|
||||
last_output = ""
|
||||
last_keepalive = 0.0
|
||||
while True:
|
||||
output = subprocess.check_output(["tmux", "capture-pane", "-t", "comma:0", "-p", "-S", "-1000"], text=True)
|
||||
|
||||
if output != last_output:
|
||||
yield "data: " + "\n".join(reversed(output.splitlines())).replace("\n", "\ndata: ") + "\n\n"
|
||||
last_output = output
|
||||
last_keepalive = time.monotonic()
|
||||
elif (time.monotonic() - last_keepalive) >= 5.0:
|
||||
# Keep SSE alive through proxies/tunnels even when output is unchanged.
|
||||
yield ": keepalive\n\n"
|
||||
last_keepalive = time.monotonic()
|
||||
|
||||
time.sleep(0.5)
|
||||
return Response(generate(), mimetype="text/event-stream")
|
||||
response = Response(generate(), mimetype="text/event-stream")
|
||||
response.headers["Cache-Control"] = "no-cache"
|
||||
response.headers["X-Accel-Buffering"] = "no"
|
||||
return response
|
||||
|
||||
@app.route("/api/tmux_log/snapshot", methods=["GET"])
|
||||
def snapshot_tmux_log():
|
||||
try:
|
||||
output = subprocess.check_output(["tmux", "capture-pane", "-t", "comma:0", "-p", "-S", "-1000"], text=True)
|
||||
except subprocess.CalledProcessError:
|
||||
run_cmd(["tmux", "new-session", "-d", "-s", "comma", "-x", "240", "-y", "70", "bash"], "Started tmux session", "Failed to start tmux session")
|
||||
output = subprocess.check_output(["tmux", "capture-pane", "-t", "comma:0", "-p", "-S", "-1000"], text=True)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
try:
|
||||
live_text = "\n".join(reversed(output.splitlines()))
|
||||
return jsonify({"data": live_text}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/tmux_log/rename/<old>/<new>", methods=["PUT"])
|
||||
def rename_tmux_log_path_params(old, new):
|
||||
|
||||
Reference in New Issue
Block a user