import React, { useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { PasswordInput } from "@/components/ui/password-input"; import { Label } from "@/components/ui/label"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Shield, AlertCircle, Upload } from "lucide-react"; import { useTranslation } from "react-i18next"; import CodeMirror from "@uiw/react-codemirror"; import { oneDark } from "@codemirror/theme-one-dark"; import { EditorView } from "@codemirror/view"; interface SSHAuthDialogProps { isOpen: boolean; reason: "no_keyboard" | "auth_failed" | "timeout"; onSubmit: (credentials: { password?: string; sshKey?: string; keyPassword?: string; }) => void; onCancel: () => void; hostInfo: { ip: string; port: number; username: string; name?: string; }; backgroundColor?: string; } export function SSHAuthDialog({ isOpen, reason, onSubmit, onCancel, hostInfo, backgroundColor = "var(--bg-base)", }: SSHAuthDialogProps) { const { t } = useTranslation(); const [authTab, setAuthTab] = useState<"password" | "key">("password"); const [password, setPassword] = useState(""); const [sshKey, setSshKey] = useState(""); const [keyPassword, setKeyPassword] = useState(""); const [loading, setLoading] = useState(false); if (!isOpen) return null; const getReasonMessage = () => { switch (reason) { case "no_keyboard": return t("auth.sshNoKeyboardInteractive"); case "auth_failed": return t("auth.sshAuthenticationFailed"); case "timeout": return t("auth.sshAuthenticationTimeout"); default: return t("auth.sshAuthenticationRequired"); } }; const getReasonDescription = () => { switch (reason) { case "no_keyboard": return t("auth.sshNoKeyboardInteractiveDescription"); case "auth_failed": return t("auth.sshAuthFailedDescription"); case "timeout": return t("auth.sshTimeoutDescription"); default: return t("auth.sshProvideCredentialsDescription"); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { const credentials: { password?: string; sshKey?: string; keyPassword?: string; } = {}; if (authTab === "password") { if (password.trim()) { credentials.password = password; } } else { if (sshKey.trim()) { credentials.sshKey = sshKey; if (keyPassword.trim()) { credentials.keyPassword = keyPassword; } } } onSubmit(credentials); } finally { setLoading(false); } }; const handleKeyFileUpload = async ( e: React.ChangeEvent, ) => { const file = e.target.files?.[0]; if (file) { try { const fileContent = await file.text(); setSshKey(fileContent); } catch (error) { console.error("Failed to read SSH key file:", error); } } }; const canSubmit = () => { if (authTab === "password") { return password.trim() !== ""; } else { return sshKey.trim() !== ""; } }; const hostDisplay = hostInfo.name ? `${hostInfo.name} (${hostInfo.username}@${hostInfo.ip}:${hostInfo.port})` : `${hostInfo.username}@${hostInfo.ip}:${hostInfo.port}`; return (
{t("auth.sshAuthenticationRequired")} {hostDisplay} {getReasonMessage()} {getReasonDescription()}
setAuthTab(v as "password" | "key")} > {t("credentials.password")} {t("credentials.sshKey")}
setPassword(e.target.value)} autoFocus />

{t("auth.sshPasswordDescription")}

setSshKey(value)} placeholder={t("placeholders.pastePrivateKey")} theme={oneDark} className="border border-input rounded-md" minHeight="200px" maxHeight="300px" basicSetup={{ lineNumbers: true, foldGutter: false, dropCursor: false, allowMultipleSelections: false, highlightSelectionMatches: false, searchKeymap: false, scrollPastEnd: false, }} extensions={[ EditorView.theme({ ".cm-scroller": { overflow: "auto", scrollbarWidth: "thin", scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)", }, }), ]} />
setKeyPassword(e.target.value)} />

{t("auth.sshKeyPasswordDescription")}

); }