Files
headquarter/apps/web/src/hooks/use-terminal-sessions.ts
T
alex a3d01dd0a5 fix(terminal): loading state, focus handling, debug logging
- 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
2026-05-28 20:14:54 +02:00

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,
};
}