Files
windmill/frontend/src/lib/components/flows/FlowHistoryInner.svelte
T
GuilhemandClaude Opus 4.8 c000bbca28 fix(frontend): scope raw-app, flow and script editors to the session workspace (#10015)
* fix(frontend): scope raw-app/flow/script editors to the session workspace

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

* fix(frontend): scope flow and script editor operations to the session workspace

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

* fix(frontend): scope flow preview, inline-script creation and datatable schema to the session workspace

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

* fix(frontend): address Codex review — thread session workspace through flow resource pickers, script fetch, preview cancel/recording and path collision check

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

* fix(frontend): address Claude review — pass session workspace to preview FlowStatusViewer and align FlowChatManager guards

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

* fix(frontend): address Pi review — show acting workspace in script-not-found message and fetch picked script from it in EditorBar

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

* fix(frontend): address Codex review round 2 — thread session workspace into flow step test, raw-app inline runnable, inline editor toolbars and MCP OAuth path

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

* fix(frontend): address Codex review round 3 — thread session workspace into dynamic-input helpers and the flow-preview argument side panel (history/saved-inputs/captures)

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

* fix(frontend): address Codex review round 4 — thread session workspace into nested flow/script drawers, flow chat inputs and the flow input side tabs

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

* fix(frontend): address Codex review round 5 — thread session workspace into script-module fork/reload and key the raw-app schema cache by workspace

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

* fix(frontend): address Codex review round 6 — key the DB manager schema cache by acting workspace

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

* fix(frontend): address Codex review round 7 — thread session workspace into resource-valued arg pickers and the editor variable/resource helper drawers

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

* fix(frontend): scope the flow asset explorer's ResourceEditorDrawer to the acting workspace

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

* fix: thread acting workspace through flow asset explore controls

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

* fix: thread acting workspace through SQL REPL, secret args, helper forms, S3 inputs, saved inputs

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:53:43 +02:00

243 lines
7.0 KiB
Svelte

<script lang="ts">
import { stopPropagation, createBubbler } from 'svelte/legacy'
const bubble = createBubbler()
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { classNames, displayDate, emptyString, sendUserToast } from '$lib/utils'
import { type Flow, FlowService, type FlowVersion } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Skeleton } from '$lib/components/common'
import Button from '../common/button/Button.svelte'
import { ArrowRight, Loader2, Pencil, X } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { FlowEditorContext } from './types'
interface Props {
path: string
allowFork?: boolean
onHistoryRestore?: () => void
}
let { path, allowFork = false, onHistoryRestore }: Props = $props()
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
let loading: boolean = $state(false)
let versions: FlowVersion[] = $state([])
let selectedVersion: FlowVersion | undefined = $state(undefined)
let selected: Flow | undefined = $state(undefined)
let deploymentMsgUpdateMode = $state(false)
let deploymentMsgUpdate: string | undefined = $state(undefined)
let selectedVersionIndex: number | undefined = $state(undefined)
async function loadFlow(version: number) {
selected = await FlowService.getFlowVersion({
workspace: opWs!,
version
})
}
async function loadVersions() {
loading = true
versions = await FlowService.getFlowHistory({
workspace: opWs!,
path: path
})
loading = false
}
async function updateDeploymentMsg(version: number | undefined) {
if (
selectedVersion === undefined ||
version === undefined ||
emptyString(deploymentMsgUpdate)
) {
return
}
await FlowService.updateFlowHistory({
workspace: opWs!,
version,
requestBody: {
deployment_msg: deploymentMsgUpdate!
}
})
selectedVersion.deployment_msg = deploymentMsgUpdate
deploymentMsgUpdateMode = false
loadVersions()
}
async function restoreVersion(flow: Flow | undefined) {
if (!flow) return
await FlowService.updateFlow({
workspace: opWs!,
requestBody: {
...flow,
path
},
path
})
onHistoryRestore?.()
sendUserToast('Flow restored from previous deployment')
}
loadVersions()
$effect.pre(() => {
selectedVersion !== undefined && loadFlow(selectedVersion.id)
})
// Get available versions for comparison (versions after selected one)
let availableVersions = $derived(
selectedVersionIndex !== undefined ? versions.slice(selectedVersionIndex + 1) : []
)
</script>
<Splitpanes class="!overflow-visible">
<Pane size={20}>
<div class="flex flex-col gap-2 w-full px-2 py-2">
{#if !loading}
{#if versions.length > 0}
<div class="flex gap-2 flex-col">
{#each versions ?? [] as version (version.id)}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class={classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-surface-hover hover:text-primary',
selectedVersion?.id == version.id ? 'bg-surface-selected text-primary' : ''
)}
role="button"
tabindex="0"
onclick={() => {
const versionIndex = versions.findIndex((v) => v.id === version.id)
selectedVersion = version
selectedVersionIndex = versionIndex
deploymentMsgUpdate = undefined
deploymentMsgUpdateMode = false
}}
>
<span class="text-xs truncate">
{#if emptyString(version.deployment_msg)}Version {version.id}{:else}{version.deployment_msg}{/if}
</span>
</div>
{/each}
</div>
{:else}
<div class="text-sm text-primary">No items</div>
{/if}
{:else}
<Skeleton layout={[[40], [40], [40], [40], [40]]} />
{/if}
</div>
</Pane>
<Pane size={80}>
<div class="h-full w-full overflow-auto pt-2">
{#if selectedVersion}
{#if selected}
<div class="px-2 flex flex-col gap-2">
<div class="flex justify-between">
<span class="flex flex-row text-sm p-1 text-primary">
{#if deploymentMsgUpdateMode}
<div class="flex w-full">
<input
type="text"
bind:value={deploymentMsgUpdate}
class="!w-auto grow"
onclick={stopPropagation(() => {})}
onkeydown={stopPropagation(bubble('keydown'))}
onkeypress={(e) => {
e.stopPropagation()
if (e.key === 'Enter') updateDeploymentMsg(selectedVersion?.id)
}}
/>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Save deployment message"
on:click={() => {
updateDeploymentMsg(selectedVersion?.id)
}}
>
<ArrowRight size={14} />
</Button>
<Button
size="xs"
color="light"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Abort"
on:click={() => {
deploymentMsgUpdateMode = false
deploymentMsgUpdate = undefined
}}
>
<X size={14} />
</Button>
</div>
{:else}
{#if selectedVersion.deployment_msg}
{selectedVersion.deployment_msg}
{:else}
Deployed {displayDate(selected.edited_at)} by {selected.edited_by}
{/if}
<button
onclick={() => {
deploymentMsgUpdate = selectedVersion?.deployment_msg
deploymentMsgUpdateMode = true
}}
title="Update commit message"
class="flex items-center px-1 rounded-sm hover:text-primary text-secondary h-5"
aria-label="Update commit message"
>
<Pencil size={14} />
</button>
{/if}
</span>
<div class="flex p-1 gap-2">
{#if allowFork}
<div class="flex">
<Button
size="sm"
variant="accent"
on:click={() =>
window.open(
`/flows/add?template_id=${selectedVersion?.id}&template=${path}`,
'_blank'
)}
>
Restore as fork
</Button>
</div>
{/if}
<div class="flex">
<Button size="sm" variant="accent" on:click={() => restoreVersion(selected)}
>Redeploy with that version
</Button>
</div>
</div>
</div>
{#await import('$lib/components/FlowViewer.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
flow={selected}
{availableVersions}
selectedVersionId={selectedVersion?.id}
/>
{/await}
</div>
{:else}
<Skeleton layout={[[40]]} />
{/if}
{:else}
<div class="text-sm p-2 text-primary">Select a deployment version to see its details</div>
{/if}
</div>
</Pane>
</Splitpanes>