diff --git a/web/app/workflows/[id]/page.tsx b/web/app/workflows/[id]/page.tsx index cc9656b..bd6e2b7 100644 --- a/web/app/workflows/[id]/page.tsx +++ b/web/app/workflows/[id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; import { useQuery, useQueryClient } from "@tanstack/react-query"; @@ -27,6 +27,14 @@ function ShellBadge({ interpreter }: { interpreter: "bash" | "powershell" }) { ); } +function DefaultBadge() { + return ( + + default + + ); +} + export default function WorkflowBuilder() { const params = useParams<{ id: string }>(); const id = params.id; @@ -39,6 +47,10 @@ export default function WorkflowBuilder() { const [saving, setSaving] = useState(false); const [running, setRunning] = useState(false); const [error, setError] = useState(null); + const [notice, setNotice] = useState(null); + const [importing, setImporting] = useState(false); + const [syncing, setSyncing] = useState(false); + const fileInputRef = useRef(null); const [groupKeys, setGroupKeys] = useState>({}); const [editWorkflowOpen, setEditWorkflowOpen] = useState(false); const [editingStep, setEditingStep] = useState(null); @@ -178,6 +190,38 @@ export default function WorkflowBuilder() { updateRef(selectedIdxInWf, { overrides: { ...selectedRef.overrides, secret_refs: next } }); }; + const onImportFile = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + setImporting(true); + setError(null); + try { + const doc = JSON.parse(await file.text()); + await api.importStep(doc); + await queryClient.invalidateQueries({ queryKey: ["steps"] }); + setNotice("Step imported."); + } catch (err) { + setError((err as Error).message); + } finally { + setImporting(false); + e.target.value = ""; + } + }; + + const onSyncDefaults = async () => { + setSyncing(true); + setError(null); + try { + const { created, updated } = await api.seedDefaults(); + await queryClient.invalidateQueries({ queryKey: ["steps"] }); + setNotice(`${created} created, ${updated} updated`); + } catch (err) { + setError((err as Error).message); + } finally { + setSyncing(false); + } + }; + const filteredLibrary = (library ?? []).filter((s) => s.name.toLowerCase().includes(search.toLowerCase())); const bashSteps = filteredLibrary.filter((s) => s.interpreter === "bash"); const pwshSteps = filteredLibrary.filter((s) => s.interpreter === "powershell"); @@ -236,6 +280,9 @@ export default function WorkflowBuilder() { {error && (
{error}
)} + {notice && ( +
{notice}
+ )}
{/* LEFT: library */} @@ -253,6 +300,26 @@ export default function WorkflowBuilder() { +
+
+ + + +
+ {step.source === "default" && } {step.name} + e.stopPropagation()} + className="ml-auto hidden text-text-secondary hover:text-text-primary group-hover:block" + title="Export step" + > + ⬇ +
-
- {outputs.map((o) => ( +
+ {(step?.declared_outputs ?? []).length === 0 && ( +

No declared outputs.

+ )} + {(step?.declared_outputs ?? []).map((o) => ( - {o} + {o} ))}
-
- setNewOut(e.target.value)} /> - -
+

Outputs are detected automatically from lines writing to $WORKFLOW_ENV.