'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { parseDeploymentLogs, DeploymentLog, DeploymentStatus } from '@/lib/deployments'; import { Icon } from './Icons'; interface DeploymentLogsProps { deploymentUuid: string; status: DeploymentStatus; initialLogs?: string; } export function DeploymentLogs({ deploymentUuid, status, initialLogs }: DeploymentLogsProps) { const [logs, setLogs] = useState(() => parseDeploymentLogs(initialLogs)); const [isPolling, setIsPolling] = useState(status === 'in_progress' || status === 'queued'); const [copied, setCopied] = useState(false); const logsEndRef = useRef(null); const containerRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); const fetchLogs = useCallback(async () => { try { const response = await fetch(`/api/deployments/${deploymentUuid}`); if (response.ok) { const data = await response.json(); const parsedLogs = parseDeploymentLogs(data.logs); setLogs(parsedLogs); // Stop polling if deployment finished if (data.status !== 'in_progress' && data.status !== 'queued') { setIsPolling(false); } } } catch (error) { console.error('Failed to fetch logs:', error); } }, [deploymentUuid]); // Poll for logs while deployment is in progress useEffect(() => { if (!isPolling) return; const interval = setInterval(fetchLogs, 2000); return () => clearInterval(interval); }, [isPolling, fetchLogs]); // Initial fetch if no logs provided useEffect(() => { if (!initialLogs) { fetchLogs(); } }, [initialLogs, fetchLogs]); // Auto-scroll to bottom when new logs arrive useEffect(() => { if (autoScroll && logsEndRef.current) { logsEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [logs, autoScroll]); // Detect manual scroll to disable auto-scroll const handleScroll = () => { if (!containerRef.current) return; const { scrollTop, scrollHeight, clientHeight } = containerRef.current; // If user scrolled up more than 100px from bottom, disable auto-scroll setAutoScroll(scrollHeight - scrollTop - clientHeight < 100); }; const copyToClipboard = async () => { const text = logs.map((log) => log.output).join('\n'); try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (error) { console.error('Failed to copy:', error); } }; return (
{/* Header */}
Build Logs
{isPolling && ( 2s )}
{/* Logs */}
{logs.length === 0 ? (
{isPolling ? 'Waiting for logs...' : 'No logs available'}
) : ( logs.map((log, index) => (
{log.timestamp && ( {log.timestamp} )} {log.output}
)) )}
{/* Auto-scroll indicator */} {!autoScroll && logs.length > 0 && ( )}
); }