Files
windmill/frontend/src/lib/components/ParentWorkspaceProtectionAlert.svelte
T
Ruben FiszelandClaude Opus 5 61f2d8dc6a feat: let the merge UI target an arbitrary workspace (#10417)
* feat: let the merge UI target an arbitrary workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep the target picker reachable when a comparison fails

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: address review findings on the arbitrary merge target

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: collapse app/raw-app conversions and offer a comparison retry

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: make a re-scan replace the candidate set and keep retry reachable

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop destructive rows from the selection when a recompute flips them

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep bulk selection and refreshes out of the removal opt-in

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: serialize a full scan against dev attachment on the same pair

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep the compare view reachable from drafts and prune stale selections

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: make the destination badge the target picker and reorder the settings

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: render the destination trigger as the same badge as the source

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:46:59 +02:00

93 lines
3.2 KiB
Svelte

<script lang="ts">
import type { ProtectionRuleset } from '$lib/gen'
import { userStore, type UserExt } from '$lib/stores'
import { getUserExt } from '$lib/user'
import {
fetchProtectionRulesForWorkspace,
isRuleActiveInRulesets,
canUserBypassRuleKindInRulesets,
getActiveRulesetsForKindInRulesets
} from '$lib/workspaceProtectionRules.svelte'
import { Alert } from './common'
import { resource } from 'runed'
let {
parentWorkspaceId,
onUpdateCanDeploy = (value) => {}
}: {
parentWorkspaceId: string
onUpdateCanDeploy?: (value: boolean) => void
} = $props()
let overrideChecked = $state(false)
// Fetch the parent's rules and the user's identity in the parent together. Both must come from the
// parent: bypass is judged with is_admin/groups in the PARENT (per-workspace), not the active/fork
// workspace's `$userStore`; getUserExt returns undefined for a non-member, treated as no bypass.
const parentDataResource = resource(
() => parentWorkspaceId,
async (wsId) => {
if (!wsId) return { rules: [] as ProtectionRuleset[], user: undefined as UserExt | undefined }
const [rules, user] = await Promise.all([
fetchProtectionRulesForWorkspace(wsId),
getUserExt(wsId)
])
return { rules, user }
}
)
let parentRulesets = $derived(parentDataResource.current?.rules ?? [])
let parentUserInfo = $derived(parentDataResource.current?.user)
let activeDeployRulesets = $derived(
getActiveRulesetsForKindInRulesets(parentRulesets, 'DisableDirectDeployment')
)
let canBypass = $derived(
canUserBypassRuleKindInRulesets(parentRulesets, 'DisableDirectDeployment', parentUserInfo)
)
// Block deploy until the parent's rules/identity have loaded: while loading `parentRulesets` is
// empty, which would otherwise read as "no lock" and briefly enable deploy before the real
// lock/bypass check resolves.
let canDeploy = $derived(
!parentDataResource.loading &&
(!isRuleActiveInRulesets(parentRulesets, 'DisableDirectDeployment') ||
(canBypass && overrideChecked))
)
// Reset override when parent workspace changes
$effect(() => {
parentWorkspaceId
overrideChecked = false
})
// Communicate deployment status to parent
$effect(() => {
onUpdateCanDeploy(canDeploy)
})
</script>
{#if !$userStore?.operator && activeDeployRulesets.length > 0}
<!-- Named for the deploy destination, not the lineage: the merge UI also points
this at a target outside the fork lineage. -->
<Alert type="info" title="Target workspace protection active" class="my-2">
<div class="flex flex-col gap-2">
<p>
The workspace {parentWorkspaceId} has a protection rule{activeDeployRulesets.length > 1
? 's'
: ''}
<b>{activeDeployRulesets.map((r) => r.name).join(', ')}</b>
that restrict{activeDeployRulesets.length > 1 ? '' : 's'} direct deployments. You need to merge
changes through the synced git repo with Git Sync, or by asking a user with the rights to bypass
this rule.
</p>
{#if canBypass}
<label class="flex items-center gap-2 cursor-pointer">
<input class="rounded max-w-4" type="checkbox" bind:checked={overrideChecked} />
<span class="text-xs">Bypass restriction and deploy anyway</span>
</label>
{/if}
</div>
</Alert>
{/if}