Skip to content

Commit d4c2e22

Browse files
committed
feature
1 parent c986370 commit d4c2e22

File tree

3 files changed

+146
-135
lines changed

3 files changed

+146
-135
lines changed

site/src/modules/resources/PortForwardButton.tsx

+137-129
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
190190
(port) => port.agent_name === agent.name,
191191
);
192192
// we don't want to show listening ports if it's a shared port
193-
const filteredListeningPorts = listeningPorts?.filter((port) => {
193+
const filteredListeningPorts = (listeningPorts ? listeningPorts : []).filter((port) => {
194194
for (let i = 0; i < filteredSharedPorts.length; i++) {
195195
if (filteredSharedPorts[i].port === port.port) {
196196
return false;
@@ -225,17 +225,10 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
225225
overflowY: "auto",
226226
}}
227227
>
228-
<header
229-
css={(theme) => ({
228+
<Stack direction="column"
229+
css={{
230230
padding: 20,
231-
paddingBottom: 10,
232-
position: "sticky",
233-
top: 0,
234-
background: theme.palette.background.paper,
235-
// For some reason the Share button label has a higher z-index than
236-
// the header. Probably some tricky stuff from MUI.
237-
zIndex: 1,
238-
})}
231+
}}
239232
>
240233
<Stack
241234
direction="row"
@@ -249,144 +242,153 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
249242
Learn more
250243
</HelpTooltipLink>
251244
</Stack>
252-
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
253-
{filteredListeningPorts?.length === 0
254-
? "No open ports were detected."
255-
: "The listening ports are exclusively accessible to you."}
256-
</HelpTooltipText>
257-
<Stack direction="row" gap={1} alignItems="flex-end" justifyContent="flex-end">
258-
<form
259-
css={styles.newPortForm}
260-
onSubmit={(e) => {
261-
e.preventDefault();
262-
const formData = new FormData(e.currentTarget);
263-
const port = Number(formData.get("portNumber"));
264-
const url = portForwardURL(
265-
host,
266-
port,
267-
agent.name,
268-
workspaceName,
269-
username,
270-
);
271-
window.open(url, "_blank");
245+
<Stack direction="column" gap={1}>
246+
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
247+
{"The listening ports are exclusively accessible to you."}
248+
</HelpTooltipText>
249+
<Stack
250+
direction="row"
251+
gap={2}
252+
css={{
253+
paddingBottom: 8,
272254
}}
273255
>
274-
<input
275-
aria-label="Port number"
276-
name="portNumber"
277-
type="number"
278-
placeholder="Connect to port..."
279-
min={9}
280-
max={65535}
281-
required
282-
css={styles.newPortInput}
283-
/>
284-
<Button
285-
type="submit"
286-
size="small"
287-
variant="text"
288-
css={{
289-
paddingLeft: 12,
290-
paddingRight: 12,
291-
minWidth: 0,
256+
<FormControl size="small" css={styles.protocolFormControl}>
257+
<Select
258+
css={styles.listeningPortProtocol}
259+
value={listeningPortProtocol}
260+
onChange={async (event) => {
261+
const selectedProtocol = event.target.value as "http" | "https";
262+
setListeningPortProtocol(selectedProtocol);
263+
saveWorkspaceListeningPortsProtocol(workspaceID, selectedProtocol);
264+
}}
265+
>
266+
<MenuItem value="http">HTTP</MenuItem>
267+
<MenuItem value="https">HTTPS</MenuItem>
268+
</Select>
269+
</FormControl>
270+
<form
271+
css={styles.newPortForm}
272+
onSubmit={(e) => {
273+
e.preventDefault();
274+
const formData = new FormData(e.currentTarget);
275+
const port = Number(formData.get("portNumber"));
276+
const url = portForwardURL(
277+
host,
278+
port,
279+
agent.name,
280+
workspaceName,
281+
username,
282+
listeningPortProtocol,
283+
);
284+
window.open(url, "_blank");
292285
}}
293286
>
294-
<OpenInNewOutlined
287+
<input
288+
aria-label="Port number"
289+
name="portNumber"
290+
type="number"
291+
placeholder="Connect to port..."
292+
min={9}
293+
max={65535}
294+
required
295+
css={styles.newPortInput}
296+
/>
297+
<Button
298+
type="submit"
299+
size="small"
300+
variant="text"
295301
css={{
296-
flexShrink: 0,
297-
width: 14,
298-
height: 14,
299-
color: theme.palette.text.primary,
302+
paddingLeft: 12,
303+
paddingRight: 12,
304+
minWidth: 0,
300305
}}
301-
/>
302-
</Button>
303-
</form>
304-
<FormControl size="small" css={styles.protocolFormControl}>
305-
<Select
306-
css={styles.listeningPortProtocol}
307-
value={listeningPortProtocol}
308-
onChange={async (event) => {
309-
const selectedProtocol = event.target.value as "http" | "https";
310-
setListeningPortProtocol(selectedProtocol);
311-
saveWorkspaceListeningPortsProtocol(workspaceID, selectedProtocol);
312-
}}
313-
>
314-
<MenuItem value="http">HTTP</MenuItem>
315-
<MenuItem value="https">HTTPS</MenuItem>
316-
</Select>
317-
</FormControl>
318-
</Stack>
319-
</header>
320-
<div
321-
css={{
322-
padding: 20,
323-
paddingTop: 0,
324-
}}
325-
>
326-
{filteredListeningPorts?.map((port) => {
327-
const url = portForwardURL(
328-
host,
329-
port.port,
330-
agent.name,
331-
workspaceName,
332-
username,
333-
);
334-
const label =
335-
port.process_name !== "" ? port.process_name : port.port;
336-
return (
337-
<Stack
338-
key={port.port}
339-
direction="row"
340-
alignItems="center"
341-
justifyContent="space-between"
342-
>
343-
<Link
344-
underline="none"
345-
css={styles.portLink}
346-
href={url}
347-
target="_blank"
348-
rel="noreferrer"
349306
>
350-
<SensorsIcon css={{ width: 14, height: 14 }} />
351-
{label}
352-
</Link>
307+
<OpenInNewOutlined
308+
css={{
309+
flexShrink: 0,
310+
width: 14,
311+
height: 14,
312+
color: theme.palette.text.primary,
313+
}}
314+
/>
315+
</Button>
316+
</form>
317+
</Stack>
318+
</Stack>
319+
{filteredListeningPorts.length === 0 && (
320+
<HelpTooltipText css={styles.noPortText}>
321+
{"No open ports were detected."}
322+
</HelpTooltipText>
323+
)}
324+
{filteredListeningPorts.map((port) => {
325+
const url = portForwardURL(
326+
host,
327+
port.port,
328+
agent.name,
329+
workspaceName,
330+
username,
331+
listeningPortProtocol,
332+
);
333+
const label =
334+
port.process_name !== "" ? port.process_name : port.port;
335+
return (
353336
<Stack
337+
key={port.port}
354338
direction="row"
355-
gap={2}
356-
justifyContent="flex-end"
357339
alignItems="center"
340+
justifyContent="space-between"
358341
>
342+
<Stack direction="row" gap={3}>
359343
<Link
360344
underline="none"
361345
css={styles.portLink}
362346
href={url}
363347
target="_blank"
364348
rel="noreferrer"
365349
>
366-
<span css={styles.portNumber}>{port.port}</span>
350+
<SensorsIcon css={{ width: 14, height: 14 }} />
351+
{port.port}
367352
</Link>
368-
{canSharePorts && (
369-
<Button
370-
size="small"
371-
variant="text"
372-
onClick={async () => {
373-
await upsertSharedPortMutation.mutateAsync({
374-
agent_name: agent.name,
375-
port: port.port,
376-
protocol: "http",
377-
share_level: "authenticated",
378-
});
379-
await sharedPortsQuery.refetch();
380-
}}
353+
<Link
354+
underline="none"
355+
css={styles.portLink}
356+
href={url}
357+
target="_blank"
358+
rel="noreferrer"
381359
>
382-
Share
383-
</Button>
384-
)}
360+
{label}
361+
</Link>
362+
</Stack>
363+
<Stack
364+
direction="row"
365+
gap={2}
366+
justifyContent="flex-end"
367+
alignItems="center"
368+
>
369+
370+
{canSharePorts && (
371+
<Button
372+
size="small"
373+
variant="text"
374+
onClick={async () => {
375+
await upsertSharedPortMutation.mutateAsync({
376+
agent_name: agent.name,
377+
port: port.port,
378+
protocol: listeningPortProtocol,
379+
share_level: "authenticated",
380+
});
381+
await sharedPortsQuery.refetch();
382+
}}
383+
>
384+
Share
385+
</Button>
386+
)}
387+
</Stack>
385388
</Stack>
386-
</Stack>
387-
);
388-
})}
389-
</div>
389+
);
390+
})}
391+
</Stack>
390392
</div>
391393
{portSharingExperimentEnabled && (
392394
<div
@@ -410,7 +412,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
410412
agent.name,
411413
workspaceName,
412414
username,
413-
share.protocol === "https",
415+
share.protocol,
414416
);
415417
const label = share.port;
416418
return (
@@ -666,6 +668,12 @@ const styles = {
666668
display: "block",
667669
width: "100%",
668670
}),
671+
noPortText: (theme) => ({
672+
color: theme.palette.text.secondary,
673+
paddingTop: 20,
674+
paddingBottom: 10,
675+
textAlign: "center",
676+
}),
669677
sharedPortLink: () => ({
670678
minWidth: 80,
671679
}),

site/src/testHelpers/entities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3261,7 +3261,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
32613261
export const MockListeningPortsResponse: TypesGen.WorkspaceAgentListeningPortsResponse =
32623262
{
32633263
ports: [
3264-
{ process_name: "webb", network: "", port: 3000 },
3264+
{ process_name: "webb", network: "", port: 30000 },
32653265
{ process_name: "gogo", network: "", port: 8080 },
32663266
{ process_name: "", network: "", port: 8081 },
32673267
],

site/src/utils/portForward.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import type { WorkspaceAgentPortShareProtocol } from "api/typesGenerated";
2+
13
export const portForwardURL = (
24
host: string,
35
port: number,
46
agentName: string,
57
workspaceName: string,
68
username: string,
7-
https = false,
9+
protocol: WorkspaceAgentPortShareProtocol,
810
): string => {
911
const { location } = window;
10-
const suffix = https ? "s" : "";
12+
const suffix = protocol === "https" ? "s" : "";
1113

1214
const subdomain = `${port}${suffix}--${agentName}--${workspaceName}--${username}`;
1315
return `${location.protocol}//${host}`.replace("*", subdomain);
@@ -56,17 +58,18 @@ export const openMaybePortForwardedURL = (
5658
agentName,
5759
workspaceName,
5860
username,
61+
url.protocol.replace(":", "") as WorkspaceAgentPortShareProtocol,
5962
) + url.pathname,
6063
);
6164
} catch (ex) {
6265
open(uri);
6366
}
6467
};
6568

66-
export const saveWorkspaceListeningPortsProtocol = (workspaceID: string, protocol: "http" | "https") => {
69+
export const saveWorkspaceListeningPortsProtocol = (workspaceID: string, protocol: WorkspaceAgentPortShareProtocol) => {
6770
localStorage.setItem(`listening-ports-protocol-workspace-${workspaceID}`, protocol);
6871
}
6972

70-
export const getWorkspaceListeningPortsProtocol = (workspaceID: string) => {
71-
return localStorage.getItem(`listening-ports-protocol-workspace-${workspaceID}`) || "http";
73+
export const getWorkspaceListeningPortsProtocol = (workspaceID: string): WorkspaceAgentPortShareProtocol => {
74+
return (localStorage.getItem(`listening-ports-protocol-workspace-${workspaceID}`) || "http") as WorkspaceAgentPortShareProtocol;
7275
}

0 commit comments

Comments
 (0)