import { useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Server } from '@/types'; import { ChevronDown, ChevronRight, AlertCircle, Copy, Check } from 'lucide-react'; import { StatusBadge } from '@/components/ui/Badge'; import ToolCard from '@/components/ui/ToolCard'; import PromptCard from '@/components/ui/PromptCard'; import DeleteDialog from '@/components/ui/DeleteDialog'; import { useToast } from '@/contexts/ToastContext'; import { useSettingsData } from '@/hooks/useSettingsData'; interface ServerCardProps { server: Server; onRemove: (serverName: string) => void; onEdit: (server: Server) => void; onToggle?: (server: Server, enabled: boolean) => Promise; onRefresh?: () => void; } const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCardProps) => { const { t } = useTranslation(); const { showToast } = useToast(); const [isExpanded, setIsExpanded] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [isToggling, setIsToggling] = useState(false); const [showErrorPopover, setShowErrorPopover] = useState(false); const [copied, setCopied] = useState(false); const errorPopoverRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (errorPopoverRef.current && !errorPopoverRef.current.contains(event.target as Node)) { setShowErrorPopover(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const { exportMCPSettings } = useSettingsData(); const handleRemove = (e: React.MouseEvent) => { e.stopPropagation(); setShowDeleteDialog(true); }; const handleEdit = (e: React.MouseEvent) => { e.stopPropagation(); onEdit(server); }; const handleToggle = async (e: React.MouseEvent) => { e.stopPropagation(); if (isToggling || !onToggle) return; setIsToggling(true); try { await onToggle(server, !(server.enabled !== false)); } finally { setIsToggling(false); } }; const handleErrorIconClick = (e: React.MouseEvent) => { e.stopPropagation(); setShowErrorPopover(!showErrorPopover); }; const copyToClipboard = (e: React.MouseEvent) => { e.stopPropagation(); if (!server.error) return; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(server.error).then(() => { setCopied(true); showToast(t('common.copySuccess') || 'Copied to clipboard', 'success'); setTimeout(() => setCopied(false), 2000); }); } else { // Fallback for HTTP or unsupported clipboard API const textArea = document.createElement('textarea'); textArea.value = server.error; // Avoid scrolling to bottom textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); setCopied(true); showToast(t('common.copySuccess') || 'Copied to clipboard', 'success'); setTimeout(() => setCopied(false), 2000); } catch (err) { showToast(t('common.copyFailed') || 'Copy failed', 'error'); console.error('Copy to clipboard failed:', err); } document.body.removeChild(textArea); } }; const handleCopyServerConfig = async (e: React.MouseEvent) => { e.stopPropagation(); try { const result = await exportMCPSettings(server.name); const configJson = JSON.stringify(result.data, null, 2); if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(configJson); showToast(t('common.copySuccess') || 'Copied to clipboard', 'success'); } else { // Fallback for HTTP or unsupported clipboard API const textArea = document.createElement('textarea'); textArea.value = configJson; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showToast(t('common.copySuccess') || 'Copied to clipboard', 'success'); } catch (err) { showToast(t('common.copyFailed') || 'Copy failed', 'error'); console.error('Copy to clipboard failed:', err); } document.body.removeChild(textArea); } } catch (error) { console.error('Error copying server configuration:', error); showToast(t('common.copyFailed') || 'Copy failed', 'error'); } }; const handleConfirmDelete = () => { onRemove(server.name); setShowDeleteDialog(false); }; const handleToolToggle = async (toolName: string, enabled: boolean) => { try { const { toggleTool } = await import('@/services/toolService'); const result = await toggleTool(server.name, toolName, enabled); if (result.success) { showToast( t(enabled ? 'tool.enableSuccess' : 'tool.disableSuccess', { name: toolName }), 'success', ); // Trigger refresh to update the tool's state in the UI if (onRefresh) { onRefresh(); } } else { showToast(result.error || t('tool.toggleFailed'), 'error'); } } catch (error) { console.error('Error toggling tool:', error); showToast(t('tool.toggleFailed'), 'error'); } }; const handlePromptToggle = async (promptName: string, enabled: boolean) => { try { const { togglePrompt } = await import('@/services/promptService'); const result = await togglePrompt(server.name, promptName, enabled); if (result.success) { showToast( t(enabled ? 'tool.enableSuccess' : 'tool.disableSuccess', { name: promptName }), 'success', ); // Trigger refresh to update the prompt's state in the UI if (onRefresh) { onRefresh(); } } else { showToast(result.error || t('tool.toggleFailed'), 'error'); } } catch (error) { console.error('Error toggling prompt:', error); showToast(t('tool.toggleFailed'), 'error'); } }; const handleOAuthAuthorization = (e: React.MouseEvent) => { e.stopPropagation(); // Open the OAuth authorization URL in a new window if (server.oauth?.authorizationUrl) { const width = 600; const height = 700; const left = window.screen.width / 2 - width / 2; const top = window.screen.height / 2 - height / 2; window.open( server.oauth.authorizationUrl, 'OAuth Authorization', `width=${width},height=${height},left=${left},top=${top}`, ); showToast(t('status.oauthWindowOpened'), 'info'); } }; return ( <>
setIsExpanded(!isExpanded)} >

{server.name}

{/* Tool count display */}
{server.tools?.length || 0} {t('server.tools')}
{/* Prompt count display */}
{server.prompts?.length || 0} {t('server.prompts')}
{server.error && (
{showErrorPopover && (
e.stopPropagation()} >

{t('server.errorDetails')}

                        {server.error}
                      
)}
)}
{isExpanded && ( <> {server.tools && (
{t('server.tools')}
{server.tools.map((tool, index) => ( ))}
)} {server.prompts && (
{t('server.prompts')}
{server.prompts.map((prompt, index) => ( ))}
)} )}
setShowDeleteDialog(false)} onConfirm={handleConfirmDelete} serverName={server.name} /> ); }; export default ServerCard;