blob: 8cfebffebfb2c9e314069976b34b60cdbd489d42 [file] [log] [blame]
gio5f2f1002025-03-20 18:38:48 +04001import { v4 as uuidv4 } from "uuid";
2import { useStateStore, AppNode, nodeLabel, useEnv, GatewayTCPNode, nodeIsConnectable } from '@/lib/state';
3import { Edge, Handle, Position, useNodes } from '@xyflow/react';
4import { NodeRect } from './node-rect';
5import { useCallback, useEffect, useMemo, useState } from 'react';
6import { z } from "zod";
7import { zodResolver } from "@hookform/resolvers/zod";
8import { useForm, EventType, DeepPartial } from 'react-hook-form';
9import { Form, FormControl, FormField, FormItem, FormMessage } from './ui/form';
10import { Input } from './ui/input';
11import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
12import { Button } from "./ui/button";
13
14const schema = z.object({
15 network: z.string().min(1, "reqired"),
16 subdomain: z.string().min(1, "required"),
17});
18
19const connectedToSchema = z.object({
20 serviceId: z.string(),
21 portId: z.string(),
22});
23
24export function NodeGatewayTCP(node: GatewayTCPNode) {
25 const { id, selected } = node;
26 const isConnectable = useMemo(() => nodeIsConnectable(node, "tcp"), [node]);
27 return (
28 <NodeRect id={id} selected={selected} type={node.type}>
29 {nodeLabel(node)}
30 <Handle
31 type={"target"}
32 id="tcp"
33 position={Position.Bottom}
34 isConnectable={isConnectable}
35 isConnectableStart={isConnectable}
36 isConnectableEnd={isConnectable}
37 />
38 </NodeRect>
39 );
40}
41
42export function NodeGatewayTCPDetails({ id, data }: GatewayTCPNode) {
43 const store = useStateStore();
44 const env = useEnv();
45 const form = useForm<z.infer<typeof schema>>({
46 resolver: zodResolver(schema),
47 mode: "onChange",
48 defaultValues: {
49 network: "",
50 subdomain: "",
51 },
52 });
53 useEffect(() => {
54 const sub = form.watch((value: DeepPartial<z.infer<typeof schema>>, { name }: { name?: keyof z.infer<typeof schema> | undefined, type?: EventType | undefined }) => {
55 if (name === "network") {
56 if (value.network !== undefined) {
57 store.updateNodeData<"gateway-tcp">(id, { network: value.network });
58 } else {
59
60 }
61 } else if (name === "subdomain") {
62 store.updateNodeData<"gateway-tcp">(id, { subdomain: value.subdomain });
63 }
64 });
65 return () => sub.unsubscribe();
66 }, [form, store]);
67 const connectedToForm = useForm<z.infer<typeof connectedToSchema>>({
68 resolver: zodResolver(connectedToSchema),
69 mode: "onSubmit",
70 defaultValues: {
71 serviceId: data.selected?.serviceId,
72 portId: data.selected?.portId,
73 },
74 });
75 useEffect(() => {
76 connectedToForm.reset({
77 serviceId: data.selected?.serviceId,
78 portId: data.selected?.portId,
79 });
80 console.log(connectedToForm.getValues());
81 }, [connectedToForm, data]);
82 const nodes = useNodes<AppNode>();
83 const [selected, setSelected] = useState<AppNode | undefined>(undefined);
84 useEffect(() => {
85 if (data.selected?.serviceId == null) {
86 setSelected(undefined);
87 } else {
88 const serviceId = data.selected.serviceId;
89 setSelected(nodes.find((n) => n.id === serviceId));
90 }
91 }, [data, setSelected]);
92 const selectable = useMemo(() => {
93 console.log(selected);
94 return nodes.filter((n) => {
95 if (n.id === id) {
96 return false;
97 }
98 if (selected != null && selected.id === id) {
99 return true;
100 }
101 if ("ports" in n.data && (n.data.ports || []).length > 0) {
102 return true;
103 }
104 return false;
105 })
106 }, [nodes, selected]);
107 useEffect(() => {
108 const sub = connectedToForm.watch((value: DeepPartial<z.infer<typeof connectedToSchema>>, { name, type }: { name?: keyof z.infer<typeof connectedToSchema> | undefined, type?: EventType | undefined }) => {
109 if (type !== "change") {
110 return;
111 }
112 switch (name) {
113 case "serviceId":
114 if (!value.serviceId) {
115 break;
116 }
117 store.updateNodeData<"gateway-tcp">(id, {
118 selected: {
119 serviceId: value.serviceId,
120 },
121 });
122 break;
123 case "portId":
124 if (!value.portId) {
125 break;
126 }
127 store.updateNodeData<"gateway-tcp">(id, {
128 selected: {
129 serviceId: value.serviceId,
130 portId: value.portId,
131 },
132 });
133 break;
134 }
135 });
136 return () => sub.unsubscribe();
137 }, [connectedToForm, store]);
138 const [nodeLabels, setNodeLabels] = useState(new Map<string, string>());
139 const [portLabels, setPortLabels] = useState(new Map<string, string>());
140 useEffect(() => {
141 setNodeLabels(new Map((data.exposed || []).map((e) => [e.serviceId, nodeLabel(nodes.find((n) => n.id === e.serviceId)!)])));
142 setPortLabels(new Map((data.exposed || []).map((e) => [`${e.serviceId} - ${e.portId}`, (nodes.find((n) => n.id === e.serviceId)!.data.ports || []).find((p) => p.id === e.portId)!.name])));
143 }, [nodes, data, setNodeLabels, setPortLabels]);
144 const onSubmit = useCallback((values: z.infer<typeof connectedToSchema>) => {
145 store.setEdges(store.edges.filter((e) => e.target !== id));
146 const exp = (data.exposed || []).concat({
147 serviceId: values.serviceId,
148 portId: values.portId,
149 });
150 store.updateNodeData<"gateway-tcp">(id, {
151 exposed: exp,
152 selected: undefined,
153 });
154 store.setEdges(store.edges.concat(exp.map((e): Edge => ({
155 id: uuidv4(),
156 source: e.serviceId,
157 sourceHandle: "ports",
158 target: id,
159 targetHandle: "tcp",
160 }))));
161 }, [id, data, connectedToForm, store, setNodeLabels, setPortLabels]);
162 return (
163 <>
164 <Form {...form}>
165 <form className="space-y-2">
166 <FormField
167 control={form.control}
168 name="network"
169 render={({ field }) => (
170 <FormItem>
171 <Select onValueChange={field.onChange} defaultValue={field.value}>
172 <FormControl>
173 <SelectTrigger>
174 <SelectValue placeholder="Network" />
175 </SelectTrigger>
176 </FormControl>
177 <SelectContent>
178 {env.networks.map((n) => (
179 <SelectItem key={n.name} value={n.domain}>{`${n.name} - ${n.domain}`}</SelectItem>
180 ))}
181 </SelectContent>
182 </Select>
183 <FormMessage />
184 </FormItem>
185 )}
186 />
187 <FormField
188 control={form.control}
189 name="subdomain"
190 render={({ field }) => (
191 <FormItem>
192 <FormControl>
193 <Input placeholder="subdomain" className="border border-black" {...field} />
194 </FormControl>
195 <FormMessage />
196 </FormItem>
197 )}
198 />
199 </form>
200 </Form>
201 Exposed Services
202 <ul>
203 {(data.exposed || []).map((e, i) => (
204 <li key={i}>
205 {nodeLabels.get(e.serviceId)} - {portLabels.get(`${e.serviceId} - ${e.portId}`)}
206 </li>
207 ))}
208 </ul>
209 <Form {...connectedToForm}>
210 <form className="space-y-2" onSubmit={connectedToForm.handleSubmit(onSubmit)}>
211 <FormField
212 control={connectedToForm.control}
213 name="serviceId"
214 render={({ field }) => (
215 <FormItem>
216 <Select onValueChange={field.onChange} defaultValue={field.value}>
217 <FormControl>
218 <SelectTrigger>
219 <SelectValue placeholder="Service" />
220 </SelectTrigger>
221 </FormControl>
222 <SelectContent>
223 {selectable.map((n) => (
224 <SelectItem value={n.id}>{nodeLabel(n)}</SelectItem>
225 ))}
226 </SelectContent>
227 </Select>
228 <FormMessage />
229 </FormItem>
230 )}
231 />
232 <FormField
233 control={connectedToForm.control}
234 name="portId"
235 render={({ field }) => (
236 <FormItem>
237 <Select onValueChange={field.onChange} defaultValue={field.value}>
238 <FormControl>
239 <SelectTrigger>
240 <SelectValue placeholder="Port" />
241 </SelectTrigger>
242 </FormControl>
243 <SelectContent>
244 {selected && (selected.data.ports || []).map((p) => (
245 <SelectItem key={p.id} value={p.id}>{p.name} - {p.value}</SelectItem>
246 ))}
247 </SelectContent>
248 </Select>
249 <FormMessage />
250 </FormItem>
251 )}
252 />
253 <Button type="submit">Expose</Button>
254 </form>
255 </Form>
256 </>
257 );
258}