import React, { useState, useEffect, useRef } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { instancesApi } from '@/lib/api' import { RefreshCw, Download, Copy, CheckCircle, AlertCircle, Loader2, Settings } from 'lucide-react' interface LogsDialogProps { open: boolean onOpenChange: (open: boolean) => void instanceName: string isRunning: boolean } const LogsDialog: React.FC = ({ open, onOpenChange, instanceName, isRunning }) => { const [logs, setLogs] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [lineCount, setLineCount] = useState(100) const [autoRefresh, setAutoRefresh] = useState(false) const [copied, setCopied] = useState(false) const [showSettings, setShowSettings] = useState(false) const logContainerRef = useRef(null) const refreshIntervalRef = useRef(null) // Fetch logs function const fetchLogs = React.useCallback( async (lines?: number) => { if (!instanceName) return setLoading(true) setError(null) try { const logText = await instancesApi.getLogs(instanceName, lines) setLogs(logText) // Auto-scroll to bottom setTimeout(() => { if (logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight } }, 100) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch logs') } finally { setLoading(false) } }, [instanceName] ) // Initial load when dialog opens useEffect(() => { if (open && instanceName) { void fetchLogs(lineCount) } }, [open, instanceName, fetchLogs, lineCount]) // Auto-refresh effect useEffect(() => { if (autoRefresh && isRunning && open) { refreshIntervalRef.current = setInterval(() => { void fetchLogs(lineCount) }, 2000) // Refresh every 2 seconds } else { if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current) refreshIntervalRef.current = null } } return () => { if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current) } } }, [autoRefresh, isRunning, open, lineCount, fetchLogs]) // Copy logs to clipboard const copyLogs = async () => { try { await navigator.clipboard.writeText(logs) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch (err) { console.error('Failed to copy logs:', err) } } // Download logs as file const downloadLogs = () => { const blob = new Blob([logs], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `${instanceName}-logs.txt` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } // Handle line count change const handleLineCountChange = (value: string) => { const num = parseInt(value) || 100 setLineCount(num) } // Apply new line count const applyLineCount = () => { void fetchLogs(lineCount) setShowSettings(false) } // Format logs with basic syntax highlighting const formatLogs = (logText: string) => { if (!logText) return '' return logText.split('\n').map((line, index) => { let className = 'font-mono text-sm leading-relaxed' // Basic log level detection if (line.includes('ERROR') || line.includes('[ERROR]')) { className += ' text-red-400' } else if (line.includes('WARN') || line.includes('[WARN]')) { className += ' text-yellow-400' } else if (line.includes('INFO') || line.includes('[INFO]')) { className += ' text-blue-400' } else if (line.includes('DEBUG') || line.includes('[DEBUG]')) { className += ' text-gray-400' } else if (line.includes('===')) { className += ' text-green-400 font-semibold' } else { className += ' text-gray-300' } return (
{line || '\u00A0'} {/* Non-breaking space for empty lines */}
) }) } return (
Logs: {instanceName} {isRunning ? "Running" : "Stopped"} Instance logs and output
{/* Settings Panel */} {showSettings && (
handleLineCountChange(e.target.value)} className="w-24" min="1" max="10000" />
setAutoRefresh(e.target.checked)} disabled={!isRunning} className="rounded" />
)} {/* Log Content */}
{error && (
{error}
)}
{loading && !logs ? (
Loading logs...
) : logs ? (
{formatLogs(logs)}
) : (
No logs available
)}
{autoRefresh && isRunning && (
Auto-refreshing every 2 seconds
)}
) } export default LogsDialog