diff --git a/messages/en-US.json b/messages/en-US.json index 25564a98b..5b53f8b7e 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -4279,6 +4279,8 @@ "resourceLauncherSitesDescription": "The resource is accessible via the following sites.", "resourceLauncherViewSiteAsAdmin": "View Site as Admin", "resourceLauncherFilterBySite": "Filter by Site", + "resourceLauncherSshCommand": "SSH Command", + "resourceLauncherSshCommandDescription": "Use the Pangolin CLI to open an SSH session to this resource.", "resourceLauncherAuthMethodsDescription": "Authentication methods enabled for this resource.", "resourceLauncherPrivateClientRequired": "Connect with a client on your device to access this resource privately.", "resourceLauncherPrivateClientRequiredTitle": "Client Connection Required", diff --git a/src/components/resource-launcher/LauncherResourcePanel.tsx b/src/components/resource-launcher/LauncherResourcePanel.tsx index e2ff2af9f..68944153f 100644 --- a/src/components/resource-launcher/LauncherResourcePanel.tsx +++ b/src/components/resource-launcher/LauncherResourcePanel.tsx @@ -1,6 +1,8 @@ "use client"; import { AiClientConfigSection } from "@app/components/ai-client-config/AiClientConfigSection"; +import { AiConfigBlocks } from "@app/components/ai-client-config/AiConfigBlocks"; +import CopyTextBox from "@app/components/CopyTextBox"; import CopyToClipboard from "@app/components/CopyToClipboard"; import { InfoSection, @@ -43,6 +45,7 @@ import { TableRow } from "@app/components/ui/table"; import { useMyVirtualApiKeySecret } from "@app/hooks/useMyVirtualApiKeySecret"; +import type { AiConfigBlock } from "@app/lib/aiClientConfig"; import { cn } from "@app/lib/cn"; import { derivePublicAuthState, @@ -259,82 +262,160 @@ function LauncherResourceSitesSection({ - - - - -
+ + + + + + + + + - {t("name")} - - - - - - - - {t("actions")} - - - - - {sortedSites.map((site) => ( - - {site.name} - - - - - - - - - - {isAdmin ? ( - - - {t( - "resourceLauncherViewSiteAsAdmin" - )} - - - ) : null} - - onFilterBySite(site) - } - > - {t( - "resourceLauncherFilterBySite" - )} - - - - + + {t("actions")} + + - ))} - -
+ + + {sortedSites.map((site) => ( + + + {site.name} + + + + + + + + + + + {isAdmin ? ( + + + {t( + "resourceLauncherViewSiteAsAdmin" + )} + + + ) : null} + + onFilterBySite(site) + } + > + {t( + "resourceLauncherFilterBySite" + )} + + + + + + ))} + + + +
+ + ); +} + +function LauncherPrivateSshCommandSection({ + alias, + niceId, + pamMode +}: { + alias?: string | null; + niceId: string; + pamMode?: string | null; +}) { + const t = useTranslations(); + const trimmedAlias = alias?.trim() || null; + const hasDistinctAlias = Boolean(trimmedAlias && trimmedAlias !== niceId); + const useUserPrefix = pamMode !== "push"; + + function formatSshTarget(target: string) { + return useUserPrefix ? `user@${target}` : target; + } + + const blocks: AiConfigBlock[] = hasDistinctAlias + ? [ + { + id: "alias", + label: t("editInternalResourceDialogAlias"), + displayText: `pangolin ssh ${formatSshTarget(trimmedAlias!)}` + }, + { + id: "niceId", + label: t("identifier"), + displayText: `pangolin ssh ${formatSshTarget(niceId)}` + } + ] + : [ + { + id: "niceId", + label: t("identifier"), + displayText: `pangolin ssh ${formatSshTarget(niceId)}` + } + ]; + + return ( + + + + {t("resourceLauncherSshCommand")} + + + {t("resourceLauncherSshCommandDescription")} + + + + {hasDistinctAlias ? ( + + ) : ( + + )} ); @@ -572,6 +653,7 @@ function PrivateResourceDetails({ }) { const t = useTranslations(); const isInference = resource.mode === "inference"; + const isSsh = resource.mode === "ssh"; return (
@@ -624,6 +706,13 @@ function PrivateResourceDetails({ isAdmin={isAdmin} onFilterBySite={onFilterBySite} /> + {isSsh ? ( + + ) : null} {isInference ? ( <>