import { useCallback, useState } from "react"; import { useRouter } from "@tanstack/react-router"; import { useJobLog } from "~/hooks/use-job-log"; import { formatDuration } from "~/lib/format"; import { StatusDot, runState } from "~/ui/StatusDot"; import { cn } from "~/utils/cn"; const ANSI = /\u001b\[[0-9;?]*[A-Za-z]/g; const SETUP_STEP = -1; type Step = { index: number; name: string; status: string; conclusion: string | null; startedAt: number | null; completedAt: number | null; }; export function JobLog({ jobId, steps }: { jobId: string; steps: Step[] }) { const router = useRouter(); const refresh = useCallback(() => { router.invalidate(); }, [router]); const log = useJobLog(jobId, refresh); const [open, setOpen] = useState>(new Set()); const sections = [ { index: SETUP_STEP, name: "Set up job", status: "completed", conclusion: null, startedAt: null, completedAt: null, }, ...steps, ]; const failing = steps.find((step) => step.conclusion === "failure")?.index; const isOpen = (index: number) => open.has(index) || (open.size === 0 && (index === failing || (log.live && steps.find((step) => step.status === "in_progress")?.index === index))); return (
{log.error &&

{log.error}

} {sections.map((section) => { const lines = log.rows .filter((row) => row.step === section.index) .flatMap((row) => row.text.split("\n").map((text, index) => ({ key: `${row.seq}:${index}`, text }))); const expanded = isOpen(section.index); return (
{expanded && (
                {lines.length === 0 ? (
                  {log.live ? "Waiting for output…" : "No output"}
                ) : null}
                {lines.map((line, number) => (
                  
{number + 1} {line.text.replace(ANSI, "")}
))}
)}
); })} {log.live &&
● streaming
}
); }