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/config/src/config.ts b/apps/canvas/config/src/config.ts
index f5957d4..7fd7066 100644
--- a/apps/canvas/config/src/config.ts
+++ b/apps/canvas/config/src/config.ts
@@ -112,25 +112,35 @@
? n.data.preBuildCommands.split("\n").map((cmd) => ({ bin: cmd }))
: [],
dev: n.data.dev?.enabled
- ? {
- enabled: true,
- mode: "VM",
- username: env.user.username,
- codeServer:
- n.data.dev.expose != null
- ? {
- network: networkMap.get(n.data.dev.expose.network)!,
- subdomain: n.data.dev.expose.subdomain,
- }
- : undefined,
- ssh:
- n.data.dev.expose != null
- ? {
- network: networkMap.get(n.data.dev.expose.network)!,
- subdomain: n.data.dev.expose.subdomain,
- }
- : undefined,
- }
+ ? n.data.dev.mode === "VM"
+ ? {
+ enabled: true,
+ mode: "VM",
+ username: env.user.username,
+ codeServer:
+ n.data.dev.expose != null
+ ? {
+ network: networkMap.get(n.data.dev.expose.network)!,
+ subdomain: n.data.dev.expose.subdomain,
+ }
+ : undefined,
+ ssh:
+ n.data.dev.expose != null
+ ? {
+ network: networkMap.get(n.data.dev.expose.network)!,
+ subdomain: n.data.dev.expose.subdomain,
+ }
+ : undefined,
+ }
+ : {
+ enabled: true,
+ mode: "PROXY",
+ address: n.data.dev.address,
+ vpn: {
+ enabled: true,
+ username: env.user.username,
+ },
+ }
: {
enabled: false,
},
@@ -311,7 +321,28 @@
? { name: "gemini", apiKey: s.model.geminiApiKey }
: { name: "claude", apiKey: s.model.anthropicApiKey },
}),
- // TODO(gio): dev
+ dev: s.dev?.enabled
+ ? s.dev.mode === "VM"
+ ? {
+ enabled: true,
+ mode: "VM",
+ expose: s.dev.ssh
+ ? {
+ network: s.dev.ssh.network,
+ subdomain: s.dev.ssh.subdomain,
+ }
+ : undefined,
+ codeServerNodeId: uuidv4(), // TODO: proper node tracking
+ sshNodeId: uuidv4(), // TODO: proper node tracking
+ }
+ : {
+ enabled: true,
+ mode: "PROXY",
+ address: s.dev.address,
+ }
+ : {
+ enabled: false,
+ },
isChoosingPortToConnect: false,
},
// TODO(gio): generate position
diff --git a/apps/canvas/config/src/graph.ts b/apps/canvas/config/src/graph.ts
index a4634cb..fc3258e 100644
--- a/apps/canvas/config/src/graph.ts
+++ b/apps/canvas/config/src/graph.ts
@@ -210,9 +210,15 @@
}
| {
enabled: true;
+ mode: "VM";
expose?: Domain;
codeServerNodeId: string;
sshNodeId: string;
+ }
+ | {
+ enabled: true;
+ mode: "PROXY";
+ address: string;
};
model?: {
name: "gemini" | "claude";
@@ -496,17 +502,23 @@
preBuildCommands: z.string().optional(),
isChoosingPortToConnect: z.boolean().optional(),
dev: z
- .discriminatedUnion("enabled", [
+ .union([
z.object({
enabled: z.literal(false),
expose: DomainSchema.optional(),
}),
z.object({
enabled: z.literal(true),
+ mode: z.literal("VM"),
expose: DomainSchema.optional(),
codeServerNodeId: z.string(),
sshNodeId: z.string(),
}),
+ z.object({
+ enabled: z.literal(true),
+ mode: z.literal("PROXY"),
+ address: z.string(),
+ }),
])
.optional(),
model: z
diff --git a/apps/canvas/config/src/index.ts b/apps/canvas/config/src/index.ts
index f6cc9a5..df1be47 100644
--- a/apps/canvas/config/src/index.ts
+++ b/apps/canvas/config/src/index.ts
@@ -7,6 +7,10 @@
ConfigWithInputSchema,
Domain,
Ingress,
+ Machine,
+ MachineSchema,
+ Machines,
+ MachinesSchema,
MongoDB,
PortDomain,
PortValue,
diff --git a/apps/canvas/config/src/types.ts b/apps/canvas/config/src/types.ts
index a49c2d5..4146f75 100644
--- a/apps/canvas/config/src/types.ts
+++ b/apps/canvas/config/src/types.ts
@@ -98,15 +98,26 @@
expose: z.array(PortDomainSchema).optional(),
volume: z.array(z.string()).optional(),
preBuildCommands: z.array(z.object({ bin: z.string() })).optional(),
- dev: z.discriminatedUnion("enabled", [
+ dev: z.union([
z.object({ enabled: z.literal(false) }),
z.object({
enabled: z.literal(true),
- mode: z.string(),
+ mode: z.literal("VM"),
username: z.string().optional(),
ssh: DomainSchema.optional(),
codeServer: DomainSchema.optional(),
}),
+ z.object({
+ enabled: z.literal(true),
+ mode: z.literal("PROXY"),
+ address: z.string(),
+ vpn: z
+ .discriminatedUnion("enabled", [
+ z.object({ enabled: z.literal(false) }),
+ z.object({ enabled: z.literal(true), username: z.string() }),
+ ])
+ .optional(),
+ }),
]),
model: ModelSchema.optional(),
});
@@ -139,6 +150,12 @@
expose: z.array(DomainSchema).optional(),
});
+export const MachineSchema = z.object({
+ name: z.string(),
+});
+
+export const MachinesSchema = z.array(MachineSchema);
+
export const ConfigSchema = z.object({
service: z.array(ServiceSchema).optional(),
agent: z.array(AgentSchema).optional(),
@@ -180,5 +197,23 @@
export type MongoDB = z.infer<typeof MongoDBSchema>;
export type Config = z.infer<typeof ConfigSchema>;
export type ConfigWithInput = z.infer<typeof ConfigWithInputSchema>;
+export type Machine = z.infer<typeof MachineSchema>;
+export type Machines = z.infer<typeof MachinesSchema>;
+
+// Dev configuration types
+export type DevDisabled = { enabled: false };
+export type DevVM = {
+ enabled: true;
+ mode: "VM";
+ username?: string;
+ ssh?: Domain;
+ codeServer?: Domain;
+};
+export type DevProxy = {
+ enabled: true;
+ mode: "PROXY";
+ address: string;
+};
+export type Dev = DevDisabled | DevVM | DevProxy;
export const isAgent = (s: Service): s is Agent => s.type === "sketch:latest";