diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index a3403963f..bac343575 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -1831,8 +1831,9 @@ "domainLabel": "Domain", "domainPlaceholder": "optional", "connectToTerminal": "Connect to Terminal", - "connectWith": "Connect via {{protocol}}", - "connectToAction": "Connect to {{name}}" + "connectToAction": "Connect to {{name}}", + "sshProtocol": "SSH", + "noTarget": "Nothing installed can connect to this host type" }, "sshTools": { "keyRecordingTitle": "Key Recording", diff --git a/src/ui/sidebar/QuickConnectPanel.tsx b/src/ui/sidebar/QuickConnectPanel.tsx index 2fd5b13b7..14866374b 100644 --- a/src/ui/sidebar/QuickConnectPanel.tsx +++ b/src/ui/sidebar/QuickConnectPanel.tsx @@ -1,25 +1,42 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { Eye, EyeOff, Terminal } from "lucide-react"; +import { Eye, EyeOff } from "lucide-react"; +import { toast } from "sonner"; import { Input } from "@/components/input"; import type { Host } from "@/types/ui-types"; import { getCredentials } from "@/api/credentials-api"; import { mapCredentials } from "./HostManagerData"; -import { createQuickConnectHost } from "./quick-connect-host"; +import { + createQuickConnectHost, + quickConnectTargets, +} from "./quick-connect-host"; import { useHostProtocols } from "./host-protocols"; import { Select2 } from "@/components/select2"; -import { resolveHostTabType } from "@/lib/host-connection-tabs"; import { useSshAuthProviders } from "@/hooks/useSshAuthProviders"; import { useSshAuthEditors } from "@/plugin-host/auth-registry"; -import { useHostActions } from "./host-contributions"; +import { useHostActions, type HostActionDef } from "./host-contributions"; // Core types Quick Connect draws its own fields for. const INLINE_AUTH_TYPES = new Set(["password", "key", "credential"]); +const LABEL_CLASS = + "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground"; +const SELECT_CLASS = + "flex h-7 w-full border border-border bg-background px-2.5 py-1 text-xs outline-none focus:ring-1 focus:ring-ring"; + interface QuickConnectPanelProps { onConnect: (host: Host, type: string) => void; } +function Field({ label, children }: { label: string; children: ReactNode }) { + return ( +
+ + {children} +
+ ); +} + export function QuickConnectPanel({ onConnect }: QuickConnectPanelProps) { const { t } = useTranslation(); const [host, setHost] = useState(""); @@ -55,9 +72,7 @@ export function QuickConnectPanel({ onConnect }: QuickConnectPanelProps) { const AuthEditor = INLINE_AUTH_TYPES.has(authType) ? undefined : authEditor?.component; - const quickActions = useHostActions().filter( - (action) => action.quickConnect && action.tabType, - ); + const allActions = useHostActions(); const pluginProtocols = useHostProtocols().filter( (entry) => entry.quickConnect, @@ -73,12 +88,9 @@ export function QuickConnectPanel({ onConnect }: QuickConnectPanelProps) { setProtocol(next); }; - // The tab comes from whichever plugin connects hosts of this protocol. - const connect = (type?: string) => { - if (!host) return; - if (!isDesktop && !username) return; - const hostConfig = createQuickConnectHost({ - ip: host, + const buildHost = () => + createQuickConnectHost({ + ip: host.trim(), port: parseInt(port) || parseInt(defaultPort(protocol)), username, authType: isDesktop ? "password" : authType, @@ -89,69 +101,65 @@ export function QuickConnectPanel({ onConnect }: QuickConnectPanelProps) { domain: domain || undefined, authFields: INLINE_AUTH_TYPES.has(authType) ? undefined : authFields, }); - const target = type ?? resolveHostTabType(hostConfig); - if (target) onConnect(hostConfig, target); + + const targets = quickConnectTargets(allActions, buildHost()); + + const connect = (action?: HostActionDef) => { + if (!host.trim()) return; + if (!isDesktop && !username) return; + const hostConfig = buildHost(); + const target = action ?? quickConnectTargets(allActions, hostConfig)[0]; + if (!target?.tabType) { + toast.error(t("newUi.sidebar.quickConnect.noTarget")); + return; + } + onConnect(hostConfig, target.tabType); }; - const connectDefault = () => connect(); + const onEnter = (e: React.KeyboardEvent) => { + if (e.key === "Enter") connect(); + }; return (
-
- -
- {["ssh", ...pluginProtocols.map((entry) => entry.id)].map( - (type) => ( - - ), - )} -
+
+ + switchProtocol(e.target.value)} + className={SELECT_CLASS} + > + + {pluginProtocols.map((entry) => ( + + ))} + + + + setPort(e.target.value)} + onKeyDown={onEnter} + className="h-7 text-xs" + /> +
-
- + setHost(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") connectDefault(); - }} + onKeyDown={onEnter} className="h-7 text-xs" /> -
-
- - setPort(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") connectDefault(); - }} - className="h-7 text-xs" - /> -
-
- + + setUsername(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") connectDefault(); - }} + onKeyDown={onEnter} className="h-7 text-xs" /> -
- {!isDesktop && ( -
- -
+ + {!isDesktop && authOptions.length > 0 && ( + + { + setAuthType(e.target.value); + setAuthFields({}); + }} + className={SELECT_CLASS} + > {authOptions.map((option) => ( - + ))} -
-
+ + )} {(isDesktop || authType === "password") && ( -
- +
setPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") connectDefault(); - }} + onKeyDown={onEnter} className="h-7 text-xs pr-8" />
-
+ )} {selected?.quickConnect?.showDomain && ( -
- + setDomain(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") connectDefault(); - }} + onKeyDown={onEnter} className="h-7 text-xs" /> -
+ )} {!isDesktop && authType === "key" && ( -
- +