diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
index ee5d069..f4594d7 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/docker-compose.yml
@@ -36,10 +36,13 @@ services:
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
OIDC_REDIRECT_URL: ${OIDC_REDIRECT_URL:-}
KEY_ENCRYPTION_KEY: ${KEY_ENCRYPTION_KEY:-}
+ VANTAGE_WORKFLOW_LOG_DIR: ${VANTAGE_WORKFLOW_LOG_DIR:-}
GUACD_ADDR: guacd:4822
depends_on:
redis:
condition: service_healthy
+ volumes:
+ - ./data:/data
web:
image: gitea.hostxtra.co.uk/mrhid6/vantage/web:latest
restart: unless-stopped
diff --git a/web/app/workflows/[id]/page.tsx b/web/app/workflows/[id]/page.tsx
index 24302e6..32f851e 100644
--- a/web/app/workflows/[id]/page.tsx
+++ b/web/app/workflows/[id]/page.tsx
@@ -105,14 +105,35 @@ export default function WorkflowBuilder() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [secretGroups]);
- if (!wf) {
- return
Loading…
;
- }
-
// Keep a ref to the latest workflow so an in-flight save can tell whether
// the user edited again while the request was on the wire.
wfRef.current = wf;
+ // Autosave: debounce 800ms after any change to the workflow (step added,
+ // removed, reordered, or edited) and persist. Diffing the serialized state
+ // against the last saved snapshot skips no-op saves and the initial load.
+ // Must stay above the early return below so hook order is stable.
+ useEffect(() => {
+ if (!wf || savedSnapshotRef.current === null) return;
+ if (JSON.stringify(wf) === savedSnapshotRef.current) return;
+ const t = setTimeout(() => {
+ save();
+ }, 800);
+ return () => clearTimeout(t);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [wf]);
+
+ // Re-render every 15s so the "Saved … ago" label stays current.
+ useEffect(() => {
+ if (!lastSaved) return;
+ const iv = setInterval(() => setTick((n) => n + 1), 15000);
+ return () => clearInterval(iv);
+ }, [lastSaved]);
+
+ if (!wf) {
+ return Loading…
;
+ }
+
const libById = (sid?: string) => (sid ? library?.find((l) => l.step_id === sid) : undefined);
const sortedSteps = [...wf.steps].sort((a, b) => a.order - b.order);
@@ -162,26 +183,6 @@ export default function WorkflowBuilder() {
}
};
- // Autosave: debounce 800ms after any change to the workflow (step added,
- // removed, reordered, or edited) and persist. Diffing the serialized state
- // against the last saved snapshot skips no-op saves and the initial load.
- useEffect(() => {
- if (!wf || savedSnapshotRef.current === null) return;
- if (JSON.stringify(wf) === savedSnapshotRef.current) return;
- const t = setTimeout(() => {
- save();
- }, 800);
- return () => clearTimeout(t);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [wf]);
-
- // Re-render every 15s so the "Saved … ago" label stays current.
- useEffect(() => {
- if (!lastSaved) return;
- const iv = setInterval(() => setTick((n) => n + 1), 15000);
- return () => clearInterval(iv);
- }, [lastSaved]);
-
const run = async () => {
setRunning(true);
setError(null);