fix: Fixed padding on add step button
Chart Release / chart (push) Successful in 22s
Server Deploy / deploy (push) Successful in 41s

This commit is contained in:
2026-08-04 17:24:40 +01:00
parent 3a77fc2abd
commit 3388d2f895
+12 -68
View File
@@ -9,8 +9,7 @@ import { Button } from "@/components/ui";
import { EditWorkflowModal } from "@/components/workflows/EditWorkflowModal";
import { StepPickerModal } from "@/components/workflows/StepPickerModal";
const inputClass =
"w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary placeholder-text-secondary/50 focus:border-signal focus:outline-none focus:ring-1 focus:ring-signal";
const inputClass = "w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary placeholder-text-secondary/50 focus:border-signal focus:outline-none focus:ring-1 focus:ring-signal";
type DragPayload = { kind: "lib"; stepId: string } | { kind: "move"; from: number };
@@ -23,9 +22,6 @@ function AdhocBadge() {
return <span className="rounded px-1.5 py-0.5 font-mono text-[10px] uppercase bg-signal/15 text-signal">ad-hoc</span>;
}
function snapshotOf(w: Workflow): string {
return JSON.stringify({
name: w.name,
@@ -87,8 +83,6 @@ export default function WorkflowBuilder() {
}
}, [loaded]);
useEffect(() => {
if (!secretGroups) return;
secretGroups.forEach((g: SecretGroupSummary) => {
@@ -104,17 +98,10 @@ export default function WorkflowBuilder() {
setGroupKeys((prev) => ({ ...prev, [g.group]: [] }));
});
});
}, [secretGroups]);
wfRef.current = wf;
useEffect(() => {
if (!wf || savedSnapshotRef.current === null) return;
if (snapshotOf(wf) === savedSnapshotRef.current) return;
@@ -122,10 +109,8 @@ export default function WorkflowBuilder() {
save();
}, 800);
return () => clearTimeout(t);
}, [wf]);
useEffect(() => {
if (!lastSaved) return;
const iv = setInterval(() => setTick((n) => n + 1), 15000);
@@ -148,11 +133,7 @@ export default function WorkflowBuilder() {
* NOTHING (a cleared field must not become a fleet-wide run), and multiple
* tag keys AND together. Change one, change both.
*/
const matched = (servers ?? []).filter(
(s) =>
wf.target_server_ids.includes(s.server_id) ||
(Object.keys(targetTags).length > 0 && Object.entries(targetTags).every(([k, v]) => s.tags?.[k] === v)),
);
const matched = (servers ?? []).filter((s) => wf.target_server_ids.includes(s.server_id) || (Object.keys(targetTags).length > 0 && Object.entries(targetTags).every(([k, v]) => s.tags?.[k] === v)));
const sortedSteps = [...wf.steps].sort((a, b) => a.order - b.order);
const selectedRef = selected !== null ? sortedSteps[selected] : null;
@@ -160,8 +141,6 @@ export default function WorkflowBuilder() {
const selectedIdxInWf = selectedRef ? wf.steps.indexOf(selectedRef) : -1;
const save = async () => {
if (savingRef.current) return;
const current = wfRef.current;
if (!current) return;
@@ -177,14 +156,9 @@ export default function WorkflowBuilder() {
return;
}
if (wfRef.current && snapshotOf(wfRef.current) === snapshot) {
savedSnapshotRef.current = snapshotOf(updated);
setWf(updated);
} else {
savedSnapshotRef.current = snapshot;
}
setLastSaved(new Date());
@@ -193,8 +167,7 @@ export default function WorkflowBuilder() {
} finally {
savingRef.current = false;
setSaving(false);
if (wfRef.current && snapshotOf(wfRef.current) !== savedSnapshotRef.current) {
setTimeout(() => save(), 0);
}
@@ -348,11 +321,8 @@ export default function WorkflowBuilder() {
<div className="flex flex-1 flex-col lg:grid lg:h-[calc(100dvh-53px)] lg:grid-cols-[1fr_320px]">
{/* CENTER: canvas */}
<main className="overflow-auto bg-background bg-[radial-gradient(circle_at_1px_1px,theme(colors.border)_1px,transparent_0)] bg-[length:22px_22px] p-4 sm:p-6 lg:p-8">
<div className="pointer-events-none sticky top-0 z-10 flex justify-center pt-4">
<button
onClick={() => setPickerOpen(true)}
className="pointer-events-auto inline-flex items-center gap-2 rounded bg-signal px-4 py-2.5 text-sm font-semibold text-signal-ink shadow-panel hover:bg-signal/90"
>
<div className="pointer-events-none sticky top-0 z-10 flex justify-center py-2">
<button onClick={() => setPickerOpen(true)} className="pointer-events-auto inline-flex items-center gap-2 rounded bg-signal px-4 py-2.5 text-sm font-semibold text-signal-ink shadow-panel hover:bg-signal/90">
<span className="text-base leading-none">+</span> Add step
</button>
</div>
@@ -365,11 +335,7 @@ export default function WorkflowBuilder() {
a workflow touches; this panel only reports the result. */}
<p className="mb-2 text-xs text-text-secondary">
{wf.target_server_ids.length} named{" "}
<button
type="button"
onClick={() => setEditWorkflowOpen(true)}
className="text-signal hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-signal"
>
<button type="button" onClick={() => setEditWorkflowOpen(true)} className="text-signal hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-signal">
Edit
</button>
</p>
@@ -430,21 +396,14 @@ export default function WorkflowBuilder() {
{lib && !ref.inline && <ShellBadge interpreter={lib.interpreter} />}
{ref.inline && <AdhocBadge />}
</div>
<pre className="max-h-16 overflow-hidden text-ellipsis whitespace-pre-wrap rounded border border-border bg-surface-2 p-2 font-mono text-xs text-text-secondary">
{script.slice(0, 200)}
</pre>
<pre className="max-h-16 overflow-hidden text-ellipsis whitespace-pre-wrap rounded border border-border bg-surface-2 p-2 font-mono text-xs text-text-secondary">{script.slice(0, 200)}</pre>
</div>
<DropZone pos={i + 1} />
</div>
);
})}
{sortedSteps.length === 0 && (
<button
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => handleDrop(e, 0)}
onClick={() => setPickerOpen(true)}
className="mt-2 w-full rounded border border-dashed border-border bg-surface py-6 text-sm text-text-secondary hover:border-signal/50 hover:text-text-primary"
>
<button onDragOver={(e) => e.preventDefault()} onDrop={(e) => handleDrop(e, 0)} onClick={() => setPickerOpen(true)} className="mt-2 w-full rounded border border-dashed border-border bg-surface py-6 text-sm text-text-secondary hover:border-signal/50 hover:text-text-primary">
+ Add your first step
</button>
)}
@@ -452,11 +411,7 @@ export default function WorkflowBuilder() {
</main>
{/* RIGHT: inspector */}
<aside
className={`overflow-auto border-border bg-surface p-4 lg:block lg:border-l ${
selected === null || !selectedRef ? "hidden" : "block max-lg:border-t max-lg:max-h-[60dvh]"
}`}
>
<aside className={`overflow-auto border-border bg-surface p-4 lg:block lg:border-l ${selected === null || !selectedRef ? "hidden" : "block max-lg:border-t max-lg:max-h-[60dvh]"}`}>
{selected === null || !selectedRef ? (
<p className="text-sm text-text-secondary">Select a step to configure it.</p>
) : (
@@ -494,14 +449,9 @@ export default function WorkflowBuilder() {
</div>
<div>
<label className="mb-1 block text-xs uppercase text-text-secondary">Command</label>
<textarea
className={`${inputClass} h-32 font-mono text-xs`}
value={selectedRef.inline.script}
onChange={(e) => updateInline(selectedIdxInWf, { script: e.target.value })}
/>
<textarea className={`${inputClass} h-32 font-mono text-xs`} value={selectedRef.inline.script} onChange={(e) => updateInline(selectedIdxInWf, { script: e.target.value })} />
<p className="mt-1 text-xs text-text-secondary">
Write <code className="text-signal">KEY=value</code> to <code className="text-signal">$WORKFLOW_ENV</code> to expose it to later steps. Outputs are derived
automatically on save.
Write <code className="text-signal">KEY=value</code> to <code className="text-signal">$WORKFLOW_ENV</code> to expose it to later steps. Outputs are derived automatically on save.
</p>
</div>
</div>
@@ -621,13 +571,7 @@ export default function WorkflowBuilder() {
{selectedRef.on_failure === "retry" && (
<div className="mt-2">
<label className="mb-1 block text-xs uppercase text-text-secondary">Max retries</label>
<input
type="number"
min={1}
className={inputClass}
value={selectedRef.max_retries}
onChange={(e) => updateRef(selectedIdxInWf, { max_retries: parseInt(e.target.value || "0", 10) })}
/>
<input type="number" min={1} className={inputClass} value={selectedRef.max_retries} onChange={(e) => updateRef(selectedIdxInWf, { max_retries: parseInt(e.target.value || "0", 10) })} />
</div>
)}
</div>