import { ArrowLeft, ChevronDown, Eye, EyeOff, Globe, Key, Lock, Plus, Terminal as TerminalIcon, User, } from "lucide-react"; import React, { useMemo, useState } from "react"; import { useI18n } from "../application/i18n/I18nProvider"; import type { QuickConnectTarget } from "../domain/quickConnect"; import { cn } from "../lib/utils"; import { Host, KnownHost, SSHKey } from "../types"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { ScrollArea } from "./ui/scroll-area"; // Protocol types supported for quick connect type Protocol = "ssh" | "mosh" | "telnet"; // Wizard steps type WizardStep = "protocol" | "username" | "knownhost" | "auth"; interface QuickConnectWizardProps { open: boolean; target: QuickConnectTarget; keys: SSHKey[]; knownHosts: KnownHost[]; warnings?: string[]; onConnect: (host: Host) => void; onSaveHost?: (host: Host) => void; onAddKey?: () => void; onClose: () => void; } const QuickConnectWizard: React.FC = ({ open, target, keys, knownHosts, warnings, onConnect, onSaveHost, onAddKey, onClose, }) => { const { t } = useI18n(); // Wizard state const [step, setStep] = useState("protocol"); const [protocol, setProtocol] = useState("ssh"); const [username, setUsername] = useState(target.username || ""); const [port, setPort] = useState(target.port || 22); const [moshServerPath, setMoshServerPath] = useState(""); const [showLogs, setShowLogs] = useState(false); // Known host verification state const [knownHostInfo, setKnownHostInfo] = useState<{ keyType: string; fingerprint: string; } | null>(null); // Auth state const [authMethod, setAuthMethod] = useState<"password" | "key">("password"); const [password, setPassword] = useState(""); const [selectedKeyId, setSelectedKeyId] = useState(null); const [showPassword, setShowPassword] = useState(false); const [saveCredentials, _setSaveCredentials] = useState(true); // Check if host is in known hosts const _existingKnownHost = useMemo(() => { return knownHosts.find( (kh) => kh.hostname === target.hostname && (kh.port === port || (!kh.port && port === 22)), ); }, [knownHosts, target.hostname, port]); // Reset state when target changes React.useEffect(() => { if (open) { setStep("protocol"); setProtocol("ssh"); setUsername(target.username || ""); setPort(target.port || 22); setPassword(""); setSelectedKeyId(null); setShowPassword(false); setKnownHostInfo(null); } }, [open, target]); // Get default port for protocol const getDefaultPort = (proto: Protocol) => { switch (proto) { case "ssh": return 22; case "mosh": return 22; case "telnet": return 23; default: return 22; } }; // Handle protocol selection const handleProtocolSelect = (proto: Protocol) => { setProtocol(proto); // Update port to default for protocol if unchanged if (port === getDefaultPort(protocol)) { setPort(getDefaultPort(proto)); } }; // Navigate to next step const handleContinue = () => { switch (step) { case "protocol": // Always go to username step to let user confirm/edit username setStep("username"); break; case "username": setStep("auth"); break; case "knownhost": setStep("auth"); break; case "auth": handleConnect(); break; } }; // Navigate back const handleBack = () => { switch (step) { case "username": setStep("protocol"); break; case "knownhost": setStep("username"); break; case "auth": // Always go back to username step setStep("username"); break; } }; // Create host and connect const handleConnect = () => { const effectiveUsername = username || target.username || "root"; const effectivePort = port || getDefaultPort(protocol); const tempHost: Host = { id: `quick-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, label: `${target.hostname}`, hostname: target.hostname, port: effectivePort, username: effectiveUsername, group: "", tags: [], os: "linux", protocol: protocol === "mosh" ? "ssh" : protocol, authMethod: authMethod, password: authMethod === "password" ? password : undefined, identityFileId: authMethod === "key" ? selectedKeyId || undefined : undefined, moshEnabled: protocol === "mosh", // Set telnet-specific fields when using telnet protocol telnetEnabled: protocol === "telnet", telnetPort: protocol === "telnet" ? effectivePort : undefined, createdAt: Date.now(), }; // Save host if requested if (saveCredentials && onSaveHost) { onSaveHost(tempHost); } onConnect(tempHost); onClose(); }; // Check if can proceed const canProceed = useMemo(() => { switch (step) { case "protocol": return true; case "username": return username.trim().length > 0; case "knownhost": return true; case "auth": if (authMethod === "password") { return password.trim().length > 0; } return !!selectedKeyId; } }, [step, username, authMethod, password, selectedKeyId]); // Render protocol selection step const renderProtocolStep = () => (

{t("protocolSelect.chooseProtocol")}

{/* SSH */} {/* Mosh */} {/* Telnet */}
); // Render username step const renderUsernameStep = () => (
setUsername(e.target.value)} placeholder={t("terminal.auth.username.placeholder")} autoFocus onKeyDown={(e) => { if (e.key === "Enter" && username.trim()) { handleContinue(); } }} />
); // Render known host verification step const renderKnownHostStep = () => (

{t("quickConnect.knownHost.title")}

{t("quickConnect.knownHost.authenticity", { hostname: target.hostname })}

{knownHostInfo && ( <>

{t("quickConnect.knownHost.fingerprintLabel", { keyType: knownHostInfo.keyType })}

{knownHostInfo.fingerprint}

)}

{t("quickConnect.knownHost.addQuestion")}

); // Render auth step const renderAuthStep = () => (
{/* Auth method tabs */}
{/* Password field */} {authMethod === "password" && (
setPassword(e.target.value)} placeholder={t("terminal.auth.password.placeholder")} className="pr-10" autoFocus onKeyDown={(e) => { if (e.key === "Enter" && password.trim()) { handleConnect(); } }} />
)} {/* Key selection */} {authMethod === "key" && (
{keys.filter((k) => k.category === "key").length === 0 ? (
{t("terminal.auth.noKeysHint")}
) : (
{keys .filter((k) => k.category === "key") .map((key) => ( ))}
)} {onAddKey && ( )}
)}
); // Get step title const getStepTitle = () => { switch (step) { case "protocol": return target.hostname; case "username": return target.hostname; case "knownhost": return target.hostname; case "auth": return target.hostname; } }; // Get step subtitle const getStepSubtitle = () => { const effectiveUsername = username || target.username || ""; switch (step) { case "protocol": return target.hostname; case "username": return `${protocol.toUpperCase()} ${target.hostname}:${port}`; case "knownhost": return `${protocol.toUpperCase()} ${effectiveUsername}@${target.hostname}:${port}`; case "auth": return `${protocol.toUpperCase()} ${target.hostname}:${port}`; } }; // Render progress indicator const renderProgressIndicator = () => { const steps: WizardStep[] = target.username ? ["protocol", "auth"] : ["protocol", "username", "auth"]; const currentIndex = steps.indexOf(step); return (
= 0 ? "bg-primary/20 text-primary" : "bg-muted text-muted-foreground", )} >
{!target.username && ( <>
= 1 ? "bg-primary/20 text-primary" : "bg-muted text-muted-foreground", )} >
)}
{authMethod === "password" ? : }
{">_"}
); }; return (
e.stopPropagation()} > {/* Header */}

{getStepTitle()}

{getStepSubtitle()}

{(step === "auth" || (step === "knownhost" && !knownHostInfo)) && ( )}
{/* Progress indicator */}
{renderProgressIndicator()}
{warnings && warnings.length > 0 && (
{t("quickConnect.warning.unparsedOptions", { options: warnings.join(", "), })}
)} {/* Content */}
{step === "protocol" && renderProtocolStep()} {step === "username" && renderUsernameStep()} {step === "knownhost" && renderKnownHostStep()} {step === "auth" && renderAuthStep()}
{/* Footer */}
{step === "knownhost" ? (
) : step === "auth" ? (
{onAddKey && authMethod === "key" && keys.filter((k) => k.category === "key").length === 0 && ( )}
) : ( )}
); }; export default QuickConnectWizard;