From c9863709345adffeda21f8c0cb50bf739fcf09f3 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 9 Apr 2024 15:18:45 +0000 Subject: [PATCH 1/6] feat: add listening ports protocol selector --- .../modules/resources/PortForwardButton.tsx | 121 +++++++++++------- site/src/utils/portForward.ts | 8 ++ 2 files changed, 85 insertions(+), 44 deletions(-) diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index e445f99ea1ca3..761582d3009ca 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -16,7 +16,7 @@ import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Tooltip from "@mui/material/Tooltip"; import { type FormikContextType, useFormik } from "formik"; -import type { FC } from "react"; +import { useState, type FC } from "react"; import { useQuery, useMutation } from "react-query"; import * as Yup from "yup"; import { getAgentListeningPorts } from "api/api"; @@ -48,7 +48,7 @@ import { type ClassName, useClassName } from "hooks/useClassName"; import { useDashboard } from "modules/dashboard/useDashboard"; import { docs } from "utils/docs"; import { getFormHelpers } from "utils/formUtils"; -import { portForwardURL } from "utils/portForward"; +import { getWorkspaceListeningPortsProtocol, portForwardURL, saveWorkspaceListeningPortsProtocol } from "utils/portForward"; export interface PortForwardButtonProps { host: string; @@ -135,6 +135,7 @@ export const PortForwardPopoverView: FC = ({ portSharingControlsEnabled, }) => { const theme = useTheme(); + const [listeningPortProtocol, setListeningPortProtocol] = useState(getWorkspaceListeningPortsProtocol(workspaceID)); const sharedPortsQuery = useQuery({ ...workspacePortShares(workspaceID), @@ -253,52 +254,68 @@ export const PortForwardPopoverView: FC = ({ ? "No open ports were detected." : "The listening ports are exclusively accessible to you."} -
{ - e.preventDefault(); - const formData = new FormData(e.currentTarget); - const port = Number(formData.get("portNumber")); - const url = portForwardURL( - host, - port, - agent.name, - workspaceName, - username, - ); - window.open(url, "_blank"); - }} - > - - -
+ > + + + + + + +
({ + boxShadow: "none", + ".MuiOutlinedInput-notchedOutline": { border: 0 }, + "&.MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline": { + border: 0, + }, + "&.MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline": { + border: 0, + }, + border: `1px solid ${theme.palette.divider}`, + borderRadius: "4px", + marginTop: 8, + minWidth: "100px", }), newPortInput: (theme) => ({ diff --git a/site/src/utils/portForward.ts b/site/src/utils/portForward.ts index bd666823b2c23..fb27b7564cea1 100644 --- a/site/src/utils/portForward.ts +++ b/site/src/utils/portForward.ts @@ -62,3 +62,11 @@ export const openMaybePortForwardedURL = ( open(uri); } }; + +export const saveWorkspaceListeningPortsProtocol = (workspaceID: string, protocol: "http" | "https") => { + localStorage.setItem(`listening-ports-protocol-workspace-${workspaceID}`, protocol); +} + +export const getWorkspaceListeningPortsProtocol = (workspaceID: string) => { + return localStorage.getItem(`listening-ports-protocol-workspace-${workspaceID}`) || "http"; +} From d4c2e2249750a7108f3c8c2240c65b092d070013 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 10 Apr 2024 16:29:33 +0000 Subject: [PATCH 2/6] feature --- .../modules/resources/PortForwardButton.tsx | 266 +++++++++--------- site/src/testHelpers/entities.ts | 2 +- site/src/utils/portForward.ts | 13 +- 3 files changed, 146 insertions(+), 135 deletions(-) diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index 761582d3009ca..3d21f6f8a693c 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -190,7 +190,7 @@ export const PortForwardPopoverView: FC = ({ (port) => port.agent_name === agent.name, ); // we don't want to show listening ports if it's a shared port - const filteredListeningPorts = listeningPorts?.filter((port) => { + const filteredListeningPorts = (listeningPorts ? listeningPorts : []).filter((port) => { for (let i = 0; i < filteredSharedPorts.length; i++) { if (filteredSharedPorts[i].port === port.port) { return false; @@ -225,17 +225,10 @@ export const PortForwardPopoverView: FC = ({ overflowY: "auto", }} > -
({ + = ({ Learn more - - {filteredListeningPorts?.length === 0 - ? "No open ports were detected." - : "The listening ports are exclusively accessible to you."} - - -
{ - e.preventDefault(); - const formData = new FormData(e.currentTarget); - const port = Number(formData.get("portNumber")); - const url = portForwardURL( - host, - port, - agent.name, - workspaceName, - username, - ); - window.open(url, "_blank"); + + + {"The listening ports are exclusively accessible to you."} + + - - - - - - - -
-
- {filteredListeningPorts?.map((port) => { - const url = portForwardURL( - host, - port.port, - agent.name, - workspaceName, - username, - ); - const label = - port.process_name !== "" ? port.process_name : port.port; - return ( - - - - {label} - + + + + + + {filteredListeningPorts.length === 0 && ( + + {"No open ports were detected."} + + )} + {filteredListeningPorts.map((port) => { + const url = portForwardURL( + host, + port.port, + agent.name, + workspaceName, + username, + listeningPortProtocol, + ); + const label = + port.process_name !== "" ? port.process_name : port.port; + return ( + = ({ target="_blank" rel="noreferrer" > - {port.port} + + {port.port} - {canSharePorts && ( - - )} + {label} + + + + + {canSharePorts && ( + + )} + - - ); - })} -
+ ); + })} +
{portSharingExperimentEnabled && (
= ({ agent.name, workspaceName, username, - share.protocol === "https", + share.protocol, ); const label = share.port; return ( @@ -666,6 +668,12 @@ const styles = { display: "block", width: "100%", }), + noPortText: (theme) => ({ + color: theme.palette.text.secondary, + paddingTop: 20, + paddingBottom: 10, + textAlign: "center", + }), sharedPortLink: () => ({ minWidth: 80, }), diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index d48cb2aeb385e..3d9f837ab333b 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -3261,7 +3261,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { export const MockListeningPortsResponse: TypesGen.WorkspaceAgentListeningPortsResponse = { ports: [ - { process_name: "webb", network: "", port: 3000 }, + { process_name: "webb", network: "", port: 30000 }, { process_name: "gogo", network: "", port: 8080 }, { process_name: "", network: "", port: 8081 }, ], diff --git a/site/src/utils/portForward.ts b/site/src/utils/portForward.ts index fb27b7564cea1..948dca8e09bb0 100644 --- a/site/src/utils/portForward.ts +++ b/site/src/utils/portForward.ts @@ -1,13 +1,15 @@ +import type { WorkspaceAgentPortShareProtocol } from "api/typesGenerated"; + export const portForwardURL = ( host: string, port: number, agentName: string, workspaceName: string, username: string, - https = false, + protocol: WorkspaceAgentPortShareProtocol, ): string => { const { location } = window; - const suffix = https ? "s" : ""; + const suffix = protocol === "https" ? "s" : ""; const subdomain = `${port}${suffix}--${agentName}--${workspaceName}--${username}`; return `${location.protocol}//${host}`.replace("*", subdomain); @@ -56,6 +58,7 @@ export const openMaybePortForwardedURL = ( agentName, workspaceName, username, + url.protocol.replace(":", "") as WorkspaceAgentPortShareProtocol, ) + url.pathname, ); } catch (ex) { @@ -63,10 +66,10 @@ export const openMaybePortForwardedURL = ( } }; -export const saveWorkspaceListeningPortsProtocol = (workspaceID: string, protocol: "http" | "https") => { +export const saveWorkspaceListeningPortsProtocol = (workspaceID: string, protocol: WorkspaceAgentPortShareProtocol) => { localStorage.setItem(`listening-ports-protocol-workspace-${workspaceID}`, protocol); } -export const getWorkspaceListeningPortsProtocol = (workspaceID: string) => { - return localStorage.getItem(`listening-ports-protocol-workspace-${workspaceID}`) || "http"; +export const getWorkspaceListeningPortsProtocol = (workspaceID: string): WorkspaceAgentPortShareProtocol => { + return (localStorage.getItem(`listening-ports-protocol-workspace-${workspaceID}`) || "http") as WorkspaceAgentPortShareProtocol; } From 6fa90abb8e0fc322387e633450db410fffc6c368 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 10 Apr 2024 16:32:59 +0000 Subject: [PATCH 3/6] fmt --- .../modules/resources/PortForwardButton.tsx | 147 ++++++++++-------- site/src/utils/portForward.ts | 22 ++- 2 files changed, 96 insertions(+), 73 deletions(-) diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index 3d21f6f8a693c..f7240622f65c5 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -48,7 +48,11 @@ import { type ClassName, useClassName } from "hooks/useClassName"; import { useDashboard } from "modules/dashboard/useDashboard"; import { docs } from "utils/docs"; import { getFormHelpers } from "utils/formUtils"; -import { getWorkspaceListeningPortsProtocol, portForwardURL, saveWorkspaceListeningPortsProtocol } from "utils/portForward"; +import { + getWorkspaceListeningPortsProtocol, + portForwardURL, + saveWorkspaceListeningPortsProtocol, +} from "utils/portForward"; export interface PortForwardButtonProps { host: string; @@ -135,7 +139,9 @@ export const PortForwardPopoverView: FC = ({ portSharingControlsEnabled, }) => { const theme = useTheme(); - const [listeningPortProtocol, setListeningPortProtocol] = useState(getWorkspaceListeningPortsProtocol(workspaceID)); + const [listeningPortProtocol, setListeningPortProtocol] = useState( + getWorkspaceListeningPortsProtocol(workspaceID), + ); const sharedPortsQuery = useQuery({ ...workspacePortShares(workspaceID), @@ -190,15 +196,17 @@ export const PortForwardPopoverView: FC = ({ (port) => port.agent_name === agent.name, ); // we don't want to show listening ports if it's a shared port - const filteredListeningPorts = (listeningPorts ? listeningPorts : []).filter((port) => { - for (let i = 0; i < filteredSharedPorts.length; i++) { - if (filteredSharedPorts[i].port === port.port) { - return false; + const filteredListeningPorts = (listeningPorts ? listeningPorts : []).filter( + (port) => { + for (let i = 0; i < filteredSharedPorts.length; i++) { + if (filteredSharedPorts[i].port === port.port) { + return false; + } } - } - return true; - }); + return true; + }, + ); // only disable the form if shared port controls are entitled and the template doesn't allow sharing ports const canSharePorts = portSharingExperimentEnabled && @@ -225,7 +233,8 @@ export const PortForwardPopoverView: FC = ({ overflowY: "auto", }} > - = ({ css={styles.listeningPortProtocol} value={listeningPortProtocol} onChange={async (event) => { - const selectedProtocol = event.target.value as "http" | "https"; + const selectedProtocol = event.target.value as + | "http" + | "https"; setListeningPortProtocol(selectedProtocol); - saveWorkspaceListeningPortsProtocol(workspaceID, selectedProtocol); + saveWorkspaceListeningPortsProtocol( + workspaceID, + selectedProtocol, + ); }} > HTTP @@ -318,28 +332,28 @@ export const PortForwardPopoverView: FC = ({ {filteredListeningPorts.length === 0 && ( - {"No open ports were detected."} + {"No open ports were detected."} )} - {filteredListeningPorts.map((port) => { - const url = portForwardURL( - host, - port.port, - agent.name, - workspaceName, - username, - listeningPortProtocol, - ); - const label = - port.process_name !== "" ? port.process_name : port.port; - return ( - - + {filteredListeningPorts.map((port) => { + const url = portForwardURL( + host, + port.port, + agent.name, + workspaceName, + username, + listeningPortProtocol, + ); + const label = + port.process_name !== "" ? port.process_name : port.port; + return ( + + = ({ {port.port} - {label} - - - - - {canSharePorts && ( - - )} - + {label} + + + + {canSharePorts && ( + + )} - ); - })} + + ); + })}
{portSharingExperimentEnabled && ( diff --git a/site/src/utils/portForward.ts b/site/src/utils/portForward.ts index 948dca8e09bb0..48ef5f39dea4f 100644 --- a/site/src/utils/portForward.ts +++ b/site/src/utils/portForward.ts @@ -66,10 +66,20 @@ export const openMaybePortForwardedURL = ( } }; -export const saveWorkspaceListeningPortsProtocol = (workspaceID: string, protocol: WorkspaceAgentPortShareProtocol) => { - localStorage.setItem(`listening-ports-protocol-workspace-${workspaceID}`, protocol); -} +export const saveWorkspaceListeningPortsProtocol = ( + workspaceID: string, + protocol: WorkspaceAgentPortShareProtocol, +) => { + localStorage.setItem( + `listening-ports-protocol-workspace-${workspaceID}`, + protocol, + ); +}; -export const getWorkspaceListeningPortsProtocol = (workspaceID: string): WorkspaceAgentPortShareProtocol => { - return (localStorage.getItem(`listening-ports-protocol-workspace-${workspaceID}`) || "http") as WorkspaceAgentPortShareProtocol; -} +export const getWorkspaceListeningPortsProtocol = ( + workspaceID: string, +): WorkspaceAgentPortShareProtocol => { + return (localStorage.getItem( + `listening-ports-protocol-workspace-${workspaceID}`, + ) || "http") as WorkspaceAgentPortShareProtocol; +}; From 939858c8129f971e056ca5c546164729e4594e73 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 10 Apr 2024 16:51:13 +0000 Subject: [PATCH 4/6] update title --- site/src/modules/resources/PortForwardButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index f7240622f65c5..fa078d6da186f 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -244,7 +244,7 @@ export const PortForwardPopoverView: FC = ({ justifyContent="space-between" alignItems="start" > - Listening ports + Listening Ports @@ -253,7 +253,7 @@ export const PortForwardPopoverView: FC = ({ - {"The listening ports are exclusively accessible to you."} + {"The listening ports are exclusively accessible to you. Selecting HTTP/S will change the protocol for all listening ports."} Date: Wed, 10 Apr 2024 16:59:11 +0000 Subject: [PATCH 5/6] make fmt --- site/src/modules/resources/PortForwardButton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index fa078d6da186f..c0d5259d3b93a 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -253,7 +253,9 @@ export const PortForwardPopoverView: FC = ({ - {"The listening ports are exclusively accessible to you. Selecting HTTP/S will change the protocol for all listening ports."} + { + "The listening ports are exclusively accessible to you. Selecting HTTP/S will change the protocol for all listening ports." + } Date: Mon, 15 Apr 2024 18:51:53 +0000 Subject: [PATCH 6/6] pr review --- .../modules/resources/PortForwardButton.tsx | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index c0d5259d3b93a..0084995040847 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -196,16 +196,8 @@ export const PortForwardPopoverView: FC = ({ (port) => port.agent_name === agent.name, ); // we don't want to show listening ports if it's a shared port - const filteredListeningPorts = (listeningPorts ? listeningPorts : []).filter( - (port) => { - for (let i = 0; i < filteredSharedPorts.length; i++) { - if (filteredSharedPorts[i].port === port.port) { - return false; - } - } - - return true; - }, + const filteredListeningPorts = (listeningPorts ?? []).filter((port) => + filteredSharedPorts.every((sharedPort) => sharedPort.port !== port.port), ); // only disable the form if shared port controls are entitled and the template doesn't allow sharing ports const canSharePorts = @@ -253,9 +245,8 @@ export const PortForwardPopoverView: FC = ({ - { - "The listening ports are exclusively accessible to you. Selecting HTTP/S will change the protocol for all listening ports." - } + The listening ports are exclusively accessible to you. Selecting + HTTP/S will change the protocol for all listening ports. = ({ {filteredListeningPorts.length === 0 && ( - {"No open ports were detected."} + No open ports were detected. )} {filteredListeningPorts.map((port) => {