diff --git a/frontend/src/lib/components/sidebar/WorkspacePickerBody.svelte b/frontend/src/lib/components/sidebar/WorkspacePickerBody.svelte index 3719e35d35..5a893316e5 100644 --- a/frontend/src/lib/components/sidebar/WorkspacePickerBody.svelte +++ b/frontend/src/lib/components/sidebar/WorkspacePickerBody.svelte @@ -36,7 +36,11 @@ findWorkspaceAncestors, isForkOwner } from '$lib/utils/workspaceHierarchy' - import { canCreateFork } from '$lib/utils/editInFork' + import { + canCreateFork, + OPERATOR_FORK_BLOCKED_REASON, + operatorForkBlocked + } from '$lib/utils/editInFork' import { workspaceRootId } from '$lib/components/sessions/sessionScope.svelte' import DevWorkspaceBadge from '$lib/components/DevWorkspaceBadge.svelte' @@ -156,6 +160,11 @@ const canForkHere = $derived( (!isCloudHosted() || $maybePremium) && $workspaceStore !== 'admins' && canCreateFork($userStore) ) + const forkBlockedHere = $derived( + (!isCloudHosted() || $maybePremium) && + $workspaceStore !== 'admins' && + operatorForkBlocked($userStore) + ) // The row mechanics below — which family to expand, which collapsed row carries the tick — key on // a depth-0 row of the list, which `buildWorkspaceHierarchy` puts at the highest workspace the // caller can see: parentless, or with a parent absent from their list. `findWorkspaceAncestors` @@ -363,7 +372,7 @@ {/each} - {#if (isCloudHosted() || $superadmin || canForkHere) && !strictWorkspaceSelect} + {#if (isCloudHosted() || $superadmin || canForkHere || forkBlockedHere) && !strictWorkspaceSelect}
{#if isCloudHosted() || $superadmin} @@ -376,16 +385,25 @@ Workspace fork + {:else if forkBlockedHere} + +
+ + Workspace fork +
+ {#snippet text()} + {OPERATOR_FORK_BLOCKED_REASON} + {/snippet} +
{/if}
{/if} {#if canManageWorkspace && !strictWorkspaceSelect}
- + {(activeWorkspace && ambiguousNames.has(activeWorkspace.name) ? activeWorkspace.id diff --git a/frontend/src/lib/utils/editInFork.ts b/frontend/src/lib/utils/editInFork.ts index db38537115..2fe79ec03f 100644 --- a/frontend/src/lib/utils/editInFork.ts +++ b/frontend/src/lib/utils/editInFork.ts @@ -76,6 +76,18 @@ export function canCreateFork(user: UserExt | undefined): boolean { ) } +export const OPERATOR_FORK_BLOCKED_REASON = + 'Operators cannot fork this workspace. A workspace admin can allow it by turning on "Allow operators to fork" in a ruleset, under Workspace settings > Rulesets.' + +/** + * Whether the only thing refusing this user a fork is the missing `AllowOperatorForking`. The fork + * entries then stay visible, disabled, so the operator learns an admin can turn it on; when forking + * is disabled for everyone they stay hidden, as they are for developers. + */ +export function operatorForkBlocked(user: UserExt | undefined): boolean { + return !!user?.operator && !canCreateFork(user) && !isRuleActive('DisableWorkspaceForking') +} + function editPathFor(itemType: ItemType, itemPath: string): string { switch (itemType) { case 'script':