Canvas: Render network nodes

Change-Id: I63938da205af9377a1e210c0e972591142211a68
diff --git a/apps/canvas/front/src/components/canvas.tsx b/apps/canvas/front/src/components/canvas.tsx
index 406f2e9..3dd8b1c 100644
--- a/apps/canvas/front/src/components/canvas.tsx
+++ b/apps/canvas/front/src/components/canvas.tsx
@@ -1,8 +1,8 @@
 import '@xyflow/react/dist/style.css';
 import { ReactFlow, Background, Controls, Connection, BackgroundVariant, Edge, useReactFlow, Panel } from '@xyflow/react';
-import { useStateStore, AppState, AppNode } from '@/lib/state';
+import { useStateStore, AppState, AppNode, useEnv } from '@/lib/state';
 import { useShallow } from "zustand/react/shallow";
-import { useCallback, useMemo } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
 import { NodeGatewayHttps } from "@/components/node-gateway-https";
 import { NodeApp } from '@/components/node-app';
 import { NodeVolume } from './node-volume';
@@ -11,6 +11,7 @@
 import { NodeGithub } from './node-github';
 import { Actions } from './actions';
 import { NodeGatewayTCP } from './node-gateway-tcp';
+import { NodeNetwork } from './node-network';
 
 const selector = (state: AppState) => ({
     nodes: state.nodes,
@@ -24,8 +25,10 @@
     const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useStateStore(
         useShallow(selector),
     );
+    const store = useStateStore();
     const flow = useReactFlow();
     const nodeTypes = useMemo(() => ({
+        "network": NodeNetwork,
         "app": NodeApp,
         "gateway-https": NodeGatewayHttps,
         "gateway-tcp": NodeGatewayTCP,
@@ -59,8 +62,41 @@
             }
             return true;
         }
+        if (tn.type === "network") {
+            if (c.sourceHandle !== "subdomain") {
+                return false;
+            }
+            if (sn.type !== "gateway-https" && sn.type !== "gateway-tcp") {
+                return false;
+            }
+        }
         return true;
     }, [flow]);
+    const env = useEnv();
+    useEffect(() => {
+        const networkNodes: AppNode[] = env.networks.map((n) => ({
+            id: n.domain,
+            type: "network",
+            position: {
+                x: 0,
+                y: 0,
+            },
+            isConnectable: true,          
+            data: {
+                domain: n.domain,
+                label: n.domain,
+                envVars: [],
+                ports: [],
+                state: null,
+            },
+        }));
+        const prevNodes = store.nodes;
+        const newNodes = networkNodes.concat(prevNodes.filter((n) => n.type !== "network"));
+        // TODO(gio): actually compare
+        if (prevNodes.length !== newNodes.length) {
+            store.setNodes(newNodes);
+        }
+    }, [env, store]);
     return (
         <div style={{ width: '100%', height: '100%' }}>
             <ReactFlow