Canvas: Monitor deployment
Change-Id: If5895724025e8e4082a372563c159cbf2216b97f
diff --git a/apps/canvas/back/index.js b/apps/canvas/back/index.js
index 6117e46..ef3449f 100644
--- a/apps/canvas/back/index.js
+++ b/apps/canvas/back/index.js
@@ -199,6 +199,43 @@
resp.end();
}
});
+const handleStatus = (req, resp) => __awaiter(void 0, void 0, void 0, function* () {
+ try {
+ const projectId = Number(req.params["projectId"]);
+ const p = yield db.project.findUnique({
+ where: {
+ id: projectId,
+ },
+ select: {
+ instanceId: true,
+ },
+ });
+ console.log(projectId, p);
+ if (p === null) {
+ resp.status(404);
+ return;
+ }
+ if (p.instanceId == null) {
+ resp.status(404);
+ return;
+ }
+ const r = yield axios_1.default.request({
+ url: `http://appmanager.hgrz-appmanager.svc.cluster.local/api/tasks/${p.instanceId}`,
+ method: "get",
+ });
+ resp.status(r.status);
+ if (r.status === 200) {
+ resp.write(JSON.stringify(r.data));
+ }
+ }
+ catch (e) {
+ console.log(e);
+ resp.status(500);
+ }
+ finally {
+ resp.end();
+ }
+});
function start() {
return __awaiter(this, void 0, void 0, function* () {
yield db.$connect();
@@ -207,6 +244,7 @@
app.post("/api/project/:projectId/saved", handleSave);
app.get("/api/project/:projectId/saved", handleSavedGet);
app.post("/api/project/:projectId/deploy", handleDeploy);
+ app.get("/api/project/:projectId/status", handleStatus);
app.get("/api/project", handleProjectAll);
app.post("/api/project", handleProjectCreate);
app.use("/", express_1.default.static("../front/dist"));
diff --git a/apps/canvas/back/index.ts b/apps/canvas/back/index.ts
index 0f7a573..d10a4a6 100644
--- a/apps/canvas/back/index.ts
+++ b/apps/canvas/back/index.ts
@@ -6,197 +6,234 @@
const db = new PrismaClient();
const handleProjectCreate: express.Handler = async (req, resp) => {
- try {
- const { id } = await db.project.create({
- data: {
- userId: "gio", // req.get("x-forwarded-userid")!,
- name: req.body.name,
- },
- });
- resp.status(200);
- resp.header("Content-Type", "application/json");
- resp.write(
- JSON.stringify({
- id,
- }),
- );
- } catch (e) {
- console.log(e);
- resp.status(500);
- } finally {
- resp.end();
- }
+ try {
+ const { id } = await db.project.create({
+ data: {
+ userId: "gio", // req.get("x-forwarded-userid")!,
+ name: req.body.name,
+ },
+ });
+ resp.status(200);
+ resp.header("Content-Type", "application/json");
+ resp.write(
+ JSON.stringify({
+ id,
+ }),
+ );
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
+ }
};
const handleProjectAll: express.Handler = async (req, resp) => {
- try {
- const r = await db.project.findMany({
- where: {
- userId: "gio", // req.get("x-forwarded-userid")!,
- },
- });
- resp.status(200);
- resp.header("Content-Type", "application/json");
- resp.write(
- JSON.stringify(
- r.map((p) => ({
- id: p.id.toString(),
- name: p.name,
- })),
- ),
- );
- } catch (e) {
- console.log(e);
- resp.status(500);
- } finally {
- resp.end();
- }
+ try {
+ const r = await db.project.findMany({
+ where: {
+ userId: "gio", // req.get("x-forwarded-userid")!,
+ },
+ });
+ resp.status(200);
+ resp.header("Content-Type", "application/json");
+ resp.write(
+ JSON.stringify(
+ r.map((p) => ({
+ id: p.id.toString(),
+ name: p.name,
+ })),
+ ),
+ );
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
+ }
};
const handleSave: express.Handler = async (req, resp) => {
- try {
- await db.project.update({
- where: {
- id: Number(req.params["projectId"]),
- },
- data: {
- draft: Buffer.from(JSON.stringify(req.body)),
- },
- });
- resp.status(200);
- } catch (e) {
- console.log(e);
- resp.status(500);
- } finally {
- resp.end();
- }
+ try {
+ await db.project.update({
+ where: {
+ id: Number(req.params["projectId"]),
+ },
+ data: {
+ draft: Buffer.from(JSON.stringify(req.body)),
+ },
+ });
+ resp.status(200);
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
+ }
};
const handleSavedGet: express.Handler = async (req, resp) => {
- try {
- const r = await db.project.findUnique({
- where: {
- id: Number(req.params["projectId"]),
- },
- select: {
- state: true,
- draft: true,
- },
- });
- if (r == null) {
- resp.status(404);
- } else {
- resp.status(200);
- resp.header("content-type", "application/json");
- if (r.draft == null) {
- if (r.state == null) {
- resp.send({
- nodes: [],
- edges: [],
- viewport: { x: 0, y: 0, zoom: 1 },
- });
+ try {
+ const r = await db.project.findUnique({
+ where: {
+ id: Number(req.params["projectId"]),
+ },
+ select: {
+ state: true,
+ draft: true,
+ },
+ });
+ if (r == null) {
+ resp.status(404);
} else {
- resp.send(JSON.parse(Buffer.from(r.state).toString("utf8")));
+ resp.status(200);
+ resp.header("content-type", "application/json");
+ if (r.draft == null) {
+ if (r.state == null) {
+ resp.send({
+ nodes: [],
+ edges: [],
+ viewport: { x: 0, y: 0, zoom: 1 },
+ });
+ } else {
+ resp.send(JSON.parse(Buffer.from(r.state).toString("utf8")));
+ }
+ } else {
+ resp.send(JSON.parse(Buffer.from(r.draft).toString("utf8")));
+ }
}
- } else {
- resp.send(JSON.parse(Buffer.from(r.draft).toString("utf8")));
- }
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
}
- } catch (e) {
- console.log(e);
- resp.status(500);
- } finally {
- resp.end();
- }
};
const handleDeploy: express.Handler = async (req, resp) => {
- try {
- const projectId = Number(req.params["projectId"]);
- const state = Buffer.from(JSON.stringify(req.body.state));
- const p = await db.project.findUnique({
- where: {
- id: projectId,
- },
- select: {
- instanceId: true,
- },
- });
- if (p === null) {
- resp.status(404);
- return;
- }
- await db.project.update({
- where: {
- id: projectId,
- },
- data: {
- draft: state,
- },
- });
- let r: { status: number; data: { id: string; deployKey: string } };
- if (p.instanceId == null) {
- r = await axios.request({
- url: "http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app",
- method: "post",
- data: {
- config: req.body.config,
- },
- });
- if (r.status === 200) {
- await db.project.update({
- where: {
- id: projectId,
- },
- data: {
- state,
- draft: null,
- instanceId: r.data.id,
- deployKey: r.data.deployKey,
- },
+ try {
+ const projectId = Number(req.params["projectId"]);
+ const state = Buffer.from(JSON.stringify(req.body.state));
+ const p = await db.project.findUnique({
+ where: {
+ id: projectId,
+ },
+ select: {
+ instanceId: true,
+ },
});
- }
- } else {
- r = await axios.request({
- url: `http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app/${p.instanceId}`,
- method: "put",
- data: {
- config: req.body.config,
- },
- });
- if (r.status === 200) {
+ if (p === null) {
+ resp.status(404);
+ return;
+ }
await db.project.update({
- where: {
- id: projectId,
- },
- data: {
- state,
- draft: null,
- },
+ where: {
+ id: projectId,
+ },
+ data: {
+ draft: state,
+ },
});
- }
+ let r: { status: number; data: { id: string; deployKey: string } };
+ if (p.instanceId == null) {
+ r = await axios.request({
+ url: "http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app",
+ method: "post",
+ data: {
+ config: req.body.config,
+ },
+ });
+ if (r.status === 200) {
+ await db.project.update({
+ where: {
+ id: projectId,
+ },
+ data: {
+ state,
+ draft: null,
+ instanceId: r.data.id,
+ deployKey: r.data.deployKey,
+ },
+ });
+ }
+ } else {
+ r = await axios.request({
+ url: `http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app/${p.instanceId}`,
+ method: "put",
+ data: {
+ config: req.body.config,
+ },
+ });
+ if (r.status === 200) {
+ await db.project.update({
+ where: {
+ id: projectId,
+ },
+ data: {
+ state,
+ draft: null,
+ },
+ });
+ }
+ }
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
}
- } catch (e) {
- console.log(e);
- resp.status(500);
- } finally {
- resp.end();
- }
+};
+
+const handleStatus: express.Handler = async (req, resp) => {
+ try {
+ const projectId = Number(req.params["projectId"]);
+ const p = await db.project.findUnique({
+ where: {
+ id: projectId,
+ },
+ select: {
+ instanceId: true,
+ },
+ });
+ console.log(projectId, p);
+ if (p === null) {
+ resp.status(404);
+ return;
+ }
+ if (p.instanceId == null) {
+ resp.status(404);
+ return;
+ }
+ const r = await axios.request({
+ url: `http://appmanager.hgrz-appmanager.svc.cluster.local/api/tasks/${p.instanceId}`,
+ method: "get",
+ });
+ resp.status(r.status);
+ if (r.status === 200) {
+ resp.write(JSON.stringify(r.data));
+ }
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
+ }
};
async function start() {
- await db.$connect();
- const app = express();
- app.use(express.json());
- app.post("/api/project/:projectId/saved", handleSave);
- app.get("/api/project/:projectId/saved", handleSavedGet);
- app.post("/api/project/:projectId/deploy", handleDeploy);
- app.get("/api/project", handleProjectAll);
- app.post("/api/project", handleProjectCreate);
- app.use("/", express.static("../front/dist"));
- app.listen(env.DODO_PORT_WEB, () => {
- console.log("started");
- });
+ await db.$connect();
+ const app = express();
+ app.use(express.json());
+ app.post("/api/project/:projectId/saved", handleSave);
+ app.get("/api/project/:projectId/saved", handleSavedGet);
+ app.post("/api/project/:projectId/deploy", handleDeploy);
+ app.get("/api/project/:projectId/status", handleStatus);
+ app.get("/api/project", handleProjectAll);
+ app.post("/api/project", handleProjectCreate);
+ app.use("/", express.static("../front/dist"));
+ app.listen(env.DODO_PORT_WEB, () => {
+ console.log("started");
+ });
}
start();
diff --git a/apps/canvas/front/src/components/actions.tsx b/apps/canvas/front/src/components/actions.tsx
index cc09a59..7d0afe7 100644
--- a/apps/canvas/front/src/components/actions.tsx
+++ b/apps/canvas/front/src/components/actions.tsx
@@ -1,4 +1,4 @@
-import { AppNode, useEnv, useMessages, useProjectId, useStateStore } from "@/lib/state";
+import { AppNode, nodeLabel, useEnv, useMessages, useProjectId, useStateStore } from "@/lib/state";
import { Button } from "./ui/button";
import { useCallback, useEffect, useState } from "react";
import { generateDodoConfig } from "@/lib/config";
@@ -18,6 +18,31 @@
useEffect(() => {
setOk(!messages.some((m) => m.type === "FATAL"));
}, [messages, setOk]);
+ const monitor = useCallback(async () => {
+ const m = async function() {
+ const resp = await fetch(`/api/project/${projectId}/status`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ if (resp.status !== 200) {
+ return;
+ }
+ const data: { type: string, name: string, status: string }[] = await resp.json();
+ console.log(data);
+ for (const n of nodes) {
+ console.log(nodeLabel(n));
+ for (const d of data) {
+ if (nodeLabel(n) === d.name) {
+ store.updateNodeData(n.id, { state: d.status });
+ }
+ }
+ }
+ setTimeout(m, 1000);
+ };
+ setTimeout(m, 100);
+ }, [projectId, nodes]);
const deploy = useCallback(async () => {
if (projectId == null) {
return;
@@ -42,6 +67,7 @@
toast({
title: "Deployment succeeded",
});
+ monitor();
} else {
toast({
variant: "destructive",
@@ -96,6 +122,10 @@
store.setEdges(inst.edges || []);
instance.setViewport({ x, y, zoom });
}, [projectId, instance, st]);
+ const clear = useCallback(() => {
+ store.setEdges([]);
+ store.setNodes([]);
+ }, [store]);
const [props, setProps] = useState({});
useEffect(() => {
if (loading) {
@@ -111,6 +141,7 @@
<Button onClick={deploy} {...props}>Deploy</Button>
<Button onClick={save}>Save</Button>
<Button onClick={restoreSaved}>Restore</Button>
+ <Button onClick={clear}>Clear</Button>
</>
)
}
\ No newline at end of file
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index a445c72..fcfe2a1 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -18,7 +18,7 @@
const isConnectablePorts = useMemo(() => nodeIsConnectable(node, "ports"), [node]);
const isConnectableRepository = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
<div style={{ padding: '10px 20px' }}>
{nodeLabel(node)}
<Handle
diff --git a/apps/canvas/front/src/components/node-gateway-https.tsx b/apps/canvas/front/src/components/node-gateway-https.tsx
index be624a3..fdf710d 100644
--- a/apps/canvas/front/src/components/node-gateway-https.tsx
+++ b/apps/canvas/front/src/components/node-gateway-https.tsx
@@ -23,7 +23,7 @@
const { id, selected } = node;
const isConnectable = useMemo(() => nodeIsConnectable(node, "https"), [node]);
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
{nodeLabel(node)}
<Handle
type={"target"}
diff --git a/apps/canvas/front/src/components/node-gateway-tcp.tsx b/apps/canvas/front/src/components/node-gateway-tcp.tsx
index 8cfebff..aeca41e 100644
--- a/apps/canvas/front/src/components/node-gateway-tcp.tsx
+++ b/apps/canvas/front/src/components/node-gateway-tcp.tsx
@@ -25,7 +25,7 @@
const { id, selected } = node;
const isConnectable = useMemo(() => nodeIsConnectable(node, "tcp"), [node]);
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
{nodeLabel(node)}
<Handle
type={"target"}
diff --git a/apps/canvas/front/src/components/node-github.tsx b/apps/canvas/front/src/components/node-github.tsx
index 13b257b..aa48cd1 100644
--- a/apps/canvas/front/src/components/node-github.tsx
+++ b/apps/canvas/front/src/components/node-github.tsx
@@ -12,7 +12,7 @@
const { id, selected } = node;
const isConnectable = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
<div style={{ padding: '10px 20px' }}>
{nodeLabel(node)}
<Handle
diff --git a/apps/canvas/front/src/components/node-mongodb.tsx b/apps/canvas/front/src/components/node-mongodb.tsx
index 89323b9..40c9748 100644
--- a/apps/canvas/front/src/components/node-mongodb.tsx
+++ b/apps/canvas/front/src/components/node-mongodb.tsx
@@ -11,7 +11,7 @@
export function NodeMongoDB(node: MongoDBNode) {
const { id, selected } = node;
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
<div style={{ padding: '10px 20px' }}>
{nodeLabel(node)}
<Handle
diff --git a/apps/canvas/front/src/components/node-postgresql.tsx b/apps/canvas/front/src/components/node-postgresql.tsx
index db7f067..4213645 100644
--- a/apps/canvas/front/src/components/node-postgresql.tsx
+++ b/apps/canvas/front/src/components/node-postgresql.tsx
@@ -11,7 +11,7 @@
export function NodePostgreSQL(node: PostgreSQLNode) {
const { id, selected } = node;
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
<div style={{ padding: '10px 20px' }}>
{nodeLabel(node)}
<Handle
diff --git a/apps/canvas/front/src/components/node-rect.tsx b/apps/canvas/front/src/components/node-rect.tsx
index 5164a13..ad0bee7 100644
--- a/apps/canvas/front/src/components/node-rect.tsx
+++ b/apps/canvas/front/src/components/node-rect.tsx
@@ -1,34 +1,43 @@
import { NodeType, useNodeMessages } from "@/lib/state";
import { Icon } from "./icon";
+import { useEffect, useState } from "react";
export type Props = {
id: string;
selected?: boolean;
children: any;
type: NodeType;
+ state: string | null;
};
export function NodeRect(p: Props) {
- const { id, selected, children } = p;
+ const { id, selected, children, state } = p;
const messages = useNodeMessages(id);
const hasFatal = messages.some((m) => m.type === "FATAL");
const hasWarning = messages.some((m) => m.type === "WARNING");
- const classes = ["px-4", "py-2", "rounded-md", "bg-white"];
- if (hasFatal) {
- classes.push("border-red-500");
- } else if (hasWarning) {
- classes.push("border-yellow-500");
- } else {
- classes.push("border-black");
- }
- if (selected) {
- classes.push("border-2");
- } else {
- classes.push("border");
- }
+ const [classes, setClasses] = useState<string[]>([]);
+ useEffect(() => {
+ const classes = ["px-4", "py-2", "rounded-md", "bg-white"];
+ if (hasFatal) {
+ classes.push("border-red-500");
+ } else if (hasWarning) {
+ classes.push("border-yellow-500");
+ } else {
+ classes.push("border-black");
+ }
+ if (selected) {
+ classes.push("border-2");
+ } else {
+ classes.push("border");
+ }
+ if (state === "running") {
+ classes.push("animate-pulse");
+ }
+ setClasses(classes);
+ }, [selected, hasFatal, hasWarning, state, setClasses]);
return (
<div className={classes.join(" ")}>
- <div style={{ position: "absolute", top: "5px", left: "5px"}}>
+ <div style={{ position: "absolute", top: "5px", left: "5px" }}>
{Icon(p.type)}
</div>
{children}
diff --git a/apps/canvas/front/src/components/node-volume.tsx b/apps/canvas/front/src/components/node-volume.tsx
index 57c488d..430d1e9 100644
--- a/apps/canvas/front/src/components/node-volume.tsx
+++ b/apps/canvas/front/src/components/node-volume.tsx
@@ -13,7 +13,7 @@
const { id, data, selected } = node;
const isConnectable = useMemo(() => nodeIsConnectable(node, "volume"), [node]);
return (
- <NodeRect id={id} selected={selected} type={node.type}>
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
<div style={{ padding: '10px 20px' }}>
<div>{nodeLabel(node)}</div>
<div>{data.type && `${data.type}`}</div>
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index 55fbc5d..f2836f1 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -21,6 +21,7 @@
export type NodeData = InitData & {
activeField?: string | undefined;
+ state: string | null;
};
export type PortConnectedTo = {