mirror of https://github.com/coder/coder.git
feat: move shared ports out of experiment
This commit is contained in:
parent
845407fe7a
commit
3e44582162
|
@ -9517,7 +9517,6 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"example",
|
||||
"shared-ports",
|
||||
"auto-fill-parameters"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
|
@ -9526,7 +9525,6 @@ const docTemplate = `{
|
|||
},
|
||||
"x-enum-varnames": [
|
||||
"ExperimentExample",
|
||||
"ExperimentSharedPorts",
|
||||
"ExperimentAutoFillParameters"
|
||||
]
|
||||
},
|
||||
|
|
|
@ -8516,16 +8516,12 @@
|
|||
},
|
||||
"codersdk.Experiment": {
|
||||
"type": "string",
|
||||
"enum": ["example", "shared-ports", "auto-fill-parameters"],
|
||||
"enum": ["example", "auto-fill-parameters"],
|
||||
"x-enum-comments": {
|
||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"ExperimentExample": "This isn't used for anything."
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"ExperimentExample",
|
||||
"ExperimentSharedPorts",
|
||||
"ExperimentAutoFillParameters"
|
||||
]
|
||||
"x-enum-varnames": ["ExperimentExample", "ExperimentAutoFillParameters"]
|
||||
},
|
||||
"codersdk.ExternalAuth": {
|
||||
"type": "object",
|
||||
|
|
|
@ -1055,9 +1055,6 @@ func New(options *Options) *API {
|
|||
r.Put("/autoupdates", api.putWorkspaceAutoupdates)
|
||||
r.Get("/resolve-autostart", api.resolveAutostart)
|
||||
r.Route("/port-share", func(r chi.Router) {
|
||||
r.Use(
|
||||
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentSharedPorts),
|
||||
)
|
||||
r.Get("/", api.workspaceAgentPortShares)
|
||||
r.Post("/", api.postWorkspaceAgentPortShare)
|
||||
r.Delete("/", api.deleteWorkspaceAgentPortShare)
|
||||
|
|
|
@ -19,11 +19,7 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) {
|
|||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
dep := coderdtest.DeploymentValues(t)
|
||||
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
|
||||
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dep,
|
||||
})
|
||||
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
|
||||
|
||||
|
@ -140,11 +136,7 @@ func TestGetWorkspaceAgentPortShares(t *testing.T) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
dep := coderdtest.DeploymentValues(t)
|
||||
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
|
||||
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dep,
|
||||
})
|
||||
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
|
||||
|
||||
|
@ -180,11 +172,7 @@ func TestDeleteWorkspaceAgentPortShare(t *testing.T) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
dep := coderdtest.DeploymentValues(t)
|
||||
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
|
||||
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dep,
|
||||
})
|
||||
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
|
||||
|
||||
|
|
|
@ -257,7 +257,6 @@ func TestWorkspaceApps(t *testing.T) {
|
|||
deploymentValues.DisablePathApps = serpent.Bool(opts.DisablePathApps)
|
||||
deploymentValues.Dangerous.AllowPathAppSharing = serpent.Bool(opts.DangerousAllowPathAppSharing)
|
||||
deploymentValues.Dangerous.AllowPathAppSiteOwnerAccess = serpent.Bool(opts.DangerousAllowPathAppSiteOwnerAccess)
|
||||
deploymentValues.Experiments = append(deploymentValues.Experiments, string(codersdk.ExperimentSharedPorts))
|
||||
|
||||
if opts.DisableSubdomainApps {
|
||||
opts.AppHost = ""
|
||||
|
|
|
@ -2192,8 +2192,7 @@ type Experiment string
|
|||
|
||||
const (
|
||||
// Add new experiments here!
|
||||
ExperimentExample Experiment = "example" // This isn't used for anything.
|
||||
ExperimentSharedPorts Experiment = "shared-ports"
|
||||
ExperimentExample Experiment = "example" // This isn't used for anything.
|
||||
ExperimentAutoFillParameters Experiment = "auto-fill-parameters" // This should not be taken out of experiments until we have redesigned the feature.
|
||||
)
|
||||
|
||||
|
@ -2201,9 +2200,7 @@ const (
|
|||
// users to opt-in to via --experimental='*'.
|
||||
// Experiments that are not ready for consumption by all users should
|
||||
// not be included here and will be essentially hidden.
|
||||
var ExperimentsAll = Experiments{
|
||||
ExperimentSharedPorts,
|
||||
}
|
||||
var ExperimentsAll = Experiments{}
|
||||
|
||||
// Experiments is a list of experiments.
|
||||
// Multiple experiments may be enabled at the same time.
|
||||
|
|
|
@ -2666,7 +2666,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
|||
| Value |
|
||||
| ---------------------- |
|
||||
| `example` |
|
||||
| `shared-ports` |
|
||||
| `auto-fill-parameters` |
|
||||
|
||||
## codersdk.ExternalAuth
|
||||
|
|
|
@ -17,12 +17,9 @@ import (
|
|||
func TestWorkspacePortShare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dep := coderdtest.DeploymentValues(t)
|
||||
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
DeploymentValues: dep,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
|
|
|
@ -1898,12 +1898,8 @@ export const Entitlements: Entitlement[] = [
|
|||
];
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type Experiment = "auto-fill-parameters" | "example" | "shared-ports";
|
||||
export const Experiments: Experiment[] = [
|
||||
"auto-fill-parameters",
|
||||
"example",
|
||||
"shared-ports",
|
||||
];
|
||||
export type Experiment = "auto-fill-parameters" | "example";
|
||||
export const Experiments: Experiment[] = ["auto-fill-parameters", "example"];
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type FeatureName =
|
||||
|
|
|
@ -65,7 +65,7 @@ export interface PortForwardButtonProps {
|
|||
|
||||
export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
|
||||
const { agent } = props;
|
||||
const { entitlements, experiments } = useDashboard();
|
||||
const { entitlements } = useDashboard();
|
||||
const paper = useClassName(classNames.paper, []);
|
||||
|
||||
const portsQuery = useQuery({
|
||||
|
@ -103,7 +103,6 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
|
|||
<PortForwardPopoverView
|
||||
{...props}
|
||||
listeningPorts={portsQuery.data?.ports}
|
||||
portSharingExperimentEnabled={experiments.includes("shared-ports")}
|
||||
portSharingControlsEnabled={
|
||||
entitlements.features.control_shared_ports.enabled
|
||||
}
|
||||
|
@ -121,7 +120,6 @@ const getValidationSchema = (): Yup.AnyObjectSchema =>
|
|||
|
||||
interface PortForwardPopoverViewProps extends PortForwardButtonProps {
|
||||
listeningPorts?: readonly WorkspaceAgentListeningPort[];
|
||||
portSharingExperimentEnabled: boolean;
|
||||
portSharingControlsEnabled: boolean;
|
||||
}
|
||||
|
||||
|
@ -135,7 +133,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
|
|||
template,
|
||||
username,
|
||||
listeningPorts,
|
||||
portSharingExperimentEnabled,
|
||||
portSharingControlsEnabled,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
@ -200,9 +197,9 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
|
|||
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 =
|
||||
portSharingExperimentEnabled &&
|
||||
!(portSharingControlsEnabled && template.max_port_share_level === "owner");
|
||||
const canSharePorts = !(
|
||||
portSharingControlsEnabled && template.max_port_share_level === "owner"
|
||||
);
|
||||
const canSharePortsPublic =
|
||||
canSharePorts && template.max_port_share_level === "public";
|
||||
|
||||
|
@ -396,183 +393,179 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
|
|||
})}
|
||||
</Stack>
|
||||
</div>
|
||||
{portSharingExperimentEnabled && (
|
||||
<div
|
||||
css={{
|
||||
padding: 20,
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
>
|
||||
<HelpTooltipTitle>Shared Ports</HelpTooltipTitle>
|
||||
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
|
||||
{canSharePorts
|
||||
? "Ports can be shared with other Coder users or with the public. Changing the protocol may take up to 1 minute to take effect."
|
||||
: "This workspace template does not allow sharing ports. Contact a template administrator to enable port sharing."}
|
||||
</HelpTooltipText>
|
||||
{canSharePorts && (
|
||||
<div>
|
||||
{filteredSharedPorts?.map((share) => {
|
||||
const url = portForwardURL(
|
||||
host,
|
||||
share.port,
|
||||
agent.name,
|
||||
workspaceName,
|
||||
username,
|
||||
share.protocol,
|
||||
);
|
||||
const label = share.port;
|
||||
return (
|
||||
<Stack
|
||||
key={share.port}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
<div
|
||||
css={{
|
||||
padding: 20,
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
>
|
||||
<HelpTooltipTitle>Shared Ports</HelpTooltipTitle>
|
||||
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
|
||||
{canSharePorts
|
||||
? "Ports can be shared with other Coder users or with the public. Changing the protocol may take up to 1 minute to take effect."
|
||||
: "This workspace template does not allow sharing ports. Contact a template administrator to enable port sharing."}
|
||||
</HelpTooltipText>
|
||||
{canSharePorts && (
|
||||
<div>
|
||||
{filteredSharedPorts?.map((share) => {
|
||||
const url = portForwardURL(
|
||||
host,
|
||||
share.port,
|
||||
agent.name,
|
||||
workspaceName,
|
||||
username,
|
||||
share.protocol,
|
||||
);
|
||||
const label = share.port;
|
||||
return (
|
||||
<Stack
|
||||
key={share.port}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Link
|
||||
underline="none"
|
||||
css={styles.portLink}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Link
|
||||
underline="none"
|
||||
css={styles.portLink}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
{share.share_level === "public" ? (
|
||||
<LockOpenIcon css={{ width: 14, height: 14 }} />
|
||||
) : (
|
||||
<LockIcon css={{ width: 14, height: 14 }} />
|
||||
)}
|
||||
{label}
|
||||
</Link>
|
||||
<FormControl size="small" css={styles.protocolFormControl}>
|
||||
<Select
|
||||
css={styles.shareLevelSelect}
|
||||
value={share.protocol}
|
||||
onChange={async (event) => {
|
||||
await upsertSharedPortMutation.mutateAsync({
|
||||
agent_name: agent.name,
|
||||
port: share.port,
|
||||
protocol: event.target
|
||||
.value as WorkspaceAgentPortShareProtocol,
|
||||
share_level: share.share_level,
|
||||
});
|
||||
await sharedPortsQuery.refetch();
|
||||
}}
|
||||
>
|
||||
<MenuItem value="http">HTTP</MenuItem>
|
||||
<MenuItem value="https">HTTPS</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Stack direction="row" justifyContent="flex-end">
|
||||
<FormControl
|
||||
size="small"
|
||||
css={styles.shareLevelFormControl}
|
||||
>
|
||||
{share.share_level === "public" ? (
|
||||
<LockOpenIcon css={{ width: 14, height: 14 }} />
|
||||
) : (
|
||||
<LockIcon css={{ width: 14, height: 14 }} />
|
||||
)}
|
||||
{label}
|
||||
</Link>
|
||||
<FormControl size="small" css={styles.protocolFormControl}>
|
||||
<Select
|
||||
css={styles.shareLevelSelect}
|
||||
value={share.protocol}
|
||||
value={share.share_level}
|
||||
onChange={async (event) => {
|
||||
await upsertSharedPortMutation.mutateAsync({
|
||||
agent_name: agent.name,
|
||||
port: share.port,
|
||||
protocol: event.target
|
||||
.value as WorkspaceAgentPortShareProtocol,
|
||||
share_level: share.share_level,
|
||||
protocol: share.protocol,
|
||||
share_level: event.target
|
||||
.value as WorkspaceAgentPortShareLevel,
|
||||
});
|
||||
await sharedPortsQuery.refetch();
|
||||
}}
|
||||
>
|
||||
<MenuItem value="http">HTTP</MenuItem>
|
||||
<MenuItem value="https">HTTPS</MenuItem>
|
||||
<MenuItem value="authenticated">Authenticated</MenuItem>
|
||||
{canSharePortsPublic ? (
|
||||
<MenuItem value="public">Public</MenuItem>
|
||||
) : (
|
||||
disabledPublicMenuItem
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Stack direction="row" justifyContent="flex-end">
|
||||
<FormControl
|
||||
size="small"
|
||||
css={styles.shareLevelFormControl}
|
||||
>
|
||||
<Select
|
||||
css={styles.shareLevelSelect}
|
||||
value={share.share_level}
|
||||
onChange={async (event) => {
|
||||
await upsertSharedPortMutation.mutateAsync({
|
||||
agent_name: agent.name,
|
||||
port: share.port,
|
||||
protocol: share.protocol,
|
||||
share_level: event.target
|
||||
.value as WorkspaceAgentPortShareLevel,
|
||||
});
|
||||
await sharedPortsQuery.refetch();
|
||||
}}
|
||||
>
|
||||
<MenuItem value="authenticated">
|
||||
Authenticated
|
||||
</MenuItem>
|
||||
{canSharePortsPublic ? (
|
||||
<MenuItem value="public">Public</MenuItem>
|
||||
) : (
|
||||
disabledPublicMenuItem
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
css={styles.deleteButton}
|
||||
onClick={async () => {
|
||||
await deleteSharedPortMutation.mutateAsync({
|
||||
agent_name: agent.name,
|
||||
port: share.port,
|
||||
});
|
||||
await sharedPortsQuery.refetch();
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
css={styles.deleteButton}
|
||||
onClick={async () => {
|
||||
await deleteSharedPortMutation.mutateAsync({
|
||||
agent_name: agent.name,
|
||||
port: share.port,
|
||||
});
|
||||
await sharedPortsQuery.refetch();
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
css={{
|
||||
width: 14,
|
||||
height: 14,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
css={{
|
||||
width: 14,
|
||||
height: 14,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
</Stack>
|
||||
/>
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={2}
|
||||
justifyContent="flex-end"
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
{...getFieldHelpers("port")}
|
||||
disabled={isSubmitting}
|
||||
label="Port"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
value={form.values.port}
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("protocol")}
|
||||
disabled={isSubmitting}
|
||||
fullWidth
|
||||
select
|
||||
value={form.values.protocol}
|
||||
label="Protocol"
|
||||
>
|
||||
<MenuItem value="http">HTTP</MenuItem>
|
||||
<MenuItem value="https">HTTPS</MenuItem>
|
||||
</TextField>
|
||||
<TextField
|
||||
{...getFieldHelpers("share_level")}
|
||||
disabled={isSubmitting}
|
||||
fullWidth
|
||||
select
|
||||
value={form.values.share_level}
|
||||
label="Sharing Level"
|
||||
>
|
||||
<MenuItem value="authenticated">Authenticated</MenuItem>
|
||||
{canSharePortsPublic ? (
|
||||
<MenuItem value="public">Public</MenuItem>
|
||||
) : (
|
||||
disabledPublicMenuItem
|
||||
)}
|
||||
</TextField>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={!form.isValid}
|
||||
>
|
||||
Share Port
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={2}
|
||||
justifyContent="flex-end"
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
{...getFieldHelpers("port")}
|
||||
disabled={isSubmitting}
|
||||
label="Port"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
value={form.values.port}
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("protocol")}
|
||||
disabled={isSubmitting}
|
||||
fullWidth
|
||||
select
|
||||
value={form.values.protocol}
|
||||
label="Protocol"
|
||||
>
|
||||
<MenuItem value="http">HTTP</MenuItem>
|
||||
<MenuItem value="https">HTTPS</MenuItem>
|
||||
</TextField>
|
||||
<TextField
|
||||
{...getFieldHelpers("share_level")}
|
||||
disabled={isSubmitting}
|
||||
fullWidth
|
||||
select
|
||||
value={form.values.share_level}
|
||||
label="Sharing Level"
|
||||
>
|
||||
<MenuItem value="authenticated">Authenticated</MenuItem>
|
||||
{canSharePortsPublic ? (
|
||||
<MenuItem value="public">Public</MenuItem>
|
||||
) : (
|
||||
disabledPublicMenuItem
|
||||
)}
|
||||
</TextField>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={!form.isValid}
|
||||
>
|
||||
Share Port
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue