Canvas: Implement worker to manager communication
Register workers on manager side.
Let user force reload service workers.
Change-Id: I2635a04167e7c853151d8a1f5c3511646181a063
diff --git a/apps/canvas/front/src/Config.tsx b/apps/canvas/front/src/Config.tsx
index d2f1aed..7306456 100644
--- a/apps/canvas/front/src/Config.tsx
+++ b/apps/canvas/front/src/Config.tsx
@@ -1,10 +1,11 @@
import { useNodes } from "@xyflow/react";
-import { AppNode, useEnv } from "./lib/state";
+import { AppNode, useEnv, useProjectId } from "./lib/state";
import { generateDodoConfig } from "./lib/config";
import { useEffect, useMemo, useState } from "react";
export function Config() {
const env = useEnv();
+ const projectId = useProjectId();
const [nodes, setNodes] = useState<AppNode[]>([]);
const n = useNodes<AppNode>();
useEffect(() => {
@@ -13,7 +14,7 @@
setNodes(n);
}
}, [n, setNodes]);
- const config = useMemo(() => generateDodoConfig(nodes, env), [nodes, env]);
+ const config = useMemo(() => generateDodoConfig(projectId, nodes, env), [nodes, env]);
const configS = useMemo(() => JSON.stringify(config, undefined, 4), [config]);
return (
<div className="px-5">
diff --git a/apps/canvas/front/src/components/actions.tsx b/apps/canvas/front/src/components/actions.tsx
index eb89b8a..509b51c 100644
--- a/apps/canvas/front/src/components/actions.tsx
+++ b/apps/canvas/front/src/components/actions.tsx
@@ -25,6 +25,7 @@
const instance = useReactFlow();
const [ok, setOk] = useState(false);
const [loading, setLoading] = useState(false);
+ const [reloading, setReloading] = useState(false);
useEffect(() => {
setOk(!messages.some((m) => m.type === "FATAL"));
}, [messages, setOk]);
@@ -64,7 +65,7 @@
}
setLoading(true);
try {
- const config = generateDodoConfig(nodes, env);
+ const config = generateDodoConfig(projectId, nodes, env);
if (config == null) {
throw new Error("MUST NOT REACH!");
}
@@ -163,21 +164,64 @@
});
}
}, [store, clear, projectId, toast]);
- const [props, setProps] = useState({});
+ const reload = useCallback(async () => {
+ if (projectId == null) {
+ return;
+ }
+ setReloading(true);
+ try {
+ const resp = await fetch(`/api/project/${projectId}/reload`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ if (resp.ok) {
+ toast({
+ title: "Reload triggered successfully",
+ });
+ } else {
+ toast({
+ variant: "destructive",
+ title: "Reload failed",
+ description: await resp.text(),
+ });
+ }
+ } catch (e) {
+ console.log(e);
+ toast({
+ variant: "destructive",
+ title: "Reload failed",
+ });
+ } finally {
+ setReloading(false);
+ }
+ }, [projectId, toast]);
+ const [deployProps, setDeployProps] = useState({});
+ const [reloadProps, setReloadProps] = useState({});
useEffect(() => {
if (loading) {
- setProps({ loading: true });
+ setDeployProps({ loading: true });
} else if (ok) {
- setProps({ disabled: false });
+ setDeployProps({ disabled: false });
} else {
- setProps({ disabled: true });
+ setDeployProps({ disabled: true });
}
- }, [ok, loading, setProps]);
+
+ if (reloading) {
+ setReloadProps({ loading: true });
+ } else {
+ setReloadProps({ disabled: projectId === undefined });
+ }
+ }, [ok, loading, reloading, projectId]);
return (
<>
- <Button onClick={deploy} {...props}>
+ <Button onClick={deploy} {...deployProps}>
Deploy
</Button>
+ <Button onClick={reload} {...reloadProps}>
+ Reload
+ </Button>
<Button onClick={save}>Save</Button>
<Button onClick={restoreSaved}>Restore</Button>
<Button onClick={clear} variant="destructive">
diff --git a/apps/canvas/front/src/lib/config.ts b/apps/canvas/front/src/lib/config.ts
index 187f51c..a3784c1 100644
--- a/apps/canvas/front/src/lib/config.ts
+++ b/apps/canvas/front/src/lib/config.ts
@@ -78,14 +78,21 @@
};
export type Config = {
+ input: {
+ appId: string;
+ managerAddr: string;
+ };
service?: Service[];
volume?: Volume[];
postgresql?: PostgreSQL[];
mongodb?: MongoDB[];
};
-export function generateDodoConfig(nodes: AppNode[], env: Env): Config | null {
+export function generateDodoConfig(appId: string | undefined, nodes: AppNode[], env: Env): Config | null {
try {
+ if (appId == null || env.managerAddr == null) {
+ return null;
+ }
const networkMap = new Map(env.networks.map((n) => [n.domain, n.name]));
const ingressNodes = nodes.filter((n) => n.type === "gateway-https").filter((n) => n.data.https !== undefined);
const tcpNodes = nodes.filter((n) => n.type === "gateway-tcp").filter((n) => n.data.exposed !== undefined);
@@ -105,6 +112,10 @@
});
};
return {
+ input: {
+ appId: appId,
+ managerAddr: env.managerAddr,
+ },
service: nodes
.filter((n) => n.type === "app")
.map((n): Service => {
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index f9c1f56..0b3507d 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -314,6 +314,7 @@
};
export const envSchema = z.object({
+ managerAddr: z.optional(z.string().min(1)),
deployKey: z.optional(z.string().min(1)),
networks: z
.array(
@@ -331,6 +332,7 @@
export type Env = z.infer<typeof envSchema>;
const defaultEnv: Env = {
+ managerAddr: undefined,
deployKey: undefined,
networks: [],
integrations: {