Cavnas: Implement basic service discovery logic

Change-Id: I71b25076dba94d6491ad4db748b259870991c526
diff --git a/apps/canvas/front/package-lock.json b/apps/canvas/front/package-lock.json
index 94ca1b2..cb422f6 100644
--- a/apps/canvas/front/package-lock.json
+++ b/apps/canvas/front/package-lock.json
@@ -13,15 +13,16 @@
 				"@radix-ui/react-accordion": "^1.2.1",
 				"@radix-ui/react-checkbox": "^1.3.1",
 				"@radix-ui/react-collapsible": "^1.1.1",
-				"@radix-ui/react-dialog": "^1.1.2",
+				"@radix-ui/react-dialog": "^1.1.14",
 				"@radix-ui/react-dropdown-menu": "^2.1.14",
 				"@radix-ui/react-icons": "^1.3.1",
-				"@radix-ui/react-label": "^2.1.0",
+				"@radix-ui/react-label": "^2.1.7",
 				"@radix-ui/react-popover": "^1.1.2",
 				"@radix-ui/react-scroll-area": "^1.2.0",
 				"@radix-ui/react-select": "^2.1.2",
 				"@radix-ui/react-separator": "^1.1.0",
 				"@radix-ui/react-slot": "^1.1.0",
+				"@radix-ui/react-switch": "^1.2.5",
 				"@radix-ui/react-tabs": "^1.1.1",
 				"@radix-ui/react-toast": "^1.2.13",
 				"@radix-ui/react-tooltip": "^1.1.4",
@@ -1403,24 +1404,25 @@
 			}
 		},
 		"node_modules/@radix-ui/react-dialog": {
-			"version": "1.1.2",
-			"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz",
-			"integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==",
+			"version": "1.1.14",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz",
+			"integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==",
+			"license": "MIT",
 			"dependencies": {
-				"@radix-ui/primitive": "1.1.0",
-				"@radix-ui/react-compose-refs": "1.1.0",
-				"@radix-ui/react-context": "1.1.1",
-				"@radix-ui/react-dismissable-layer": "1.1.1",
-				"@radix-ui/react-focus-guards": "1.1.1",
-				"@radix-ui/react-focus-scope": "1.1.0",
-				"@radix-ui/react-id": "1.1.0",
-				"@radix-ui/react-portal": "1.1.2",
-				"@radix-ui/react-presence": "1.1.1",
-				"@radix-ui/react-primitive": "2.0.0",
-				"@radix-ui/react-slot": "1.1.0",
-				"@radix-ui/react-use-controllable-state": "1.1.0",
-				"aria-hidden": "^1.1.1",
-				"react-remove-scroll": "2.6.0"
+				"@radix-ui/primitive": "1.1.2",
+				"@radix-ui/react-compose-refs": "1.1.2",
+				"@radix-ui/react-context": "1.1.2",
+				"@radix-ui/react-dismissable-layer": "1.1.10",
+				"@radix-ui/react-focus-guards": "1.1.2",
+				"@radix-ui/react-focus-scope": "1.1.7",
+				"@radix-ui/react-id": "1.1.1",
+				"@radix-ui/react-portal": "1.1.9",
+				"@radix-ui/react-presence": "1.1.4",
+				"@radix-ui/react-primitive": "2.1.3",
+				"@radix-ui/react-slot": "1.2.3",
+				"@radix-ui/react-use-controllable-state": "1.2.2",
+				"aria-hidden": "^1.2.4",
+				"react-remove-scroll": "^2.6.3"
 			},
 			"peerDependencies": {
 				"@types/react": "*",
@@ -1437,6 +1439,308 @@
 				}
 			}
 		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
+			"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
+			"license": "MIT"
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+			"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+			"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
+			"version": "1.1.10",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
+			"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/primitive": "1.1.2",
+				"@radix-ui/react-compose-refs": "1.1.2",
+				"@radix-ui/react-primitive": "2.1.3",
+				"@radix-ui/react-use-callback-ref": "1.1.1",
+				"@radix-ui/react-use-escape-keydown": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
+			"integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": {
+			"version": "1.1.7",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+			"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-compose-refs": "1.1.2",
+				"@radix-ui/react-primitive": "2.1.3",
+				"@radix-ui/react-use-callback-ref": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+			"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-use-layout-effect": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
+			"version": "1.1.9",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+			"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-primitive": "2.1.3",
+				"@radix-ui/react-use-layout-effect": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
+			"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-compose-refs": "1.1.2",
+				"@radix-ui/react-use-layout-effect": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+			"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-slot": "1.2.3"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+			"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-compose-refs": "1.1.2"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+			"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+			"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-use-effect-event": "0.0.2",
+				"@radix-ui/react-use-layout-effect": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+			"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-use-callback-ref": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+			"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": {
+			"version": "2.7.0",
+			"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.0.tgz",
+			"integrity": "sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg==",
+			"license": "MIT",
+			"dependencies": {
+				"react-remove-scroll-bar": "^2.3.7",
+				"react-style-singleton": "^2.2.3",
+				"tslib": "^2.1.0",
+				"use-callback-ref": "^1.3.3",
+				"use-sidecar": "^1.1.3"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
 		"node_modules/@radix-ui/react-direction": {
 			"version": "1.1.0",
 			"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
@@ -1699,11 +2003,12 @@
 			}
 		},
 		"node_modules/@radix-ui/react-label": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
-			"integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
+			"version": "2.1.7",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
+			"integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
+			"license": "MIT",
 			"dependencies": {
-				"@radix-ui/react-primitive": "2.0.0"
+				"@radix-ui/react-primitive": "2.1.3"
 			},
 			"peerDependencies": {
 				"@types/react": "*",
@@ -1720,6 +2025,62 @@
 				}
 			}
 		},
+		"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-compose-refs": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+			"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+			"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-slot": "1.2.3"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-slot": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+			"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-compose-refs": "1.1.2"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
 		"node_modules/@radix-ui/react-menu": {
 			"version": "2.1.14",
 			"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.14.tgz",
@@ -2535,6 +2896,179 @@
 				}
 			}
 		},
+		"node_modules/@radix-ui/react-switch": {
+			"version": "1.2.5",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz",
+			"integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/primitive": "1.1.2",
+				"@radix-ui/react-compose-refs": "1.1.2",
+				"@radix-ui/react-context": "1.1.2",
+				"@radix-ui/react-primitive": "2.1.3",
+				"@radix-ui/react-use-controllable-state": "1.2.2",
+				"@radix-ui/react-use-previous": "1.1.1",
+				"@radix-ui/react-use-size": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/primitive": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
+			"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
+			"license": "MIT"
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-compose-refs": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+			"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+			"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+			"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-slot": "1.2.3"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"@types/react-dom": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+				"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				},
+				"@types/react-dom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+			"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-compose-refs": "1.1.2"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-controllable-state": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+			"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-use-effect-event": "0.0.2",
+				"@radix-ui/react-use-layout-effect": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-layout-effect": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+			"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-previous": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+			"integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+			"license": "MIT",
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-size": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+			"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+			"license": "MIT",
+			"dependencies": {
+				"@radix-ui/react-use-layout-effect": "1.1.1"
+			},
+			"peerDependencies": {
+				"@types/react": "*",
+				"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+			},
+			"peerDependenciesMeta": {
+				"@types/react": {
+					"optional": true
+				}
+			}
+		},
 		"node_modules/@radix-ui/react-tabs": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz",
diff --git a/apps/canvas/front/package.json b/apps/canvas/front/package.json
index b25d17b..43d1232 100644
--- a/apps/canvas/front/package.json
+++ b/apps/canvas/front/package.json
@@ -21,15 +21,16 @@
 		"@radix-ui/react-accordion": "^1.2.1",
 		"@radix-ui/react-checkbox": "^1.3.1",
 		"@radix-ui/react-collapsible": "^1.1.1",
-		"@radix-ui/react-dialog": "^1.1.2",
+		"@radix-ui/react-dialog": "^1.1.14",
 		"@radix-ui/react-dropdown-menu": "^2.1.14",
 		"@radix-ui/react-icons": "^1.3.1",
-		"@radix-ui/react-label": "^2.1.0",
+		"@radix-ui/react-label": "^2.1.7",
 		"@radix-ui/react-popover": "^1.1.2",
 		"@radix-ui/react-scroll-area": "^1.2.0",
 		"@radix-ui/react-select": "^2.1.2",
 		"@radix-ui/react-separator": "^1.1.0",
 		"@radix-ui/react-slot": "^1.1.0",
+		"@radix-ui/react-switch": "^1.2.5",
 		"@radix-ui/react-tabs": "^1.1.1",
 		"@radix-ui/react-toast": "^1.2.13",
 		"@radix-ui/react-tooltip": "^1.1.4",
diff --git a/apps/canvas/front/src/ProjectSelect.tsx b/apps/canvas/front/src/ProjectSelect.tsx
index 278db67..312e6aa 100644
--- a/apps/canvas/front/src/ProjectSelect.tsx
+++ b/apps/canvas/front/src/ProjectSelect.tsx
@@ -117,7 +117,6 @@
 				});
 			});
 	}, [name, setCreateNewOpen, toast, refreshProjects]);
-	console.log("asd", projectId);
 	return (
 		<>
 			<Select onValueChange={onSelect} value={projectId}>
diff --git a/apps/canvas/front/src/Tools.tsx b/apps/canvas/front/src/Tools.tsx
index f355d3e..4c9b19e 100644
--- a/apps/canvas/front/src/Tools.tsx
+++ b/apps/canvas/front/src/Tools.tsx
@@ -27,7 +27,7 @@
 				<TabsContent value="gateways">
 					<Gateways />
 				</TabsContent>
-				<TabsContent value="deployKeys">{env.deployKey && <>{env.deployKey}</>}</TabsContent>
+				<TabsContent value="deployKeys">{env.deployKeyPublic && <>{env.deployKeyPublic}</>}</TabsContent>
 			</div>
 		</Tabs>
 	);
diff --git a/apps/canvas/front/src/components/actions.tsx b/apps/canvas/front/src/components/actions.tsx
index 24198cb..dbb5ea6 100644
--- a/apps/canvas/front/src/components/actions.tsx
+++ b/apps/canvas/front/src/components/actions.tsx
@@ -169,6 +169,10 @@
 		}
 		const resp = await fetch(`/api/project/${projectId}`, {
 			method: "DELETE",
+			headers: {
+				"Content-Type": "application/json",
+			},
+			body: JSON.stringify({ state: JSON.stringify(instance.toObject()) }),
 		});
 		if (resp.ok) {
 			clear();
@@ -177,7 +181,7 @@
 		} else {
 			error("Failed to delete project", await resp.text());
 		}
-	}, [store, clear, projectId, info, error]);
+	}, [store, clear, projectId, info, error, instance]);
 	const reload = useCallback(async () => {
 		if (projectId == null) {
 			return;
diff --git a/apps/canvas/front/src/components/node-github.tsx b/apps/canvas/front/src/components/node-github.tsx
index 4c065be..dd7c68a 100644
--- a/apps/canvas/front/src/components/node-github.tsx
+++ b/apps/canvas/front/src/components/node-github.tsx
@@ -1,6 +1,19 @@
 import { NodeRect } from "./node-rect";
-import { GithubNode, nodeIsConnectable, nodeLabel, useStateStore, useGithubService } from "@/lib/state";
-import { useEffect, useMemo, useState } from "react";
+import {
+	GithubNode,
+	nodeIsConnectable,
+	nodeLabel,
+	serviceAnalyzisSchema,
+	useStateStore,
+	useGithubService,
+	ServiceType,
+	ServiceData,
+	useGithubRepositories,
+	useGithubRepositoriesLoading,
+	useGithubRepositoriesError,
+	useFetchGithubRepositories,
+} from "@/lib/state";
+import { useCallback, useEffect, useMemo, useState } from "react";
 import { z } from "zod";
 import { DeepPartial, EventType, useForm } from "react-hook-form";
 import { zodResolver } from "@hookform/resolvers/zod";
@@ -10,7 +23,12 @@
 import { GitHubRepository } from "../lib/github";
 import { useProjectId } from "@/lib/state";
 import { Alert, AlertDescription } from "./ui/alert";
-import { AlertCircle } from "lucide-react";
+import { AlertCircle, LoaderCircle, RefreshCw } from "lucide-react";
+import { Button } from "./ui/button";
+import { v4 as uuidv4 } from "uuid";
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog";
+import { Switch } from "./ui/switch";
+import { Label } from "./ui/label";
 
 export function NodeGithub(node: GithubNode) {
 	const { id, selected } = node;
@@ -39,34 +57,42 @@
 export function NodeGithubDetails({ id, data, disabled }: GithubNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const projectId = useProjectId();
-	const [repos, setRepos] = useState<GitHubRepository[]>([]);
-	const [loading, setLoading] = useState(false);
-	const [error, setError] = useState<string | null>(null);
 	const githubService = useGithubService();
 
+	const storeRepos = useGithubRepositories();
+	const isLoadingRepos = useGithubRepositoriesLoading();
+	const repoError = useGithubRepositoriesError();
+	const fetchStoreRepositories = useFetchGithubRepositories();
+
+	const [displayRepos, setDisplayRepos] = useState<GitHubRepository[]>([]);
+
+	const [isAnalyzing, setIsAnalyzing] = useState(false);
+	const [showModal, setShowModal] = useState(false);
+	const [discoveredServices, setDiscoveredServices] = useState<z.infer<typeof serviceAnalyzisSchema>[]>([]);
+	const [selectedServices, setSelectedServices] = useState<Record<string, boolean>>({});
+
 	useEffect(() => {
+		let currentRepoInStore = false;
 		if (data.repository) {
-			const { id, sshURL } = data.repository;
-			setRepos((prevRepos) => {
-				if (!prevRepos.some((r) => r.id === id)) {
-					return [
-						...prevRepos,
-						{
-							id,
-							name: sshURL.split("/").pop() || "",
-							full_name: sshURL.split("/").slice(-2).join("/"),
-							html_url: "",
-							ssh_url: sshURL,
-							description: null,
-							private: false,
-							default_branch: "main",
-						},
-					];
-				}
-				return prevRepos;
-			});
+			currentRepoInStore = storeRepos.some((r) => r.id === data.repository!.id);
 		}
-	}, [data.repository]);
+
+		if (data.repository && !currentRepoInStore) {
+			const currentRepoForDisplay: GitHubRepository = {
+				id: data.repository.id,
+				name: data.repository.sshURL.split("/").pop() || "",
+				full_name: data.repository.fullName || data.repository.sshURL.split("/").slice(-2).join("/"),
+				html_url: "",
+				ssh_url: data.repository.sshURL,
+				description: null,
+				private: false,
+				default_branch: "main",
+			};
+			setDisplayRepos([currentRepoForDisplay, ...storeRepos.filter((r) => r.id !== data.repository!.id)]);
+		} else {
+			setDisplayRepos(storeRepos);
+		}
+	}, [data.repository, storeRepos]);
 
 	const form = useForm<z.infer<typeof schema>>({
 		resolver: zodResolver(schema),
@@ -77,6 +103,10 @@
 	});
 
 	useEffect(() => {
+		form.reset({ repositoryId: data.repository?.id });
+	}, [data.repository?.id, form]);
+
+	useEffect(() => {
 		const sub = form.watch(
 			(
 				value: DeepPartial<z.infer<typeof schema>>,
@@ -88,7 +118,7 @@
 				switch (name) {
 					case "repositoryId":
 						if (value.repositoryId) {
-							const repo = repos.find((r) => r.id === value.repositoryId);
+							const repo = displayRepos.find((r) => r.id === value.repositoryId);
 							if (repo) {
 								store.updateNodeData<"github">(id, {
 									repository: {
@@ -104,26 +134,87 @@
 			},
 		);
 		return () => sub.unsubscribe();
-	}, [form, store, id, repos]);
+	}, [form, store, id, displayRepos]);
 
-	useEffect(() => {
-		const fetchRepositories = async () => {
-			if (!githubService) return;
+	const analyze = useCallback(async () => {
+		if (!data.repository?.sshURL) return;
 
-			setLoading(true);
-			setError(null);
-			try {
-				const repositories = await githubService.getRepositories();
-				setRepos(repositories);
-			} catch (err) {
-				setError(err instanceof Error ? err.message : "Failed to fetch repositories");
-			} finally {
-				setLoading(false);
+		setIsAnalyzing(true);
+		try {
+			const resp = await fetch(`/api/project/${projectId}/analyze`, {
+				method: "POST",
+				body: JSON.stringify({
+					address: data.repository?.sshURL,
+				}),
+				headers: {
+					"Content-Type": "application/json",
+				},
+			});
+			const servicesResult = z.array(serviceAnalyzisSchema).safeParse(await resp.json());
+			if (!servicesResult.success) {
+				console.error(servicesResult.error);
+				setIsAnalyzing(false);
+				return;
 			}
-		};
 
-		fetchRepositories();
-	}, [githubService]);
+			setDiscoveredServices(servicesResult.data);
+			const initialSelectedServices: Record<string, boolean> = {};
+			servicesResult.data.forEach((service) => {
+				initialSelectedServices[service.name] = true;
+			});
+			setSelectedServices(initialSelectedServices);
+			setShowModal(true);
+		} catch (err) {
+			console.error("Analysis failed:", err);
+		} finally {
+			setIsAnalyzing(false);
+		}
+	}, [projectId, data.repository?.sshURL]);
+
+	const handleImportServices = () => {
+		discoveredServices.forEach((service) => {
+			if (selectedServices[service.name]) {
+				const newNodeData: Omit<ServiceData, "activeField" | "state"> = {
+					label: service.name,
+					repository: {
+						id: id,
+					},
+					info: service,
+					type: "nodejs:24.0.2" as ServiceType,
+					env: [],
+					volume: [],
+					preBuildCommands: "",
+					isChoosingPortToConnect: false,
+					envVars: [],
+					ports: [],
+				};
+				const newNodeId = uuidv4();
+				store.addNode({
+					id: newNodeId,
+					type: "app",
+					data: newNodeData,
+				});
+				let edges = store.edges;
+				edges = edges.concat({
+					id: uuidv4(),
+					source: id,
+					sourceHandle: "repository",
+					target: newNodeId,
+					targetHandle: "repository",
+				});
+				store.setEdges(edges);
+			}
+		});
+		setShowModal(false);
+		setDiscoveredServices([]);
+		setSelectedServices({});
+	};
+
+	const handleCancelModal = () => {
+		setShowModal(false);
+		setDiscoveredServices([]);
+		setSelectedServices({});
+	};
 
 	return (
 		<>
@@ -134,32 +225,57 @@
 						name="repositoryId"
 						render={({ field }) => (
 							<FormItem>
-								<Select
-									onValueChange={(value) => field.onChange(Number(value))}
-									value={field.value?.toString()}
-									disabled={loading || !projectId || !githubService || disabled}
-								>
-									<FormControl>
-										<SelectTrigger>
-											<SelectValue
-												placeholder={
-													githubService ? "Select a repository" : "GitHub not configured"
-												}
-											/>
-										</SelectTrigger>
-									</FormControl>
-									<SelectContent>
-										{repos.map((repo) => (
-											<SelectItem key={repo.id} value={repo.id.toString()}>
-												{repo.full_name}
-												{repo.description && ` - ${repo.description}`}
-											</SelectItem>
-										))}
-									</SelectContent>
-								</Select>
+								<div className="flex items-center gap-2 w-full">
+									<div className="flex-grow">
+										<Select
+											onValueChange={(value) => field.onChange(Number(value))}
+											value={field.value?.toString()}
+											disabled={isLoadingRepos || !projectId || !githubService || disabled}
+										>
+											<FormControl>
+												<SelectTrigger>
+													<SelectValue
+														placeholder={
+															githubService
+																? isLoadingRepos
+																	? "Loading..."
+																	: displayRepos.length === 0
+																		? "No repositories found"
+																		: "Select a repository"
+																: "GitHub not configured"
+														}
+													/>
+												</SelectTrigger>
+											</FormControl>
+											<SelectContent>
+												{displayRepos.map((repo) => (
+													<SelectItem key={repo.id} value={repo.id.toString()}>
+														{repo.full_name}
+														{repo.description && ` - ${repo.description}`}
+													</SelectItem>
+												))}
+											</SelectContent>
+										</Select>
+									</div>
+									{isLoadingRepos && (
+										<Button variant="ghost" size="icon" disabled>
+											<LoaderCircle className="h-5 w-5 animate-spin text-muted-foreground" />
+										</Button>
+									)}
+									{!isLoadingRepos && githubService && (
+										<Button
+											variant="ghost"
+											size="icon"
+											onClick={fetchStoreRepositories}
+											disabled={disabled}
+											aria-label="Refresh repositories"
+										>
+											<RefreshCw className="h-5 w-5 text-muted-foreground" />
+										</Button>
+									)}
+								</div>
 								<FormMessage />
-								{error && <p className="text-sm text-red-500">{error}</p>}
-								{loading && <p className="text-sm text-gray-500">Loading repositories...</p>}
+								{repoError && <p className="text-sm text-red-500 mt-1">{repoError}</p>}
 								{!githubService && (
 									<Alert variant="destructive" className="mt-2">
 										<AlertCircle className="h-4 w-4" />
@@ -173,6 +289,62 @@
 					/>
 				</form>
 			</Form>
+			<Button disabled={!data.repository?.sshURL || isAnalyzing || !githubService || disabled} onClick={analyze}>
+				{isAnalyzing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
+				Scan for services
+			</Button>
+			{showModal && (
+				<Dialog open={showModal} onOpenChange={setShowModal}>
+					<DialogContent className="sm:max-w-[425px]">
+						<DialogHeader>
+							<DialogTitle>Discovered Services</DialogTitle>
+							<DialogDescription>Select the services you want to import.</DialogDescription>
+						</DialogHeader>
+						<div className="grid gap-4 py-4">
+							{discoveredServices.map((service) => (
+								<div key={service.name} className="flex flex-col space-y-2 p-2 border rounded-md">
+									<div className="flex items-center space-x-2">
+										<Switch
+											id={service.name}
+											checked={selectedServices[service.name]}
+											onCheckedChange={(checked: boolean) =>
+												setSelectedServices((prev) => ({
+													...prev,
+													[service.name]: checked,
+												}))
+											}
+										/>
+										<Label htmlFor={service.name} className="font-semibold">
+											{service.name}
+										</Label>
+									</div>
+									<div className="pl-6 text-sm text-gray-600">
+										<p>
+											<span className="font-medium">Location:</span> {service.location}
+										</p>
+										{service.configVars && service.configVars.length > 0 && (
+											<div className="mt-1">
+												<p className="font-medium">Environment Variables:</p>
+												<ul className="list-disc list-inside pl-4">
+													{service.configVars.map((envVar) => (
+														<li key={envVar.name}>{envVar.name}</li>
+													))}
+												</ul>
+											</div>
+										)}
+									</div>
+								</div>
+							))}
+						</div>
+						<DialogFooter>
+							<Button variant="outline" onClick={handleCancelModal}>
+								Cancel
+							</Button>
+							<Button onClick={handleImportServices}>Import</Button>
+						</DialogFooter>
+					</DialogContent>
+				</Dialog>
+			)}
 		</>
 	);
 }
diff --git a/apps/canvas/front/src/components/ui/switch.tsx b/apps/canvas/front/src/components/ui/switch.tsx
new file mode 100644
index 0000000..5139bf2
--- /dev/null
+++ b/apps/canvas/front/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import * as React from "react";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
+
+import { cn } from "@/lib/utils";
+
+const Switch = React.forwardRef<
+	React.ElementRef<typeof SwitchPrimitives.Root>,
+	React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+	<SwitchPrimitives.Root
+		className={cn(
+			"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+			className,
+		)}
+		{...props}
+		ref={ref}
+	>
+		<SwitchPrimitives.Thumb
+			className={cn(
+				"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
+			)}
+		/>
+	</SwitchPrimitives.Root>
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
+
+export { Switch };
diff --git a/apps/canvas/front/src/lib/config.ts b/apps/canvas/front/src/lib/config.ts
index 572fc78..513f1f0 100644
--- a/apps/canvas/front/src/lib/config.ts
+++ b/apps/canvas/front/src/lib/config.ts
@@ -309,6 +309,7 @@
 			EmptyValidator,
 			GitRepositoryValidator,
 			ServiceValidator,
+			ServiceAnalyzisValidator,
 			GatewayHTTPSValidator,
 			GatewayTCPValidator,
 		),
@@ -488,6 +489,56 @@
 	return noName.concat(noSource).concat(noRuntime).concat(noPorts).concat(noIngress).concat(multipleIngress);
 }
 
+function ServiceAnalyzisValidator(nodes: AppNode[]): Message[] {
+	const apps = nodes.filter((n) => n.type === "app");
+	return apps
+		.filter((n) => n.data.info != null)
+		.flatMap((n) => {
+			return n.data
+				.info!.configVars.map((cv): Message | undefined => {
+					if (cv.semanticType === "PORT") {
+						if (
+							!(n.data.envVars || []).some(
+								(p) => ("name" in p && p.name === cv.name) || ("alias" in p && p.alias === cv.name),
+							)
+						) {
+							return {
+								id: `${n.id}-missing-port-${cv.name}`,
+								type: "WARNING",
+								nodeId: n.id,
+								message: `Service requires port env variable ${cv.name}`,
+							};
+						}
+					}
+					if (cv.category === "EnvironmentVariable") {
+						if (
+							!(n.data.envVars || []).some(
+								(p) => ("name" in p && p.name === cv.name) || ("alias" in p && p.alias === cv.name),
+							)
+						) {
+							if (cv.semanticType === "FILESYSTEM_PATH") {
+								return {
+									id: `${n.id}-missing-env-${cv.name}`,
+									type: "FATAL",
+									nodeId: n.id,
+									message: `Service requires env variable ${cv.name}, representing filesystem path`,
+								};
+							} else if (cv.semanticType === "POSTGRES_URL") {
+								return {
+									id: `${n.id}-missing-env-${cv.name}`,
+									type: "FATAL",
+									nodeId: n.id,
+									message: `Service requires env variable ${cv.name}, representing postgres connection URL`,
+								};
+							}
+						}
+					}
+					return undefined;
+				})
+				.filter((m) => m !== undefined);
+		});
+}
+
 function GatewayHTTPSValidator(nodes: AppNode[]): Message[] {
 	const ing = nodes.filter((n) => n.type === "gateway-https");
 	const noNetwork: Message[] = ing
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index b7413d9..5d8013d 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -1,6 +1,6 @@
 import { Category, defaultCategories } from "./categories";
 import { CreateValidators, Validator } from "./config";
-import { GitHubService, GitHubServiceImpl } from "./github";
+import { GitHubService, GitHubServiceImpl, GitHubRepository } from "./github";
 import type { Edge, Node, OnConnect, OnEdgesChange, OnNodesChange, Viewport as ReactFlowViewport } from "@xyflow/react";
 import {
 	addEdge,
@@ -16,6 +16,41 @@
 import { z } from "zod";
 import { create } from "zustand";
 
+export const serviceAnalyzisSchema = z.object({
+	name: z.string(),
+	location: z.string(),
+	configVars: z.array(
+		z.object({
+			name: z.string(),
+			category: z.enum(["CommandLineFlag", "EnvironmentVariable"]),
+			type: z.optional(z.enum(["String", "Number", "Boolean"])),
+			semanticType: z.optional(
+				z.enum([
+					"EXPANDED_ENV_VAR",
+					"PORT",
+					"FILESYSTEM_PATH",
+					"DATABASE_URL",
+					"SQLITE_PATH",
+					"POSTGRES_URL",
+					"POSTGRES_PASSWORD",
+					"POSTGRES_USER",
+					"POSTGRES_DB",
+					"POSTGRES_PORT",
+					"POSTGRES_HOST",
+					"POSTGRES_SSL",
+					"MONGO_URL",
+					"MONGO_PASSWORD",
+					"MONGO_USER",
+					"MONGO_DB",
+					"MONGO_PORT",
+					"MONGO_HOST",
+					"MONGO_SSL",
+				]),
+			),
+		}),
+	),
+});
+
 export type InitData = {
 	label: string;
 	envVars: BoundEnvVar[];
@@ -125,6 +160,7 @@
 				codeServerNodeId: string;
 				sshNodeId: string;
 		  };
+	info?: z.infer<typeof serviceAnalyzisSchema>;
 };
 
 export type ServiceNode = Node<ServiceData> & {
@@ -425,7 +461,8 @@
 
 export const envSchema = z.object({
 	managerAddr: z.optional(z.string().min(1)),
-	deployKey: z.optional(z.nullable(z.string().min(1))),
+	instanceId: z.optional(z.string().min(1)),
+	deployKeyPublic: z.optional(z.nullable(z.string().min(1))),
 	networks: z
 		.array(
 			z.object({
@@ -451,7 +488,8 @@
 
 const defaultEnv: Env = {
 	managerAddr: undefined,
-	deployKey: undefined,
+	deployKeyPublic: undefined,
+	instanceId: undefined,
 	networks: [],
 	integrations: {
 		github: false,
@@ -499,6 +537,9 @@
 	viewport: Viewport;
 	setViewport: (viewport: Viewport) => void;
 	githubService: GitHubService | null;
+	githubRepositories: GitHubRepository[];
+	githubRepositoriesLoading: boolean;
+	githubRepositoriesError: string | null;
 	setHighlightCategory: (name: string, active: boolean) => void;
 	onNodesChange: OnNodesChange<AppNode>;
 	onEdgesChange: OnEdgesChange;
@@ -512,6 +553,7 @@
 	updateNodeData: <T extends NodeType>(id: string, data: NodeDataUpdate<T>) => void;
 	replaceEdge: (c: Connection, id?: string) => void;
 	refreshEnv: () => Promise<void>;
+	fetchGithubRepositories: () => Promise<void>;
 };
 
 const projectIdSelector = (state: AppState) => state.projectId;
@@ -520,6 +562,9 @@
 const githubServiceSelector = (state: AppState) => state.githubService;
 const envSelector = (state: AppState) => state.env;
 const zoomSelector = (state: AppState) => state.zoom;
+const githubRepositoriesSelector = (state: AppState) => state.githubRepositories;
+const githubRepositoriesLoadingSelector = (state: AppState) => state.githubRepositoriesLoading;
+const githubRepositoriesErrorSelector = (state: AppState) => state.githubRepositoriesError;
 
 export function useZoom(): ReactFlowViewport {
 	return useStateStore(zoomSelector);
@@ -561,6 +606,22 @@
 	return useStateStore(githubServiceSelector);
 }
 
+export function useGithubRepositories(): GitHubRepository[] {
+	return useStateStore(githubRepositoriesSelector);
+}
+
+export function useGithubRepositoriesLoading(): boolean {
+	return useStateStore(githubRepositoriesLoadingSelector);
+}
+
+export function useGithubRepositoriesError(): string | null {
+	return useStateStore(githubRepositoriesErrorSelector);
+}
+
+export function useFetchGithubRepositories(): () => Promise<void> {
+	return useStateStore((state) => state.fetchGithubRepositories);
+}
+
 export function useMode(): "edit" | "deploy" {
 	return useStateStore((state) => state.mode);
 }
@@ -851,6 +912,29 @@
 			}
 		}
 	}
+
+	const fetchGithubRepositories = async () => {
+		const { githubService, projectId } = get();
+		if (!githubService || !projectId) {
+			set({
+				githubRepositories: [],
+				githubRepositoriesError: "GitHub service or Project ID not available.",
+				githubRepositoriesLoading: false,
+			});
+			return;
+		}
+
+		set({ githubRepositoriesLoading: true, githubRepositoriesError: null });
+		try {
+			const repos = await githubService.getRepositories();
+			set({ githubRepositories: repos, githubRepositoriesLoading: false });
+		} catch (error) {
+			console.error("Failed to fetch GitHub repositories in store:", error);
+			const errorMessage = error instanceof Error ? error.message : "Unknown error fetching repositories";
+			set({ githubRepositories: [], githubRepositoriesError: errorMessage, githubRepositoriesLoading: false });
+		}
+	};
+
 	return {
 		projectId: undefined,
 		mode: "edit",
@@ -873,6 +957,9 @@
 			zoom: 1,
 		},
 		githubService: null,
+		githubRepositories: [],
+		githubRepositoriesLoading: false,
+		githubRepositoriesError: null,
 		setViewport: (viewport) => {
 			const { viewport: vp } = get();
 			if (
@@ -970,13 +1057,30 @@
 			} catch (error) {
 				console.error("Failed to fetch integrations:", error);
 			} finally {
-				if (JSON.stringify(get().env) !== JSON.stringify(env)) {
+				const oldEnv = get().env;
+				const oldGithubIntegrationStatus = oldEnv.integrations.github;
+				if (JSON.stringify(oldEnv) !== JSON.stringify(env)) {
 					set({ env });
 					injectNetworkNodes();
+					let ghService = null;
 					if (env.integrations.github) {
-						set({ githubService: new GitHubServiceImpl(projectId!) });
-					} else {
-						set({ githubService: null });
+						ghService = new GitHubServiceImpl(projectId!);
+					}
+					if (get().githubService !== ghService || (ghService && !get().githubService)) {
+						set({ githubService: ghService });
+					}
+					if (
+						ghService &&
+						(oldGithubIntegrationStatus !== env.integrations.github || !oldEnv.integrations.github)
+					) {
+						get().fetchGithubRepositories();
+					}
+					if (!env.integrations.github) {
+						set({
+							githubRepositories: [],
+							githubRepositoriesError: null,
+							githubRepositoriesLoading: false,
+						});
 					}
 				}
 			}
@@ -992,10 +1096,13 @@
 			stopRefreshEnvInterval();
 			set({
 				projectId,
+				githubRepositories: [],
+				githubRepositoriesLoading: false,
+				githubRepositoriesError: null,
 			});
 			if (projectId) {
 				await get().refreshEnv();
-				if (get().env.deployKey) {
+				if (get().env.instanceId) {
 					set({ mode: "deploy" });
 				} else {
 					set({ mode: "edit" });
@@ -1008,8 +1115,12 @@
 					edges: [],
 					env: defaultEnv,
 					githubService: null,
+					githubRepositories: [],
+					githubRepositoriesLoading: false,
+					githubRepositoriesError: null,
 				});
 			}
 		},
+		fetchGithubRepositories: fetchGithubRepositories,
 	};
 });