"use client"; import { useState, useEffect } from "react"; import { useParams } from "next/navigation"; import { Activity, Loader2, RefreshCw } from "lucide-react"; import { useAnatomy, type Anatomy } from "@/components/project/use-anatomy"; import { THEME, PageHeader, Card, EmptyState, SecondaryButton, } from "@/components/project/dashboard-ui"; import { Terminal } from "@/components/ui/terminal"; type LiveApp = Anatomy["hosting"]["live"][number]; export default function LogsPage() { const params = useParams(); const projectId = params.projectId as string; const { anatomy, loading } = useAnatomy(projectId, { pollMs: 8000 }); const live = anatomy?.hosting.live ?? []; const previews = anatomy?.hosting.previews ?? []; const databases = anatomy?.infrastructure.databases ?? []; const deployments: { uuid: string; name: string }[] = []; live.forEach((app) => { if (app.inFlightBuild) { deployments.push({ uuid: app.inFlightBuild.uuid, name: `Build (Active): ${app.name}`, }); } if (app.lastBuild) { deployments.push({ uuid: app.lastBuild.uuid, name: `Build (Latest): ${app.name}`, }); } }); const [activeItem, setActiveItem] = useState<{ type: "app" | "preview" | "database" | "deployment"; id: string; name: string; } | null>(null); const [logs, setLogs] = useState(null); const [logsLoading, setLogsLoading] = useState(false); // Auto-select first available item useEffect(() => { if (activeItem) return; if (live.length > 0) { setActiveItem({ type: "app", id: live[0].uuid, name: live[0].name }); } else if (previews.length > 0) { setActiveItem({ type: "preview", id: previews[0].id, name: previews[0].name || `Port ${previews[0].port}`, }); } else if (databases.length > 0) { setActiveItem({ type: "database", id: databases[0].uuid, name: databases[0].name, }); } else if (deployments.length > 0) { setActiveItem({ type: "deployment", id: deployments[0].uuid, name: deployments[0].name, }); } }, [live, previews, databases, deployments, activeItem]); const fetchLogs = async (item: { type: "app" | "preview" | "database" | "deployment"; id: string; name: string; }) => { setLogsLoading(true); try { let action = "apps.logs"; let payloadParams: Record = { uuid: item.id, lines: 100 }; if (item.type === "preview") { action = "dev_server.logs"; payloadParams = { id: item.id, lines: 100, projectId }; } else if (item.type === "database") { action = "databases.logs"; } else if (item.type === "deployment") { action = "deployments.logs"; } const r = await fetch(`/api/mcp`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, params: payloadParams, }), }); const d = await r.json(); let out = ""; let obj = d.result; if (typeof obj === "string") { try { obj = JSON.parse(obj); } catch {} } const reverseLogs = (str: string) => str.trim().split("\n").reverse().join("\n"); if (typeof obj === "object" && obj !== null) { if (obj.services) { out = Object.values(obj.services) .map((s: any) => reverseLogs(s.logs || "")) .join("\n\n"); } else if (obj.log) { out = reverseLogs(obj.log); } else if (obj.logs) { out = reverseLogs(obj.logs); } else { out = JSON.stringify(obj, null, 2); } } else { out = String(obj || d.error || "No logs available."); if (typeof obj === "string") { out = reverseLogs(out); } } setLogs(out || "No logs available."); } catch { setLogs("Failed to load logs. Is the container running?"); } finally { setLogsLoading(false); } }; // Fetch when active item changes useEffect(() => { if (activeItem) fetchLogs(activeItem); }, [activeItem]); return (
{loading && !anatomy ? (
Loading…
) : live.length === 0 && previews.length === 0 && databases.length === 0 && deployments.length === 0 ? (
} title="No active servers" hint="Once you deploy an app or start a dev server, its runtime logs will appear here." />
) : (
{/* App Picker Column */}
{live.length > 0 && (

Live Apps

)}
{live.map((app) => ( ))}
{previews.length > 0 && (

Dev Previews

)}
{previews.map((preview) => ( ))}
{databases.length > 0 && (

Databases

)}
{databases.map((db) => ( ))}
{deployments.length > 0 && (

Build Logs

)}
{deployments.map((dep) => ( ))}
{/* Log Viewer Column */}
{activeItem?.name ?? "Logs"} ) : ( ) } onClick={() => activeItem && fetchLogs(activeItem)} disabled={logsLoading} style={{ padding: "4px 8px", fontSize: "0.75rem" }} > Refresh
{logsLoading && !logs ? "Loading..." : logs || "No logs available."}
)}
); }