Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,10 @@ export const getWorkspaceQuota = async (userID: string): Promise<TypesGen.Worksp
const response = await axios.get(`/api/v2/workspace-quota/${userID}`)
return response.data
}

export const getAgentListeningPorts = async (
agentID: string,
): Promise<TypesGen.ListeningPortsResponse> => {
const response = await axios.get(`/api/v2/workspaceagents/${agentID}/listening-ports`)
return response.data
}
84 changes: 69 additions & 15 deletions site/src/components/PortForwardButton/PortForwardButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,50 @@ import TextField from "@material-ui/core/TextField"
import OpenInNewOutlined from "@material-ui/icons/OpenInNewOutlined"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { Stack } from "components/Stack/Stack"
import { useRef, useState } from "react"
import { useRef, useState, Fragment } from "react"
import { colors } from "theme/colors"
import { CodeExample } from "../CodeExample/CodeExample"
import { HelpTooltipLink, HelpTooltipLinksGroup, HelpTooltipText } from "../Tooltips/HelpTooltip"
import {
HelpTooltipLink,
HelpTooltipLinksGroup,
HelpTooltipText,
HelpTooltipTitle,
} from "../Tooltips/HelpTooltip"
import { Maybe } from "components/Conditionals/Maybe"
import { useMachine } from "@xstate/react"
import { portForwardMachine } from "xServices/portForward/portForwardXService"

export interface PortForwardButtonProps {
host: string
username: string
workspaceName: string
agentName: string
agentId: string
}

const EnabledView: React.FC<PortForwardButtonProps> = (props) => {
const { host, workspaceName, agentName, username } = props
const { host, workspaceName, agentName, agentId, username } = props
const styles = useStyles()
const [port, setPort] = useState("3000")
const { location } = window
const urlExample = `${location.protocol}//${port}--${agentName}--${workspaceName}--${username}.${host}`
const [state] = useMachine(portForwardMachine, {
context: { agentId: agentId },
})
const ports = state.context.listeningPorts?.ports

return (
<Stack direction="column" spacing={1}>
<>
<HelpTooltipText>
Access ports running on the agent with the <strong>port, agent name, workspace name</strong>{" "}
and <strong>your username</strong> URL schema, as shown below.
</HelpTooltipText>

<CodeExample code={urlExample} />
<CodeExample code={urlExample} className={styles.code} />

<HelpTooltipText>Use the form to open applications in a new tab.</HelpTooltipText>

<Stack direction="row" spacing={1} alignItems="center">
<Stack direction="row" spacing={1} alignItems="center" className={styles.form}>
<TextField
label="Port"
type="number"
Expand All @@ -57,29 +70,61 @@ const EnabledView: React.FC<PortForwardButtonProps> = (props) => {
</Link>
</Stack>

<Maybe condition={Boolean(ports && ports.length > 0)}>
<HelpTooltipText>
{ports &&
ports.map((p, i) => {
const url = `${location.protocol}//${p.port}--${agentName}--${workspaceName}--${username}.${host}`
let label = `${p.port}`
if (p.process_name) {
label = `${p.process_name} - ${p.port}`
}

return (
<Fragment key={i}>
{i > 0 && <span style={{ margin: "0 0.6em" }}>&middot;</span>}
<Link href={url} target="_blank" rel="noreferrer">
{label}
</Link>
</Fragment>
)
})}
</HelpTooltipText>
</Maybe>

<HelpTooltipLinksGroup>
<HelpTooltipLink href="https://coder.com/docs/coder-oss/latest/networking/port-forwarding#dashboard">
Learn more about port forward
Learn more about web port forwarding
</HelpTooltipLink>
</HelpTooltipLinksGroup>
</Stack>
</>
)
}

const DisabledView: React.FC<PortForwardButtonProps> = () => {
const DisabledView: React.FC<PortForwardButtonProps> = ({ workspaceName, agentName }) => {
const cliExample = `coder port-forward ${workspaceName}.${agentName} --tcp 3000`
const styles = useStyles()

return (
<Stack direction="column" spacing={1}>
<>
<HelpTooltipText>
<strong>Your deployment does not have web port forwarding enabled.</strong> See the docs for
more details.
</HelpTooltipText>

<HelpTooltipText>
<strong>Your deployment does not have port forward enabled.</strong> See the docs for more
details.
You can use the Coder CLI to forward ports from your workspace to your local machine, as
shown below.
</HelpTooltipText>

<CodeExample code={cliExample} className={styles.code} />

<HelpTooltipLinksGroup>
<HelpTooltipLink href="https://coder.com/docs/coder-oss/latest/networking/port-forwarding#dashboard">
Learn more about port forward
Learn more about web port forwarding
</HelpTooltipLink>
</HelpTooltipLinksGroup>
</Stack>
</>
)
}

Expand Down Expand Up @@ -121,6 +166,7 @@ export const PortForwardButton: React.FC<PortForwardButtonProps> = (props) => {
horizontal: "left",
}}
>
<HelpTooltipTitle>Port forward</HelpTooltipTitle>
<ChooseOne>
<Cond condition={host !== ""}>
<EnabledView {...props} />
Expand All @@ -137,7 +183,7 @@ export const PortForwardButton: React.FC<PortForwardButtonProps> = (props) => {
const useStyles = makeStyles((theme) => ({
popoverPaper: {
padding: `${theme.spacing(2.5)}px ${theme.spacing(3.5)}px ${theme.spacing(3.5)}px`,
width: theme.spacing(46),
width: theme.spacing(52),
color: theme.palette.text.secondary,
marginTop: theme.spacing(0.25),
},
Expand All @@ -152,4 +198,12 @@ const useStyles = makeStyles((theme) => ({
borderColor: colors.gray[10],
},
},

code: {
margin: theme.spacing(2, 0),
},

form: {
margin: theme.spacing(1.5, 0, 0),
},
}))
1 change: 1 addition & 0 deletions site/src/components/Resources/Resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
host={applicationsHost}
workspaceName={workspace.name}
agentName={agent.name}
agentId={agent.id}
username={workspace.owner_name}
/>
)}
Expand Down
46 changes: 46 additions & 0 deletions site/src/xServices/portForward/portForwardXService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getAgentListeningPorts } from "api/api"
import { ListeningPortsResponse } from "api/typesGenerated"
import { createMachine, assign } from "xstate"

export const portForwardMachine = createMachine(
{
id: "portForwardMachine",
schema: {
context: {} as {
agentId: string
listeningPorts?: ListeningPortsResponse
},
services: {} as {
getListeningPorts: {
data: ListeningPortsResponse
}
},
},
tsTypes: {} as import("./portForwardXService.typegen").Typegen0,
initial: "loading",
states: {
loading: {
invoke: {
src: "getListeningPorts",
onDone: {
target: "success",
actions: ["assignListeningPorts"],
},
},
},
success: {
type: "final",
},
},
},
{
services: {
getListeningPorts: ({ agentId }) => getAgentListeningPorts(agentId),
},
actions: {
assignListeningPorts: assign({
listeningPorts: (_, { data }) => data,
}),
},
},
)