fix: show a notice when workspace sharing is disabled globally in organization settings (#22580)

This commit is contained in:
Kayla はな
2026-03-04 11:14:52 -07:00
committed by GitHub
parent fda181bb26
commit e35717bc19
12 changed files with 142 additions and 52 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ func (r *RootCmd) organizationSettings(orgContext *OrganizationContext) *serpent
Aliases: []string{"workspacesharing"}, Aliases: []string{"workspacesharing"},
Short: "Workspace sharing settings for the organization.", Short: "Workspace sharing settings for the organization.",
Patch: func(ctx context.Context, cli *codersdk.Client, org uuid.UUID, input json.RawMessage) (any, error) { Patch: func(ctx context.Context, cli *codersdk.Client, org uuid.UUID, input json.RawMessage) (any, error) {
var req codersdk.WorkspaceSharingSettings var req codersdk.UpdateWorkspaceSharingSettingsRequest
err := json.Unmarshal(input, &req) err := json.Unmarshal(input, &req)
if err != nil { if err != nil {
return nil, xerrors.Errorf("unmarshalling workspace sharing settings: %w", err) return nil, xerrors.Errorf("unmarshalling workspace sharing settings: %w", err)
+13 -1
View File
@@ -4808,7 +4808,7 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/codersdk.WorkspaceSharingSettings" "$ref": "#/definitions/codersdk.UpdateWorkspaceSharingSettingsRequest"
} }
} }
} }
@@ -20283,6 +20283,14 @@ const docTemplate = `{
} }
} }
}, },
"codersdk.UpdateWorkspaceSharingSettingsRequest": {
"type": "object",
"properties": {
"sharing_disabled": {
"type": "boolean"
}
}
},
"codersdk.UpdateWorkspaceTTLRequest": { "codersdk.UpdateWorkspaceTTLRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -22121,6 +22129,10 @@ const docTemplate = `{
"properties": { "properties": {
"sharing_disabled": { "sharing_disabled": {
"type": "boolean" "type": "boolean"
},
"sharing_globally_disabled": {
"description": "SharingGloballyDisabled is true if sharing has been disabled for this\norganization because of a deployment-wide setting.",
"type": "boolean"
} }
} }
}, },
+13 -1
View File
@@ -4253,7 +4253,7 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/codersdk.WorkspaceSharingSettings" "$ref": "#/definitions/codersdk.UpdateWorkspaceSharingSettingsRequest"
} }
} }
} }
@@ -18601,6 +18601,14 @@
} }
} }
}, },
"codersdk.UpdateWorkspaceSharingSettingsRequest": {
"type": "object",
"properties": {
"sharing_disabled": {
"type": "boolean"
}
}
},
"codersdk.UpdateWorkspaceTTLRequest": { "codersdk.UpdateWorkspaceTTLRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -20337,6 +20345,10 @@
"properties": { "properties": {
"sharing_disabled": { "sharing_disabled": {
"type": "boolean" "type": "boolean"
},
"sharing_globally_disabled": {
"description": "SharingGloballyDisabled is true if sharing has been disabled for this\norganization because of a deployment-wide setting.",
"type": "boolean"
} }
} }
}, },
+12 -2
View File
@@ -7,8 +7,18 @@ import (
"net/http" "net/http"
) )
// WorkspaceSharingSettings represents workspace sharing settings for an organization. // WorkspaceSharingSettings represents workspace sharing settings affecting an
// organization.
type WorkspaceSharingSettings struct { type WorkspaceSharingSettings struct {
// SharingGloballyDisabled is true if sharing has been disabled for this
// organization because of a deployment-wide setting.
SharingGloballyDisabled bool `json:"sharing_globally_disabled"`
SharingDisabled bool `json:"sharing_disabled"`
}
// UpdateWorkspaceSharingSettingsRequest represents workspace sharing settings
// that can be updated for an organization.
type UpdateWorkspaceSharingSettingsRequest struct {
SharingDisabled bool `json:"sharing_disabled"` SharingDisabled bool `json:"sharing_disabled"`
} }
@@ -28,7 +38,7 @@ func (c *Client) WorkspaceSharingSettings(ctx context.Context, orgID string) (Wo
} }
// PatchWorkspaceSharingSettings modifies the workspace sharing settings for an organization. // PatchWorkspaceSharingSettings modifies the workspace sharing settings for an organization.
func (c *Client) PatchWorkspaceSharingSettings(ctx context.Context, orgID string, req WorkspaceSharingSettings) (WorkspaceSharingSettings, error) { func (c *Client) PatchWorkspaceSharingSettings(ctx context.Context, orgID string, req UpdateWorkspaceSharingSettingsRequest) (WorkspaceSharingSettings, error) {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/workspace-sharing", orgID), req) res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/workspace-sharing", orgID), req)
if err != nil { if err != nil {
return WorkspaceSharingSettings{}, err return WorkspaceSharingSettings{}, err
+7 -5
View File
@@ -2851,7 +2851,8 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/setting
```json ```json
{ {
"sharing_disabled": true "sharing_disabled": true,
"sharing_globally_disabled": true
} }
``` ```
@@ -2881,7 +2882,8 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/setti
```json ```json
{ {
"sharing_disabled": true "sharing_disabled": true,
"sharing_globally_disabled": true
} }
``` ```
@@ -2904,9 +2906,9 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/setti
### Responses ### Responses
| Status | Meaning | Description | Schema | | Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|----------------------------------------------------------------------------------| |--------|---------------------------------------------------------|-------------|------------------------------------------------------------------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceSharingSettings](schemas.md#codersdkworkspacesharingsettings) | | 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UpdateWorkspaceSharingSettingsRequest](schemas.md#codersdkupdateworkspacesharingsettingsrequest) |
To perform this operation, you must be authenticated. [Learn more](authentication.md). To perform this operation, you must be authenticated. [Learn more](authentication.md).
+20 -4
View File
@@ -9809,6 +9809,20 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|--------|--------|----------|--------------|-------------| |--------|--------|----------|--------------|-------------|
| `name` | string | false | | | | `name` | string | false | | |
## codersdk.UpdateWorkspaceSharingSettingsRequest
```json
{
"sharing_disabled": true
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|---------|----------|--------------|-------------|
| `sharing_disabled` | boolean | false | | |
## codersdk.UpdateWorkspaceTTLRequest ## codersdk.UpdateWorkspaceTTLRequest
```json ```json
@@ -12278,15 +12292,17 @@ If the schedule is empty, the user will be updated to use the default schedule.|
```json ```json
{ {
"sharing_disabled": true "sharing_disabled": true,
"sharing_globally_disabled": true
} }
``` ```
### Properties ### Properties
| Name | Type | Required | Restrictions | Description | | Name | Type | Required | Restrictions | Description |
|--------------------|---------|----------|--------------|-------------| |-----------------------------|---------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------|
| `sharing_disabled` | boolean | false | | | | `sharing_disabled` | boolean | false | | |
| `sharing_globally_disabled` | boolean | false | | Sharing globally disabled is true if sharing has been disabled for this organization because of a deployment-wide setting. |
## codersdk.WorkspaceStatus ## codersdk.WorkspaceStatus
+4 -2
View File
@@ -34,8 +34,10 @@ func (api *API) workspaceSharingSettings(rw http.ResponseWriter, r *http.Request
return return
} }
globallyDisabled := bool(api.DeploymentValues.DisableWorkspaceSharing)
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceSharingSettings{ httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceSharingSettings{
SharingDisabled: org.WorkspaceSharingDisabled, SharingGloballyDisabled: globallyDisabled,
SharingDisabled: org.WorkspaceSharingDisabled || globallyDisabled,
}) })
} }
@@ -47,7 +49,7 @@ func (api *API) workspaceSharingSettings(rw http.ResponseWriter, r *http.Request
// @Tags Enterprise // @Tags Enterprise
// @Param organization path string true "Organization ID" format(uuid) // @Param organization path string true "Organization ID" format(uuid)
// @Param request body codersdk.WorkspaceSharingSettings true "Workspace sharing settings" // @Param request body codersdk.WorkspaceSharingSettings true "Workspace sharing settings"
// @Success 200 {object} codersdk.WorkspaceSharingSettings // @Success 200 {object} codersdk.UpdateWorkspaceSharingSettingsRequest
// @Router /organizations/{organization}/settings/workspace-sharing [patch] // @Router /organizations/{organization}/settings/workspace-sharing [patch]
func (api *API) patchWorkspaceSharingSettings(rw http.ResponseWriter, r *http.Request) { func (api *API) patchWorkspaceSharingSettings(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
+7 -7
View File
@@ -54,7 +54,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitMedium) ctx := testutil.Context(t, testutil.WaitMedium)
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID)) orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
settings, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ settings, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: true, SharingDisabled: true,
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -64,7 +64,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.True(t, settings.SharingDisabled) require.True(t, settings.SharingDisabled)
settings, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ settings, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: false, SharingDisabled: false,
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -85,7 +85,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitMedium) ctx := testutil.Context(t, testutil.WaitMedium)
memberClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) memberClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID)
_, err := memberClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ _, err := memberClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: true, SharingDisabled: true,
}) })
var apiErr *codersdk.Error var apiErr *codersdk.Error
@@ -116,7 +116,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID)) orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
auditor.ResetLogs() auditor.ResetLogs()
_, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ _, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: true, SharingDisabled: true,
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -152,7 +152,7 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitMedium) ctx := testutil.Context(t, testutil.WaitMedium)
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID)) orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
_, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ _, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: true, SharingDisabled: true,
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -235,12 +235,12 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
require.Equal(t, codersdk.WorkspaceRoleUse, acl.Groups[0].Role) require.Equal(t, codersdk.WorkspaceRoleUse, acl.Groups[0].Role)
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID)) orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
_, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ _, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: true, SharingDisabled: true,
}) })
require.NoError(t, err) require.NoError(t, err)
_, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.WorkspaceSharingSettings{ _, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: false, SharingDisabled: false,
}) })
require.NoError(t, err) require.NoError(t, err)
+1 -1
View File
@@ -828,7 +828,7 @@ class ApiMethods {
*/ */
patchWorkspaceSharingSettings = async ( patchWorkspaceSharingSettings = async (
organization: string, organization: string,
data: TypesGen.WorkspaceSharingSettings, data: TypesGen.UpdateWorkspaceSharingSettingsRequest,
): Promise<TypesGen.WorkspaceSharingSettings> => { ): Promise<TypesGen.WorkspaceSharingSettings> => {
const response = await this.axios.patch<TypesGen.WorkspaceSharingSettings>( const response = await this.axios.patch<TypesGen.WorkspaceSharingSettings>(
`/api/v2/organizations/${organization}/settings/workspace-sharing`, `/api/v2/organizations/${organization}/settings/workspace-sharing`,
+16 -1
View File
@@ -6523,6 +6523,15 @@ export interface UpdateWorkspaceRequest {
readonly name?: string; readonly name?: string;
} }
// From codersdk/workspacesharing.go
/**
* UpdateWorkspaceSharingSettingsRequest represents workspace sharing settings
* that can be updated for an organization.
*/
export interface UpdateWorkspaceSharingSettingsRequest {
readonly sharing_disabled: boolean;
}
// From codersdk/workspaces.go // From codersdk/workspaces.go
/** /**
* UpdateWorkspaceTTLRequest is a request to update a workspace's TTL. * UpdateWorkspaceTTLRequest is a request to update a workspace's TTL.
@@ -7556,9 +7565,15 @@ export const WorkspaceRoles: WorkspaceRole[] = ["admin", "", "use"];
// From codersdk/workspacesharing.go // From codersdk/workspacesharing.go
/** /**
* WorkspaceSharingSettings represents workspace sharing settings for an organization. * WorkspaceSharingSettings represents workspace sharing settings affecting an
* organization.
*/ */
export interface WorkspaceSharingSettings { export interface WorkspaceSharingSettings {
/**
* SharingGloballyDisabled is true if sharing has been disabled for this
* organization because of a deployment-wide setting.
*/
readonly sharing_globally_disabled: boolean;
readonly sharing_disabled: boolean; readonly sharing_disabled: boolean;
} }
@@ -29,7 +29,7 @@ const OrganizationSettingsPage: FC = () => {
const sharingSettingsQuery = useQuery({ const sharingSettingsQuery = useQuery({
...workspaceSharingSettings(organization?.id ?? ""), ...workspaceSharingSettings(organization?.id ?? ""),
enabled: !!organization, enabled: Boolean(organization),
}); });
const patchSharingSettingsMutation = useMutation( const patchSharingSettingsMutation = useMutation(
@@ -112,6 +112,9 @@ const OrganizationSettingsPage: FC = () => {
); );
} }
}} }}
workspaceSharingGloballyDisabled={
sharingSettingsQuery.data?.sharing_globally_disabled
}
workspaceSharingEnabled={ workspaceSharingEnabled={
!(sharingSettingsQuery.data?.sharing_disabled ?? false) !(sharingSettingsQuery.data?.sharing_disabled ?? false)
} }
@@ -4,6 +4,7 @@ import type {
Organization, Organization,
UpdateOrganizationRequest, UpdateOrganizationRequest,
} from "api/typesGenerated"; } from "api/typesGenerated";
import { Alert, AlertTitle } from "components/Alert/Alert";
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Button } from "components/Button/Button"; import { Button } from "components/Button/Button";
import { Checkbox } from "components/Checkbox/Checkbox"; import { Checkbox } from "components/Checkbox/Checkbox";
@@ -50,9 +51,10 @@ interface OrganizationSettingsPageViewProps {
error: unknown; error: unknown;
onSubmit: (values: UpdateOrganizationRequest) => Promise<void>; onSubmit: (values: UpdateOrganizationRequest) => Promise<void>;
onDeleteOrganization: () => void; onDeleteOrganization: () => void;
workspaceSharingEnabled?: boolean; workspaceSharingGloballyDisabled?: boolean;
onToggleWorkspaceSharing?: (enabled: boolean) => void; workspaceSharingEnabled: boolean;
isTogglingWorkspaceSharing?: boolean; onToggleWorkspaceSharing: (enabled: boolean) => void;
isTogglingWorkspaceSharing: boolean;
} }
export const OrganizationSettingsPageView: FC< export const OrganizationSettingsPageView: FC<
@@ -62,7 +64,8 @@ export const OrganizationSettingsPageView: FC<
error, error,
onSubmit, onSubmit,
onDeleteOrganization, onDeleteOrganization,
workspaceSharingEnabled = true, workspaceSharingGloballyDisabled,
workspaceSharingEnabled,
onToggleWorkspaceSharing, onToggleWorkspaceSharing,
isTogglingWorkspaceSharing, isTogglingWorkspaceSharing,
}) => { }) => {
@@ -156,29 +159,44 @@ export const OrganizationSettingsPageView: FC<
} }
description="Control whether workspace owners can share their workspaces." description="Control whether workspace owners can share their workspaces."
> >
<div className="flex items-start gap-3"> <div className="flex flex-col gap-2">
<Checkbox {workspaceSharingGloballyDisabled && (
id="workspace-sharing" <Alert severity="warning" className="mb-4">
checked={workspaceSharingEnabled} <AlertTitle>Disabled by deployment settings</AlertTitle>
disabled={isTogglingWorkspaceSharing} Workspace sharing has been disallowed by an administrator.
onCheckedChange={(checked) => { Sharing must be allowed by an administrator before sharing can
if (checked) { be used in this organization.
onToggleWorkspaceSharing(true); </Alert>
} else { )}
setIsDisableSharingDialogOpen(true); <div className="flex items-start gap-3">
<Checkbox
id="workspace-sharing"
checked={
!workspaceSharingGloballyDisabled && workspaceSharingEnabled
} }
}} disabled={
/> workspaceSharingGloballyDisabled ||
<div className="flex flex-col"> isTogglingWorkspaceSharing
<label }
htmlFor="workspace-sharing" onCheckedChange={(checked) => {
className="text-sm cursor-pointer" if (checked) {
> onToggleWorkspaceSharing(true);
Allow workspace sharing } else {
</label> setIsDisableSharingDialogOpen(true);
<div className="text-sm text-content-secondary"> }
When enabled, workspace owners can share their workspaces with }}
other users in this organization. />
<div className="flex flex-col">
<label
htmlFor="workspace-sharing"
className="text-sm cursor-pointer"
>
Allow workspace sharing
</label>
<div className="text-sm text-content-secondary">
When enabled, workspace owners can share their workspaces
with other users in this organization.
</div>
</div> </div>
</div> </div>
</div> </div>