blob: fdf710d29c18797c330d09e0340638170e097b91 [file] [log] [blame]
gio5f2f1002025-03-20 18:38:48 +04001import { useStateStore, AppNode, GatewayHttpsNode, ServiceNode, nodeLabel, useEnv, nodeIsConnectable } from '@/lib/state';
2import { Handle, Position, useNodes } from '@xyflow/react';
3import { NodeRect } from './node-rect';
4import { useEffect, useMemo } from 'react';
5import { z } from "zod";
6import { zodResolver } from "@hookform/resolvers/zod";
7import { useForm, EventType, DeepPartial } from 'react-hook-form';
8import { Form, FormControl, FormField, FormItem, FormMessage } from './ui/form';
9import { Input } from './ui/input';
10import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
11
12const schema = z.object({
13 network: z.string().min(1, "reqired"),
14 subdomain: z.string().min(1, "required"),
15});
16
17const connectedToSchema = z.object({
18 id: z.string(),
19 portId: z.string(),
20});
21
22export function NodeGatewayHttps(node: GatewayHttpsNode) {
23 const { id, selected } = node;
24 const isConnectable = useMemo(() => nodeIsConnectable(node, "https"), [node]);
25 return (
gio1dc800a2025-04-24 17:15:43 +000026 <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
gio5f2f1002025-03-20 18:38:48 +040027 {nodeLabel(node)}
28 <Handle
29 type={"target"}
30 id="https"
31 position={Position.Bottom}
32 isConnectable={isConnectable}
33 isConnectableStart={isConnectable}
34 isConnectableEnd={isConnectable}
35 />
36 </NodeRect>
37 );
38}
39
40export function NodeGatewayHttpsDetails({ id, data }: GatewayHttpsNode) {
41 const store = useStateStore();
42 const env = useEnv();
43 const form = useForm<z.infer<typeof schema>>({
44 resolver: zodResolver(schema),
45 mode: "onChange",
46 defaultValues: {
giof96ffb82025-04-24 09:31:05 +000047 network: data.network,
48 subdomain: data.subdomain,
gio5f2f1002025-03-20 18:38:48 +040049 },
50 });
51 useEffect(() => {
52 const sub = form.watch((value: DeepPartial<z.infer<typeof schema>>, { name }: { name?: keyof z.infer<typeof schema> | undefined, type?: EventType | undefined }) => {
53 if (name === "network") {
54 if (value.network !== undefined) {
55 store.updateNodeData<"gateway-https">(id, { network: value.network });
56 } else {
57
58 }
59 } else if (name === "subdomain") {
60 store.updateNodeData<"gateway-https">(id, { subdomain: value.subdomain });
61 }
62 });
63 return () => sub.unsubscribe();
64 }, [form, store]);
65 const connectedToForm = useForm<z.infer<typeof connectedToSchema>>({
66 resolver: zodResolver(connectedToSchema),
67 mode: "onChange",
68 defaultValues: {
69 id: data.https?.serviceId,
70 portId: data.https?.portId,
71 },
72 });
73 useEffect(() => {
74 connectedToForm.reset({
75 id: data.https?.serviceId,
76 portId: data.https?.portId,
77 });
78 }, [connectedToForm, data]);
79 const nodes = useNodes<AppNode>();
80 const selected = useMemo(() => {
81 if (data !== undefined && data.https !== undefined) {
82 const https = data.https;
83 return nodes.find((n) => n.id === https.serviceId)! as ServiceNode;
84 }
85 return null;
86 }, [data]);
87 const selectable = useMemo(() => {
88 return nodes.filter((n) => {
89 if (n.id === id) {
90 return false;
91 }
92 if (selected !== null && selected.id === id) {
93 return true;
94 }
95 if (n.type !== "app") {
96 return false;
97 }
98 return n.data && n.data.ports && n.data.ports.length > 0;
99 })
100 }, [nodes, selected]);
101 useEffect(() => {
102 const sub = connectedToForm.watch((value: DeepPartial<z.infer<typeof connectedToSchema>>, { name, type }: { name?: keyof z.infer<typeof connectedToSchema> | undefined, type?: EventType | undefined }) => {
103 console.log({ name, type });
104 if (type !== "change") {
105 return;
106 }
107 switch (name) {
108 case "id":
109 if (!value.id) {
110 break;
111 }
112 const current = store.edges.filter((e) => e.target === id);
113 const cid = current[0] ? current[0].id : undefined;
114 store.replaceEdge({
115 source: value.id,
116 sourceHandle: "ports",
117 target: id,
118 targetHandle: "https",
119 }, cid);
120 break;
121 case "portId":
122 store.updateNodeData<"gateway-https">(id, {
123 https: {
124 serviceId: value.id,
125 portId: value.portId,
126 }
127 });
128 break;
129 }
130 });
131 return () => sub.unsubscribe();
132 }, [connectedToForm, store, selectable]);
133 return (
134 <>
135 <Form {...form}>
136 <form className="space-y-2">
137 <FormField
138 control={form.control}
139 name="network"
140 render={({ field }) => (
141 <FormItem>
142 <Select onValueChange={field.onChange} defaultValue={field.value}>
143 <FormControl>
144 <SelectTrigger>
145 <SelectValue placeholder="Network" />
146 </SelectTrigger>
147 </FormControl>
148 <SelectContent>
149 {env.networks.map((n) => (
150 <SelectItem key={n.name} value={n.domain}>{`${n.name} - ${n.domain}`}</SelectItem>
151 ))}
152 </SelectContent>
153 </Select>
154 <FormMessage />
155 </FormItem>
156 )}
157 />
158 <FormField
159 control={form.control}
160 name="subdomain"
161 render={({ field }) => (
162 <FormItem>
163 <FormControl>
164 <Input placeholder="subdomain" className="border border-black" {...field} />
165 </FormControl>
166 <FormMessage />
167 </FormItem>
168 )}
169 />
170 </form>
171 </Form>
172 <Form {...connectedToForm}>
173 <form className="space-y-2">
174 <FormField
175 control={connectedToForm.control}
176 name="id"
177 render={({ field }) => (
178 <FormItem>
179 <Select onValueChange={field.onChange} defaultValue={field.value}>
180 <FormControl>
181 <SelectTrigger>
182 <SelectValue placeholder="Service" />
183 </SelectTrigger>
184 </FormControl>
185 <SelectContent>
186 {selectable.map((n) => (
187 <SelectItem key={n.id} value={n.id}>{nodeLabel(n)}</SelectItem>
188 ))}
189 </SelectContent>
190 </Select>
191 <FormMessage />
192 </FormItem>
193 )}
194 />
195 <FormField
196 control={connectedToForm.control}
197 name="portId"
198 render={({ field }) => (
199 <FormItem>
200 <Select onValueChange={field.onChange} defaultValue={field.value}>
201 <FormControl>
202 <SelectTrigger>
203 <SelectValue placeholder="Port" />
204 </SelectTrigger>
205 </FormControl>
206 <SelectContent>
207 {selected && selected.data.ports.map((p) => (
208 <SelectItem key={p.id} value={p.id}>{p.name} - {p.value}</SelectItem>
209 ))}
210 </SelectContent>
211 </Select>
212 <FormMessage />
213 </FormItem>
214 )}
215 />
216 </form>
217 </Form>
218 </>
219 );
220}