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"},
Short: "Workspace sharing settings for the organization.",
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)
if err != nil {
return nil, xerrors.Errorf("unmarshalling workspace sharing settings: %w", err)
+13 -1
View File
@@ -4808,7 +4808,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"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": {
"type": "object",
"properties": {
@@ -22121,6 +22129,10 @@ const docTemplate = `{
"properties": {
"sharing_disabled": {
"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": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.WorkspaceSharingSettings"
"$ref": "#/definitions/codersdk.UpdateWorkspaceSharingSettingsRequest"
}
}
}
@@ -18601,6 +18601,14 @@
}
}
},
"codersdk.UpdateWorkspaceSharingSettingsRequest": {
"type": "object",
"properties": {
"sharing_disabled": {
"type": "boolean"
}
}
},
"codersdk.UpdateWorkspaceTTLRequest": {
"type": "object",
"properties": {
@@ -20337,6 +20345,10 @@
"properties": {
"sharing_disabled": {
"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"
)
// WorkspaceSharingSettings represents workspace sharing settings for an organization.
// WorkspaceSharingSettings represents workspace sharing settings affecting an
// organization.
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"`
}
@@ -28,7 +38,7 @@ func (c *Client) WorkspaceSharingSettings(ctx context.Context, orgID string) (Wo
}
// 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)
if err != nil {
return WorkspaceSharingSettings{}, err
+7 -5
View File
@@ -2851,7 +2851,8 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/setting
```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
{
"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
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|----------------------------------------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceSharingSettings](schemas.md#codersdkworkspacesharingsettings) |
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|------------------------------------------------------------------------------------------------------------|
| 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).
+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 | | |
## codersdk.UpdateWorkspaceSharingSettingsRequest
```json
{
"sharing_disabled": true
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|---------|----------|--------------|-------------|
| `sharing_disabled` | boolean | false | | |
## codersdk.UpdateWorkspaceTTLRequest
```json
@@ -12278,15 +12292,17 @@ If the schedule is empty, the user will be updated to use the default schedule.|
```json
{
"sharing_disabled": true
"sharing_disabled": true,
"sharing_globally_disabled": true
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------|---------|----------|--------------|-------------|
| `sharing_disabled` | boolean | false | | |
| Name | Type | Required | Restrictions | Description |
|-----------------------------|---------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------|
| `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
+4 -2
View File
@@ -34,8 +34,10 @@ func (api *API) workspaceSharingSettings(rw http.ResponseWriter, r *http.Request
return
}
globallyDisabled := bool(api.DeploymentValues.DisableWorkspaceSharing)
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
// @Param organization path string true "Organization ID" format(uuid)
// @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]
func (api *API) patchWorkspaceSharingSettings(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
+7 -7
View File
@@ -54,7 +54,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitMedium)
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,
})
require.NoError(t, err)
@@ -64,7 +64,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
require.NoError(t, err)
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,
})
require.NoError(t, err)
@@ -85,7 +85,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitMedium)
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,
})
var apiErr *codersdk.Error
@@ -116,7 +116,7 @@ func TestWorkspaceSharingSettings(t *testing.T) {
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
auditor.ResetLogs()
_, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.WorkspaceSharingSettings{
_, err := orgAdminClient.PatchWorkspaceSharingSettings(ctx, first.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: true,
})
require.NoError(t, err)
@@ -152,7 +152,7 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitMedium)
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,
})
require.NoError(t, err)
@@ -235,12 +235,12 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
require.Equal(t, codersdk.WorkspaceRoleUse, acl.Groups[0].Role)
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,
})
require.NoError(t, err)
_, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.WorkspaceSharingSettings{
_, err = orgAdminClient.PatchWorkspaceSharingSettings(ctx, owner.OrganizationID.String(), codersdk.UpdateWorkspaceSharingSettingsRequest{
SharingDisabled: false,
})
require.NoError(t, err)
+1 -1
View File
@@ -828,7 +828,7 @@ class ApiMethods {
*/
patchWorkspaceSharingSettings = async (
organization: string,
data: TypesGen.WorkspaceSharingSettings,
data: TypesGen.UpdateWorkspaceSharingSettingsRequest,
): Promise<TypesGen.WorkspaceSharingSettings> => {
const response = await this.axios.patch<TypesGen.WorkspaceSharingSettings>(
`/api/v2/organizations/${organization}/settings/workspace-sharing`,
+16 -1
View File
@@ -6523,6 +6523,15 @@ export interface UpdateWorkspaceRequest {
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
/**
* UpdateWorkspaceTTLRequest is a request to update a workspace's TTL.
@@ -7556,9 +7565,15 @@ export const WorkspaceRoles: WorkspaceRole[] = ["admin", "", "use"];
// From codersdk/workspacesharing.go
/**
* WorkspaceSharingSettings represents workspace sharing settings for an organization.
* WorkspaceSharingSettings represents workspace sharing settings affecting an
* organization.
*/
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;
}
@@ -29,7 +29,7 @@ const OrganizationSettingsPage: FC = () => {
const sharingSettingsQuery = useQuery({
...workspaceSharingSettings(organization?.id ?? ""),
enabled: !!organization,
enabled: Boolean(organization),
});
const patchSharingSettingsMutation = useMutation(
@@ -112,6 +112,9 @@ const OrganizationSettingsPage: FC = () => {
);
}
}}
workspaceSharingGloballyDisabled={
sharingSettingsQuery.data?.sharing_globally_disabled
}
workspaceSharingEnabled={
!(sharingSettingsQuery.data?.sharing_disabled ?? false)
}
@@ -4,6 +4,7 @@ import type {
Organization,
UpdateOrganizationRequest,
} from "api/typesGenerated";
import { Alert, AlertTitle } from "components/Alert/Alert";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Button } from "components/Button/Button";
import { Checkbox } from "components/Checkbox/Checkbox";
@@ -50,9 +51,10 @@ interface OrganizationSettingsPageViewProps {
error: unknown;
onSubmit: (values: UpdateOrganizationRequest) => Promise<void>;
onDeleteOrganization: () => void;
workspaceSharingEnabled?: boolean;
onToggleWorkspaceSharing?: (enabled: boolean) => void;
isTogglingWorkspaceSharing?: boolean;
workspaceSharingGloballyDisabled?: boolean;
workspaceSharingEnabled: boolean;
onToggleWorkspaceSharing: (enabled: boolean) => void;
isTogglingWorkspaceSharing: boolean;
}
export const OrganizationSettingsPageView: FC<
@@ -62,7 +64,8 @@ export const OrganizationSettingsPageView: FC<
error,
onSubmit,
onDeleteOrganization,
workspaceSharingEnabled = true,
workspaceSharingGloballyDisabled,
workspaceSharingEnabled,
onToggleWorkspaceSharing,
isTogglingWorkspaceSharing,
}) => {
@@ -156,29 +159,44 @@ export const OrganizationSettingsPageView: FC<
}
description="Control whether workspace owners can share their workspaces."
>
<div className="flex items-start gap-3">
<Checkbox
id="workspace-sharing"
checked={workspaceSharingEnabled}
disabled={isTogglingWorkspaceSharing}
onCheckedChange={(checked) => {
if (checked) {
onToggleWorkspaceSharing(true);
} else {
setIsDisableSharingDialogOpen(true);
<div className="flex flex-col gap-2">
{workspaceSharingGloballyDisabled && (
<Alert severity="warning" className="mb-4">
<AlertTitle>Disabled by deployment settings</AlertTitle>
Workspace sharing has been disallowed by an administrator.
Sharing must be allowed by an administrator before sharing can
be used in this organization.
</Alert>
)}
<div className="flex items-start gap-3">
<Checkbox
id="workspace-sharing"
checked={
!workspaceSharingGloballyDisabled && workspaceSharingEnabled
}
}}
/>
<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.
disabled={
workspaceSharingGloballyDisabled ||
isTogglingWorkspaceSharing
}
onCheckedChange={(checked) => {
if (checked) {
onToggleWorkspaceSharing(true);
} else {
setIsDisableSharingDialogOpen(true);
}
}}
/>
<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>