a3d01dd0a5
- Initialize loading=true in useTerminalSessions to prevent auto-create from firing before initial load completes - Remove hasAutoCreated ref from TerminalPage (no longer needed) - Add focus() to TerminalRef, call on tab switch - Add term.focus() after term.open() in TerminalComponent - Add console logging for WebSocket send/receive to debug no-i/o - Revert backend _read_loop retry logic to original break-on-error
146 lines
3.7 KiB
TypeScript
146 lines
3.7 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import {
|
|
listTerminalSessions,
|
|
createTerminalSession,
|
|
closeTerminalSession,
|
|
resetTerminalSession,
|
|
renameTerminalSession,
|
|
type TerminalSession,
|
|
} from "../api/terminal";
|
|
|
|
export interface UseTerminalSessionsResult {
|
|
sessions: TerminalSession[];
|
|
activeSessionId: string | null;
|
|
setActiveSessionId: (id: string) => void;
|
|
createSession: (name?: string) => Promise<TerminalSession | null>;
|
|
closeSession: (sessionId: string) => Promise<void>;
|
|
renameSession: (sessionId: string, name: string) => Promise<void>;
|
|
resetSession: (sessionId: string) => Promise<void>;
|
|
loading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
export function useTerminalSessions(
|
|
instanceId: string,
|
|
): UseTerminalSessionsResult {
|
|
const [sessions, setSessions] = useState<TerminalSession[]>([]);
|
|
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const loadSessions = useCallback(async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const sess = await listTerminalSessions(instanceId);
|
|
setSessions(sess);
|
|
if (sess.length > 0 && !activeSessionId) {
|
|
setActiveSessionId(sess[0].id);
|
|
}
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Failed to load sessions");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [instanceId, activeSessionId]);
|
|
|
|
const createSession = useCallback(
|
|
async (name?: string) => {
|
|
setError(null);
|
|
try {
|
|
const newSession = await createTerminalSession(instanceId, name);
|
|
const session: TerminalSession = {
|
|
id: newSession.id,
|
|
name: newSession.name,
|
|
status: newSession.status,
|
|
has_websockets: false,
|
|
created_at: newSession.created_at,
|
|
last_activity_at: null,
|
|
};
|
|
setSessions((prev) => [...prev, session]);
|
|
setActiveSessionId(session.id);
|
|
return session;
|
|
} catch (err) {
|
|
const msg =
|
|
err instanceof Error ? err.message : "Failed to create session";
|
|
setError(msg);
|
|
return null;
|
|
}
|
|
},
|
|
[instanceId],
|
|
);
|
|
|
|
const closeSession = useCallback(
|
|
async (sessionId: string) => {
|
|
setError(null);
|
|
try {
|
|
await closeTerminalSession(instanceId, sessionId);
|
|
setSessions((prev) => {
|
|
const filtered = prev.filter((s) => s.id !== sessionId);
|
|
if (activeSessionId === sessionId && filtered.length > 0) {
|
|
setActiveSessionId(filtered[0].id);
|
|
} else if (filtered.length === 0) {
|
|
setActiveSessionId(null);
|
|
}
|
|
return filtered;
|
|
});
|
|
} catch (err) {
|
|
setError(
|
|
err instanceof Error ? err.message : "Failed to close session",
|
|
);
|
|
}
|
|
},
|
|
[instanceId, activeSessionId],
|
|
);
|
|
|
|
const renameSession = useCallback(
|
|
async (sessionId: string, name: string) => {
|
|
setError(null);
|
|
try {
|
|
await renameTerminalSession(instanceId, sessionId, name);
|
|
setSessions((prev) =>
|
|
prev.map((s) => (s.id === sessionId ? { ...s, name } : s)),
|
|
);
|
|
} catch (err) {
|
|
setError(
|
|
err instanceof Error ? err.message : "Failed to rename session",
|
|
);
|
|
}
|
|
},
|
|
[instanceId],
|
|
);
|
|
|
|
const resetSession = useCallback(
|
|
async (sessionId: string) => {
|
|
setError(null);
|
|
try {
|
|
await resetTerminalSession(instanceId, sessionId);
|
|
// Refetch to get updated session info
|
|
await loadSessions();
|
|
} catch (err) {
|
|
setError(
|
|
err instanceof Error ? err.message : "Failed to reset session",
|
|
);
|
|
}
|
|
},
|
|
[instanceId, loadSessions],
|
|
);
|
|
|
|
// Initial load
|
|
useEffect(() => {
|
|
void loadSessions();
|
|
}, [loadSessions]);
|
|
|
|
return {
|
|
sessions,
|
|
activeSessionId,
|
|
setActiveSessionId,
|
|
createSession,
|
|
closeSession,
|
|
renameSession,
|
|
resetSession,
|
|
loading,
|
|
error,
|
|
};
|
|
}
|