8000 feat: add organization scope for shared ports by aslilac · Pull Request #18314 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: add organization scope for shared ports #18314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
let 'er rip some more
  • Loading branch information
aslilac committed Jun 11, 2025
commit 429bc13f17b8ade180aeb91c59aebcedb934a9b4
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,6 @@ Read [cursor rules](.cursorrules).

## Frontend

The frontend is contained in the site folder.

For building Frontend refer to [this document](docs/contributing/frontend.md)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this line and the next one directly contradict each other.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seemed to help prevent claude from trying to run commands not mentioned in that doc from the root of the repo for me. 🤷‍♀️

3 changes: 3 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion codersdk/workspaceapps.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type WorkspaceAppSharingLevel string
const (
WorkspaceAppSharingLevelOwner WorkspaceAppSharingLevel = "owner"
WorkspaceAppSharingLevelAuthenticated WorkspaceAppSharingLevel = "authenticated"
WorkspaceAppSharingLevelOrganization WorkspaceAppSharingLevel = "organization"
WorkspaceAppSharingLevelPublic WorkspaceAppSharingLevel = "public"
)

Expand Down Expand Up @@ -79,7 +80,7 @@ type WorkspaceApp struct {
Subdomain bool `json:"subdomain"`
// SubdomainName is the application domain exposed on the `coder server`.
SubdomainName string `json:"subdomain_name,omitempty"`
SharingLevel WorkspaceAppSharingLevel `json:"sharing_level" enums:"owner,authenticated,public"`
SharingLevel WorkspaceAppSharingLevel `json:"sharing_level" enums:"owner,authenticated,organization,public"`
// Healthcheck specifies the configuration for checking app health.
Healthcheck Healthcheck `json:"healthcheck,omitempty"`
Health WorkspaceAppHealth `json:"health"`
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/api/builds.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions docs/reference/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions docs/reference/api/templates.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions site/src/modules/resources/AppLink/AppLink.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ export const SharingLevelAuthenticated: Story = {
},
};

export const SharingLevelOrganization: Story = {
args: {
workspace: MockWorkspace,
app: {
...MockWorkspaceApp,
sharing_level: "organization",
},
agent: MockWorkspaceAgent,
},
};

export const SharingLevelPublic: Story = {
args: {
workspace: MockWorkspace,
Expand Down
8 changes: 8 additions & 0 deletions site/src/modules/resources/AppLink/ShareIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BusinessIcon from "@mui/icons-material/Business";
import GroupOutlinedIcon from "@mui/icons-material/GroupOutlined";
import PublicOutlinedIcon from "@mui/icons-material/PublicOutlined";
import Tooltip from "@mui/material/Tooltip";
Expand All @@ -23,6 +24,13 @@ export const ShareIcon = ({ app }: ShareIconProps) => {
</Tooltip>
);
}
if (app.sharing_level === "organization") {
return (
<Tooltip title="Shared with organization members">
<BusinessIcon />
</Tooltip>
);
}
if (app.sharing_level === "public") {
return (
<Tooltip title="Shared publicly">
Expand Down
51 changes: 44 additions & 7 deletions site/src/modules/resources/PortForwardButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import BusinessIcon from "@mui/icons-material/Business";
import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
import SensorsIcon from "@mui/icons-material/Sensors";
Expand Down Expand Up @@ -207,6 +208,19 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
);
const canSharePortsPublic =
canSharePorts && template.max_port_share_level === "public";
const canSharePortsOrganization =
canSharePorts &&
(template.max_port_share_level === "organization" ||
template.max_port_share_level === "public");

// Default share level for quick share button based on template's max level
const defaultShareLevel: WorkspaceAgentPortShareLevel = (() => {
if (template.max_port_share_level === "organization") {
return "organization";
}
// For authenticated or public max levels, default to organization as it's more restrictive
return canSharePortsOrganization ? "organization" : "authenticated";
})();

const disabledPublicMenuItem = (
<MUITooltip title="This workspace template does not allow sharing ports with unauthenticated users.">
Expand All @@ -219,6 +233,17 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
</MUITooltip>
);

const disabledOrganizationMenuItem = (
<MUITooltip title="This workspace template does not allow sharing ports at the organization level.">
{/* Tooltips don't work directly on disabled MenuItem components so you must wrap in div. */}
<div>
<MenuItem value="organization" disabled>
Organization
</MenuItem>
</div>
</MUITooltip>
);

return (
<>
<div
Expand Down Expand Up @@ -379,7 +404,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
agent_name: agent.name,
port: port.port,
protocol: listeningPortProtocol,
share_level: "authenticated",
share_level: defaultShareLevel,
});
}}
>
Expand All @@ -406,7 +431,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
<HelpTooltipTitle>Shared Ports</HelpTooltipTitle>
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
{canSharePorts
? "Ports can be shared with other Coder users or with the public."
? "Ports can be shared with organization members, other Coder users, or with the public."
: "This w 10000 orkspace template does not allow sharing ports. Contact a template administrator to enable port sharing."}
</HelpTooltipText>
{canSharePorts && (
Expand Down Expand Up @@ -437,6 +462,8 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
>
{share.share_level === "public" ? (
<LockOpenIcon css={{ width: 14, height: 14 }} />
) : share.share_level === "organization" ? (
<BusinessIcon css={{ width: 14, height: 14 }} />
) : (
<LockIcon css={{ width: 14, height: 14 }} />
)}
Expand Down Expand Up @@ -480,6 +507,11 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
}}
>
<MenuItem value="authenticated">Authenticated</MenuItem>
{canSharePortsOrganization ? (
<MenuItem value="organization">Organization</MenuItem>
) : (
disabledOrganizationMenuItem
)}
{canSharePortsPublic ? (
<MenuItem value="public">Public</MenuItem>
) : (
Expand Down Expand Up @@ -547,6 +579,11 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
label="Sharing Level"
>
<MenuItem value="authenticated">Authenticated</MenuItem>
{canSharePortsOrganization ? (
<MenuItem value="organization">Organization</MenuItem>
) : (
disabledOrganizationMenuItem
)}
{canSharePortsPublic ? (
<MenuItem value="public">Public</MenuItem>
) : (
Expand All @@ -568,11 +605,11 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({

const classNames = {
paper: (css, theme) => css`
padding: 0;
width: 404px;
color: ${theme.palette.text.secondary};
margin-top: 4px;
`,
padding: 0;
width: 404px;
color: ${theme.palette.text.secondary};
margin-top: 4px;
`,
} satisfies Record<string, ClassName>;

const styles = {
Expand Down
7 changes: 7 additions & 0 deletions site/src/testHelpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4002,6 +4002,13 @@ export const MockSharedPortsResponse: TypesGen.WorkspaceAgentPortShares = {
share_level: "authenticated",
protocol: "http",
},
{
workspace_id: MockWorkspace.id,
agent_name: "a-workspace-agent",
port: 8080,
share_level: "organization",
protocol: "http",
},
{
workspace_id: MockWorkspace.id,
agent_name: "a-workspace-agent",
Expand Down
Loading
0