diff --git a/canvas/.env.example b/canvas/.env.example new file mode 100644 index 00000000..977948ba --- /dev/null +++ b/canvas/.env.example @@ -0,0 +1,8 @@ +# Platform API base URL — used by the canvas for all REST calls and WebSocket connection. +# Set this to your deployed platform URL. +NEXT_PUBLIC_PLATFORM_URL=http://localhost:8080 + +# WebSocket URL override — optional. +# If not set, derived automatically from NEXT_PUBLIC_PLATFORM_URL (http→ws, appends /ws). +# Only set this if your WS endpoint is at a different host/path than the REST API. +# NEXT_PUBLIC_WS_URL=ws://localhost:8080/ws diff --git a/canvas/src/components/Toolbar.tsx b/canvas/src/components/Toolbar.tsx index ce766c32..11ed276c 100644 --- a/canvas/src/components/Toolbar.tsx +++ b/canvas/src/components/Toolbar.tsx @@ -10,6 +10,7 @@ import { showToast } from "@/components/Toaster"; export function Toolbar() { const nodes = useCanvasStore((s) => s.nodes); + const wsStatus = useCanvasStore((s) => s.wsStatus); const [stopping, setStopping] = useState(false); const [restartingAll, setRestartingAll] = useState(false); @@ -17,6 +18,23 @@ export function Toolbar() { const [helpOpen, setHelpOpen] = useState(false); const helpRef = useRef(null); + // Suppress toast on the very first connect at page load; only fire on reconnects. + const mountedRef = useRef(false); + useEffect(() => { + const t = setTimeout(() => { mountedRef.current = true; }, 2000); + return () => clearTimeout(t); + }, []); + + const prevWsStatus = useRef("connecting"); + useEffect(() => { + if (prevWsStatus.current === "connecting" && wsStatus === "connected") { + if (mountedRef.current) { + showToast("Live updates restored", "success"); + } + } + prevWsStatus.current = wsStatus; + }, [wsStatus]); + const counts = useMemo(() => { const c = { total: nodes.length, roots: 0, children: 0, online: 0, offline: 0, failed: 0, provisioning: 0, activeTasks: 0 }; for (const n of nodes) { @@ -122,6 +140,11 @@ export function Toolbar() { + {/* WebSocket connection status */} +
+ +
+ {/* Stop All — visible when agents have active tasks */} {counts.activeTasks > 0 && (