fix: improve tunnel system

This commit is contained in:
LukeGus
2025-12-28 01:59:14 -06:00
parent 761a1eb6d3
commit 5865019c8c
5 changed files with 511 additions and 141 deletions

View File

@@ -126,26 +126,25 @@ export function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement {
tunnelIndex: number,
) => {
const tunnel = host.tunnelConnections[tunnelIndex];
const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${
tunnel.sourcePort
}_${tunnel.endpointHost}_${tunnel.endpointPort}`;
const tunnelName = `${host.id}::${tunnelIndex}::${host.name || `${host.username}@${host.ip}`}::${tunnel.sourcePort}::${tunnel.endpointHost}::${tunnel.endpointPort}`;
setTunnelActions((prev) => ({ ...prev, [tunnelName]: true }));
try {
if (action === "connect") {
// Try to find endpoint host in user's accessible hosts
const endpointHost = allHosts.find(
(h) =>
h.name === tunnel.endpointHost ||
`${h.username}@${h.ip}` === tunnel.endpointHost,
);
if (!endpointHost) {
throw new Error(t("tunnels.endpointHostNotFound"));
}
// For shared users who don't have access to endpoint host,
// send a minimal config and let backend resolve endpoint details
const tunnelConfig = {
name: tunnelName,
sourceHostId: host.id,
tunnelIndex: tunnelIndex,
hostName: host.name || `${host.username}@${host.ip}`,
sourceIP: host.ip,
sourceSSHPort: host.port,
@@ -159,24 +158,25 @@ export function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement {
sourceKeyType: host.authType === "key" ? host.keyType : undefined,
sourceCredentialId: host.credentialId,
sourceUserId: host.userId,
endpointIP: endpointHost.ip,
endpointSSHPort: endpointHost.port,
endpointUsername: endpointHost.username,
endpointHost: tunnel.endpointHost,
endpointIP: endpointHost?.ip,
endpointSSHPort: endpointHost?.port,
endpointUsername: endpointHost?.username,
endpointPassword:
endpointHost.authType === "password"
endpointHost?.authType === "password"
? endpointHost.password
: undefined,
endpointAuthMethod: endpointHost.authType,
endpointAuthMethod: endpointHost?.authType,
endpointSSHKey:
endpointHost.authType === "key" ? endpointHost.key : undefined,
endpointHost?.authType === "key" ? endpointHost.key : undefined,
endpointKeyPassword:
endpointHost.authType === "key"
endpointHost?.authType === "key"
? endpointHost.keyPassword
: undefined,
endpointKeyType:
endpointHost.authType === "key" ? endpointHost.keyType : undefined,
endpointCredentialId: endpointHost.credentialId,
endpointUserId: endpointHost.userId,
endpointHost?.authType === "key" ? endpointHost.keyType : undefined,
endpointCredentialId: endpointHost?.credentialId,
endpointUserId: endpointHost?.userId,
sourcePort: tunnel.sourcePort,
endpointPort: tunnel.endpointPort,
maxRetries: tunnel.maxRetries,
@@ -191,6 +191,19 @@ export function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement {
socks5ProxyChain: host.socks5ProxyChain,
};
console.log("Tunnel connect config:", {
tunnelName,
sourceHostId: tunnelConfig.sourceHostId,
sourceCredentialId: tunnelConfig.sourceCredentialId,
sourceUserId: tunnelConfig.sourceUserId,
hasSourcePassword: !!tunnelConfig.sourcePassword,
hasSourceKey: !!tunnelConfig.sourceSSHKey,
hasEndpointHost: !!endpointHost,
endpointHost: tunnel.endpointHost,
isShared: (host as any).isShared,
ownerId: (host as any).ownerId,
});
await connectTunnel(tunnelConfig);
} else if (action === "disconnect") {
await disconnectTunnel(tunnelName);
@@ -199,7 +212,15 @@ export function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement {
}
await fetchTunnelStatuses();
} catch {
} catch (error) {
console.error("Tunnel action failed:", {
action,
tunnelName,
hostId: host.id,
tunnelIndex,
error: error instanceof Error ? error.message : String(error),
fullError: error,
});
} finally {
setTunnelActions((prev) => ({ ...prev, [tunnelName]: false }));
}

View File

@@ -34,9 +34,7 @@ export function TunnelObject({
const getTunnelStatus = (tunnelIndex: number): TunnelStatus | undefined => {
const tunnel = host.tunnelConnections[tunnelIndex];
const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${
tunnel.sourcePort
}_${tunnel.endpointHost}_${tunnel.endpointPort}`;
const tunnelName = `${host.id}::${tunnelIndex}::${host.name || `${host.username}@${host.ip}`}::${tunnel.sourcePort}::${tunnel.endpointHost}::${tunnel.endpointPort}`;
return tunnelStatuses[tunnelName];
};
@@ -121,9 +119,7 @@ export function TunnelObject({
{host.tunnelConnections.map((tunnel, tunnelIndex) => {
const status = getTunnelStatus(tunnelIndex);
const statusDisplay = getTunnelStatusDisplay(status);
const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${
tunnel.sourcePort
}_${tunnel.endpointHost}_${tunnel.endpointPort}`;
const tunnelName = `${host.id}::${tunnelIndex}::${host.name || `${host.username}@${host.ip}`}::${tunnel.sourcePort}::${tunnel.endpointHost}::${tunnel.endpointPort}`;
const isActionLoading = tunnelActions[tunnelName];
const statusValue =
status?.status?.toUpperCase() || "DISCONNECTED";
@@ -356,9 +352,7 @@ export function TunnelObject({
{host.tunnelConnections.map((tunnel, tunnelIndex) => {
const status = getTunnelStatus(tunnelIndex);
const statusDisplay = getTunnelStatusDisplay(status);
const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${
tunnel.sourcePort
}_${tunnel.endpointHost}_${tunnel.endpointPort}`;
const tunnelName = `${host.id}::${tunnelIndex}::${host.name || `${host.username}@${host.ip}`}::${tunnel.sourcePort}::${tunnel.endpointHost}::${tunnel.endpointPort}`;
const isActionLoading = tunnelActions[tunnelName];
const statusValue =
status?.status?.toUpperCase() || "DISCONNECTED";

View File

@@ -7,10 +7,20 @@ import {
} from "@/components/ui/form.tsx";
import { Switch } from "@/components/ui/switch.tsx";
import type { HostDockerTabProps } from "./shared/tab-types";
import { Button } from "@/components/ui/button.tsx";
import React from "react";
export function HostDockerTab({ control, t }: HostDockerTabProps) {
return (
<div className="space-y-4">
<Button
variant="outline"
size="sm"
className="h-8 px-3 text-xs"
onClick={() => window.open("https://docs.termix.site/docker", "_blank")}
>
{t("common.documentation")}
</Button>
<FormField
control={control}
name="enableDocker"