fix(web): only connect terminal ws when process is running
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { FitAddon } from "@xterm/addon-fit";
|
|||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { useProcessWebSocket } from "@/lib/api/process";
|
import { useProcessStatus, useProcessWebSocket } from "@/lib/api/process";
|
||||||
|
|
||||||
interface TerminalPanelProps {
|
interface TerminalPanelProps {
|
||||||
workspaceId: string | null;
|
workspaceId: string | null;
|
||||||
@@ -16,7 +16,12 @@ export function TerminalPanel({ workspaceId }: TerminalPanelProps) {
|
|||||||
const isDeadRef = useRef(false);
|
const isDeadRef = useRef(false);
|
||||||
const [failed, setFailed] = useState(false);
|
const [failed, setFailed] = useState(false);
|
||||||
const [isDead, setIsDead] = useState(false);
|
const [isDead, setIsDead] = useState(false);
|
||||||
const { status, send, onData } = useProcessWebSocket(workspaceId);
|
const { data: processStatus } = useProcessStatus(workspaceId);
|
||||||
|
const processRunning = processStatus?.running === true;
|
||||||
|
const { status, send, onData } = useProcessWebSocket(
|
||||||
|
workspaceId,
|
||||||
|
processRunning,
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize xterm once and keep it alive across WS status changes.
|
// Initialize xterm once and keep it alive across WS status changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -148,11 +153,15 @@ export function TerminalPanel({ workspaceId }: TerminalPanelProps) {
|
|||||||
? "connecting…"
|
? "connecting…"
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const hint = processRunning
|
||||||
|
? statusHint
|
||||||
|
: "process not running — start it from the toolbar";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex h-full w-full flex-col overflow-hidden bg-[#171717]">
|
<section className="flex h-full w-full flex-col overflow-hidden bg-[#171717]">
|
||||||
{statusHint && (
|
{hint && (
|
||||||
<div className="flex h-6 shrink-0 items-center px-2 text-[11px] text-muted-foreground">
|
<div className="flex h-6 shrink-0 items-center px-2 text-[11px] text-muted-foreground">
|
||||||
{statusHint}
|
{hint}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={containerRef} className="min-h-0 flex-1 p-2" />
|
<div ref={containerRef} className="min-h-0 flex-1 p-2" />
|
||||||
|
|||||||
@@ -97,9 +97,10 @@ export interface ProcessWebSocket {
|
|||||||
|
|
||||||
export function useProcessWebSocket(
|
export function useProcessWebSocket(
|
||||||
workspaceId: string | null,
|
workspaceId: string | null,
|
||||||
|
enabled: boolean = true,
|
||||||
): ProcessWebSocket {
|
): ProcessWebSocket {
|
||||||
const [status, setStatus] = useState<ProcessWebSocketStatus>(() =>
|
const [status, setStatus] = useState<ProcessWebSocketStatus>(() =>
|
||||||
workspaceId ? "connecting" : "idle",
|
workspaceId && enabled ? "connecting" : "idle",
|
||||||
);
|
);
|
||||||
const statusRef = useRef(status);
|
const statusRef = useRef(status);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,14 +134,26 @@ export function useProcessWebSocket(
|
|||||||
}, [clearReconnectTimeout]);
|
}, [clearReconnectTimeout]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspaceId) {
|
if (!workspaceId || !enabled) {
|
||||||
setStatus("idle");
|
setStatus("idle");
|
||||||
|
clearReconnectTimeout();
|
||||||
|
const socket = socketRef.current;
|
||||||
|
if (socket) {
|
||||||
|
socket.onclose = null;
|
||||||
|
socket.close();
|
||||||
|
socketRef.current = null;
|
||||||
|
}
|
||||||
|
reconnectAttemptsRef.current = 0;
|
||||||
|
lastFrameWasExitRef.current = false;
|
||||||
|
intentionalCloseRef.current = false;
|
||||||
|
serverCleanCloseRef.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnectAttemptsRef.current = 0;
|
reconnectAttemptsRef.current = 0;
|
||||||
lastFrameWasExitRef.current = false;
|
lastFrameWasExitRef.current = false;
|
||||||
intentionalCloseRef.current = false;
|
intentionalCloseRef.current = false;
|
||||||
|
serverCleanCloseRef.current = false;
|
||||||
|
|
||||||
const openSocket = () => {
|
const openSocket = () => {
|
||||||
const wsUrl =
|
const wsUrl =
|
||||||
@@ -211,9 +224,15 @@ export function useProcessWebSocket(
|
|||||||
openSocket();
|
openSocket();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
closeSocket();
|
clearReconnectTimeout();
|
||||||
|
const socket = socketRef.current;
|
||||||
|
if (socket) {
|
||||||
|
socket.onclose = null;
|
||||||
|
socket.close();
|
||||||
|
socketRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [workspaceId, closeSocket]);
|
}, [workspaceId, enabled, clearReconnectTimeout]);
|
||||||
|
|
||||||
const send = useCallback((data: string) => {
|
const send = useCallback((data: string) => {
|
||||||
const socket = socketRef.current;
|
const socket = socketRef.current;
|
||||||
|
|||||||
Reference in New Issue
Block a user