mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add workspace sharing toggle on organization settings page (#21456)
resolves coder/internal#1211 <img width="1448" height="757" alt="Screenshot 2026-01-08 at 11 16 34" src="https://github.com/user-attachments/assets/8d1e1b8b-e808-42a4-825a-f7f0f6fd8689" /> <img width="600" height="384" alt="Screenshot 2026-01-08 at 11 03 49" src="https://github.com/user-attachments/assets/7fbe9b77-4617-4621-a566-972712210cbb" /> --------- Co-authored-by: George Katsitadze <george.katsitadze@gmail.com>
This commit is contained in:
co-authored by
George Katsitadze
parent
3a62a8e70e
commit
d25d95231f
@@ -742,6 +742,32 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param organization Can be the organization's ID or name
|
||||
*/
|
||||
getWorkspaceSharingSettings = async (
|
||||
organization: string,
|
||||
): Promise<TypesGen.WorkspaceSharingSettings> => {
|
||||
const response = await this.axios.get<TypesGen.WorkspaceSharingSettings>(
|
||||
`/api/v2/organizations/${organization}/settings/workspace-sharing`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param organization Can be the organization's ID or name
|
||||
*/
|
||||
patchWorkspaceSharingSettings = async (
|
||||
organization: string,
|
||||
data: TypesGen.WorkspaceSharingSettings,
|
||||
): Promise<TypesGen.WorkspaceSharingSettings> => {
|
||||
const response = await this.axios.patch<TypesGen.WorkspaceSharingSettings>(
|
||||
`/api/v2/organizations/${organization}/settings/workspace-sharing`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
getProvisionerDaemonsByOrganization = async (
|
||||
organization: string,
|
||||
params?: GetProvisionerDaemonsParams,
|
||||
|
||||
@@ -248,6 +248,33 @@ export const patchRoleSyncSettings = (
|
||||
};
|
||||
};
|
||||
|
||||
const getWorkspaceSharingSettingsKey = (organization: string) => [
|
||||
"organization",
|
||||
organization,
|
||||
"workspaceSharingSettings",
|
||||
];
|
||||
|
||||
export const workspaceSharingSettings = (organization: string) => {
|
||||
return {
|
||||
queryKey: getWorkspaceSharingSettingsKey(organization),
|
||||
queryFn: () => API.getWorkspaceSharingSettings(organization),
|
||||
};
|
||||
};
|
||||
|
||||
export const patchWorkspaceSharingSettings = (
|
||||
organization: string,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
mutationFn: (request: { sharing_disabled: boolean }) =>
|
||||
API.patchWorkspaceSharingSettings(organization, request),
|
||||
onSuccess: async () =>
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: getWorkspaceSharingSettingsKey(organization),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const provisionerJobsQueryKey = (
|
||||
orgId: string,
|
||||
params: GetProvisionerJobsParams = {},
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { API } from "api/api";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "components/Dialog/Dialog";
|
||||
import { Skeleton } from "components/Skeleton/Skeleton";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
interface DisableWorkspaceSharingDialogProps {
|
||||
isOpen: boolean;
|
||||
organizationId: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const DisableWorkspaceSharingDialog: FC<
|
||||
DisableWorkspaceSharingDialogProps
|
||||
> = ({ isOpen, organizationId, onConfirm, onCancel, isLoading }) => {
|
||||
// Fetch the count of shared workspaces in this organization
|
||||
const sharedWorkspacesQuery = useQuery({
|
||||
queryKey: ["workspaces", organizationId, "shared", "count"],
|
||||
queryFn: async () => {
|
||||
const response = await API.getWorkspaces({
|
||||
q: `organization:${organizationId} shared:true`,
|
||||
limit: 0, // Avoid fetching workspaces as we only need the count
|
||||
});
|
||||
return response.count;
|
||||
},
|
||||
enabled: isOpen,
|
||||
});
|
||||
|
||||
const sharedCount = sharedWorkspacesQuery.data ?? 0;
|
||||
const isLoadingCount = sharedWorkspacesQuery.isLoading;
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onCancel()}>
|
||||
<DialogContent variant="destructive" className="max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Disable workspace sharing</DialogTitle>
|
||||
<DialogDescription asChild>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>
|
||||
Disabling workspace sharing will{" "}
|
||||
<strong className="text-content-primary">
|
||||
immediately remove
|
||||
</strong>{" "}
|
||||
all existing workspace sharing permissions for all users in this
|
||||
organization.
|
||||
</p>
|
||||
{isLoadingCount ? (
|
||||
<Skeleton className="h-6 w-4/5" />
|
||||
) : sharedCount > 0 ? (
|
||||
<p className="text-content-danger font-medium m-0">
|
||||
This action will affect{" "}
|
||||
<strong className="text-content-primary">
|
||||
{sharedCount} workspace{sharedCount !== 1 ? "s" : ""}
|
||||
</strong>{" "}
|
||||
that {sharedCount !== 1 ? "are" : "is"} currently shared.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-content-secondary m-0">
|
||||
No workspaces are currently shared in this organization.
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
Re-enabling workspace sharing will{" "}
|
||||
<strong className="text-content-primary">not restore</strong>{" "}
|
||||
these permissions.
|
||||
</p>
|
||||
</div>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCancel} disabled={isLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={onConfirm}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Spinner loading={isLoading} />
|
||||
Disable sharing
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,16 @@
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import {
|
||||
deleteOrganization,
|
||||
patchWorkspaceSharingSettings,
|
||||
updateOrganization,
|
||||
workspaceSharingSettings,
|
||||
} from "api/queries/organizations";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||
import type { FC } from "react";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useNavigate } from "react-router";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView";
|
||||
@@ -25,6 +27,15 @@ const OrganizationSettingsPage: FC = () => {
|
||||
deleteOrganization(queryClient),
|
||||
);
|
||||
|
||||
const sharingSettingsQuery = useQuery({
|
||||
...workspaceSharingSettings(organization?.id ?? ""),
|
||||
enabled: !!organization,
|
||||
});
|
||||
|
||||
const patchSharingSettingsMutation = useMutation(
|
||||
patchWorkspaceSharingSettings(organization?.id ?? "", queryClient),
|
||||
);
|
||||
|
||||
if (!organization) {
|
||||
return <EmptyState message="Organization not found" />;
|
||||
}
|
||||
@@ -47,6 +58,26 @@ const OrganizationSettingsPage: FC = () => {
|
||||
const error =
|
||||
updateOrganizationMutation.error ?? deleteOrganizationMutation.error;
|
||||
|
||||
const handleToggleWorkspaceSharing = async (enabled: boolean) => {
|
||||
try {
|
||||
await patchSharingSettingsMutation.mutateAsync({
|
||||
sharing_disabled: !enabled,
|
||||
});
|
||||
displaySuccess(
|
||||
enabled ? "Workspace sharing enabled." : "Workspace sharing disabled.",
|
||||
);
|
||||
} catch (error) {
|
||||
displayError(
|
||||
getErrorMessage(
|
||||
error,
|
||||
enabled
|
||||
? "Failed to enable workspace sharing"
|
||||
: "Failed to disable workspace sharing",
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
@@ -73,6 +104,11 @@ const OrganizationSettingsPage: FC = () => {
|
||||
);
|
||||
}
|
||||
}}
|
||||
workspaceSharingEnabled={
|
||||
!(sharingSettingsQuery.data?.sharing_disabled ?? false)
|
||||
}
|
||||
onToggleWorkspaceSharing={handleToggleWorkspaceSharing}
|
||||
isTogglingWorkspaceSharing={patchSharingSettingsMutation.isPending}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
MockOrganization,
|
||||
} from "testHelpers/entities";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { within } from "@testing-library/react";
|
||||
import { action } from "storybook/actions";
|
||||
import { userEvent } from "storybook/test";
|
||||
import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView";
|
||||
|
||||
const meta: Meta<typeof OrganizationSettingsPageView> = {
|
||||
@@ -25,3 +28,32 @@ export const DefaultOrg: Story = {
|
||||
organization: MockDefaultOrganization,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithWorkspaceSharingEnabled: Story = {
|
||||
args: {
|
||||
workspaceSharingEnabled: true,
|
||||
onToggleWorkspaceSharing: action("onToggleWorkspaceSharing"),
|
||||
},
|
||||
};
|
||||
|
||||
export const WithWorkspaceSharingDisabled: Story = {
|
||||
args: {
|
||||
workspaceSharingEnabled: false,
|
||||
onToggleWorkspaceSharing: action("onToggleWorkspaceSharing"),
|
||||
},
|
||||
};
|
||||
|
||||
export const DisableSharingDialog: Story = {
|
||||
args: {
|
||||
workspaceSharingEnabled: true,
|
||||
onToggleWorkspaceSharing: action("onToggleWorkspaceSharing"),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const user = userEvent.setup();
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
const checkbox = await body.findByRole("checkbox", {
|
||||
name: /allow workspace sharing/i,
|
||||
});
|
||||
await user.click(checkbox);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
} from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { Checkbox } from "components/Checkbox/Checkbox";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import {
|
||||
FormFields,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
onChangeTrimmed,
|
||||
} from "utils/formUtils";
|
||||
import * as Yup from "yup";
|
||||
import { DisableWorkspaceSharingDialog } from "./DisableWorkspaceSharingDialog";
|
||||
import { HorizontalContainer, HorizontalSection } from "./Horizontal";
|
||||
|
||||
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
|
||||
@@ -47,11 +49,22 @@ interface OrganizationSettingsPageViewProps {
|
||||
error: unknown;
|
||||
onSubmit: (values: UpdateOrganizationRequest) => Promise<void>;
|
||||
onDeleteOrganization: () => void;
|
||||
workspaceSharingEnabled?: boolean;
|
||||
onToggleWorkspaceSharing?: (enabled: boolean) => void;
|
||||
isTogglingWorkspaceSharing?: boolean;
|
||||
}
|
||||
|
||||
export const OrganizationSettingsPageView: FC<
|
||||
OrganizationSettingsPageViewProps
|
||||
> = ({ organization, error, onSubmit, onDeleteOrganization }) => {
|
||||
> = ({
|
||||
organization,
|
||||
error,
|
||||
onSubmit,
|
||||
onDeleteOrganization,
|
||||
workspaceSharingEnabled = true,
|
||||
onToggleWorkspaceSharing,
|
||||
isTogglingWorkspaceSharing,
|
||||
}) => {
|
||||
const form = useFormik<UpdateOrganizationRequest>({
|
||||
initialValues: {
|
||||
name: organization.name,
|
||||
@@ -66,6 +79,8 @@ export const OrganizationSettingsPageView: FC<
|
||||
const getFieldHelpers = getFormHelpers(form, error);
|
||||
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isDisableSharingDialogOpen, setIsDisableSharingDialogOpen] =
|
||||
useState(false);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-screen-2xl pb-10">
|
||||
@@ -129,21 +144,59 @@ export const OrganizationSettingsPageView: FC<
|
||||
</FormFooter>
|
||||
</HorizontalForm>
|
||||
|
||||
{onToggleWorkspaceSharing && (
|
||||
<HorizontalContainer className="mt-12">
|
||||
<HorizontalSection
|
||||
title="Workspace Sharing"
|
||||
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">
|
||||
<label
|
||||
htmlFor="workspace-sharing"
|
||||
className="text-sm font-medium cursor-pointer leading-none"
|
||||
>
|
||||
Allow workspace sharing
|
||||
</label>
|
||||
<p className="text-sm font-medium text-content-secondary mt-2">
|
||||
When enabled, workspace owners can share their workspaces with
|
||||
other users in this organization.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</HorizontalSection>
|
||||
</HorizontalContainer>
|
||||
)}
|
||||
|
||||
{!organization.is_default && (
|
||||
<HorizontalContainer className="mt-12">
|
||||
<HorizontalSection
|
||||
title="Settings"
|
||||
description="Change or delete your organization."
|
||||
title="Delete Organization"
|
||||
description="Delete your organization permanently."
|
||||
>
|
||||
<div className="flex bg-surface-orange items-center justify-between border border-solid border-orange-600 rounded-md p-3 pl-4 gap-2 flex-grow">
|
||||
<span>Deleting an organization is irreversible.</span>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setIsDeleting(true)}
|
||||
className="min-w-fit"
|
||||
>
|
||||
Delete this organization
|
||||
</Button>
|
||||
<div className="flex flex-col gap-4 flex-grow">
|
||||
<div className="flex bg-surface-orange items-center justify-between border border-solid border-orange-600 rounded-md p-3 pl-4 gap-2">
|
||||
<span>Deleting an organization is irreversible.</span>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setIsDeleting(true)}
|
||||
className="min-w-fit"
|
||||
>
|
||||
Delete this organization
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</HorizontalSection>
|
||||
</HorizontalContainer>
|
||||
@@ -159,6 +212,17 @@ export const OrganizationSettingsPageView: FC<
|
||||
entity="organization"
|
||||
name={organization.name}
|
||||
/>
|
||||
|
||||
<DisableWorkspaceSharingDialog
|
||||
isOpen={isDisableSharingDialogOpen}
|
||||
organizationId={organization.id}
|
||||
onConfirm={async () => {
|
||||
await onToggleWorkspaceSharing?.(false);
|
||||
setIsDisableSharingDialogOpen(false);
|
||||
}}
|
||||
onCancel={() => setIsDisableSharingDialogOpen(false)}
|
||||
isLoading={isTogglingWorkspaceSharing}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ interface WorkspaceProps {
|
||||
buildLogs?: TypesGen.ProvisionerJobLog[];
|
||||
latestVersion?: TypesGen.TemplateVersion;
|
||||
timings?: TypesGen.WorkspaceBuildTimings;
|
||||
sharingDisabled?: boolean;
|
||||
handleStart: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
handleStop: () => void;
|
||||
handleRestart: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
@@ -56,6 +57,7 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
latestVersion,
|
||||
permissions,
|
||||
timings,
|
||||
sharingDisabled,
|
||||
handleStart,
|
||||
handleStop,
|
||||
handleRestart,
|
||||
@@ -110,6 +112,7 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
latestVersion={latestVersion}
|
||||
isUpdating={isUpdating}
|
||||
isRestarting={isRestarting}
|
||||
sharingDisabled={sharingDisabled}
|
||||
handleStart={handleStart}
|
||||
handleStop={handleStop}
|
||||
handleRestart={handleRestart}
|
||||
|
||||
@@ -29,6 +29,7 @@ interface WorkspaceActionsProps {
|
||||
isUpdating: boolean;
|
||||
isRestarting: boolean;
|
||||
permissions: WorkspacePermissions;
|
||||
sharingDisabled?: boolean;
|
||||
handleToggleFavorite: () => void;
|
||||
handleStart: (buildParameters?: WorkspaceBuildParameter[]) => void;
|
||||
handleStop: () => void;
|
||||
@@ -45,6 +46,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
isUpdating,
|
||||
isRestarting,
|
||||
permissions,
|
||||
sharingDisabled,
|
||||
handleToggleFavorite,
|
||||
handleStart,
|
||||
handleStop,
|
||||
@@ -189,10 +191,12 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
onToggle={handleToggleFavorite}
|
||||
/>
|
||||
|
||||
<ShareButton
|
||||
workspace={workspace}
|
||||
canUpdatePermissions={permissions.updateWorkspace}
|
||||
/>
|
||||
{!sharingDisabled && (
|
||||
<ShareButton
|
||||
workspace={workspace}
|
||||
canUpdatePermissions={permissions.updateWorkspace}
|
||||
/>
|
||||
)}
|
||||
|
||||
<WorkspaceMoreActions workspace={workspace} disabled={!canAcceptJobs} />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { watchWorkspace } from "api/api";
|
||||
import { workspaceSharingSettings } from "api/queries/organizations";
|
||||
import { template as templateQueryOptions } from "api/queries/templates";
|
||||
import { workspaceBuildsKey } from "api/queries/workspaceBuilds";
|
||||
import {
|
||||
@@ -44,6 +45,12 @@ const WorkspacePage: FC = () => {
|
||||
const permissionsQuery = useQuery(workspacePermissions(workspace));
|
||||
const permissions = permissionsQuery.data;
|
||||
|
||||
const sharingSettingsQuery = useQuery({
|
||||
...workspaceSharingSettings(workspace?.organization_id ?? ""),
|
||||
enabled: !!workspace,
|
||||
});
|
||||
const sharingDisabled = sharingSettingsQuery.data?.sharing_disabled ?? false;
|
||||
|
||||
// Watch workspace changes
|
||||
const updateWorkspaceData = useEffectEvent(
|
||||
async (newWorkspaceData: Workspace) => {
|
||||
@@ -114,6 +121,7 @@ const WorkspacePage: FC = () => {
|
||||
workspace={workspace}
|
||||
template={template}
|
||||
permissions={permissions}
|
||||
sharingDisabled={sharingDisabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,12 +34,14 @@ interface WorkspaceReadyPageProps {
|
||||
template: TypesGen.Template;
|
||||
workspace: TypesGen.Workspace;
|
||||
permissions: WorkspacePermissions;
|
||||
sharingDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
||||
workspace,
|
||||
template,
|
||||
permissions,
|
||||
sharingDisabled,
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -283,6 +285,7 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
||||
template={template}
|
||||
buildLogs={buildLogs}
|
||||
timings={timingsQuery.data}
|
||||
sharingDisabled={sharingDisabled}
|
||||
handleStart={async (buildParameters) => {
|
||||
const { hasEphemeral, ephemeralParameters } =
|
||||
await checkEphemeralParameters(buildParameters);
|
||||
|
||||
@@ -44,6 +44,7 @@ interface WorkspaceProps {
|
||||
template: TypesGen.Template;
|
||||
permissions: WorkspacePermissions;
|
||||
latestVersion?: TypesGen.TemplateVersion;
|
||||
sharingDisabled?: boolean;
|
||||
handleStart: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
handleStop: () => void;
|
||||
handleRestart: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
@@ -62,6 +63,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
|
||||
permissions,
|
||||
isUpdating,
|
||||
isRestarting,
|
||||
sharingDisabled,
|
||||
handleStart,
|
||||
handleStop,
|
||||
handleRestart,
|
||||
@@ -236,6 +238,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
|
||||
permissions={permissions}
|
||||
isUpdating={isUpdating}
|
||||
isRestarting={isRestarting}
|
||||
sharingDisabled={sharingDisabled}
|
||||
handleStart={handleStart}
|
||||
handleStop={handleStop}
|
||||
handleRestart={handleRestart}
|
||||
|
||||
@@ -17,9 +17,14 @@ import type { FC } from "react";
|
||||
interface SidebarProps {
|
||||
username: string;
|
||||
workspace: Workspace;
|
||||
sharingDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const Sidebar: FC<SidebarProps> = ({ username, workspace }) => {
|
||||
export const Sidebar: FC<SidebarProps> = ({
|
||||
username,
|
||||
workspace,
|
||||
sharingDisabled,
|
||||
}) => {
|
||||
const { experiments } = useDashboard();
|
||||
|
||||
return (
|
||||
@@ -46,7 +51,7 @@ export const Sidebar: FC<SidebarProps> = ({ username, workspace }) => {
|
||||
<SidebarNavItem href="schedule" icon={ScheduleIcon}>
|
||||
Schedule
|
||||
</SidebarNavItem>
|
||||
{experiments.includes("workspace-sharing") && (
|
||||
{experiments.includes("workspace-sharing") && !sharingDisabled && (
|
||||
<SidebarNavItem href="sharing" icon={SharingIcon}>
|
||||
Sharing
|
||||
</SidebarNavItem>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { workspaceSharingSettings } from "api/queries/organizations";
|
||||
import { workspaceByOwnerAndName } from "api/queries/workspaces";
|
||||
import type { Workspace } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
@@ -37,6 +38,12 @@ export const WorkspaceSettingsLayout: FC = () => {
|
||||
isError,
|
||||
} = useQuery(workspaceByOwnerAndName(username, workspaceName));
|
||||
|
||||
const sharingSettingsQuery = useQuery({
|
||||
...workspaceSharingSettings(workspace?.organization_id ?? ""),
|
||||
enabled: !!workspace,
|
||||
});
|
||||
const sharingDisabled = sharingSettingsQuery.data?.sharing_disabled ?? false;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
@@ -52,7 +59,11 @@ export const WorkspaceSettingsLayout: FC = () => {
|
||||
) : (
|
||||
workspace && (
|
||||
<WorkspaceSettings.Provider value={workspace}>
|
||||
<Sidebar workspace={workspace} username={username} />
|
||||
<Sidebar
|
||||
workspace={workspace}
|
||||
username={username}
|
||||
sharingDisabled={sharingDisabled}
|
||||
/>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<main css={{ width: "100%" }}>
|
||||
<Outlet />
|
||||
|
||||
Reference in New Issue
Block a user