diff --git a/site/src/api/api.ts b/site/src/api/api.ts index a1aeeca8a9e59..ede6f90a0133b 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2374,6 +2374,18 @@ class ApiMethods { ); } }; + + getAgentContainers = async (agentId: string, labels?: string[]) => { + const params = new URLSearchParams( + labels?.map((label) => ["label", label]), + ); + + const res = + await this.axios.get( + `/api/v2/workspaceagents/${agentId}/containers?${params.toString()}`, + ); + return res.data; + }; } // This is a hard coded CSRF token/cookie pair for local development. In prod, diff --git a/site/src/modules/resources/AgentDevcontainerCard.tsx b/site/src/modules/resources/AgentDevcontainerCard.tsx new file mode 100644 index 0000000000000..fc58c21f95bcb --- /dev/null +++ b/site/src/modules/resources/AgentDevcontainerCard.tsx @@ -0,0 +1,74 @@ +import Link from "@mui/material/Link"; +import type { Workspace, WorkspaceAgentDevcontainer } from "api/typesGenerated"; +import { ExternalLinkIcon } from "lucide-react"; +import type { FC } from "react"; +import { portForwardURL } from "utils/portForward"; +import { AgentButton } from "./AgentButton"; +import { AgentDevcontainerSSHButton } from "./SSHButton/SSHButton"; +import { TerminalLink } from "./TerminalLink/TerminalLink"; + +type AgentDevcontainerCardProps = { + container: WorkspaceAgentDevcontainer; + workspace: Workspace; + wildcardHostname: string; + agentName: string; +}; + +export const AgentDevcontainerCard: FC = ({ + container, + workspace, + agentName, + wildcardHostname, +}) => { + return ( +
+
+

+ {container.name} +

+ + +
+ +

Forwarded ports

+ +
+ + {wildcardHostname !== "" && + container.ports.map((port) => { + return ( + } + href={portForwardURL( + wildcardHostname, + port.port, + agentName, + workspace.name, + workspace.owner_name, + location.protocol === "https" ? "https" : "http", + )} + > + {port.process_name || + `${port.port}/${port.network.toUpperCase()}`} + + ); + })} +
+
+ ); +}; diff --git a/site/src/modules/resources/AgentRow.tsx b/site/src/modules/resources/AgentRow.tsx index 9e5caed677ee1..1b9761f28ea40 100644 --- a/site/src/modules/resources/AgentRow.tsx +++ b/site/src/modules/resources/AgentRow.tsx @@ -3,6 +3,7 @@ import Button from "@mui/material/Button"; import Collapse from "@mui/material/Collapse"; import Divider from "@mui/material/Divider"; import Skeleton from "@mui/material/Skeleton"; +import { API } from "api/api"; import { xrayScan } from "api/queries/integrations"; import type { Template, @@ -25,6 +26,7 @@ import { import { useQuery } from "react-query"; import AutoSizer from "react-virtualized-auto-sizer"; import type { FixedSizeList as List, ListOnScrollProps } from "react-window"; +import { AgentDevcontainerCard } from "./AgentDevcontainerCard"; import { AgentLatency } from "./AgentLatency"; import { AGENT_LOG_LINE_HEIGHT } from "./AgentLogs/AgentLogLine"; import { AgentLogs } from "./AgentLogs/AgentLogs"; @@ -35,7 +37,7 @@ import { AgentVersion } from "./AgentVersion"; import { AppLink } from "./AppLink/AppLink"; import { DownloadAgentLogsButton } from "./DownloadAgentLogsButton"; import { PortForwardButton } from "./PortForwardButton"; -import { SSHButton } from "./SSHButton/SSHButton"; +import { AgentSSHButton } from "./SSHButton/SSHButton"; import { TerminalLink } from "./TerminalLink/TerminalLink"; import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton"; import { XRayScanAlert } from "./XRayScanAlert"; @@ -152,6 +154,18 @@ export const AgentRow: FC = ({ setBottomOfLogs(distanceFromBottom < AGENT_LOG_LINE_HEIGHT); }, []); + const { data: containers } = useQuery({ + queryKey: ["agents", agent.id, "containers"], + queryFn: () => + // Only return devcontainers + API.getAgentContainers(agent.id, [ + "devcontainer.config_file=", + "devcontainer.local_folder=", + ]), + enabled: agent.status === "connected", + select: (res) => res.containers.filter((c) => c.status === "running"), + }); + return ( = ({ {showBuiltinApps && (
{!hideSSHButton && agent.display_apps.includes("ssh_helper") && ( - )} - {proxy.preferredWildcardHostname && - proxy.preferredWildcardHostname !== "" && + {proxy.preferredWildcardHostname !== "" && agent.display_apps.includes("port_forwarding_helper") && ( = ({ )} + {containers && containers.length > 0 && ( +
+ {containers.map((container) => { + return ( + + ); + })} +
+ )} + = { - title: "modules/resources/SSHButton", - component: SSHButton, +const meta: Meta = { + title: "modules/resources/AgentSSHButton", + component: AgentSSHButton, }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Closed: Story = { args: { diff --git a/site/src/modules/resources/SSHButton/SSHButton.tsx b/site/src/modules/resources/SSHButton/SSHButton.tsx index 3d94b33375c0b..d5351a3ff5466 100644 --- a/site/src/modules/resources/SSHButton/SSHButton.tsx +++ b/site/src/modules/resources/SSHButton/SSHButton.tsx @@ -17,13 +17,13 @@ import { type ClassName, useClassName } from "hooks/useClassName"; import type { FC } from "react"; import { docs } from "utils/docs"; -export interface SSHButtonProps { +export interface AgentSSHButtonProps { workspaceName: string; agentName: string; sshPrefix?: string; } -export const SSHButton: FC = ({ +export const AgentSSHButton: FC = ({ workspaceName, agentName, sshPrefix, @@ -82,6 +82,56 @@ export const SSHButton: FC = ({ ); }; +export interface AgentDevcontainerSSHButtonProps { + workspace: string; + container: string; +} + +export const AgentDevcontainerSSHButton: FC< + AgentDevcontainerSSHButtonProps +> = ({ workspace, container }) => { + const paper = useClassName(classNames.paper, []); + + return ( + + + + + + + + Run the following commands to connect with SSH: + + +
    + + + +
+ + + + Install Coder CLI + + + SSH configuration + + +
+
+ ); +}; + interface SSHStepProps { helpText: string; codeExample: string; diff --git a/site/src/modules/resources/TerminalLink/TerminalLink.tsx b/site/src/modules/resources/TerminalLink/TerminalLink.tsx index 4d709dc482e70..f7a07131e4cd0 100644 --- a/site/src/modules/resources/TerminalLink/TerminalLink.tsx +++ b/site/src/modules/resources/TerminalLink/TerminalLink.tsx @@ -11,9 +11,10 @@ export const Language = { }; export interface TerminalLinkProps { - agentName?: TypesGen.WorkspaceAgent["name"]; - userName?: TypesGen.User["username"]; - workspaceName: TypesGen.Workspace["name"]; + workspaceName: string; + agentName?: string; + userName?: string; + containerName?: string; } /** @@ -27,11 +28,16 @@ export const TerminalLink: FC = ({ agentName, userName = "me", workspaceName, + containerName, }) => { + const params = new URLSearchParams(); + if (containerName) { + params.append("container", containerName); + } // Always use the primary for the terminal link. This is a relative link. const href = `/@${userName}/${workspaceName}${ agentName ? `.${agentName}` : "" - }/terminal`; + }/terminal?${params.toString()}`; return ( pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy