env-manager: ui polish (#127)

* env-manager: migrate to pico 2.0.6

* env: option to hide children from ui

* introduce template hierarchy

* style: improve menu styling

* env: reorganize tasks, pull before install

---------

Co-authored-by: Giorgi Lekveishvili <lekva@gl-mbp-m1-max.local>
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
index 5436702..f8ee70d 100644
--- a/core/installer/welcome/env.go
+++ b/core/installer/welcome/env.go
@@ -1,11 +1,11 @@
 package welcome
 
 import (
-	_ "embed"
+	"embed"
 	"encoding/json"
 	"errors"
 	"fmt"
-	htemplate "html/template"
+	"html/template"
 	"io"
 	"io/fs"
 	"log"
@@ -21,11 +21,46 @@
 	"github.com/giolekva/pcloud/core/installer/tasks"
 )
 
-//go:embed create-env.html
-var createEnvFormHtml []byte
+//go:embed env-manager-tmpl/*
+var tmpls embed.FS
 
-//go:embed env-created.html
-var envCreatedHtml string
+var tmplsParsed templates
+
+func init() {
+	if t, err := parseTemplates(tmpls); err != nil {
+		panic(err)
+	} else {
+		tmplsParsed = t
+	}
+}
+
+type templates struct {
+	form   *template.Template
+	status *template.Template
+}
+
+func parseTemplates(fs embed.FS) (templates, error) {
+	base, err := template.ParseFS(fs, "env-manager-tmpl/base.html")
+	if err != nil {
+		return templates{}, err
+	}
+	parse := func(path string) (*template.Template, error) {
+		if b, err := base.Clone(); err != nil {
+			return nil, err
+		} else {
+			return b.ParseFS(fs, path)
+		}
+	}
+	form, err := parse("env-manager-tmpl/form.html")
+	if err != nil {
+		return templates{}, err
+	}
+	status, err := parse("env-manager-tmpl/status.html")
+	if err != nil {
+		return templates{}, err
+	}
+	return templates{form, status}, nil
+}
 
 type Status string
 
@@ -108,12 +143,7 @@
 	if !ready && len(info.Records) > 0 {
 		panic("!! SHOULD NOT REACH !!")
 	}
-	tmpl, err := htemplate.New("response").Parse(envCreatedHtml)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	if err := tmpl.Execute(w, map[string]any{
+	if err := tmplsParsed.status.Execute(w, map[string]any{
 		"Root":       t,
 		"DNSRecords": info.Records,
 	}); err != nil {
@@ -158,7 +188,7 @@
 }
 
 func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
-	if _, err := w.Write(createEnvFormHtml); err != nil {
+	if err := tmplsParsed.form.Execute(w, nil); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 	}
 }
@@ -294,10 +324,6 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	if err := s.repo.CommitAndPush(fmt.Sprintf("Allocate CIDR for %s", req.Name)); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
 	// if err := s.acceptInvitation(req.SecretToken); err != nil {
 	// 	http.Error(w, err.Error(), http.StatusInternalServerError)
 	// 	return
@@ -329,6 +355,10 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
+	if err := s.repo.CommitAndPush(fmt.Sprintf("Allocate CIDR for %s", req.Name)); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
 	t, dns := tasks.NewCreateEnvTask(
 		tasks.Env{
 			PCloudEnvName:  env.Name,