Canvas: Rework monitoring page

Display worker statuses with list of commands
Commit hash

Change-Id: I7054ecc5ce81f35cad3fe26fc20677b6f50d3147
diff --git a/apps/canvas/back/src/index.ts b/apps/canvas/back/src/index.ts
index aa25383..de18582 100644
--- a/apps/canvas/back/src/index.ts
+++ b/apps/canvas/back/src/index.ts
@@ -5,12 +5,12 @@
 import { GithubClient } from "./github";
 import { AppManager } from "./app_manager";
 import { z } from "zod";
+import { ProjectMonitor, WorkerSchema } from "./project_monitor";
 
 const db = new PrismaClient();
 const appManager = new AppManager();
 
-const workers = new Map<number, string[]>();
-const logs = new Map<number, Map<string, string>>();
+const projectMonitors = new Map<number, ProjectMonitor>();
 
 const handleProjectCreate: express.Handler = async (req, resp) => {
 	try {
@@ -486,8 +486,18 @@
 			resp.write(JSON.stringify({ error: "Project not found" }));
 			return;
 		}
-		const projectLogs = logs.get(projectId) || new Map();
-		const services = Array.from(projectLogs.keys());
+		const monitor = projectMonitors.get(projectId);
+		const serviceNames = monitor ? monitor.getAllServiceNames() : [];
+		const services = serviceNames.map((name) => ({
+			name,
+			workers: [...(monitor ? monitor.getWorkerStatusesForService(name) : new Map()).entries()].map(
+				([id, status]) => ({
+					...status,
+					id,
+				}),
+			),
+		}));
+
 		resp.status(200);
 		resp.write(
 			JSON.stringify({
@@ -540,6 +550,7 @@
 	try {
 		const projectId = Number(req.params["projectId"]);
 		const service = req.params["service"];
+		const workerId = req.params["workerId"];
 		const project = await db.project.findUnique({
 			where: {
 				id: projectId,
@@ -551,16 +562,16 @@
 			resp.write(JSON.stringify({ error: "Project not found" }));
 			return;
 		}
-		const projectLogs = logs.get(projectId);
-		if (!projectLogs) {
+		const monitor = projectMonitors.get(projectId);
+		if (!monitor || !monitor.hasLogs()) {
 			resp.status(404);
 			resp.write(JSON.stringify({ error: "No logs found for this project" }));
 			return;
 		}
-		const serviceLog = projectLogs.get(service);
+		const serviceLog = monitor.getWorkerLog(service, workerId);
 		if (!serviceLog) {
 			resp.status(404);
-			resp.write(JSON.stringify({ error: "No logs found for this service" }));
+			resp.write(JSON.stringify({ error: "No logs found for this service/worker" }));
 			return;
 		}
 		resp.status(200);
@@ -574,12 +585,6 @@
 	}
 };
 
-const WorkerSchema = z.object({
-	service: z.string(),
-	address: z.string().url(),
-	logs: z.optional(z.string()),
-});
-
 const handleRegisterWorker: express.Handler = async (req, resp) => {
 	try {
 		const projectId = Number(req.params["projectId"]);
@@ -594,17 +599,12 @@
 			);
 			return;
 		}
-		const { service, address, logs: log } = result.data;
-		const projectWorkers = workers.get(projectId) || [];
-		if (!projectWorkers.includes(address)) {
-			projectWorkers.push(address);
+		let monitor = projectMonitors.get(projectId);
+		if (!monitor) {
+			monitor = new ProjectMonitor();
+			projectMonitors.set(projectId, monitor);
 		}
-		workers.set(projectId, projectWorkers);
-		if (log) {
-			const svcLogs: Map<string, string> = logs.get(projectId) || new Map();
-			svcLogs.set(service, log);
-			logs.set(projectId, svcLogs);
-		}
+		monitor.registerWorker(result.data);
 		resp.status(200);
 		resp.write(
 			JSON.stringify({
@@ -621,7 +621,8 @@
 };
 
 async function reloadProject(projectId: number): Promise<boolean> {
-	const projectWorkers = workers.get(projectId) || [];
+	const monitor = projectMonitors.get(projectId);
+	const projectWorkers = monitor ? monitor.getWorkerAddresses() : [];
 	const workerCount = projectWorkers.length;
 	if (workerCount === 0) {
 		return true;
@@ -750,7 +751,7 @@
 	projectRouter.post("/:projectId/github-token", handleUpdateGithubToken);
 	projectRouter.get("/:projectId/env", handleEnv);
 	projectRouter.post("/:projectId/reload", handleReload);
-	projectRouter.get("/:projectId/logs/:service", handleServiceLogs);
+	projectRouter.get("/:projectId/logs/:service/:workerId", handleServiceLogs);
 	projectRouter.post("/:projectId/remove-deployment", handleRemoveDeployment);
 	projectRouter.get("/", handleProjectAll);
 	projectRouter.post("/", handleProjectCreate);