"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 [activeItem, setActiveItem] = useState<{ type: "app" | "preview"; id: 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 }); } else if (previews.length > 0) { setActiveItem({ type: "preview", id: previews[0].id }); } }, [live, previews, activeItem]); const fetchLogs = async (item: { type: "app" | "preview"; id: string }) => { setLogsLoading(true); try { const action = item.type === "app" ? "apps.logs" : "dev_server.logs"; const payloadParams = item.type === "app" ? { uuid: item.id, lines: 100 } : { id: item.id, lines: 100, projectId }; 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 {} } if (typeof obj === "object" && obj !== null) { if (obj.services) { out = Object.values(obj.services) .map((s: any) => s.logs) .join("\n\n"); } else if (obj.log) { out = obj.log; } else if (obj.logs) { out = obj.logs; } else { out = JSON.stringify(obj, null, 2); } } else { out = String(obj || d.error || "No logs available."); } 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 ? (
} 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) => ( ))}
{/* Log Viewer Column */}
{activeItem?.type === "app" ? (live.find((a) => a.uuid === activeItem.id)?.name ?? "Logs") : previews.find((p) => p.id === activeItem?.id)?.name || "Dev Server Logs"} ) : ( ) } onClick={() => activeItem && fetchLogs(activeItem)} disabled={logsLoading} style={{ padding: "4px 8px", fontSize: "0.75rem" }} > Refresh
{logsLoading && !logs ? "Loading..." : logs || "No logs available."}
)}
); }