import React from "react"; import {Button} from "@/components/ui/button.tsx"; import {Card} from "@/components/ui/card.tsx"; import {Separator} from "@/components/ui/separator.tsx"; import {useTranslation} from 'react-i18next'; import { Loader2, Pin, Network, Tag, Play, Square, AlertCircle, Clock, Wifi, WifiOff, X } from "lucide-react"; import {Badge} from "@/components/ui/badge.tsx"; import type {TunnelStatus, SSHTunnelObjectProps} from '../../../types/index.js'; export function TunnelObject({ host, tunnelStatuses, tunnelActions, onTunnelAction, compact = false, bare = false }: SSHTunnelObjectProps): React.ReactElement { const {t} = useTranslation(); const getTunnelStatus = (tunnelIndex: number): TunnelStatus | undefined => { const tunnel = host.tunnelConnections[tunnelIndex]; const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${tunnel.sourcePort}_${tunnel.endpointPort}`; return tunnelStatuses[tunnelName]; }; const getTunnelStatusDisplay = (status: TunnelStatus | undefined) => { if (!status) return { icon: , text: t('tunnels.unknown'), color: 'text-muted-foreground', bgColor: 'bg-muted/50', borderColor: 'border-border' }; const statusValue = status.status || 'DISCONNECTED'; switch (statusValue.toUpperCase()) { case 'CONNECTED': return { icon: , text: t('tunnels.connected'), color: 'text-green-600 dark:text-green-400', bgColor: 'bg-green-500/10 dark:bg-green-400/10', borderColor: 'border-green-500/20 dark:border-green-400/20' }; case 'CONNECTING': return { icon: , text: t('tunnels.connecting'), color: 'text-blue-600 dark:text-blue-400', bgColor: 'bg-blue-500/10 dark:bg-blue-400/10', borderColor: 'border-blue-500/20 dark:border-blue-400/20' }; case 'DISCONNECTING': return { icon: , text: t('tunnels.disconnecting'), color: 'text-orange-600 dark:text-orange-400', bgColor: 'bg-orange-500/10 dark:bg-orange-400/10', borderColor: 'border-orange-500/20 dark:border-orange-400/20' }; case 'DISCONNECTED': return { icon: , text: t('tunnels.disconnected'), color: 'text-muted-foreground', bgColor: 'bg-muted/30', borderColor: 'border-border' }; case 'WAITING': return { icon: , color: 'text-blue-600 dark:text-blue-400', bgColor: 'bg-blue-500/10 dark:bg-blue-400/10', borderColor: 'border-blue-500/20 dark:border-blue-400/20' }; case 'ERROR': case 'FAILED': return { icon: , text: status.reason || t('tunnels.error'), color: 'text-red-600 dark:text-red-400', bgColor: 'bg-red-500/10 dark:bg-red-400/10', borderColor: 'border-red-500/20 dark:border-red-400/20' }; default: return { icon: , text: statusValue, color: 'text-muted-foreground', bgColor: 'bg-muted/30', borderColor: 'border-border' }; } }; if (bare) { return (
{host.tunnelConnections && host.tunnelConnections.length > 0 ? (
{host.tunnelConnections.map((tunnel, tunnelIndex) => { const status = getTunnelStatus(tunnelIndex); const statusDisplay = getTunnelStatusDisplay(status); const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${tunnel.sourcePort}_${tunnel.endpointPort}`; const isActionLoading = tunnelActions[tunnelName]; const statusValue = status?.status?.toUpperCase() || 'DISCONNECTED'; const isConnected = statusValue === 'CONNECTED'; const isConnecting = statusValue === 'CONNECTING'; const isDisconnecting = statusValue === 'DISCONNECTING'; const isRetrying = statusValue === 'RETRYING'; const isWaiting = statusValue === 'WAITING'; return (
{statusDisplay.icon}
{t('tunnels.port')} {tunnel.sourcePort} → {tunnel.endpointHost}:{tunnel.endpointPort}
{statusDisplay.text}
{!isActionLoading ? (
{isConnected ? ( <> ) : isRetrying || isWaiting ? ( ) : ( )}
) : ( )}
{(statusValue === 'ERROR' || statusValue === 'FAILED') && status?.reason && (
{t('tunnels.error')}:
{status.reason} {status.reason && status.reason.includes('Max retries exhausted') && ( <>
{t('tunnels.checkDockerLogs')} Discord or create a GitHub issue for help.
)}
)} {(statusValue === 'RETRYING' || statusValue === 'WAITING') && status?.retryCount && status?.maxRetries && (
{statusValue === 'WAITING' ? t('tunnels.waitingForRetry') : t('tunnels.retryingConnection')}
{t('tunnels.attempt', { current: status.retryCount, max: status.maxRetries })} {status.nextRetryIn && ( • {t('tunnels.nextRetryIn', {seconds: status.nextRetryIn})} )}
)}
); })}
) : (

{t('tunnels.noTunnelConnections')}

)}
); } return (
{!compact && (
{host.pin && }

{host.name || `${host.username}@${host.ip}`}

{host.ip}:{host.port} • {host.username}

)} {!compact && host.tags && host.tags.length > 0 && (
{host.tags.slice(0, 3).map((tag, index) => ( {tag} ))} {host.tags.length > 3 && ( +{host.tags.length - 3} )}
)} {!compact && }
{!compact && (

{t('tunnels.tunnelConnections')} ({host.tunnelConnections.length})

)} {host.tunnelConnections && host.tunnelConnections.length > 0 ? (
{host.tunnelConnections.map((tunnel, tunnelIndex) => { const status = getTunnelStatus(tunnelIndex); const statusDisplay = getTunnelStatusDisplay(status); const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${tunnel.sourcePort}_${tunnel.endpointPort}`; const isActionLoading = tunnelActions[tunnelName]; const statusValue = status?.status?.toUpperCase() || 'DISCONNECTED'; const isConnected = statusValue === 'CONNECTED'; const isConnecting = statusValue === 'CONNECTING'; const isDisconnecting = statusValue === 'DISCONNECTING'; const isRetrying = statusValue === 'RETRYING'; const isWaiting = statusValue === 'WAITING'; return (
{statusDisplay.icon}
{t('tunnels.port')} {tunnel.sourcePort} → {tunnel.endpointHost}:{tunnel.endpointPort}
{statusDisplay.text}
{!isActionLoading && (
{isConnected ? ( <> ) : isRetrying || isWaiting ? ( ) : ( )}
)} {isActionLoading && ( )}
{(statusValue === 'ERROR' || statusValue === 'FAILED') && status?.reason && (
{t('tunnels.error')}:
{status.reason} {status.reason && status.reason.includes('Max retries exhausted') && ( <>
{t('tunnels.checkDockerLogs')} Discord or create a GitHub issue for help.
)}
)} {(statusValue === 'RETRYING' || statusValue === 'WAITING') && status?.retryCount && status?.maxRetries && (
{statusValue === 'WAITING' ? t('tunnels.waitingForRetry') : t('tunnels.retryingConnection')}
{t('tunnels.attempt', { current: status.retryCount, max: status.maxRetries })} {status.nextRetryIn && ( • {t('tunnels.nextRetryIn', {seconds: status.nextRetryIn})} )}
)}
); })}
) : (

{t('tunnels.noTunnelConnections')}

)}
); }