Canvas: Add VM/PROXY dev modes support

- Update ServiceSchema to discriminate between VM and PROXY dev modes
- Add DevDisabled, DevVM, DevProxy TypeScript types
- Update ServiceData type in graph.ts for new dev structure
- Update generateDodoConfig to handle both VM and PROXY modes
- Update configToGraph to properly convert dev configurations
- Maintain backward compatibility with existing dev configurations
- Update UI and introduce two new DevVM and DevProxy components
- Fetch user machine list from headscale API

Change-Id: I8f9df4ab9bd34c049fffadb748115335e8260a54
diff --git a/apps/canvas/back/.env.dev b/apps/canvas/back/.env.dev
index 2db1e80..e672e62 100644
--- a/apps/canvas/back/.env.dev
+++ b/apps/canvas/back/.env.dev
@@ -1,2 +1,4 @@
 DATABASE_URL=file:/home/gio/dodo.db
-INTERNAL_API_ADDR=http://10.42.1.95:8081
+VPN_API_ADDR=http://headscale-api.hgrz-app-headscale.svc.cluster.local
+INTERNAL_API_ADDR=http://10.42.0.193:8081
+
diff --git a/apps/canvas/back/src/index.ts b/apps/canvas/back/src/index.ts
index 97df61f..f00549f 100644
--- a/apps/canvas/back/src/index.ts
+++ b/apps/canvas/back/src/index.ts
@@ -26,6 +26,7 @@
 import { Instant, DateTimeFormatter, ZoneId } from "@js-joda/core";
 import LogStore from "./log.js";
 import { GraphOrConfigSchema, GraphSchema, GraphConfigOrDraft, AgentAccess } from "config/dist/graph.js";
+import { MachineManager } from "./machine_manager.js";
 
 async function generateKey(root: string): Promise<[string, string]> {
 	const privKeyPath = path.join(root, "key");
@@ -41,6 +42,7 @@
 const db = new PrismaClient();
 const logStore = new LogStore(db);
 const appManager = new AppManager();
+const machineManager = new MachineManager(env.VPN_API_ADDR!);
 
 const projectMonitors = new Map<number, ProjectMonitor>();
 
@@ -791,6 +793,20 @@
 	};
 };
 
+const handleMachines: express.Handler = async (req, resp) => {
+	try {
+		const machines = await machineManager.getUserMachines(resp.locals.username);
+		resp.status(200);
+		resp.write(JSON.stringify(machines));
+	} catch (error) {
+		console.error("Error getting machines:", error);
+		resp.status(500);
+		resp.write(JSON.stringify({ error: "Internal server error" }));
+	} finally {
+		resp.end();
+	}
+};
+
 const handleEnv: express.Handler = async (req, resp) => {
 	const projectId = Number(req.params["projectId"]);
 	try {
@@ -1268,6 +1284,9 @@
 	// Public webhook route - no auth needed
 	app.post("/api/webhook/github/push", handleGithubPushWebhook);
 
+	// Public machines route - no auth needed
+	app.get("/api/machines", auth, handleMachines);
+
 	// Authenticated project routes
 	const projectRouter = express.Router();
 	projectRouter.use(auth);
diff --git a/apps/canvas/back/src/machine_manager.ts b/apps/canvas/back/src/machine_manager.ts
new file mode 100644
index 0000000..ab4d684
--- /dev/null
+++ b/apps/canvas/back/src/machine_manager.ts
@@ -0,0 +1,10 @@
+import { Machines, MachinesSchema } from "config";
+
+export class MachineManager {
+	constructor(private readonly apiAddr: string) {}
+
+	async getUserMachines(username: string): Promise<Machines> {
+		const response = await fetch(`${this.apiAddr}/user/${username}/node`);
+		return MachinesSchema.parse(await response.json());
+	}
+}