blob: 66235b36d03c2d8bc572f610e8338be42c8668eb [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
8export function Actions() {
9 const { toast } = useToast();
10 const store = useStateStore();
11 const projectId = useProjectId();
12 const nodes = useNodes<AppNode>();
13 const env = useEnv();
14 const messages = useMessages();
15 const instance = useReactFlow();
16 const [ok, setOk] = useState(false);
17 const [loading, setLoading] = useState(false);
18 useEffect(() => {
19 setOk(!messages.some((m) => m.type === "FATAL"));
20 }, [messages, setOk]);
gio1dc800a2025-04-24 17:15:43 +000021 const monitor = useCallback(async () => {
22 const m = async function() {
23 const resp = await fetch(`/api/project/${projectId}/status`, {
24 method: "GET",
25 headers: {
26 "Content-Type": "application/json",
27 },
28 })
29 if (resp.status !== 200) {
30 return;
31 }
32 const data: { type: string, name: string, status: string }[] = await resp.json();
33 console.log(data);
34 for (const n of nodes) {
35 console.log(nodeLabel(n));
36 for (const d of data) {
37 if (nodeLabel(n) === d.name) {
38 store.updateNodeData(n.id, { state: d.status });
39 }
40 }
41 }
42 setTimeout(m, 1000);
43 };
44 setTimeout(m, 100);
45 }, [projectId, nodes]);
gio5f2f1002025-03-20 18:38:48 +040046 const deploy = useCallback(async () => {
47 if (projectId == null) {
48 return;
49 }
50 setLoading(true);
51 try {
52 const config = generateDodoConfig(nodes, env);
53 if (config == null) {
54 throw new Error("MUST NOT REACH!");
55 }
56 const resp = await fetch(`/api/project/${projectId}/deploy`, {
57 method: "POST",
58 headers: {
59 "Content-Type": "application/json",
60 },
61 body: JSON.stringify({
62 state: JSON.stringify(instance.toObject()),
63 config,
64 }),
65 });
66 if (resp.ok) {
67 toast({
68 title: "Deployment succeeded",
69 });
gio1dc800a2025-04-24 17:15:43 +000070 monitor();
gio5f2f1002025-03-20 18:38:48 +040071 } else {
72 toast({
73 variant: "destructive",
74 title: "Deployment failed",
75 description: await resp.text(),
76 });
77 }
78 } catch (e) {
79 console.log(e);
80 toast({
81 variant: "destructive",
82 title: "Deployment failed",
83 });
84 } finally {
85 setLoading(false);
86 }
87 }, [projectId, instance, nodes, env, setLoading]);
88 const [st, setSt] = useState<string>();
89 const save = useCallback(async () => {
90 if (projectId == null) {
91 return;
92 }
93 const resp = await fetch(`/api/project/${projectId}/saved`, {
94 method: "POST",
95 headers: {
96 "Content-Type": "application/json",
97 },
98 body: JSON.stringify(instance.toObject()),
99 });
100 if (resp.ok) {
101 toast({
102 title: "Save succeeded",
103 });
104 } else {
105 toast({
106 variant: "destructive",
107 title: "Save failed",
108 description: await resp.text(),
109 });
110 }
111 }, [projectId, instance, setSt]);
112 const restoreSaved = useCallback(async () => {
113 if (projectId == null) {
114 return;
115 }
116 const resp = await fetch(`/api/project/${projectId}/saved`, {
117 method: "GET",
118 });
119 const inst = await resp.json();
120 const { x = 0, y = 0, zoom = 1 } = inst.viewport;
121 store.setNodes(inst.nodes || []);
122 store.setEdges(inst.edges || []);
123 instance.setViewport({ x, y, zoom });
124 }, [projectId, instance, st]);
gio1dc800a2025-04-24 17:15:43 +0000125 const clear = useCallback(() => {
126 store.setEdges([]);
127 store.setNodes([]);
giob68003c2025-04-25 03:05:21 +0000128 instance.setViewport({ x: 0, y: 0, zoom: 1 });
gio1dc800a2025-04-24 17:15:43 +0000129 }, [store]);
giob68003c2025-04-25 03:05:21 +0000130 // TODO(gio): Update store
131 const deleteProject = useCallback(async () => {
132 if (projectId == null) {
133 return;
134 }
135 const resp = await fetch(`/api/project/${projectId}`, {
136 method: "DELETE",
137 });
138 if (resp.ok) {
139 clear();
140 store.setProject(undefined);
141 toast({
142 title: "Save succeeded",
143 });
144 } else {
145 toast({
146 variant: "destructive",
147 title: "Save failed",
148 description: await resp.text(),
149 });
150 }
151 }, [store, clear]);
gio5f2f1002025-03-20 18:38:48 +0400152 const [props, setProps] = useState({});
153 useEffect(() => {
154 if (loading) {
155 setProps({ loading: true });
156 } else if (ok) {
157 setProps({ disabled: false });
158 } else {
159 setProps({ disabled: true });
160 }
161 }, [ok, loading, setProps]);
162 return (
163 <>
164 <Button onClick={deploy} {...props}>Deploy</Button>
165 <Button onClick={save}>Save</Button>
166 <Button onClick={restoreSaved}>Restore</Button>
giob68003c2025-04-25 03:05:21 +0000167 <Button onClick={clear} variant="destructive">Clear</Button>
168 <Button onClick={deleteProject} variant="destructive" disabled={projectId === undefined}>Delete</Button>
gio5f2f1002025-03-20 18:38:48 +0400169 </>
170 )
171}