mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 00:03:11 +00:00
* feat: rework git sync settings into a flat repo list and setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add the connect step for gitlab and github in git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: open the github app install page from the git sync setup next step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: let git sync setup set the repository folder Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add the configure sync step to git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: even out the git sync filter settings columns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: pin the filter input to the bottom of the filter list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: frame the git sync path filters in a box with an empty hint Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: use a multiselect for the git sync path filters Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: grey out the skipped connect step in git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the check job line to the bottom of the setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hide the check job line once the repository check resolved Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: center the repository check loader in the setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the setup modal shifting when the check job id arrives Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the existing resource hint to the setup modal footer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: always show the existing resource option when one exists Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: drop the card and collapse from the git sync filter settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: use a success alert for the pull from git notice Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: make initializing the repository a step of the git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: hide the push job lines once they succeed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: reword the push step intro Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: copy button on the cli snippet and a taller changes list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: match the push step loading to the check step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the push step shifting when a job id arrives Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: let the changes list fill the push step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: end the git sync setup on a done step instead of a modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: delete a git sync repository from a row menu with a confirmation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: lift the git sync done message off the notices Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: restore github app installation transfer between instances Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address regressions from the git sync setup rework Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the overlay z-index floor in step with the ai chat Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the push state in the setup dialog, not in the step it destroys Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: block every dismissal while setup work is in flight and keep sync reachable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: regenerate the resource path when the repository changes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: configure the resource the connect step actually created Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a modal open when a drawer above it takes the click Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: snapshot the topmost surface for keyboard clicks too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: size the add repository buttons and explain git promotion Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * revert: drop the promotion-first action from the empty state Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: shrink the test connection result line Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: offer the fork sync and fork pull request toggles during setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: group promotion repositories under their own label Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: offer the deploy branch pull request toggle during setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: wait for the repository credential before saving the setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a superseded credential load and drop the bindable default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: key setup readiness to the repository it was loaded for Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: tie setup readiness to the attempt that asked for it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: link to where the access token is created Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: say plainly how each provider connects Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: sync resources, variables, schedules and triggers by default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: use the accent colour for the token help link Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the CI/CD deploy hint next to the pull toggle Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep setup gated when the connection could not be read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: only explain pull-from-git where the workspace can turn it on Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: offer a save retry once the repository is initialized Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: say when pulling fell back to polling on the last setup step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clear the initialized flag when a new draft starts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
233 lines
7.1 KiB
Svelte
233 lines
7.1 KiB
Svelte
<script lang="ts">
|
|
import { Download, ExternalLink, Loader2, Minus, RotateCw, Upload } from 'lucide-svelte'
|
|
import { Alert, Button } from '$lib/components/common'
|
|
import Select from '$lib/components/select/Select.svelte'
|
|
import DropdownV2 from '$lib/components/DropdownV2.svelte'
|
|
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
|
import RepositorySelector from '$lib/components/RepositorySelector.svelte'
|
|
import {
|
|
addInstallationToWorkspace,
|
|
deleteInstallation,
|
|
exportInstallation,
|
|
importInstallation
|
|
} from '$lib/githubApp'
|
|
import type { RepoConnection } from './repoConnection'
|
|
import type { GithubAppSetup } from './githubAppSetup.svelte'
|
|
|
|
type Props = {
|
|
workspace: string
|
|
setup: GithubAppSetup
|
|
connection: RepoConnection | undefined
|
|
}
|
|
|
|
let { workspace, setup, connection = $bindable() }: Props = $props()
|
|
|
|
const gh = $derived(setup.gh)
|
|
const usable = $derived(setup.usable)
|
|
const broken = $derived(gh.workspaceGithubInstallations.filter((i) => !!i.error))
|
|
// Installations reachable from another workspace the user is in, deduplicated.
|
|
const elsewhere = $derived(
|
|
gh.githubInstallations.filter(
|
|
(i, idx, all) =>
|
|
!i.error &&
|
|
!gh.workspaceGithubInstallations.some((w) => w.installation_id === i.installation_id) &&
|
|
all.findIndex((o) => o.installation_id === i.installation_id) === idx
|
|
)
|
|
)
|
|
const selected = $derived(
|
|
usable.find((i) => i.installation_id === gh.selectedGHAppInstallationId)
|
|
)
|
|
|
|
const reload = () => setup.reload()
|
|
|
|
$effect(() => {
|
|
connection = gh.selectedGHAppRepository
|
|
? { url: gh.selectedGHAppRepository, isGithubApp: true }
|
|
: undefined
|
|
})
|
|
|
|
async function useFrom(installationId: number, sourceWorkspace: string) {
|
|
await addInstallationToWorkspace(workspace, installationId, sourceWorkspace, reload)
|
|
}
|
|
|
|
/** Pasting a JWT exported from another Windmill instance. */
|
|
let importing = $state(false)
|
|
|
|
// Export hands out a JWT the other instance imports; an installation reached through a
|
|
// GitHub Enterprise Server of its own is not transferable that way.
|
|
const installationActions = $derived([
|
|
...(selected && !selected.github_base_url
|
|
? [
|
|
{
|
|
displayName: 'Copy installation token (to another instance)',
|
|
icon: Download,
|
|
action: () => void exportInstallation(workspace, selected.installation_id)
|
|
}
|
|
]
|
|
: []),
|
|
...(gh.isGhesSelfManaged
|
|
? []
|
|
: [
|
|
{
|
|
displayName: 'Import an installation from another instance',
|
|
icon: Upload,
|
|
action: () => (importing = true)
|
|
}
|
|
]),
|
|
...(selected && !selected.provisioned_by_admin
|
|
? [
|
|
{
|
|
displayName: 'Remove installation from this workspace',
|
|
icon: Minus,
|
|
type: 'delete' as const,
|
|
action: () => void deleteInstallation(workspace, selected.installation_id, reload)
|
|
}
|
|
]
|
|
: [])
|
|
])
|
|
</script>
|
|
|
|
<div class="flex flex-col gap-4">
|
|
{#if (!setup.loaded || gh.loadingGithubInstallations) && gh.workspaceGithubInstallations.length === 0}
|
|
<div class="flex items-center gap-2 text-xs text-secondary">
|
|
<Loader2 size={14} class="animate-spin" /> Loading GitHub App installations...
|
|
</div>
|
|
{:else}
|
|
{#if usable.length === 0}
|
|
<Alert type="info" size="xs" bgClass="border-0" title="">
|
|
{#if gh.isCheckingInstallation}
|
|
Finish installing the Windmill GitHub App in the tab that opened. This dialog picks up the
|
|
installation on its own.
|
|
{:else}
|
|
Install the Windmill GitHub App on the GitHub account or organization that owns the
|
|
repository, then come back here.
|
|
{/if}
|
|
</Alert>
|
|
{:else}
|
|
<label class="flex flex-col gap-1">
|
|
<span class="text-xs font-semibold text-emphasis">GitHub account</span>
|
|
<Select
|
|
items={usable.map((i) => ({
|
|
label: i.account_id,
|
|
value: i.installation_id,
|
|
subtitle: `${i.total_count} repositories`
|
|
}))}
|
|
bind:value={
|
|
() => gh.selectedGHAppInstallationId,
|
|
(v) => {
|
|
gh.selectedGHAppInstallationId = v
|
|
gh.selectedGHAppRepository = undefined
|
|
}
|
|
}
|
|
clearable={false}
|
|
/>
|
|
</label>
|
|
{#if selected}
|
|
<label class="flex flex-col gap-1">
|
|
<span class="text-xs font-semibold text-emphasis">Repository</span>
|
|
<!-- RepositorySelector snapshots its repositories at mount -->
|
|
{#key selected.installation_id}
|
|
<RepositorySelector
|
|
bind:selectedRepository={gh.selectedGHAppRepository}
|
|
installationId={selected.installation_id}
|
|
initialRepositories={selected.repositories}
|
|
totalCount={selected.total_count}
|
|
perPage={selected.per_page}
|
|
/>
|
|
{/key}
|
|
</label>
|
|
{/if}
|
|
{/if}
|
|
|
|
{#if broken.length > 0}
|
|
<Alert type="warning" size="xs" title="Some installations cannot be used">
|
|
{broken.map((i) => `${i.account_id}: ${i.error}`).join('; ')}
|
|
</Alert>
|
|
{/if}
|
|
|
|
<div class="flex items-center gap-2">
|
|
<Button
|
|
variant="default"
|
|
unifiedSize="sm"
|
|
disabled={!gh.githubInstallationUrl}
|
|
startIcon={gh.isCheckingInstallation
|
|
? { icon: Loader2, classes: 'animate-spin' }
|
|
: undefined}
|
|
endIcon={{ icon: ExternalLink }}
|
|
title={gh.isCheckingInstallation ? 'Open the GitHub installation page again' : undefined}
|
|
onClick={() => setup.openInstall()}
|
|
>
|
|
{gh.isCheckingInstallation
|
|
? 'Waiting for the installation...'
|
|
: usable.length === 0
|
|
? 'Install the GitHub App'
|
|
: 'Install on another account'}
|
|
</Button>
|
|
<Button
|
|
variant="subtle"
|
|
unifiedSize="sm"
|
|
iconOnly
|
|
title="Refresh installations"
|
|
startIcon={{ icon: RotateCw }}
|
|
disabled={gh.loadingGithubInstallations}
|
|
onClick={reload}
|
|
/>
|
|
{#if installationActions.length > 0}
|
|
<DropdownV2 items={installationActions} />
|
|
{/if}
|
|
</div>
|
|
|
|
{#if importing}
|
|
<div class="flex flex-col gap-1">
|
|
<span class="text-xs font-semibold text-emphasis">Installation token</span>
|
|
<span class="text-xs text-secondary">
|
|
Paste the token copied from the instance that holds the installation.
|
|
</span>
|
|
<div class="flex items-center gap-2">
|
|
<TextInput
|
|
bind:value={gh.importJwt}
|
|
size="sm"
|
|
class="flex-1 min-w-0"
|
|
inputProps={{ placeholder: 'JWT token', autocomplete: 'off' }}
|
|
/>
|
|
<Button
|
|
variant="default"
|
|
unifiedSize="sm"
|
|
disabled={!gh.importJwt}
|
|
onClick={async () => {
|
|
await importInstallation(workspace, gh.importJwt, () => {
|
|
gh.importJwt = ''
|
|
importing = false
|
|
void reload()
|
|
})
|
|
}}
|
|
>
|
|
Import
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if elsewhere.length > 0}
|
|
<div class="flex flex-col gap-1">
|
|
<span class="text-xs font-semibold text-emphasis">Installed in your other workspaces</span>
|
|
{#each elsewhere as inst (inst.installation_id)}
|
|
<div class="flex items-center justify-between gap-2 text-xs">
|
|
<span class="text-primary">
|
|
{inst.account_id}
|
|
<span class="text-secondary">from {inst.workspace_id}</span>
|
|
</span>
|
|
<Button
|
|
variant="default"
|
|
unifiedSize="xs"
|
|
onClick={() => inst.workspace_id && useFrom(inst.installation_id, inst.workspace_id)}
|
|
>
|
|
Use here
|
|
</Button>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|