import React, { useEffect, useRef, useState } from "react"; import { useOptionalConnectionLog } from "@/ssh/connection-log/ConnectionLogContext.tsx"; import { useTranslation } from "react-i18next"; import { copyToClipboard } from "@/lib/clipboard.ts"; import { Button } from "@/components/button.tsx"; import { cn } from "@/lib/utils.ts"; import { ChevronDown, ChevronUp, Copy, Info, CheckCircle2, AlertTriangle, XCircle, } from "lucide-react"; import { toast } from "sonner"; interface ConnectionLogPanelProps { isConnecting: boolean; isConnected: boolean; hasConnectionError: boolean; position?: "top" | "bottom"; className?: string; } const COLLAPSED_HEIGHT = "h-[140px]"; const EXPANDED_HEIGHT = "h-[45%] min-h-[240px]"; export function ConnectionLogPanel({ isConnecting, isConnected, hasConnectionError, position = "bottom", className, }: ConnectionLogPanelProps) { const { t } = useTranslation(); const connectionLog = useOptionalConnectionLog(); const { logs, clearLogs, isExpanded, toggleExpanded, setIsExpanded } = connectionLog ?? {}; const lastLogRef = useRef(null); const [manuallyCollapsed, setManuallyCollapsed] = useState(false); useEffect(() => { if (hasConnectionError && setIsExpanded) { setManuallyCollapsed(false); setIsExpanded(true); } }, [hasConnectionError, setIsExpanded]); useEffect(() => { if (isConnected && !hasConnectionError && !isConnecting && clearLogs) { clearLogs(); setManuallyCollapsed(false); } }, [isConnected, hasConnectionError, isConnecting, clearLogs]); useEffect(() => { if (lastLogRef.current) { lastLogRef.current.scrollIntoView({ block: "end" }); } }, [logs]); const shouldShow = !!connectionLog && !isConnected && (isConnecting || hasConnectionError || logs.length > 0); if (!shouldShow) { return null; } const expanded = isExpanded && !manuallyCollapsed; const handleToggle = () => { if (hasConnectionError) { setManuallyCollapsed((prev) => !prev); return; } toggleExpanded(); }; const copyLogsToClipboard = async () => { const logsText = logs .map((log) => { const time = log.timestamp.toLocaleTimeString(); return `[${time}] [${log.type.toUpperCase()}] ${log.message}`; }) .join("\n"); const ok = await copyToClipboard(logsText); if (ok) toast.success(t("terminal.connectionLogCopied")); else toast.error(t("terminal.connectionLogCopyFailed")); }; const getIcon = (type: string) => { switch (type) { case "info": return ; case "success": return ; case "warning": return ; case "error": return ; default: return ; } }; const getTextColor = (type: string) => { switch (type) { case "info": return "text-blue-400"; case "success": return "text-green-400"; case "warning": return "text-yellow-400"; case "error": return "text-red-400"; default: return "text-muted-foreground"; } }; return (
{logs.length > 0 && ( )}
{logs.length === 0 ? (
{isConnecting ? t("terminal.connectionLogWaiting") : t("terminal.connectionLogEmpty")}
) : (
{logs.map((log, index) => (
{log.timestamp.toLocaleTimeString()} {getIcon(log.type)} {log.message}
))}
)}
); }