mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
update
This commit is contained in:
@@ -236,8 +236,7 @@
|
||||
pathPrefix,
|
||||
defaultPathSuffix,
|
||||
producers: producersByAsset.get(`${a.kind}:${a.path}`) ?? [],
|
||||
onRunProducer,
|
||||
onSelectAsset: () => onselect?.({ kind: 'asset', asset_kind: a.kind, path: a.path })
|
||||
onRunProducer
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -279,12 +278,6 @@
|
||||
: undefined,
|
||||
downstreamCount: downstreamByScript.get(r.path) ?? 0,
|
||||
runState,
|
||||
onSelectSelf: () =>
|
||||
onselect?.({
|
||||
kind: 'runnable',
|
||||
runnable_kind: r.usage_kind,
|
||||
path: r.path
|
||||
}),
|
||||
onRequestRemove: onRunnableMenuRemove
|
||||
? () =>
|
||||
onRunnableMenuRemove({
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
// cached draft content). Without this callback, the play button
|
||||
// is hidden — runs only make sense in editor contexts.
|
||||
onRunProducer?: (producer: AssetProducer) => Promise<string | undefined>
|
||||
// Forwarded from the canvas. Called when the user runs producers
|
||||
// from this node so the page can auto-select the asset and open
|
||||
// the runs panel — matches what clicking the node would do.
|
||||
onSelectAsset?: () => void
|
||||
}
|
||||
// SvelteFlow injects this on the node component when the user clicks
|
||||
// the node. Combined with our own `hovered` state to drive the
|
||||
@@ -75,11 +71,6 @@
|
||||
e.stopPropagation()
|
||||
if (!$workspaceStore || running || !data.onRunProducer) return
|
||||
if (scriptProducers.length === 0) return
|
||||
// Select the asset so the runs panel opens (or refocuses) on this
|
||||
// node — without this, dispatching a run silently goes off into the
|
||||
// void with no UI feedback when the panel was closed or pointed at
|
||||
// a different node.
|
||||
if (!selected) data.onSelectAsset?.()
|
||||
running = true
|
||||
const handler = data.onRunProducer
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import Modal from '$lib/components/common/modal/Modal.svelte'
|
||||
import { AlertTriangle, ArrowRight } from 'lucide-svelte'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
onAck: () => void
|
||||
}
|
||||
let { open = $bindable(), onAck }: Props = $props()
|
||||
</script>
|
||||
|
||||
<Modal bind:open title="Pipelines is in alpha" kind="X">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="shrink-0 flex h-10 w-10 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-800/50"
|
||||
>
|
||||
<AlertTriangle size={20} class="text-amber-500 dark:text-amber-400" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 text-sm text-secondary">
|
||||
<p>
|
||||
Pipelines is an <span class="font-semibold text-emphasis">alpha feature preview</span>.
|
||||
The API, on-disk format, and UI are still evolving and may change without notice.
|
||||
</p>
|
||||
<p>
|
||||
Pipelines built today may need migration as the feature stabilizes. Avoid relying on them
|
||||
for production workloads until they leave alpha.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 pt-2">
|
||||
<Button variant="accent" unifiedSize="sm" onclick={onAck} endIcon={{ icon: ArrowRight }}>
|
||||
I understand, continue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -3,13 +3,11 @@
|
||||
import { base } from '$lib/base'
|
||||
import { goto } from '$app/navigation'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
import Select from '$lib/components/select/Select.svelte'
|
||||
import FolderPicker from '$lib/components/FolderPicker.svelte'
|
||||
import Modal from '$lib/components/common/modal/Modal.svelte'
|
||||
import { FolderService, OpenAPI } from '$lib/gen'
|
||||
import { OpenAPI } from '$lib/gen'
|
||||
import { resource } from 'runed'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { ArrowRight, FolderPlus, Loader2 } from 'lucide-svelte'
|
||||
import { ArrowRight, Loader2 } from 'lucide-svelte'
|
||||
|
||||
interface PipelineFolder {
|
||||
folder: string
|
||||
@@ -38,53 +36,21 @@
|
||||
}
|
||||
)
|
||||
|
||||
let allFolders = resource(
|
||||
() => $workspaceStore,
|
||||
async (ws) => {
|
||||
if (!ws) return [] as string[]
|
||||
return await FolderService.listFolderNames({ workspace: ws })
|
||||
}
|
||||
)
|
||||
|
||||
let selectedExistingFolder = $state<string | undefined>(undefined)
|
||||
let newFolderName = $state('')
|
||||
let creatingFolder = $state(false)
|
||||
let pickedFolder = $state('')
|
||||
|
||||
let visiblePipelines = $derived(
|
||||
(pipelines.current ?? []).filter((p) => p.folder !== currentFolder)
|
||||
)
|
||||
|
||||
let foldersWithoutPipeline = $derived.by(() => {
|
||||
const existing = new Set((pipelines.current ?? []).map((p) => p.folder))
|
||||
return (allFolders.current ?? []).filter((f) => !existing.has(f) && f !== currentFolder)
|
||||
})
|
||||
|
||||
async function openExistingPipeline(folder: string) {
|
||||
open = false
|
||||
await goto(`${base}/pipeline/${encodeURIComponent(folder)}`)
|
||||
}
|
||||
|
||||
async function startInExistingFolder() {
|
||||
if (!selectedExistingFolder) return
|
||||
await openExistingPipeline(selectedExistingFolder)
|
||||
}
|
||||
|
||||
async function createFolderAndStart() {
|
||||
const name = newFolderName.trim()
|
||||
if (!name || !$workspaceStore) return
|
||||
creatingFolder = true
|
||||
try {
|
||||
await FolderService.createFolder({
|
||||
workspace: $workspaceStore,
|
||||
requestBody: { name }
|
||||
})
|
||||
sendUserToast(`Created folder f/${name}`)
|
||||
await openExistingPipeline(name)
|
||||
} catch (e: any) {
|
||||
sendUserToast(`Failed to create folder: ${e?.body ?? e?.message ?? e}`, true)
|
||||
} finally {
|
||||
creatingFolder = false
|
||||
}
|
||||
async function openPicked() {
|
||||
const name = pickedFolder.trim()
|
||||
if (!name) return
|
||||
await openExistingPipeline(name)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -124,53 +90,22 @@
|
||||
|
||||
<section class="flex flex-col gap-2">
|
||||
<h3 class="text-xs font-semibold text-secondary uppercase tracking-wide">
|
||||
Start in an existing folder
|
||||
Pick or create a folder
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1">
|
||||
<Select
|
||||
items={foldersWithoutPipeline.map((f) => ({ label: `f/${f}`, value: f }))}
|
||||
bind:value={selectedExistingFolder}
|
||||
placeholder={foldersWithoutPipeline.length === 0
|
||||
? 'No folders available'
|
||||
: 'Pick a folder…'}
|
||||
clearable
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<FolderPicker bind:folderName={pickedFolder} />
|
||||
</div>
|
||||
<Button
|
||||
variant="accent"
|
||||
unifiedSize="sm"
|
||||
disabled={!selectedExistingFolder}
|
||||
onclick={startInExistingFolder}
|
||||
disabled={!pickedFolder.trim()}
|
||||
onclick={openPicked}
|
||||
startIcon={{ icon: ArrowRight }}
|
||||
>
|
||||
Open
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex flex-col gap-2">
|
||||
<h3 class="text-xs font-semibold text-secondary uppercase tracking-wide">
|
||||
Or create a new folder
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1">
|
||||
<TextInput
|
||||
bind:value={newFolderName}
|
||||
placeholder="new-folder-name"
|
||||
disabled={creatingFolder}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="accent"
|
||||
unifiedSize="sm"
|
||||
disabled={!newFolderName.trim() || creatingFolder}
|
||||
onclick={createFolderAndStart}
|
||||
startIcon={{ icon: FolderPlus }}
|
||||
>
|
||||
{creatingFolder ? 'Creating…' : 'Create & open'}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -52,10 +52,6 @@
|
||||
// downstream" alternative; the round Play button stays a single-
|
||||
// click default. Undefined / 0 hides the cascade menu item.
|
||||
downstreamCount?: number
|
||||
// Called before running so the details pane focuses this script —
|
||||
// mirrors AssetNode.onSelectAsset, keeps the runs/output in view
|
||||
// instead of dispatching into nowhere.
|
||||
onSelectSelf?: () => void
|
||||
// Wired by the canvas. When set, the node renders an
|
||||
// EllipsisVertical hover-button that opens a small action menu —
|
||||
// "Discard" for drafts, "Delete…" (which the page maps to its
|
||||
@@ -97,9 +93,6 @@
|
||||
async function runSelf(e: MouseEvent, cascade?: boolean) {
|
||||
e.stopPropagation()
|
||||
if (!$workspaceStore || running || !data.onRunSelf) return
|
||||
// Focus this runnable so the details pane opens to its editor — same
|
||||
// rationale as AssetNode.onSelectAsset before runProducers.
|
||||
if (!selected) data.onSelectSelf?.()
|
||||
running = true
|
||||
try {
|
||||
await data.onRunSelf(cascade != undefined ? { cascade } : undefined)
|
||||
|
||||
@@ -51,40 +51,40 @@
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-row gap-2">
|
||||
<Button
|
||||
id="create-app-button"
|
||||
aiId="apps-create-actions-app"
|
||||
aiDescription="Create a new app"
|
||||
unifiedSize="lg"
|
||||
startIcon={{ icon: Plus }}
|
||||
endIcon={{ icon: LayoutDashboard }}
|
||||
on:click={openAppTypeModal}
|
||||
variant="accent"
|
||||
dropdownItems={[
|
||||
{
|
||||
label: 'Import low-code app',
|
||||
onClick: () => {
|
||||
appKind = 'lowcode'
|
||||
importType = 'yaml'
|
||||
drawer?.toggleDrawer?.()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Import full-code app',
|
||||
onClick: () => {
|
||||
appKind = 'fullcode'
|
||||
importType = 'yaml'
|
||||
drawer?.toggleDrawer?.()
|
||||
}
|
||||
<Button
|
||||
id="create-app-button"
|
||||
aiId="apps-create-actions-app"
|
||||
aiDescription="Create a new app"
|
||||
unifiedSize="lg"
|
||||
startIcon={{ icon: Plus }}
|
||||
endIcon={{ icon: LayoutDashboard }}
|
||||
on:click={openAppTypeModal}
|
||||
variant="accent"
|
||||
dropdownItems={[
|
||||
{
|
||||
label: 'Import low-code app',
|
||||
onClick: () => {
|
||||
appKind = 'lowcode'
|
||||
importType = 'yaml'
|
||||
drawer?.toggleDrawer?.()
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div class="flex flex-row items-center"> App </div>
|
||||
</Button>
|
||||
</div>
|
||||
},
|
||||
{
|
||||
label: 'Import full-code app',
|
||||
onClick: () => {
|
||||
appKind = 'fullcode'
|
||||
importType = 'yaml'
|
||||
drawer?.toggleDrawer?.()
|
||||
}
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div class="flex flex-row items-center"> App </div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- App Type Selection Modal -->
|
||||
<Modal bind:open={appTypeModalOpen} title="Choose your app builder">
|
||||
<Modal bind:open={appTypeModalOpen} title="Choose your app builder" class="sm:max-w-3xl">
|
||||
<div class="flex flex-col gap-4 pr-4">
|
||||
<div class="grid grid-cols-2 gap-8">
|
||||
<!-- Low-code option -->
|
||||
@@ -122,8 +122,8 @@
|
||||
<p class="text-xs text-tertiary mt-1">
|
||||
Build with React or Svelte with full control and a powerful AI agent.
|
||||
|
||||
<br /><br />
|
||||
Better for complex apps or apps that require full flexibility and control.
|
||||
<br /><br />
|
||||
Better for complex apps or apps that require full flexibility and control.
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
|
||||
import Tab from '$lib/components/common/tabs/Tab.svelte'
|
||||
import { PythonIcon, TypeScriptIcon } from '$lib/components/common/languageIcons'
|
||||
import { Code2, Loader2, NetworkIcon, Plus } from 'lucide-svelte'
|
||||
import { Code2, Loader2, Plus } from 'lucide-svelte'
|
||||
import YAML from 'yaml'
|
||||
|
||||
let drawer: Drawer | undefined = $state(undefined)
|
||||
@@ -95,6 +95,10 @@
|
||||
onClick: () => {
|
||||
wacDrawer?.toggleDrawer?.()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Pipeline (alpha)',
|
||||
onClick: () => selectPipeline()
|
||||
}
|
||||
]}
|
||||
>
|
||||
@@ -103,13 +107,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Flow Type Selection Modal -->
|
||||
<!-- max-w-4xl widens the modal to fit three tiles comfortably; default
|
||||
`max-w-lg` (~512px) was sized for two tiles and squeezed the grid.
|
||||
kind="X" replaces the bottom Cancel button with a top-right close (X)
|
||||
so the action area is purely the three tile buttons. -->
|
||||
<Modal bind:open={flowModalOpen} title="Create a new flow" class="sm:max-w-4xl" kind="X">
|
||||
<!-- kind="X" replaces the bottom Cancel button with a top-right close (X)
|
||||
so the action area is purely the tile buttons. -->
|
||||
<Modal bind:open={flowModalOpen} title="Create a new flow" class="sm:max-w-3xl" kind="X">
|
||||
<div class="flex flex-col gap-4 pr-4">
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<!-- Flow Editor option -->
|
||||
<button
|
||||
class="flex flex-col items-center gap-3 p-6 rounded-lg border-2 border-gray-200 dark:border-gray-700 hover:border-blue-500 dark:hover:border-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all cursor-pointer group"
|
||||
@@ -183,31 +185,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pipeline option -->
|
||||
<button
|
||||
class="relative flex flex-col items-center gap-3 p-6 rounded-lg border-2 border-gray-200 dark:border-gray-700 hover:border-emerald-500 dark:hover:border-emerald-400 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 transition-all cursor-pointer group"
|
||||
onclick={selectPipeline}
|
||||
>
|
||||
<!-- Alpha badge — pipeline is the newest of the three; same
|
||||
positioning the WAC tile used so it reads consistently. -->
|
||||
<div
|
||||
class="absolute top-2 right-2 z-10 px-2 py-0.5 rounded-full bg-emerald-500 text-white text-2xs font-bold uppercase tracking-wide"
|
||||
>
|
||||
Alpha
|
||||
</div>
|
||||
<div
|
||||
class="w-32 h-32 rounded-xl bg-emerald-100 dark:bg-emerald-900/40 flex items-center justify-center group-hover:bg-emerald-200 dark:group-hover:bg-emerald-800/50 transition-colors"
|
||||
>
|
||||
<NetworkIcon size={32} class="text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h3 class="font-semibold text-primary">Pipeline</h3>
|
||||
<p class="text-xs text-tertiary mt-1">
|
||||
Asset-driven DAG of scripts. Trigger on schedule, asset change, or external event.
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -2,11 +2,37 @@
|
||||
import { userStore } from '$lib/stores'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import PipelinePickerModal from '$lib/components/assets/AssetGraph/PipelinePickerModal.svelte'
|
||||
import PipelineAlphaAckModal from '$lib/components/assets/AssetGraph/PipelineAlphaAckModal.svelte'
|
||||
import { ArrowRight, NetworkIcon } from 'lucide-svelte'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
// Modal is open by default on landing; the editor shell stays empty
|
||||
// behind it until the user picks or creates a folder.
|
||||
let pickerOpen = $state(true)
|
||||
const ACK_STORAGE_KEY = 'pipeline-alpha-ack'
|
||||
|
||||
// Gate the picker behind a one-time alpha acknowledgement. We read
|
||||
// localStorage in onMount (not at module scope) so SSR doesn't blow up.
|
||||
let ackOpen = $state(false)
|
||||
let pickerOpen = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
const acked =
|
||||
typeof localStorage !== 'undefined' && localStorage.getItem(ACK_STORAGE_KEY) === 'true'
|
||||
if (acked) {
|
||||
pickerOpen = true
|
||||
} else {
|
||||
ackOpen = true
|
||||
}
|
||||
})
|
||||
|
||||
function handleAck() {
|
||||
try {
|
||||
localStorage.setItem(ACK_STORAGE_KEY, 'true')
|
||||
} catch {
|
||||
// Storage may be unavailable (private mode, quota); the ack still
|
||||
// flows through for this visit, the user just sees the modal again next time.
|
||||
}
|
||||
ackOpen = false
|
||||
pickerOpen = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -43,5 +69,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PipelineAlphaAckModal bind:open={ackOpen} onAck={handleAck} />
|
||||
<PipelinePickerModal bind:open={pickerOpen} />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user