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') && (
<>
>
)}
)}
{(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') && (
<>
>
)}
)}
{(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')}
)}
);
}