Canvas: Organize back and front components

Change-Id: I0f2c0bbe47b2693127a367a72321b24eb1af7796
diff --git a/core/installer/server/appmanager/server.go b/core/installer/server/appmanager/server.go
index 91594ce..7621213 100644
--- a/core/installer/server/appmanager/server.go
+++ b/core/installer/server/appmanager/server.go
@@ -160,10 +160,22 @@
 }
 
 type dodoAppInstallReq struct {
-	Id     string         `json:"id"`
 	Config map[string]any `json:"config"`
 }
 
+type dodoAppInstallResp struct {
+	Id        string `json:"id"`
+	DeployKey string `json:"deployKey"`
+}
+
+type dodoAppRendered struct {
+	Input struct {
+		Key struct {
+			Public string `json:"public"`
+		} `json:"key"`
+	} `json:"input"`
+}
+
 func (s *Server) handleDodoAppInstall(w http.ResponseWriter, r *http.Request) {
 	var req dodoAppInstallReq
 	// TODO(gio): validate that no internal fields are overridden by request
@@ -187,11 +199,20 @@
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	if instanceId, err := s.install(app, map[string]any{}); err != nil {
+	if instanceId, rr, err := s.install(app, map[string]any{}); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	} else {
-		fmt.Fprintf(w, "/tasks/%s", instanceId)
+		var cfg dodoAppRendered
+		if err := json.NewDecoder(bytes.NewReader(rr.RenderedRaw)).Decode(&cfg); err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		}
+		if err := json.NewEncoder(w).Encode(dodoAppInstallResp{
+			Id:        instanceId,
+			DeployKey: cfg.Input.Key.Public,
+		}); err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		}
 	}
 }
 
@@ -326,21 +347,21 @@
 	}
 }
 
-func (s *Server) install(app installer.EnvApp, values map[string]any) (string, error) {
+func (s *Server) install(app installer.EnvApp, values map[string]any) (string, installer.ReleaseResources, error) {
 	env, err := s.m.Config()
 	if err != nil {
-		return "", err
+		return "", installer.ReleaseResources{}, err
 	}
 	suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
 	suffix, err := suffixGen.Generate()
 	if err != nil {
-		return "", err
+		return "", installer.ReleaseResources{}, err
 	}
 	instanceId := app.Slug() + suffix
 	appDir := fmt.Sprintf("/apps/%s", instanceId)
 	namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, app.Namespace(), suffix)
+	rr, err := s.m.Install(app, instanceId, appDir, namespace, values)
 	t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
-		rr, err := s.m.Install(app, instanceId, appDir, namespace, values)
 		if err == nil {
 			ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
 			go s.reconciler.Reconcile(ctx)
@@ -353,7 +374,7 @@
 	s.tasks[instanceId] = &taskForward{t, fmt.Sprintf("/instance/%s", instanceId), 0}
 	t.OnDone(s.cleanTask(instanceId, 0))
 	go t.Start()
-	return instanceId, nil
+	return instanceId, rr, nil
 }
 
 func (s *Server) handleAppInstall(w http.ResponseWriter, r *http.Request) {
@@ -374,7 +395,7 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	if instanceId, err := s.install(app, values); err != nil {
+	if instanceId, _, err := s.install(app, values); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	} else {