import { useCallback, useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { getDashboardSummary, type DashboardSummary } from "../api/dashboard"; import { getUserSessions, startInstance, stopInstance, deleteInstance, recreateInstanceTunnel, checkInstanceHealth, type Session as SessionApi, type InstanceHealth } from "../api/sessions"; import { listProjects } from "../api/projects"; import { listRepositories, type GitRepository } from "../api/git_repositories"; import { listToolTypes, type ToolType } from "../api/tool_types"; import { updateUserConfig } from "../api/settings"; import type { Project } from "../types"; import { Icon } from "../components/icon"; import { CreateSessionForm } from "../components/create-session-form"; type HomeStatus = "loading" | "ready" | "error"; const summaryCards = [ { label: "Open sessions", key: "openSessions" }, { label: "Projects", key: "projects" }, { label: "Repositories", key: "repositories" }, ] as const; type SessionView = SessionApi; export const HomePage = () => { const navigate = useNavigate(); const [status, setStatus] = useState("loading"); const [summary, setSummary] = useState(null); const [sessions, setSessions] = useState([]); const [projects, setProjects] = useState([]); const [repositories, setRepositories] = useState([]); const [toolTypes, setToolTypes] = useState([]); const [selectedProject, setSelectedProject] = useState(""); const [actionBusy, setActionBusy] = useState(null); const [stopConfirmId, setStopConfirmId] = useState(null); const [deleteConfirmId, setDeleteConfirmId] = useState(null); const [tunnelHealth, setTunnelHealth] = useState>({}); const safeSessions = Array.isArray(sessions) ? sessions : []; const loadHome = useCallback(async () => { setStatus("loading"); try { const [dashboard, sessionData, projectData, toolTypeData] = await Promise.all([ getDashboardSummary(), getUserSessions(), listProjects(), listToolTypes(), ]); setSummary(dashboard); setSessions(sessionData as SessionView[]); setProjects(projectData); setToolTypes(toolTypeData); setStatus("ready"); } catch { setStatus("error"); } }, []); useEffect(() => { void loadHome(); }, [loadHome]); // Poll tunnel health every 30 seconds for running instances useEffect(() => { const checkHealth = async () => { const runningSessions = safeSessions.filter( (s) => s.status === "running" && s.url ); for (const session of runningSessions) { try { const health = await checkInstanceHealth( session.project_id, session.repository_id, session.id ); setTunnelHealth((prev) => ({ ...prev, [session.id]: health, })); } catch { setTunnelHealth((prev) => ({ ...prev, [session.id]: { healthy: false, container_status: "unknown", container_health: null, container_exit_code: null, tunnel_status: "error", tunnel_status_code: null, probe_status: "error", last_probe_output: null, error: "check failed", }, })); } } }; void checkHealth(); const interval = setInterval(() => { void checkHealth(); }, 30000); return () => clearInterval(interval); }, [safeSessions]); useEffect(() => { if (!selectedProject) { setRepositories([]); return; } const loadRepos = async () => { try { const data = await listRepositories(selectedProject); setRepositories(data); } catch { setRepositories([]); } }; void loadRepos(); }, [selectedProject]); const activeSessions = useMemo( () => safeSessions.filter((session) => ["running", "building", "pending"].includes(session.status)), [safeSessions] ); const recentSessions = useMemo( () => safeSessions.filter((session) => ["stopped", "error"].includes(session.status)).slice(0, 5), [safeSessions] ); const handleCreateSuccess = async (instance: { id: string }) => { await updateUserConfig({ last_session_id: instance.id }); setSelectedProject(""); await loadHome(); }; const handleOpen = (session: SessionView) => { if (session.url) { window.open(session.url, "_blank", "noopener,noreferrer"); return; } if (session.tool_type_interfaces.includes("terminal")) { navigate(`/instances/${session.id}/terminal`); return; } navigate(`/projects/${session.project_id}`); }; const handleStop = async (session: SessionView) => { if (stopConfirmId !== session.id) { setStopConfirmId(session.id); return; } setActionBusy(session.id); setStopConfirmId(null); try { await stopInstance(session.project_id, session.repository_id, session.id); await loadHome(); } finally { setActionBusy(null); } }; const handleDelete = async (session: SessionView) => { if (deleteConfirmId !== session.id) { setDeleteConfirmId(session.id); return; } setActionBusy(session.id); setDeleteConfirmId(null); try { await deleteInstance(session.project_id, session.repository_id, session.id); setSessions((prev) => prev.filter((s) => s.id !== session.id)); } catch { // error - session remains in state } finally { setActionBusy(null); } }; const handleRecreateTunnel = async (session: SessionView) => { setActionBusy(session.id); try { await recreateInstanceTunnel(session.project_id, session.repository_id, session.id); await loadHome(); } finally { setActionBusy(null); } }; return (

Workspace overview

Home

Open sessions, available projects, and the fastest path back into work.

{status === "loading" &&

Loading overview...

} {status === "error" && (

Unable to load your workspace overview.

)} {status === "ready" && summary && ( <>
{summaryCards.map((card) => (

{card.label}

{card.key === "openSessions" ? activeSessions.length : card.key === "projects" ? summary.projects : summary.repositories}

))}

Open sessions

{activeSessions.length}

{activeSessions.length === 0 ? (

No active sessions right now.

) : (
{activeSessions.map((session) => { const health = tunnelHealth[session.id]; const isUnhealthy = health && !health.healthy; return (

{session.display_name}

{isUnhealthy && ( ! )} {session.status}

{session.project_name} · {session.repository_name}

{session.tool_type_name}

{stopConfirmId === session.id ? (
Stop?
) : ( )} {deleteConfirmId === session.id ? (
Delete?
) : ( )}
); })}
)}

Available projects

{projects.length}

{projects.length === 0 ? (

No projects yet.

) : (
{projects.map((project) => (

{project.name}

{project.description &&

{project.description}

}
))}
)}

Quick create

Start a session

setSelectedProject(projectId)} onSuccess={handleCreateSuccess} />
{recentSessions.length > 0 && (

Recent sessions

{recentSessions.length}

{recentSessions.map((session) => (
{session.display_name} {session.project_name} · {session.tool_type_name}
))}
)} )}
); }; export { HomePage as DashboardPage };