mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 08:01:35 +00:00
237 lines
7.1 KiB
Svelte
237 lines
7.1 KiB
Svelte
<script lang="ts">
|
|
import { base } from '$lib/base'
|
|
import { ConcurrencyGroupsService, type Job, type WorkflowStatus } from '../../gen'
|
|
import TestJobLoader from '../TestJobLoader.svelte'
|
|
import DisplayResult from '../DisplayResult.svelte'
|
|
import JobArgs from '../JobArgs.svelte'
|
|
import LogViewer from '../LogViewer.svelte'
|
|
import { Badge, Button, Skeleton, Tab, Tabs } from '../common'
|
|
import HighlightCode from '../HighlightCode.svelte'
|
|
import { forLater } from '$lib/forLater'
|
|
import FlowProgressBar from '../flows/FlowProgressBar.svelte'
|
|
import FlowStatusViewer from '../FlowStatusViewer.svelte'
|
|
import DurationMs from '../DurationMs.svelte'
|
|
import { workspaceStore } from '$lib/stores'
|
|
import WorkflowTimeline from '../WorkflowTimeline.svelte'
|
|
import Popover from '../Popover.svelte'
|
|
import { truncateRev } from '$lib/utils'
|
|
import { createEventDispatcher } from 'svelte'
|
|
import { ListFilter } from 'lucide-svelte'
|
|
|
|
export let id: string
|
|
export let blankLink = false
|
|
export let workspace: string | undefined
|
|
|
|
let job: Job | undefined = undefined
|
|
let watchJob: ((id: string) => Promise<void>) | undefined = undefined
|
|
let getLogs: (() => Promise<void>) | undefined = undefined
|
|
|
|
let result: any
|
|
|
|
function onDone(event: { detail: Job }) {
|
|
job = event.detail
|
|
result = job['result']
|
|
}
|
|
|
|
let currentJob: Job | undefined = undefined
|
|
|
|
$: if (currentJob?.id == id) {
|
|
job = currentJob
|
|
}
|
|
|
|
$: id && watchJob && watchJob(id)
|
|
|
|
$: job?.logs == undefined && job && viewTab == 'logs' && getLogs?.()
|
|
|
|
let lastJobId: string | undefined = undefined
|
|
let concurrencyKey: string | undefined = undefined
|
|
$: job?.id && lastJobId !== job.id && getConcurrencyKey(job)
|
|
async function getConcurrencyKey(job: Job) {
|
|
lastJobId = job.id
|
|
concurrencyKey = await ConcurrencyGroupsService.getConcurrencyKey({ id: job.id })
|
|
}
|
|
|
|
let viewTab = 'result'
|
|
|
|
function asWorkflowStatus(x: any): Record<string, WorkflowStatus> {
|
|
return x as Record<string, WorkflowStatus>
|
|
}
|
|
const dispatch = createEventDispatcher()
|
|
</script>
|
|
|
|
<TestJobLoader
|
|
lazyLogs
|
|
workspaceOverride={workspace}
|
|
bind:job={currentJob}
|
|
bind:getLogs
|
|
bind:watchJob
|
|
on:done={onDone}
|
|
/>
|
|
<div class="p-4 flex flex-col gap-2 items-start h-full">
|
|
{#if job}
|
|
<div class="flex gap-2 flex-wrap">
|
|
{#if job?.['priority']}
|
|
<Badge color="red">
|
|
priority: {job?.['priority']}
|
|
</Badge>
|
|
{/if}
|
|
{#if job && 'duration_ms' in job && job.duration_ms != undefined}
|
|
<DurationMs
|
|
duration_ms={job.duration_ms}
|
|
self_wait_time_ms={job?.self_wait_time_ms}
|
|
aggregate_wait_time_ms={job?.aggregate_wait_time_ms}
|
|
/>
|
|
{/if}
|
|
{#if job?.['mem_peak']}
|
|
<Badge large>
|
|
Mem: {job?.['mem_peak'] ? `${(job['mem_peak'] / 1024).toPrecision(4)}MB` : 'N/A'}
|
|
</Badge>
|
|
{/if}
|
|
{#if workspace && $workspaceStore != workspace}
|
|
<Badge large>
|
|
Workspace: {workspace}
|
|
</Badge>
|
|
{/if}
|
|
{#if job.tag}
|
|
<Badge large>
|
|
Tag: {job.tag}
|
|
</Badge>
|
|
{/if}
|
|
{#if job?.['labels'] && Array.isArray(job?.['labels']) && job?.['labels'].length > 0}
|
|
{#each job?.['labels'] as label}
|
|
<Badge baseClass="text-2xs">Label: {label}</Badge>
|
|
{/each}
|
|
{/if}
|
|
{#if concurrencyKey}
|
|
<Popover notClickable>
|
|
<svelte:fragment slot="text">
|
|
This jobs has concurrency limits enabled with the key:
|
|
<Button
|
|
class="inline-text"
|
|
size="xs2"
|
|
color="light"
|
|
on:click={() => {
|
|
dispatch('filterByConcurrencyKey', concurrencyKey)
|
|
}}
|
|
>
|
|
{concurrencyKey}
|
|
<ListFilter class="inline-block" size={10} />
|
|
</Button>
|
|
</svelte:fragment>
|
|
<Badge large>Concurrency: {truncateRev(concurrencyKey, 20)}</Badge>
|
|
</Popover>
|
|
{/if}
|
|
</div>
|
|
<a
|
|
href="{base}/run/{job?.id}?workspace={job?.workspace_id}"
|
|
class="flex flex-row gap-1 items-center"
|
|
target={blankLink ? '_blank' : undefined}
|
|
>
|
|
<span class="font-semibold text-sm leading-6">ID:</span>
|
|
<span class="text-sm">{job?.id ?? ''}</span>
|
|
</a>
|
|
|
|
<span class="font-semibold text-xs leading-6">Arguments</span>
|
|
|
|
<div class="w-full">
|
|
<JobArgs
|
|
id={job?.id}
|
|
workspace={job?.workspace_id ?? $workspaceStore ?? 'no_w'}
|
|
args={job?.args}
|
|
/>
|
|
</div>
|
|
|
|
{#if job?.type === 'CompletedJob'}
|
|
<span class="font-semibold text-xs leading-6">Results</span>
|
|
{/if}
|
|
|
|
{#if job && 'scheduled_for' in job && !job.running && job.scheduled_for && forLater(job.scheduled_for)}
|
|
<div class="text-sm font-semibold text-tertiary mb-1">
|
|
<div>Job is scheduled for</div>
|
|
<div>{new Date(job?.['scheduled_for']).toLocaleString()}</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class=" w-full rounded-md min-h-full">
|
|
{#if job?.is_flow_step == false && job?.flow_status && (job?.job_kind == 'preview' || job?.job_kind == 'script') && !(typeof job.flow_status == 'object' && '_metadata' in job.flow_status)}
|
|
<WorkflowTimeline
|
|
flow_status={asWorkflowStatus(job.flow_status)}
|
|
flowDone={job.type == 'CompletedJob'}
|
|
/>
|
|
{/if}
|
|
|
|
{#if job?.type === 'CompletedJob'}
|
|
<Tabs bind:selected={viewTab}>
|
|
<Tab size="xs" value="result">Result</Tab>
|
|
<Tab size="xs" value="logs">Logs</Tab>
|
|
{#if job?.job_kind == 'preview'}
|
|
<Tab size="xs" value="code">Code</Tab>
|
|
{/if}
|
|
</Tabs>
|
|
|
|
<Skeleton loading={!job} layout={[[5]]} />
|
|
{#if job}
|
|
{#if viewTab == 'result' && (job?.job_kind == 'flow' || job?.job_kind == 'flowpreview')}
|
|
<div class="flex flex-col gap-2">
|
|
<div class="w-full mt-10 mb-20">
|
|
<FlowStatusViewer jobId={job.id} workspaceId={job.workspace_id} />
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="flex flex-col border rounded-md p-2 mt-2 h-full overflow-auto">
|
|
{#if viewTab == 'logs'}
|
|
<div class="w-full">
|
|
<LogViewer
|
|
jobId={job.id}
|
|
duration={job?.['duration_ms']}
|
|
mem={job?.['mem_peak']}
|
|
isLoading={job?.['running'] == false}
|
|
content={job?.logs}
|
|
tag={job?.tag}
|
|
/>
|
|
</div>
|
|
{:else if viewTab == 'code'}
|
|
{#if job && 'raw_code' in job && job.raw_code}
|
|
<div class="text-xs">
|
|
<HighlightCode lines language={job.language} code={job.raw_code} />
|
|
</div>
|
|
{:else if job}
|
|
No code is available
|
|
{:else}
|
|
<Skeleton layout={[[5]]} />
|
|
{/if}
|
|
{:else if job !== undefined && 'result' in job && job.result !== undefined}
|
|
<DisplayResult
|
|
workspaceId={job?.workspace_id}
|
|
jobId={job?.id}
|
|
{result}
|
|
disableExpand
|
|
/>
|
|
{:else if job}
|
|
No output is available yet
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
{:else if job && `running` in job ? job.running : false}
|
|
{#if job?.job_kind == 'flow' || job?.job_kind == 'flowpreview'}
|
|
<div class="flex flex-col gap-2 w-full">
|
|
<FlowProgressBar {job} class="py-4" />
|
|
<FlowStatusViewer jobId={job.id} workspaceId={job.workspace_id} />
|
|
</div>
|
|
{:else}
|
|
<div class="text-sm font-semibold text-tertiary mb-1"> Job is still running </div>
|
|
<LogViewer
|
|
jobId={job?.id}
|
|
duration={job?.['duration_ms']}
|
|
mem={job?.['mem_peak']}
|
|
content={job?.logs}
|
|
isLoading={job?.['running'] == false}
|
|
tag={job?.tag}
|
|
/>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|