Files
windmill/frontend/src/lib/components/FlowStatusWaitingForEvents.svelte
Guilhem 3b5c1657c7 fix(frontend): improve runs detail page (#7694)
* separate flow and status with splitpane

* fix autoscroll behavior

* Flow log viewer nit

* not graph viewer

* Improve flow job result

* Create job detail header to replace metadata

* Remove FlowPreviewResult

* Create compact job header

* Use job header in runs page

* Improve runs page run preview

* Use flow header in detail section

* Show logs for script steps

* Clean old schedule status

* Limit result height

* Script run detail improvement

* Script run preview improvement

* Fix csv table overflow

* surface tertiary as background for Inputs

* nit

* Improve runs detail skeleton

* fix check

* nit

* Improve node definition

* fix flow module component overflow

* Use component DataTable for flow schema viewer

* Add language icon to step detail

* improve run header

* Improve Job detail header

* nit

* restore isOwner logic

* Handle resume flows

* restore execution status in run preview

* restore flow execution status in the preview

* flow preview, add status bar

* nit module status

* nit

* Remove flor preview result

* nit

* nit

* fix flow result card

* nit

* nit

* nit

* improve field selection on runs detail based on job type

* Improve column layout

* create JobStatusIcon component

* remove job status badge icons

* improve compact version

* use shared job field display

* improve job detail field display

* fix badge alignment

* increase padding

* nit

* nit

* improve compact display

* make background darker for metadata

* use auto layout

* fix auto layout

* improve display

* fix truncate logic

* fix compact

* improve flex adaptibility

* improve responsive layout

* improve extra compact header

* nit

* remove unused icons

* nit

* Improve flow result display

* nit

* merge progressbar and execution status

* handle canceled flow better
2026-02-03 17:20:35 +00:00

181 lines
5.1 KiB
Svelte

<script lang="ts">
import { mergeSchema } from '$lib/common'
import { type Job, JobService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { X } from 'lucide-svelte'
import DisplayResult from './DisplayResult.svelte'
import Tooltip from './Tooltip.svelte'
import { Button } from './common'
import SchemaForm from './SchemaForm.svelte'
import { twMerge } from 'tailwind-merge'
import { untrack } from 'svelte'
interface Props {
isOwner: boolean
workspaceId: string | undefined
job: Job
light?: boolean
}
let { isOwner, workspaceId, job, light = false }: Props = $props()
let default_payload: object = $state({})
let resumeUrl: string | undefined = $state(undefined)
let cancelUrl: string | undefined = $state(undefined)
let description: any = $state(undefined)
let hide_cancel = $state(false)
let defaultValues = $state({})
let schema = $state({})
let lastJobId: string | undefined = undefined
async function getDefaultArgs() {
let jobId = job?.flow_status?.modules?.[approvalStep]?.job
if (jobId === lastJobId) {
return
}
if (!jobId) {
return {}
}
lastJobId = jobId
let job_result = (await JobService.getCompletedJobResult({
workspace: workspaceId ?? $workspaceStore ?? '',
id: jobId
})) as any
const args = job_result?.default_args ?? {}
description = job_result?.description
defaultValues = JSON.parse(JSON.stringify(args))
default_payload = args
resumeUrl = job_result?.['resume']
cancelUrl = job_result?.['cancel']
hide_cancel = job?.raw_flow?.modules?.[approvalStep]?.suspend?.hide_cancel ?? false
schema = mergeSchema(
job?.raw_flow?.modules?.[approvalStep]?.suspend?.resume_form?.schema ?? {},
job_result?.enums ?? {}
)
}
let loading = $state(false)
async function continu(approve: boolean) {
loading = true
if ((resumeUrl && approve) || (cancelUrl && !approve)) {
let split = (approve ? resumeUrl : cancelUrl)!.split('/')
let signatureUrl = split.pop() ?? ''
const regex = /([^?]+)(?:\?[^=]+=(\w+))?/
const matches = signatureUrl.match(regex)
const signature = matches?.[1]
if (!signature) {
sendUserToast(`Could not parse signature: ${signatureUrl}`, true)
return
}
const approver = matches?.[2] || undefined
let resumeId = -1
let parsedResumeId = split.pop() ?? ''
try {
resumeId = new Number(parsedResumeId).valueOf()
} catch (e) {
console.error(`Could not parse resume id: ${parsedResumeId}`)
}
let jobId = split.pop() ?? ''
if (approve) {
await JobService.resumeSuspendedJobPost({
workspace: workspaceId ?? $workspaceStore ?? '',
id: jobId,
requestBody: default_payload as any,
resumeId,
signature,
approver
})
} else {
await JobService.cancelSuspendedJobPost({
workspace: workspaceId ?? $workspaceStore ?? '',
id: jobId,
resumeId,
signature,
approver,
requestBody: {}
})
}
} else {
if (approve) {
await JobService.resumeSuspendedFlowAsOwner({
workspace: workspaceId ?? $workspaceStore ?? '',
id: job?.id ?? '',
requestBody: default_payload as any
})
} else {
await JobService.cancelQueuedJob({
workspace: workspaceId ?? $workspaceStore ?? '',
id: job?.id ?? '',
requestBody: {}
})
}
}
}
let approvalStep = $derived((job?.flow_status?.step ?? 1) - 1)
$effect(() => {
job && untrack(() => getDefaultArgs())
})
</script>
<div class="w-full h-full text-xs text-primary">
{#if description != undefined}
<DisplayResult {workspaceId} noControls result={description} language={job?.language} />
<div class="mt-2"></div>
{/if}
<div>
{#if isOwner || resumeUrl}
<div class={twMerge('flex gap-2', light ? 'flex-col' : 'flex-row ')}>
{#if !hide_cancel}
<div>
<Button
title="Cancel the step"
{loading}
iconOnly
startIcon={{ icon: X }}
variant="default"
disabled={!cancelUrl}
destructive
unifiedSize="md"
on:click={() => continu(false)}
/>
</div>
{/if}
<div>
<Button variant="accent" onClick={() => continu(true)} {loading} unifiedSize="md">
Resume
<Tooltip class="text-white">
Since you are an owner of this flow, you can send resume events without necessarily
knowing the resume id sent by the approval step
</Tooltip>
</Button>
</div>
{#if job?.raw_flow?.modules?.[approvalStep]?.suspend?.resume_form?.schema}
<div
class={twMerge(
'w-full border rounded-lg p-2',
light ? 'min-w-96 max-h-svh overflow-y-auto' : ''
)}
>
<SchemaForm onlyMaskPassword bind:args={default_payload} {defaultValues} {schema} />
</div>
<Tooltip>
The payload is optional, it is passed to the following step through the `resume`
variable
</Tooltip>
{/if}
</div>
{:else}
You cannot resume the flow yourself without receiving the resume secret since you are not an
owner of {job.script_path} and the approval step did not contain the resume url at key `resume`
{/if}
</div>
</div>