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")}
)}
);
}