Ssh tunnel backup before forwardIn rewrite

This commit is contained in:
LukeGus
2025-07-27 14:21:15 -05:00
parent 5e88f8496e
commit 32945adcd9
11 changed files with 371 additions and 2643 deletions
+16 -50
View File
@@ -1,8 +1,7 @@
import React, { useState, useEffect, useCallback } from "react";
import { SSHTunnelSidebar } from "@/apps/SSH/Tunnel/SSHTunnelSidebar.tsx";
import { SSHTunnelViewer } from "@/apps/SSH/Tunnel/SSHTunnelViewer.tsx";
import { getSSHHosts } from "@/apps/SSH/ssh-axios";
import axios from "axios";
import { getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel } from "@/apps/SSH/ssh-axios";
interface ConfigEditorProps {
onSelectView: (view: string) => void;
@@ -76,8 +75,7 @@ export function SSHTunnel({ onSelectView }: ConfigEditorProps): React.ReactEleme
// Poll backend for tunnel statuses
const fetchTunnelStatuses = useCallback(async () => {
try {
const res = await axios.get('http://localhost:8083/status');
const statusData = res.data || {};
const statusData = await getTunnelStatuses();
// Convert tunnel statuses to host statuses
const newHostStatuses: Record<number, HostStatus> = {};
@@ -95,37 +93,17 @@ export function SSHTunnel({ onSelectView }: ConfigEditorProps): React.ReactEleme
});
if (hostTunnelStatuses.length > 0) {
// Determine overall host status based on tunnel statuses
const connectedTunnels = hostTunnelStatuses.filter(s => s.status === 'connected');
const failedTunnels = hostTunnelStatuses.filter(s => s.status === 'failed');
const connectingTunnels = hostTunnelStatuses.filter(s =>
['connecting', 'verifying', 'retrying'].includes(s.status)
);
let overallStatus: string;
let statusReason: string | undefined;
if (connectingTunnels.length > 0) {
overallStatus = 'connecting';
} else if (failedTunnels.length === hostTunnelStatuses.length) {
overallStatus = 'failed';
statusReason = failedTunnels[0]?.reason;
} else if (connectedTunnels.length === hostTunnelStatuses.length) {
overallStatus = 'connected';
} else if (connectedTunnels.length > 0) {
overallStatus = 'connected';
} else {
overallStatus = 'disconnected';
}
// Just use the first tunnel's status for now - simplify
const firstTunnelStatus = hostTunnelStatuses[0];
newHostStatuses[host.id] = {
connectionState: overallStatus,
statusReason,
statusErrorType: failedTunnels[0]?.errorType,
statusRetryCount: connectingTunnels.find(s => s.status === 'retrying')?.retryCount,
statusMaxRetries: connectingTunnels.find(s => s.status === 'retrying')?.maxRetries,
statusNextRetryIn: connectingTunnels.find(s => s.status === 'retrying')?.nextRetryIn,
statusRetryExhausted: failedTunnels.some(s => s.retryExhausted),
connectionState: firstTunnelStatus.status,
statusReason: firstTunnelStatus.reason,
statusErrorType: firstTunnelStatus.errorType,
statusRetryCount: firstTunnelStatus.retryCount,
statusMaxRetries: firstTunnelStatus.maxRetries,
statusNextRetryIn: firstTunnelStatus.nextRetryIn,
statusRetryExhausted: firstTunnelStatus.retryExhausted,
};
} else {
// Set default disconnected status
@@ -159,11 +137,7 @@ export function SSHTunnel({ onSelectView }: ConfigEditorProps): React.ReactEleme
return;
}
// Immediately set to CONNECTING for instant UI feedback
setHostStatuses(prev => ({
...prev,
[hostId]: { ...prev[hostId], connectionState: "connecting" }
}));
// Let the backend handle the status updates
try {
// For each tunnel connection, create a tunnel configuration
@@ -206,14 +180,10 @@ export function SSHTunnel({ onSelectView }: ConfigEditorProps): React.ReactEleme
isPinned: host.pin
};
await axios.post('http://localhost:8083/connect', tunnelConfig);
await connectTunnel(tunnelConfig);
}
} catch (err) {
// Reset status on error
setHostStatuses(prev => ({
...prev,
[hostId]: { ...prev[hostId], connectionState: "failed", statusReason: "Failed to connect" }
}));
// Let the backend handle error status updates
}
};
@@ -221,17 +191,13 @@ export function SSHTunnel({ onSelectView }: ConfigEditorProps): React.ReactEleme
const host = hosts.find(h => h.id === hostId);
if (!host) return;
// Immediately set to DISCONNECTING for instant UI feedback
setHostStatuses(prev => ({
...prev,
[hostId]: { ...prev[hostId], connectionState: "disconnecting" }
}));
// Let the backend handle the status updates
try {
// Disconnect all tunnels for this host
for (const tunnelConnection of host.tunnelConnections) {
const tunnelName = `${host.name || `${host.username}@${host.ip}`}_${tunnelConnection.sourcePort}_${tunnelConnection.endpointPort}`;
await axios.post('http://localhost:8083/disconnect', { tunnelName });
await disconnectTunnel(tunnelName);
}
} catch (err) {
// Silent error handling
+8 -21
View File
@@ -13,6 +13,7 @@ const CONNECTION_STATES = {
FAILED: "failed",
UNSTABLE: "unstable",
RETRYING: "retrying",
WAITING: "waiting",
DISCONNECTING: "disconnecting"
};
@@ -77,6 +78,7 @@ export function SSHTunnelObject({
case "CONNECTING":
case "VERIFYING":
case "RETRYING":
case "WAITING":
return "bg-yellow-500";
case "FAILED":
return "bg-red-500";
@@ -88,27 +90,12 @@ export function SSHTunnelObject({
};
const getStatusText = (state: string) => {
const upperState = state.toUpperCase();
switch (upperState) {
case "CONNECTED":
return "Connected";
case "CONNECTING":
return "Connecting";
case "VERIFYING":
return "Verifying";
case "FAILED":
return "Failed";
case "UNSTABLE":
return "Unstable";
case "RETRYING":
return "Retrying";
default:
return "Disconnected";
}
// Just capitalize the first letter of the status from backend
return state.charAt(0).toUpperCase() + state.slice(1);
};
const isConnected = connectionState === "CONNECTED" || connectionState === "connected";
const isConnecting = ["CONNECTING", "VERIFYING", "RETRYING", "connecting", "verifying", "retrying"].includes(connectionState);
const isConnecting = ["CONNECTING", "VERIFYING", "RETRYING", "WAITING", "connecting", "verifying", "retrying", "waiting"].includes(connectionState);
const isDisconnecting = connectionState === "DISCONNECTING" || connectionState === "disconnecting";
return (
@@ -193,9 +180,9 @@ export function SSHTunnelObject({
)}
{/* Retry Info */}
{connectionState === "RETRYING" && statusRetryCount && statusMaxRetries && (
{(connectionState === "retrying" || connectionState === "waiting") && statusRetryCount && statusMaxRetries && (
<div className="mb-2 text-xs text-yellow-600 bg-yellow-500/10 rounded px-2 py-1 border border-yellow-500/20">
Retry {statusRetryCount}/{statusMaxRetries}
{connectionState === "waiting" ? "Waiting" : "Retry"} {statusRetryCount}/{statusMaxRetries}
{statusNextRetryIn && (
<span> Next retry in {statusNextRetryIn}s</span>
)}
@@ -213,7 +200,7 @@ export function SSHTunnelObject({
{isConnecting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Connecting...
{getStatusText(connectionState)}...
</>
) : isConnected ? (
"Connected"