mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
fix: Flow status viewer layout nits (avoid excess y space and scroll) (#8780)
This commit is contained in:
@@ -1864,7 +1864,7 @@
|
||||
<div class="border h-full rounded-md" bind:clientHeight={wrapperHeight}>
|
||||
<Splitpanes>
|
||||
<Pane bind:size={graphPaneSize} minSize={30}>
|
||||
<div class="bg-surface-secondary h-full overflow-auto">
|
||||
<div class="bg-surface-secondary h-full max-h-screen">
|
||||
<div class="flex flex-col" bind:clientHeight={retryStatusHeight}>
|
||||
{#each Object.values(retryStatus?.val ?? {}) as count}
|
||||
{#if count}
|
||||
@@ -1884,6 +1884,7 @@
|
||||
triggerNode={true}
|
||||
download={!hideDownloadInGraph}
|
||||
minHeight={wrapperHeight - retryStatusHeight}
|
||||
outerDivClass="max-h-screen"
|
||||
success={jobId != undefined && isSuccess(job?.['success'])}
|
||||
flowModuleStates={localModuleStates}
|
||||
bind:expandedSubflows
|
||||
@@ -1932,8 +1933,8 @@
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane bind:size={detailsPaneSize} minSize={25} class="!overflow-visible">
|
||||
<div class="pt-1 overflow-auto min-h-[700px] flex flex-col h-full">
|
||||
<Tabs bind:selected={rightColumnSelect}>
|
||||
<div class="pt-1 min-h-[min(700px,100vh)] max-h-screen flex flex-col h-full relative">
|
||||
<Tabs bind:selected={rightColumnSelect} wrapperClass="shrink-0">
|
||||
{#if !hideTimeline}
|
||||
<Tab value="timeline" label="Timeline" />
|
||||
{/if}
|
||||
@@ -1946,238 +1947,244 @@
|
||||
{/if}
|
||||
<Tab value="tracing" label="Tracing" />
|
||||
</Tabs>
|
||||
{#if rightColumnSelect == 'timeline'}
|
||||
<FlowTimeline
|
||||
{localModuleStates}
|
||||
{onSelectedIteration}
|
||||
selfWaitTime={job?.self_wait_time_ms}
|
||||
aggregateWaitTime={job?.aggregate_wait_time_ms}
|
||||
flowDone={job?.['success'] != undefined}
|
||||
bind:this={flowTimeline}
|
||||
flowModules={allModulesForTimeline(
|
||||
job?.raw_flow?.modules ?? [],
|
||||
expandedSubflows ?? {}
|
||||
)}
|
||||
buildSubflowKey={(key) => buildSubflowKey(key, prefix)}
|
||||
durationStatuses={localDurationStatuses}
|
||||
/>
|
||||
{:else if rightColumnSelect == 'node_status'}
|
||||
<div class="p-4 grow flex flex-col gap-6">
|
||||
{#if selectedNode?.startsWith(AI_TOOL_MESSAGE_PREFIX)}
|
||||
<div class="pt-2 pb-4">
|
||||
<div class="overflow-auto">
|
||||
{#if rightColumnSelect == 'timeline'}
|
||||
<FlowTimeline
|
||||
{localModuleStates}
|
||||
{onSelectedIteration}
|
||||
selfWaitTime={job?.self_wait_time_ms}
|
||||
aggregateWaitTime={job?.aggregate_wait_time_ms}
|
||||
flowDone={job?.['success'] != undefined}
|
||||
bind:this={flowTimeline}
|
||||
flowModules={allModulesForTimeline(
|
||||
job?.raw_flow?.modules ?? [],
|
||||
expandedSubflows ?? {}
|
||||
)}
|
||||
buildSubflowKey={(key) => buildSubflowKey(key, prefix)}
|
||||
durationStatuses={localDurationStatuses}
|
||||
/>
|
||||
{:else if rightColumnSelect == 'node_status'}
|
||||
<div class="p-4 grow flex flex-col gap-6">
|
||||
{#if selectedNode?.startsWith(AI_TOOL_MESSAGE_PREFIX)}
|
||||
<div class="pt-2 pb-4">
|
||||
<Alert
|
||||
type="info"
|
||||
title="Message output is available on the AI agent node"
|
||||
/>
|
||||
</div>
|
||||
{:else if selectedNode?.startsWith(AI_MCP_TOOL_CALL_PREFIX)}
|
||||
{@const [, agentModuleId, toolCallIndex] = selectedNode.split('-')}
|
||||
{@const agentNode = localModuleStates?.[agentModuleId]}
|
||||
{@const agentActions = agentNode?.agent_actions}
|
||||
{@const mcpActionIndex = parseInt(toolCallIndex)}
|
||||
{@const mcpAction =
|
||||
agentActions &&
|
||||
mcpActionIndex >= 0 &&
|
||||
mcpActionIndex < agentActions.length
|
||||
? agentActions[mcpActionIndex]
|
||||
: undefined}
|
||||
{#if mcpAction?.type === 'mcp_tool_call' && agentNode?.result?.messages}
|
||||
{@const message = agentNode.result.messages.find(
|
||||
(m: { agent_action?: { call_id?: string }; content?: any }) =>
|
||||
m.agent_action?.call_id === mcpAction.call_id
|
||||
)}
|
||||
<McpToolCallDetails
|
||||
functionName={mcpAction.function_name}
|
||||
args={mcpAction.arguments ?? {}}
|
||||
result={message?.content}
|
||||
type="Success"
|
||||
workspaceId={job?.workspace_id}
|
||||
/>
|
||||
{/if}
|
||||
{:else if selectedNode?.startsWith(AI_WEBSEARCH_PREFIX)}
|
||||
<Alert
|
||||
type="info"
|
||||
title="Message output is available on the AI agent node"
|
||||
title="Web search output is available on the AI agent node"
|
||||
/>
|
||||
</div>
|
||||
{:else if selectedNode?.startsWith(AI_MCP_TOOL_CALL_PREFIX)}
|
||||
{@const [, agentModuleId, toolCallIndex] = selectedNode.split('-')}
|
||||
{@const agentNode = localModuleStates?.[agentModuleId]}
|
||||
{@const agentActions = agentNode?.agent_actions}
|
||||
{@const mcpActionIndex = parseInt(toolCallIndex)}
|
||||
{@const mcpAction =
|
||||
agentActions && mcpActionIndex >= 0 && mcpActionIndex < agentActions.length
|
||||
? agentActions[mcpActionIndex]
|
||||
: undefined}
|
||||
{#if mcpAction?.type === 'mcp_tool_call' && agentNode?.result?.messages}
|
||||
{@const message = agentNode.result.messages.find(
|
||||
(m: { agent_action?: { call_id?: string }; content?: any }) =>
|
||||
m.agent_action?.call_id === mcpAction.call_id
|
||||
)}
|
||||
<McpToolCallDetails
|
||||
functionName={mcpAction.function_name}
|
||||
args={mcpAction.arguments ?? {}}
|
||||
result={message?.content}
|
||||
type="Success"
|
||||
workspaceId={job?.workspace_id}
|
||||
/>
|
||||
{/if}
|
||||
{:else if selectedNode?.startsWith(AI_WEBSEARCH_PREFIX)}
|
||||
<Alert
|
||||
type="info"
|
||||
title="Web search output is available on the AI agent node"
|
||||
/>
|
||||
{:else if selectedNode}
|
||||
{@const node = localModuleStates[selectedNode]}
|
||||
{#if selectedNode == 'end'}
|
||||
<FlowJobResult
|
||||
tagLabel={customUi?.tagLabel}
|
||||
workspaceId={isReplay ? undefined : job?.workspace_id}
|
||||
jobId={isReplay ? undefined : job?.id}
|
||||
filename={job.id}
|
||||
loading={job['running']}
|
||||
tag={job?.tag}
|
||||
col
|
||||
result={job['result']}
|
||||
logs={job.logs ?? ''}
|
||||
downloadLogs={!hideDownloadLogs && !isReplay}
|
||||
/>
|
||||
{:else if selectedNode == 'start'}
|
||||
{#if job.args}
|
||||
<JobArgs
|
||||
id={isReplay ? undefined : job.id}
|
||||
workspace={isReplay
|
||||
? undefined
|
||||
: (job.workspace_id ?? $workspaceStore ?? 'no_w')}
|
||||
args={job.args}
|
||||
{:else if selectedNode}
|
||||
{@const node = localModuleStates[selectedNode]}
|
||||
{#if selectedNode == 'end'}
|
||||
<FlowJobResult
|
||||
tagLabel={customUi?.tagLabel}
|
||||
workspaceId={isReplay ? undefined : job?.workspace_id}
|
||||
jobId={isReplay ? undefined : job?.id}
|
||||
filename={job.id}
|
||||
loading={job['running']}
|
||||
tag={job?.tag}
|
||||
col
|
||||
result={job['result']}
|
||||
logs={job.logs ?? ''}
|
||||
downloadLogs={!hideDownloadLogs && !isReplay}
|
||||
/>
|
||||
{:else}
|
||||
<p class="text-secondary">No arguments</p>
|
||||
{/if}
|
||||
{:else if node}
|
||||
{@const module =
|
||||
stepDetail && typeof stepDetail !== 'string' ? stepDetail : undefined}
|
||||
{@const agentTools =
|
||||
module && module.value.type === 'aiagent'
|
||||
? module.value.tools
|
||||
: undefined}
|
||||
{@const parentLoopsPrefix = getParentLoopsPrefix(module?.id ?? '')}
|
||||
{#if node.flow_jobs_results}
|
||||
<div>
|
||||
<span class="pl-1 text-emphasis text-xs font-medium"
|
||||
>Result of step as collection of all subflows</span
|
||||
>
|
||||
<div class="overflow-auto max-h-[200px] p-2">
|
||||
<DisplayResult
|
||||
workspaceId={isReplay ? undefined : job?.workspace_id}
|
||||
result={node.flow_jobs_results}
|
||||
nodeId={selectedNode}
|
||||
jobId={isReplay ? undefined : job?.id}
|
||||
language={job?.language}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if node.flow_jobs_results}
|
||||
<span class="pl-1 text-xs font-medium text-emphasis pt-4"
|
||||
>Selected subflow</span
|
||||
>
|
||||
{:else if selectedNode == 'start'}
|
||||
{#if job.args}
|
||||
<JobArgs
|
||||
id={isReplay ? undefined : job.id}
|
||||
workspace={isReplay
|
||||
? undefined
|
||||
: (job.workspace_id ?? $workspaceStore ?? 'no_w')}
|
||||
args={job.args}
|
||||
/>
|
||||
{:else}
|
||||
<p class="text-secondary">No arguments</p>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex gap-2 min-w-0 w-full items-center">
|
||||
<ModuleStatus
|
||||
type={node.type}
|
||||
scheduled_for={node.scheduled_for}
|
||||
skipped={node.skipped}
|
||||
{:else if node}
|
||||
{@const module =
|
||||
stepDetail && typeof stepDetail !== 'string' ? stepDetail : undefined}
|
||||
{@const agentTools =
|
||||
module && module.value.type === 'aiagent'
|
||||
? module.value.tools
|
||||
: undefined}
|
||||
{@const parentLoopsPrefix = getParentLoopsPrefix(module?.id ?? '')}
|
||||
{#if node.flow_jobs_results}
|
||||
<div>
|
||||
<span class="pl-1 text-emphasis text-xs font-medium"
|
||||
>Result of step as collection of all subflows</span
|
||||
>
|
||||
<div class="overflow-auto max-h-[200px] p-2">
|
||||
<DisplayResult
|
||||
workspaceId={isReplay ? undefined : job?.workspace_id}
|
||||
result={node.flow_jobs_results}
|
||||
nodeId={selectedNode}
|
||||
jobId={isReplay ? undefined : job?.id}
|
||||
language={job?.language}
|
||||
/>
|
||||
{#if node.duration_ms}
|
||||
<Badge>
|
||||
<Hourglass class="mr-2" size={10} />
|
||||
{msToSec(node.duration_ms)} s
|
||||
</Badge>
|
||||
{/if}
|
||||
{#if node.job_id && !isReplay}
|
||||
<div class="grow w-full flex flex-row-reverse">
|
||||
<a
|
||||
class="text-right text-xs"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
href="{base}/run/{node.job_id ??
|
||||
''}?workspace={job?.workspace_id}"
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if node.flow_jobs_results}
|
||||
<span class="pl-1 text-xs font-medium text-emphasis pt-4"
|
||||
>Selected subflow</span
|
||||
>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex gap-2 min-w-0 w-full items-center">
|
||||
<ModuleStatus
|
||||
type={node.type}
|
||||
scheduled_for={node.scheduled_for}
|
||||
skipped={node.skipped}
|
||||
/>
|
||||
{#if node.duration_ms}
|
||||
<Badge>
|
||||
<Hourglass class="mr-2" size={10} />
|
||||
{msToSec(node.duration_ms)} s
|
||||
</Badge>
|
||||
{/if}
|
||||
{#if node.job_id && !isReplay}
|
||||
<div class="grow w-full flex flex-row-reverse">
|
||||
<a
|
||||
class="text-right text-xs"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
href="{base}/run/{node.job_id ??
|
||||
''}?workspace={job?.workspace_id}"
|
||||
>
|
||||
{truncateRev(node.job_id ?? '', 10)}
|
||||
<ExternalLink size={12} class="inline-block" />
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !node.isListJob}
|
||||
<div>
|
||||
<div class="text-xs text-emphasis font-semibold mb-1"
|
||||
>Inputs</div
|
||||
>
|
||||
{truncateRev(node.job_id ?? '', 10)}
|
||||
<ExternalLink size={12} class="inline-block" />
|
||||
</a>
|
||||
<JobArgs
|
||||
id={isReplay ? undefined : node.job_id}
|
||||
workspace={isReplay
|
||||
? undefined
|
||||
: (job.workspace_id ?? $workspaceStore ?? 'no_w')}
|
||||
args={node.args}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if node.workflow_as_code_status}
|
||||
<div>
|
||||
<div class="text-xs text-emphasis font-semibold mb-1"
|
||||
>Workflow timeline</div
|
||||
>
|
||||
<WorkflowTimeline
|
||||
flow_status={asWorkflowStatus(node.workflow_as_code_status)}
|
||||
flowDone={node.type === 'Success' || node.type === 'Failure'}
|
||||
stepResults={getStepResults(node.workflow_as_code_status)}
|
||||
result={node.result}
|
||||
success={node.type === 'Success'}
|
||||
jobId={node.job_id}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !node.isListJob}
|
||||
<div>
|
||||
<div class="text-xs text-emphasis font-semibold mb-1">Inputs</div>
|
||||
<JobArgs
|
||||
id={isReplay ? undefined : node.job_id}
|
||||
workspace={isReplay
|
||||
? undefined
|
||||
: (job.workspace_id ?? $workspaceStore ?? 'no_w')}
|
||||
args={node.args}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if node.workflow_as_code_status}
|
||||
<div>
|
||||
<div class="text-xs text-emphasis font-semibold mb-1"
|
||||
>Workflow timeline</div
|
||||
>
|
||||
<WorkflowTimeline
|
||||
flow_status={asWorkflowStatus(node.workflow_as_code_status)}
|
||||
flowDone={node.type === 'Success' || node.type === 'Failure'}
|
||||
stepResults={getStepResults(node.workflow_as_code_status)}
|
||||
result={node.result}
|
||||
success={node.type === 'Success'}
|
||||
jobId={node.job_id}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<FlowJobResult
|
||||
tagLabel={customUi?.tagLabel}
|
||||
workspaceId={isReplay ? undefined : job?.workspace_id}
|
||||
jobId={isReplay ? undefined : node.job_id}
|
||||
loading={node.type != 'Success' && node.type != 'Failure'}
|
||||
waitingForExecutor={node.type == 'WaitingForExecutor'}
|
||||
refreshLog={node.type == 'InProgress'}
|
||||
col
|
||||
result_stream={resultStreams[node.job_id ?? '']}
|
||||
result={node.result}
|
||||
tag={node.tag}
|
||||
logs={node.logs}
|
||||
downloadLogs={!hideDownloadLogs && !isReplay}
|
||||
aiAgentStatus={agentTools &&
|
||||
node?.job_id &&
|
||||
(node.type === 'Success' || node.type === 'Failure')
|
||||
? {
|
||||
tools: agentTools,
|
||||
agentJob: {
|
||||
id: node.job_id,
|
||||
result: node.result,
|
||||
logs: node.logs,
|
||||
args: node.args,
|
||||
success: node.type === 'Success',
|
||||
type: 'CompletedJob'
|
||||
},
|
||||
storedToolCallJobs: module
|
||||
? toolCallStore?.getLocalToolCallJobs(parentLoopsPrefix)
|
||||
: undefined,
|
||||
onToolJobLoaded: (job, idx) => {
|
||||
if (module) {
|
||||
const storeKey = parentLoopsPrefix + module.id + '-' + idx
|
||||
toolCallStore?.setStoredToolCallJob(storeKey, job)
|
||||
<FlowJobResult
|
||||
tagLabel={customUi?.tagLabel}
|
||||
workspaceId={isReplay ? undefined : job?.workspace_id}
|
||||
jobId={isReplay ? undefined : node.job_id}
|
||||
loading={node.type != 'Success' && node.type != 'Failure'}
|
||||
waitingForExecutor={node.type == 'WaitingForExecutor'}
|
||||
refreshLog={node.type == 'InProgress'}
|
||||
col
|
||||
result_stream={resultStreams[node.job_id ?? '']}
|
||||
result={node.result}
|
||||
tag={node.tag}
|
||||
logs={node.logs}
|
||||
downloadLogs={!hideDownloadLogs && !isReplay}
|
||||
aiAgentStatus={agentTools &&
|
||||
node?.job_id &&
|
||||
(node.type === 'Success' || node.type === 'Failure')
|
||||
? {
|
||||
tools: agentTools,
|
||||
agentJob: {
|
||||
id: node.job_id,
|
||||
result: node.result,
|
||||
logs: node.logs,
|
||||
args: node.args,
|
||||
success: node.type === 'Success',
|
||||
type: 'CompletedJob'
|
||||
},
|
||||
storedToolCallJobs: module
|
||||
? toolCallStore?.getLocalToolCallJobs(parentLoopsPrefix)
|
||||
: undefined,
|
||||
onToolJobLoaded: (job, idx) => {
|
||||
if (module) {
|
||||
const storeKey = parentLoopsPrefix + module.id + '-' + idx
|
||||
toolCallStore?.setStoredToolCallJob(storeKey, job)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="p-2 text-primary italic"
|
||||
>The execution of this node has no information attached to it. The job
|
||||
likely did not run yet</p
|
||||
>
|
||||
{/if}
|
||||
{:else}<p class="text-secondary text-xs italic"
|
||||
>Select a node to see its details here</p
|
||||
>{/if}
|
||||
</div>
|
||||
{:else if rightColumnSelect == 'node_definition'}
|
||||
{@const node = selectedNode ? localModuleStates[selectedNode] : undefined}
|
||||
<FlowGraphViewerStep {stepDetail} jobScriptHash={node?.script_hash} />
|
||||
{:else if rightColumnSelect == 'user_states'}
|
||||
<div class="p-2">
|
||||
<JobArgs argLabel="Key" args={job?.flow_status?.user_states ?? {}} />
|
||||
</div>
|
||||
{:else if rightColumnSelect == 'tracing'}
|
||||
{@const node = selectedNode ? localModuleStates[selectedNode] : undefined}
|
||||
{#if node?.job_id}
|
||||
<JobOtelTraces jobId={node.job_id} />
|
||||
{:else}
|
||||
<div class="p-4 text-secondary text-xs italic"
|
||||
>Select a node with a job to see HTTP request traces</div
|
||||
>
|
||||
{:else}
|
||||
<p class="p-2 text-primary italic"
|
||||
>The execution of this node has no information attached to it. The job
|
||||
likely did not run yet</p
|
||||
>
|
||||
{/if}
|
||||
{:else}<p class="text-secondary text-xs italic"
|
||||
>Select a node to see its details here</p
|
||||
>{/if}
|
||||
</div>
|
||||
{:else if rightColumnSelect == 'node_definition'}
|
||||
{@const node = selectedNode ? localModuleStates[selectedNode] : undefined}
|
||||
<FlowGraphViewerStep {stepDetail} jobScriptHash={node?.script_hash} />
|
||||
{:else if rightColumnSelect == 'user_states'}
|
||||
<div class="p-2">
|
||||
<JobArgs argLabel="Key" args={job?.flow_status?.user_states ?? {}} />
|
||||
</div>
|
||||
{:else if rightColumnSelect == 'tracing'}
|
||||
{@const node = selectedNode ? localModuleStates[selectedNode] : undefined}
|
||||
{#if node?.job_id}
|
||||
<JobOtelTraces jobId={node.job_id} />
|
||||
{:else}
|
||||
<div class="p-4 text-secondary text-xs italic"
|
||||
>Select a node with a job to see HTTP request traces</div
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
diffBeforeFlow?: OpenFlow
|
||||
currentInputSchema?: Record<string, any>
|
||||
markRemovedAsShadowed?: boolean
|
||||
outerDivClass?: string
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -269,7 +270,8 @@
|
||||
onDeleteMultiple = undefined,
|
||||
onDuplicateMultiple = undefined,
|
||||
onMoveMultiple = undefined,
|
||||
movingIds = undefined
|
||||
movingIds = undefined,
|
||||
outerDivClass = ''
|
||||
}: Props = $props()
|
||||
|
||||
// Initialize note manager with fine-grained reactivity
|
||||
@@ -1059,7 +1061,7 @@
|
||||
{/if}
|
||||
<div
|
||||
style={`height: ${height}px; max-height: ${maxHeight}px;`}
|
||||
class="overflow-clip relative"
|
||||
class="overflow-clip relative {outerDivClass}"
|
||||
bind:clientWidth={debouncedWidth}
|
||||
bind:this={flowContainer}
|
||||
>
|
||||
|
||||
@@ -505,10 +505,10 @@
|
||||
{#snippet left()}
|
||||
<h1 class="text-sm font-semibold text-primary">run/{page.params.run}</h1>
|
||||
{/snippet}
|
||||
{#snippet right()}
|
||||
{@const isScript = job?.job_kind === 'script'}
|
||||
{@const isHubFlowPreview = isFlowPreview(job?.job_kind) && isHubFlowPath(job?.script_path)}
|
||||
{@const runsHref = `/runs/${job?.script_path}${!isScript ? '?jobKind=flow' : ''}`}
|
||||
{#snippet right()}
|
||||
{@const isScript = job?.job_kind === 'script'}
|
||||
{@const isHubFlowPreview = isFlowPreview(job?.job_kind) && isHubFlowPath(job?.script_path)}
|
||||
{@const runsHref = `/runs/${job?.script_path}${!isScript ? '?jobKind=flow' : ''}`}
|
||||
{#if job && 'deleted' in job && !job?.deleted && ($superadmin || ($userStore?.is_admin ?? false))}
|
||||
<Dropdown
|
||||
items={[
|
||||
@@ -561,18 +561,18 @@
|
||||
</Dropdown>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isFlowPreview(job?.job_kind) || isScriptPreview(job?.job_kind)}
|
||||
<Button
|
||||
unifiedSize="md"
|
||||
variant="default"
|
||||
startIcon={{ icon: GitBranch }}
|
||||
on:click={forkPreview}
|
||||
>
|
||||
{isHubFlowPreview
|
||||
? 'Fork flow into workspace'
|
||||
: `Fork ${isFlowPreview(job?.job_kind) ? 'flow' : 'code'} preview`}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if isFlowPreview(job?.job_kind) || isScriptPreview(job?.job_kind)}
|
||||
<Button
|
||||
unifiedSize="md"
|
||||
variant="default"
|
||||
startIcon={{ icon: GitBranch }}
|
||||
on:click={forkPreview}
|
||||
>
|
||||
{isHubFlowPreview
|
||||
? 'Fork flow into workspace'
|
||||
: `Fork ${isFlowPreview(job?.job_kind) ? 'flow' : 'code'} preview`}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if persistentScriptDefinition !== undefined}
|
||||
<Button
|
||||
unifiedSize="md"
|
||||
@@ -712,7 +712,7 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ActionRow>
|
||||
<div class="w-full pb-8">
|
||||
<div class="w-full">
|
||||
<!-- Flow Detail Header Card -->
|
||||
<div class="max-w-7xl mx-auto px-4 py-0">
|
||||
<Skeleton loading={!job} layout={[[24]]} />
|
||||
|
||||
Reference in New Issue
Block a user