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