blob: 0f39e4e2f2134688e51faa3658a7a1d2cd92ea3a [file] [log] [blame]
gio1dc800a2025-04-24 17:15:43 +00001import { AppNode, nodeLabel, useEnv, useMessages, useProjectId, useStateStore } from "@/lib/state";
gio5f2f1002025-03-20 18:38:48 +04002import { Button } from "./ui/button";
3import { useCallback, useEffect, useState } from "react";
4import { generateDodoConfig } from "@/lib/config";
5import { useNodes, useReactFlow } from "@xyflow/react";
6import { useToast } from "@/hooks/use-toast";
7
gioda708652025-04-30 14:57:38 +04008function toNodeType(t: string): string {
9 if (t === "ingress") {
10 return "gateway-https";
gioa6024622025-05-01 18:37:16 +040011 } else if (t === "service") {
12 return "app";
gioda708652025-04-30 14:57:38 +040013 } else {
14 return t;
15 }
16}
17
gio5f2f1002025-03-20 18:38:48 +040018export function Actions() {
19 const { toast } = useToast();
20 const store = useStateStore();
21 const projectId = useProjectId();
22 const nodes = useNodes<AppNode>();
23 const env = useEnv();
24 const messages = useMessages();
25 const instance = useReactFlow();
26 const [ok, setOk] = useState(false);
27 const [loading, setLoading] = useState(false);
28 useEffect(() => {
29 setOk(!messages.some((m) => m.type === "FATAL"));
30 }, [messages, setOk]);
gio1dc800a2025-04-24 17:15:43 +000031 const monitor = useCallback(async () => {
giof8acc612025-04-26 08:20:55 +040032 const m = async function () {
gio1dc800a2025-04-24 17:15:43 +000033 const resp = await fetch(`/api/project/${projectId}/status`, {
34 method: "GET",
35 headers: {
36 "Content-Type": "application/json",
37 },
38 })
39 if (resp.status !== 200) {
40 return;
41 }
42 const data: { type: string, name: string, status: string }[] = await resp.json();
43 console.log(data);
44 for (const n of nodes) {
giof8acc612025-04-26 08:20:55 +040045 if (n.type === "network") {
46 continue;
47 }
gioda708652025-04-30 14:57:38 +040048 const d = data.find((d) => n.type === toNodeType(d.type) && nodeLabel(n) === d.name);
giof8acc612025-04-26 08:20:55 +040049 if (d !== undefined) {
50 store.updateNodeData(n.id, {
51 state: d?.status,
52 });
gio1dc800a2025-04-24 17:15:43 +000053 }
54 }
giof8acc612025-04-26 08:20:55 +040055 if (data.find((d) => d.status !== "success" && d.status != "failure") !== undefined) {
56 setTimeout(m, 1000);
57 }
gio1dc800a2025-04-24 17:15:43 +000058 };
59 setTimeout(m, 100);
60 }, [projectId, nodes]);
gio5f2f1002025-03-20 18:38:48 +040061 const deploy = useCallback(async () => {
62 if (projectId == null) {
63 return;
64 }
65 setLoading(true);
66 try {
67 const config = generateDodoConfig(nodes, env);
68 if (config == null) {
69 throw new Error("MUST NOT REACH!");
70 }
71 const resp = await fetch(`/api/project/${projectId}/deploy`, {
72 method: "POST",
73 headers: {
74 "Content-Type": "application/json",
75 },
76 body: JSON.stringify({
77 state: JSON.stringify(instance.toObject()),
78 config,
79 }),
80 });
81 if (resp.ok) {
82 toast({
83 title: "Deployment succeeded",
84 });
gio1dc800a2025-04-24 17:15:43 +000085 monitor();
gio5f2f1002025-03-20 18:38:48 +040086 } else {
87 toast({
88 variant: "destructive",
89 title: "Deployment failed",
90 description: await resp.text(),
91 });
giof8acc612025-04-26 08:20:55 +040092 }
gio5f2f1002025-03-20 18:38:48 +040093 } catch (e) {
94 console.log(e);
95 toast({
96 variant: "destructive",
97 title: "Deployment failed",
98 });
99 } finally {
100 setLoading(false);
101 }
102 }, [projectId, instance, nodes, env, setLoading]);
103 const [st, setSt] = useState<string>();
104 const save = useCallback(async () => {
105 if (projectId == null) {
106 return;
107 }
108 const resp = await fetch(`/api/project/${projectId}/saved`, {
109 method: "POST",
110 headers: {
111 "Content-Type": "application/json",
112 },
113 body: JSON.stringify(instance.toObject()),
114 });
115 if (resp.ok) {
116 toast({
117 title: "Save succeeded",
118 });
119 } else {
120 toast({
121 variant: "destructive",
122 title: "Save failed",
123 description: await resp.text(),
124 });
giof8acc612025-04-26 08:20:55 +0400125 }
gio5f2f1002025-03-20 18:38:48 +0400126 }, [projectId, instance, setSt]);
127 const restoreSaved = useCallback(async () => {
128 if (projectId == null) {
129 return;
130 }
131 const resp = await fetch(`/api/project/${projectId}/saved`, {
132 method: "GET",
133 });
134 const inst = await resp.json();
135 const { x = 0, y = 0, zoom = 1 } = inst.viewport;
136 store.setNodes(inst.nodes || []);
137 store.setEdges(inst.edges || []);
138 instance.setViewport({ x, y, zoom });
139 }, [projectId, instance, st]);
gio1dc800a2025-04-24 17:15:43 +0000140 const clear = useCallback(() => {
141 store.setEdges([]);
142 store.setNodes([]);
giob68003c2025-04-25 03:05:21 +0000143 instance.setViewport({ x: 0, y: 0, zoom: 1 });
gio1dc800a2025-04-24 17:15:43 +0000144 }, [store]);
giob68003c2025-04-25 03:05:21 +0000145 // TODO(gio): Update store
146 const deleteProject = useCallback(async () => {
147 if (projectId == null) {
148 return;
149 }
150 const resp = await fetch(`/api/project/${projectId}`, {
151 method: "DELETE",
152 });
153 if (resp.ok) {
154 clear();
155 store.setProject(undefined);
156 toast({
157 title: "Save succeeded",
158 });
159 } else {
160 toast({
161 variant: "destructive",
162 title: "Save failed",
163 description: await resp.text(),
164 });
giof8acc612025-04-26 08:20:55 +0400165 }
giob68003c2025-04-25 03:05:21 +0000166 }, [store, clear]);
gio5f2f1002025-03-20 18:38:48 +0400167 const [props, setProps] = useState({});
168 useEffect(() => {
169 if (loading) {
170 setProps({ loading: true });
171 } else if (ok) {
172 setProps({ disabled: false });
173 } else {
174 setProps({ disabled: true });
175 }
176 }, [ok, loading, setProps]);
177 return (
178 <>
179 <Button onClick={deploy} {...props}>Deploy</Button>
180 <Button onClick={save}>Save</Button>
181 <Button onClick={restoreSaved}>Restore</Button>
giob68003c2025-04-25 03:05:21 +0000182 <Button onClick={clear} variant="destructive">Clear</Button>
183 <Button onClick={deleteProject} variant="destructive" disabled={projectId === undefined}>Delete</Button>
gio5f2f1002025-03-20 18:38:48 +0400184 </>
185 )
giof8acc612025-04-26 08:20:55 +0400186}