Canvas: Wait during deploy and reload
Change-Id: I8f39a46e263c99dd342b640474de421043ff0d38
diff --git a/apps/canvas/front/src/components/actions.tsx b/apps/canvas/front/src/components/actions.tsx
index ec0d28a..c7481c1 100644
--- a/apps/canvas/front/src/components/actions.tsx
+++ b/apps/canvas/front/src/components/actions.tsx
@@ -11,7 +11,7 @@
DropdownMenuContent,
DropdownMenuTrigger,
} from "./ui/dropdown-menu";
-import { Menu } from "lucide-react";
+import { LoaderCircle, Menu } from "lucide-react";
function toNodeType(t: string): string {
if (t === "ingress") {
@@ -34,6 +34,27 @@
const [ok, setOk] = useState(false);
const [loading, setLoading] = useState(false);
const [reloading, setReloading] = useState(false);
+ const info = useCallback(
+ (title: string, description?: string, duration?: number) => {
+ return toast({
+ title,
+ description,
+ duration: duration ?? 2000,
+ });
+ },
+ [toast],
+ );
+ const error = useCallback(
+ (title: string, description?: string, duration?: number) => {
+ return toast({
+ variant: "destructive",
+ title,
+ description,
+ duration: duration ?? 5000,
+ });
+ },
+ [toast],
+ );
useEffect(() => {
setOk(!messages.some((m) => m.type === "FATAL"));
}, [messages, setOk]);
@@ -72,7 +93,6 @@
return;
}
setLoading(true);
- store.setMode("deploy");
try {
const config = generateDodoConfig(projectId, nodes, env);
if (config == null) {
@@ -89,28 +109,18 @@
}),
});
if (resp.ok) {
- toast({
- title: "Deployment succeeded",
- });
+ store.setMode("deploy");
+ info("Deployment succeeded");
monitor();
} else {
- toast({
- variant: "destructive",
- title: "Deployment failed",
- description: await resp.text(),
- });
+ error("Deployment failed", await resp.text());
}
} catch (e) {
- store.setMode("edit");
- console.log(e);
- toast({
- variant: "destructive",
- title: "Deployment failed",
- });
+ error("Deployment failed", e instanceof Error ? e.message : undefined);
} finally {
setLoading(false);
}
- }, [projectId, instance, nodes, env, setLoading, toast, monitor, store]);
+ }, [projectId, instance, nodes, env, setLoading, info, error, monitor, store]);
const save = useCallback(async () => {
if (projectId == null) {
return;
@@ -123,17 +133,11 @@
body: JSON.stringify(instance.toObject()),
});
if (resp.ok) {
- toast({
- title: "Save succeeded",
- });
+ info("Save succeeded");
} else {
- toast({
- variant: "destructive",
- title: "Save failed",
- description: await resp.text(),
- });
+ error("Save failed", await resp.text());
}
- }, [projectId, instance, toast]);
+ }, [projectId, instance, info, error]);
const restoreSaved = useCallback(async () => {
if (projectId == null) {
return;
@@ -166,22 +170,17 @@
if (resp.ok) {
clear();
store.setProject(undefined);
- toast({
- title: "Project deleted",
- });
+ info("Project deleted");
} else {
- toast({
- variant: "destructive",
- title: "Failed to delete project",
- description: await resp.text(),
- });
+ error("Failed to delete project", await resp.text());
}
- }, [store, clear, projectId, toast]);
+ }, [store, clear, projectId, info, error]);
const reload = useCallback(async () => {
if (projectId == null) {
return;
}
setReloading(true);
+ const { dismiss } = info("Reloading services", "This may take a while...", Infinity);
try {
const resp = await fetch(`/api/project/${projectId}/reload`, {
method: "POST",
@@ -190,26 +189,19 @@
},
});
if (resp.ok) {
- toast({
- title: "Reload triggered successfully",
- });
+ dismiss();
+ info("Reloaded services successfully");
} else {
- toast({
- variant: "destructive",
- title: "Reload failed",
- description: await resp.text(),
- });
+ dismiss();
+ error("Reload failed", await resp.text());
}
} catch (e) {
- console.log(e);
- toast({
- variant: "destructive",
- title: "Reload failed",
- });
+ dismiss();
+ error("Reload failed", e instanceof Error ? e.message : undefined);
} finally {
setReloading(false);
}
- }, [projectId, toast]);
+ }, [projectId, info, error]);
const removeDeployment = useCallback(async () => {
if (projectId == null) {
return;
@@ -226,33 +218,29 @@
},
});
if (resp.ok) {
- toast({
- title: "Deployment removed successfully",
- });
+ info("Deployment removed successfully");
store.setMode("edit");
} else {
const errorData = await resp.json();
- toast({
- variant: "destructive",
- title: "Failed to remove deployment",
- description: errorData.error || "Unknown error",
- });
+ error("Failed to remove deployment", errorData.error || "Unknown error");
}
} catch (e) {
- console.log(e);
- toast({
- variant: "destructive",
- title: "Failed to remove deployment",
- });
+ error("Failed to remove deployment", e instanceof Error ? e.message : undefined);
} finally {
setReloading(false);
}
- }, [projectId, toast, store]);
- const [deployProps, setDeployProps] = useState({});
- const [reloadProps, setReloadProps] = useState({});
+ }, [projectId, info, error, store]);
+ const [deployProps, setDeployProps] = useState<{ loading?: boolean; disabled?: boolean }>({
+ loading: false,
+ disabled: false,
+ });
+ const [reloadProps, setReloadProps] = useState<{ loading?: boolean; disabled?: boolean }>({
+ loading: false,
+ disabled: false,
+ });
useEffect(() => {
if (loading) {
- setDeployProps({ loading: true });
+ setDeployProps({ loading: true, disabled: true });
} else if (ok) {
setDeployProps({ disabled: false });
} else {
@@ -260,7 +248,7 @@
}
if (reloading) {
- setReloadProps({ loading: true });
+ setReloadProps({ loading: true, disabled: true });
} else {
setReloadProps({ disabled: projectId === undefined });
}
@@ -282,21 +270,28 @@
className="cursor-pointer hover:bg-gray-200"
{...reloadProps}
>
- Reload Services
+ {reloadProps.loading ? (
+ <>
+ <LoaderCircle className="animate-spin" />
+ Reloading...
+ </>
+ ) : (
+ "Reload services"
+ )}
</DropdownMenuItem>
<DropdownMenuItem
onClick={removeDeployment}
disabled={projectId === undefined}
className="cursor-pointer hover:bg-gray-200"
>
- Remove Deployment
+ Remove deployment
</DropdownMenuItem>
<DropdownMenuItem
onClick={deleteProject}
disabled={projectId === undefined}
className="cursor-pointer hover:bg-gray-200"
>
- Delete Project
+ Delete project
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
@@ -307,7 +302,14 @@
return (
<div className="flex flex-row gap-1 items-center">
<Button onClick={deploy} {...deployProps}>
- Deploy
+ {deployProps.loading ? (
+ <>
+ <LoaderCircle className="animate-spin" />
+ Deploying...
+ </>
+ ) : (
+ "Deploy"
+ )}
</Button>
<Button onClick={save}>Save</Button>
<DropdownMenu>
@@ -331,7 +333,7 @@
disabled={projectId === undefined}
className="cursor-pointer hover:bg-gray-200"
>
- Delete Project
+ Delete project
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>