AppManager: Recursively monitor dodo resources

Change-Id: I23f3014e416f5b68f8909dbfbcba27db66eaee3f
diff --git a/apps/canvas/front/src/components/actions.tsx b/apps/canvas/front/src/components/actions.tsx
index dc3503f..58598ca 100644
--- a/apps/canvas/front/src/components/actions.tsx
+++ b/apps/canvas/front/src/components/actions.tsx
@@ -5,6 +5,14 @@
 import { useNodes, useReactFlow } from "@xyflow/react";
 import { useToast } from "@/hooks/use-toast";
 
+function toNodeType(t: string): string {
+    if (t === "ingress") {
+        return "gateway-https";
+    } else {
+        return t;
+    }
+}
+
 export function Actions() {
     const { toast } = useToast();
     const store = useStateStore();
@@ -35,7 +43,7 @@
                 if (n.type === "network") {
                     continue;
                 }
-                const d = data.find((d) => n.type === d.type && nodeLabel(n) === d.name);
+                const d = data.find((d) => n.type === toNodeType(d.type) && nodeLabel(n) === d.name);
                 if (d !== undefined) {
                     store.updateNodeData(n.id, {
                         state: d?.status,
diff --git a/apps/canvas/front/src/components/canvas.tsx b/apps/canvas/front/src/components/canvas.tsx
index 3dd8b1c..2f261f0 100644
--- a/apps/canvas/front/src/components/canvas.tsx
+++ b/apps/canvas/front/src/components/canvas.tsx
@@ -87,7 +87,7 @@
                 label: n.domain,
                 envVars: [],
                 ports: [],
-                state: null,
+                state: "success", // TODO(gio): monitor network health
             },
         }));
         const prevNodes = store.nodes;
diff --git a/apps/canvas/front/src/components/node-rect.tsx b/apps/canvas/front/src/components/node-rect.tsx
index a568bc0..06f3307 100644
--- a/apps/canvas/front/src/components/node-rect.tsx
+++ b/apps/canvas/front/src/components/node-rect.tsx
@@ -16,8 +16,9 @@
     const hasFatal = messages.some((m) => m.type === "FATAL");
     const hasWarning = messages.some((m) => m.type === "WARNING");
     const [classes, setClasses] = useState<string[]>([]);
+    const [stateClasses, setStateClasses] = useState<string[]>([]);
     useEffect(() => {
-        const classes = ["px-4", "py-2", "rounded-md"];
+        const classes = ["px-4", "py-2", "rounded-md", "bg-white"];
         if (hasFatal) {
             classes.push("border-red-500");
         } else if (hasWarning) {
@@ -30,22 +31,27 @@
         } else {
             classes.push("border");
         }
-        if (state === "processing") {
-            classes.push("animate-pulse");
-        } else if (state === "success") {
-            classes.push("bg-green-500");
-        } else if (state === "failure") {
-            classes.push("bg-red-500");
-        } else {
-            classes.push("bg-white");
-        }
         setClasses(classes);
-    }, [selected, hasFatal, hasWarning, state, setClasses]);
+        let stateClasses: string[] = [];
+        if (state === "processing") {
+            stateClasses.push("bg-yellow-500");
+            stateClasses.push("animate-pulse");
+        } else if (state === "success") {
+            stateClasses.push("bg-green-500");
+        } else if (state === "failure") {
+            stateClasses.push("bg-red-500");
+        } else {
+            stateClasses.push("bg-black");
+        }
+        setStateClasses(stateClasses);
+    }, [selected, hasFatal, hasWarning, state, setClasses, setStateClasses]);
     return (
         <div className={classes.join(" ")}>
             <div style={{ position: "absolute", top: "5px", left: "5px" }}>
                 {Icon(p.type)}
             </div>
+            <div style={{ position: "absolute", top: "5px", right: "5px", borderRadius: "50%", width: "5px", height: "5px" }} className={stateClasses.join(" ")}>
+            </div>
             {children}
         </div>
     )