import { useEffect, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router-dom"; import { api, type Installation } from "../api/client"; import { Shell } from "../components/Shell"; import { StatusBadge } from "../components/StatusBadge"; function CopyChip({ value }: { value: string }) { const [copied, setCopied] = useState(false); async function copy() { try { await navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { // clipboard access denied — nothing to recover, the value is still selectable } } return (
{value}
); } const DEPLOY_LOCKED_STATUSES = new Set(["cloning", "building", "starting"]); export function InstallationDetail({ onLoggedOut }: { onLoggedOut: () => void }) { const { id } = useParams<{ id: string }>(); const location = useLocation(); const navigate = useNavigate(); const [installation, setInstallation] = useState(null); const [envRows, setEnvRows] = useState<[string, string][]>([]); const [portEnvVar, setPortEnvVar] = useState("PORT"); const [hostEnvVar, setHostEnvVar] = useState("HOST"); const [httpPath, setHttpPath] = useState("/mcp"); const [log, setLog] = useState(""); const [logLoaded, setLogLoaded] = useState(false); const [busy, setBusy] = useState(null); const [error, setError] = useState(null); async function refresh() { if (!id) return; const data = await api.getInstallation(id); setInstallation(data); setPortEnvVar(data.portEnvVar); setHostEnvVar(data.hostEnvVar); setHttpPath(data.httpPath); const detectedEnvKeys = (location.state as { detectedEnvKeys?: string[] } | null)?.detectedEnvKeys ?? []; const keys = new Set([...Object.keys(data.env), ...detectedEnvKeys]); setEnvRows([...keys].map((k) => [k, data.env[k] ?? ""])); } useEffect(() => { refresh(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); async function withBusy(label: string, fn: () => Promise) { setBusy(label); setError(null); try { await fn(); } catch (err) { setError((err as Error).message); } finally { setBusy(null); } } async function loadLogs() { if (!installation) return; await withBusy("logs", async () => { const { log } = await api.logs(installation.id); setLog(log); setLogLoaded(true); }); } if (!installation) { return (

Loading…

); } const deployLocked = DEPLOY_LOCKED_STATUSES.has(installation.status); return ( { e.preventDefault(); navigate("/"); }} > ← Back to installations

{installation.name}

{installation.slug}
{installation.statusDetail &&

{installation.statusDetail}

}
{error &&
{error}
} {installation.status === "running" && installation.bearerToken && (
Access Used by MCP clients to reach this server
Public URL
Bearer token
{installation.bearerToken}
)}
Configuration Environment variables and runtime wiring
{envRows.length === 0 &&

No environment variables configured.

} {envRows.map(([k, v], idx) => (
{ const next = [...envRows]; next[idx] = [e.target.value, v]; setEnvRows(next); }} /> { const next = [...envRows]; next[idx] = [k, e.target.value]; setEnvRows(next); }} />
))}
setPortEnvVar(e.target.value)} />
setHostEnvVar(e.target.value)} />
setHttpPath(e.target.value)} />
Actions
Build log
{log}
); }