Canvas: Expose ingress auth details

Change-Id: I337163f5919db5c8e48d6d429dcdc6420b196d3d
diff --git a/apps/canvas/front/src/components/node-gateway-https.tsx b/apps/canvas/front/src/components/node-gateway-https.tsx
index 1397f5c..e618943 100644
--- a/apps/canvas/front/src/components/node-gateway-https.tsx
+++ b/apps/canvas/front/src/components/node-gateway-https.tsx
@@ -2,13 +2,17 @@
 import { useStateStore, AppNode, GatewayHttpsNode, ServiceNode, nodeLabel, useEnv, nodeIsConnectable } from '@/lib/state';
 import { Handle, Position, useNodes } from '@xyflow/react';
 import { NodeRect } from './node-rect';
-import { useEffect, useMemo } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
 import { z } from "zod";
 import { zodResolver } from "@hookform/resolvers/zod";
 import { useForm, EventType, DeepPartial } from 'react-hook-form';
 import { Form, FormControl, FormField, FormItem, FormMessage } from './ui/form';
 import { Input } from './ui/input';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
+import { Checkbox } from "./ui/checkbox";
+import { Label } from "./ui/label";
+import { Button } from "./ui/button";
+import { XIcon } from "lucide-react";
 
 const schema = z.object({
   network: z.string().min(1, "reqired"),
@@ -20,6 +24,18 @@
   portId: z.string(),
 });
 
+const authEnabledSchema = z.object({
+  enabled: z.boolean(),
+});
+
+const authGroupSchema = z.object({
+  group: z.string(),
+});
+
+const authNoAuthPatternSchema = z.object({
+  noAuthPathPattern: z.string(),
+});
+
 export function NodeGatewayHttps(node: GatewayHttpsNode) {
   const { id, selected } = node;
   const isConnectableNetwork = useMemo(() => nodeIsConnectable(node, "subdomain"), [node]);
@@ -29,19 +45,19 @@
       {nodeLabel(node)}
       <Handle
         type={"source"}
-        id="subdomain" 
+        id="subdomain"
         position={Position.Top}
         isConnectable={isConnectableNetwork}
-        isConnectableStart={isConnectableNetwork} 
-        isConnectableEnd={isConnectableNetwork} 
+        isConnectableStart={isConnectableNetwork}
+        isConnectableEnd={isConnectableNetwork}
       />
       <Handle
         type={"target"}
-        id="https" 
+        id="https"
         position={Position.Bottom}
         isConnectable={isConnectable}
-        isConnectableStart={isConnectable} 
-        isConnectableEnd={isConnectable} 
+        isConnectableStart={isConnectable}
+        isConnectableEnd={isConnectable}
       />
     </NodeRect>
   );
@@ -64,7 +80,6 @@
         let edges = store.edges;
         if (data.network !== undefined) {
           edges = edges.filter((e) => {
-            console.log(e);
             if (e.source === id && e.sourceHandle === "subdomain" && e.target === data.network && e.targetHandle === "subdomain") {
               return false;
             } else {
@@ -127,7 +142,6 @@
   }, [nodes, selected]);
   useEffect(() => {
     const sub = connectedToForm.watch((value: DeepPartial<z.infer<typeof connectedToSchema>>, { name, type }: { name?: keyof z.infer<typeof connectedToSchema> | undefined, type?: EventType | undefined }) => {
-      console.log({ name, type });
       if (type !== "change") {
         return;
       }
@@ -157,11 +171,86 @@
     });
     return () => sub.unsubscribe();
   }, [connectedToForm, store, selectable]);
+  const authEnabledForm = useForm<z.infer<typeof authEnabledSchema>>({
+    resolver: zodResolver(authEnabledSchema),
+    mode: "onChange",
+    defaultValues: {
+      enabled: data.auth ? data.auth.enabled : false,
+    },
+  });
+  const authGroupForm = useForm<z.infer<typeof authGroupSchema>>({
+    resolver: zodResolver(authGroupSchema),
+    mode: "onSubmit",
+    defaultValues: {
+      group: "",
+    },
+  }); const authNoAuthPatternFrom = useForm<z.infer<typeof authNoAuthPatternSchema>>({
+    resolver: zodResolver(authNoAuthPatternSchema),
+    mode: "onChange",
+    defaultValues: {
+      noAuthPathPattern: "",
+    },
+  });
+  useEffect(() => {
+    const sub = authEnabledForm.watch((value, { name }) => {
+      if (name === "enabled") {
+        store.updateNodeData<"gateway-https">(id, {
+          auth: {
+            ...data.auth,
+            enabled: value.enabled,
+          }
+        })
+      }
+    });
+    return () => sub.unsubscribe();
+  }, [id, data, authEnabledForm, store]);
+  const removeGroup = useCallback((group: string) => {
+    const groups = data?.auth?.groups || [];
+    store.updateNodeData<"gateway-https">(id, {
+      auth: {
+        ...data.auth,
+        groups: groups.filter((g) => g !== group),
+      },
+    });
+    return true;
+  }, [id, data, store]);
+  const onGroupSubmit = useCallback((values: z.infer<typeof authGroupSchema>) => {
+    const groups = data.auth?.groups || [];
+    groups.push(values.group)
+    store.updateNodeData<"gateway-https">(id, {
+      auth: {
+        ...data.auth,
+        groups,
+      },
+    });
+    authGroupForm.reset();
+  }, [id, data, store]);
+  const removeNoAuthPathPattern = useCallback((path: string) => {
+    const noAuthPathPatterns = data?.auth?.noAuthPathPatterns || [];
+    store.updateNodeData<"gateway-https">(id, {
+      auth: {
+        ...data.auth,
+        noAuthPathPatterns: noAuthPathPatterns.filter((p) => p !== path),
+      },
+    });
+    return true;
+  }, [id, data, store]);
+  const onNoAuthPathPatternSubmit = useCallback((values: z.infer<typeof authNoAuthPatternSchema>) => {
+    const noAuthPathPatterns = data.auth?.noAuthPathPatterns || [];
+    noAuthPathPatterns.push(values.noAuthPathPattern)
+    store.updateNodeData<"gateway-https">(id, {
+      auth: {
+        ...data.auth,
+        noAuthPathPatterns,
+      },
+    });
+    authNoAuthPatternFrom.reset();
+  }, [id, data, store]);
   return (
     <>
       <Form {...form}>
         <form className="space-y-2">
-          <FormField 
+          <FormField
             control={form.control}
             name="network"
             render={({ field }) => (
@@ -182,7 +271,7 @@
               </FormItem>
             )}
           />
-          <FormField 
+          <FormField
             control={form.control}
             name="subdomain"
             render={({ field }) => (
@@ -198,7 +287,7 @@
       </Form>
       <Form {...connectedToForm}>
         <form className="space-y-2">
-        <FormField
+          <FormField
             control={connectedToForm.control}
             name="id"
             render={({ field }) => (
@@ -219,7 +308,7 @@
               </FormItem>
             )}
           />
-          <FormField 
+          <FormField
             control={connectedToForm.control}
             name="portId"
             render={({ field }) => (
@@ -237,11 +326,73 @@
                   </SelectContent>
                 </Select>
                 <FormMessage />
-              </FormItem>           
+              </FormItem>
             )}
           />
         </form>
       </Form>
+      Auth
+      <Form {...authEnabledForm}>
+        <form className="space-y-2">
+          <FormField
+            control={authEnabledForm.control}
+            name="enabled"
+            render={({ field }) => (
+              <FormItem>
+                <Checkbox id="authEnabled" onCheckedChange={field.onChange} checked={field.value} />
+                <Label htmlFor="authEnabled">Enabled</Label>
+                <FormMessage />
+              </FormItem>
+            )}
+          />
+        </form>
+      </Form>
+      {data && data.auth && data.auth.enabled ? (
+        <>
+          Authorized Groups
+          <ul>
+            {(data.auth.groups || []).map((p) => (<li key={p}><Button size={"icon"} variant={"ghost"} onClick={() => removeGroup(p)}><XIcon /></Button> {p}</li>))}
+          </ul>
+          <Form {...authGroupForm}>
+            <form className="flex flex-row space-x-1" onSubmit={authGroupForm.handleSubmit(onGroupSubmit)}>
+              <FormField
+                control={authGroupForm.control}
+                name="group"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormControl>
+                      <Input placeholder="group" className="border border-black" {...field} />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+              <Button type="submit">Add Group</Button>
+            </form>
+          </Form>
+          Auth optional path patterns
+          <ul>
+            {(data.auth.noAuthPathPatterns || []).map((p) => (<li key={p}><Button size={"icon"} variant={"ghost"} onClick={() => removeNoAuthPathPattern(p)}><XIcon /></Button> {p}</li>))}
+          </ul>
+          <Form {...authNoAuthPatternFrom}>
+            <form className="flex flex-row space-x-1" onSubmit={authNoAuthPatternFrom.handleSubmit(onNoAuthPathPatternSubmit)}>
+              <FormField
+                control={authNoAuthPatternFrom.control}
+                name="noAuthPathPattern"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormControl>
+                      <Input placeholder="group" className="border border-black" {...field} />
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+              <Button type="submit">Add</Button>
+            </form>
+          </Form>
+        </>
+      ) : (<></>)}
     </>
   );
 }
\ No newline at end of file