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/app_manager.go b/core/installer/app_manager.go
index 744f9a2..e3fc8b1 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -52,9 +52,9 @@
}
func (m *AppManager) Install(app App, ns NamespaceGenerator, suffixGen SuffixGenerator, config map[string]any) error {
- // if err := m.repoIO.Fetch(); err != nil {
- // return err
- // }
+ if err := m.repoIO.Pull(); err != nil {
+ return err
+ }
suffix, err := suffixGen.Generate()
if err != nil {
return err
@@ -100,9 +100,9 @@
}
func (m *AppManager) Update(app App, instanceId string, config map[string]any) error {
- // if err := m.repoIO.Fetch(); err != nil {
- // return err
- // }
+ if err := m.repoIO.Pull(); err != nil {
+ return err
+ }
globalConfig, err := m.repoIO.ReadConfig()
if err != nil {
return err
@@ -126,9 +126,9 @@
}
func (m *AppManager) Remove(instanceId string) error {
- // if err := m.repoIO.Fetch(); err != nil {
- // return err
- // }
+ if err := m.repoIO.Pull(); err != nil {
+ return err
+ }
return m.repoIO.RemoveApp(filepath.Join(appDir, instanceId))
}
diff --git a/core/installer/bootstrapper.go b/core/installer/bootstrapper.go
index e496709..fa1a997 100644
--- a/core/installer/bootstrapper.go
+++ b/core/installer/bootstrapper.go
@@ -50,7 +50,7 @@
if err := b.installSoftServe(bootstrapJobKeys.AuthorizedKey(), env.Name, env.ServiceIPs.ConfigRepo); err != nil {
return err
}
- time.Sleep(2 * time.Minute)
+ time.Sleep(30 * time.Second)
ss, err := soft.WaitForClient(
netip.AddrPortFrom(env.ServiceIPs.ConfigRepo, 22).String(),
bootstrapJobKeys.RawPrivateKey(),
@@ -383,6 +383,7 @@
func (b Bootstrapper) installInfrastructureServices(repo RepoIO, nsGen NamespaceGenerator, nsCreator NamespaceCreator, env EnvConfig) error {
appRepo := NewInMemoryAppRepository(CreateAllApps())
install := func(name string) error {
+ fmt.Printf("Installing infrastructure service %s\n", name)
app, err := appRepo.Find(name)
if err != nil {
return err
diff --git a/core/installer/repoio.go b/core/installer/repoio.go
index dbbeda9..01a8f91 100644
--- a/core/installer/repoio.go
+++ b/core/installer/repoio.go
@@ -24,7 +24,7 @@
type RepoIO interface {
Addr() string
- Fetch() error
+ Pull() error
ReadConfig() (Config, error)
ReadAppConfig(path string) (AppConfig, error)
ReadKustomization(path string) (*Kustomization, error)
@@ -61,16 +61,27 @@
return r.repo.Addr.Addr
}
-func (r *repoIO) Fetch() error {
- err := r.repo.Fetch(&git.FetchOptions{
- RemoteName: "origin",
- Auth: auth(r.signer),
- Force: true,
- })
- if err == nil || err == git.NoErrAlreadyUpToDate {
+func (r *repoIO) Pull() error {
+ r.l.Lock()
+ defer r.l.Unlock()
+ return r.pullWithoutLock()
+}
+
+func (r *repoIO) pullWithoutLock() error {
+ wt, err := r.repo.Worktree()
+ if err != nil {
+ fmt.Printf("EEEER wt: %s\b", err)
return nil
}
- return err
+ err = wt.Pull(&git.PullOptions{
+ Auth: auth(r.signer),
+ Force: true,
+ })
+ // TODO(gio): propagate error
+ if err != nil {
+ fmt.Printf("EEEER: %s\b", err)
+ }
+ return nil
}
func (r *repoIO) ReadConfig() (Config, error) {
@@ -238,6 +249,9 @@
func (r *repoIO) InstallApp(app App, appRootDir string, values map[string]any, derived Derived) error {
r.l.Lock()
defer r.l.Unlock()
+ if err := r.pullWithoutLock(); err != nil {
+ return err
+ }
if !filepath.IsAbs(appRootDir) {
return fmt.Errorf("Expected absolute path: %s", appRootDir)
}
diff --git a/core/installer/tasks/activate.go b/core/installer/tasks/activate.go
index 0980dee..1333f28 100644
--- a/core/installer/tasks/activate.go
+++ b/core/installer/tasks/activate.go
@@ -13,15 +13,10 @@
//go:embed env-tmpl
var filesTmpls embed.FS
-type activateEnvTask struct {
- basicTask
- env Env
- st *state
-}
-
func NewActivateEnvTask(env Env, st *state) Task {
return newSequentialParentTask(
- fmt.Sprintf("Activate new %s instance", env.PCloudEnvName),
+ "Activate GitOps",
+ false,
AddNewEnvTask(env, st),
// TODO(gio): sync dodo-flux
)
diff --git a/core/installer/tasks/dns.go b/core/installer/tasks/dns.go
index 0880782..25af486 100644
--- a/core/installer/tasks/dns.go
+++ b/core/installer/tasks/dns.go
@@ -16,7 +16,8 @@
func SetupZoneTask(env Env, ingressIP net.IP, st *state) Task {
return newSequentialParentTask(
- fmt.Sprintf("Setup DNS zone records for %s", env.Domain),
+ "Configure DNS",
+ true,
CreateZoneRecords(env.Domain, st.publicIPs, ingressIP, env, st),
WaitToPropagate(env.Domain, st.publicIPs),
)
@@ -29,7 +30,7 @@
env Env,
st *state,
) Task {
- t := newLeafTask("Configure DNS", func() error {
+ t := newLeafTask("Generate and publish DNS records", func() error {
repo, err := st.ssClient.GetRepo("config")
if err != nil {
return err
@@ -90,12 +91,17 @@
}); err != nil {
return err
}
- rootKust := installer.NewKustomization()
- rootKust.AddResources("dns-zone.yaml")
- if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
+ rootKust, err := r.ReadKustomization("kustomization.yaml")
+ if err != nil {
return err
}
- r.CommitAndPush("configure dns zone")
+ rootKust.AddResources("dns-zone.yaml")
+ if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
+ return err
+ }
+ if err := r.CommitAndPush("configure dns zone"); err != nil {
+ return err
+ }
}
return nil
})
@@ -106,7 +112,7 @@
name string,
expected []net.IP,
) Task {
- t := newLeafTask("Propagate DNS records", func() error {
+ t := newLeafTask("Wait to propagate", func() error {
ctx := context.TODO()
gotExpectedIPs := func(actual []net.IP) bool {
for _, a := range actual {
diff --git a/core/installer/tasks/env.go b/core/installer/tasks/env.go
index eb537da..80289c4 100644
--- a/core/installer/tasks/env.go
+++ b/core/installer/tasks/env.go
@@ -52,14 +52,10 @@
}
t := newSequentialParentTask(
"Create env",
- append(
- []Task{
- SetupConfigRepoTask(env, &st),
- NewActivateEnvTask(env, &st),
- SetupZoneTask(env, startIP, &st),
- },
- SetupInfra(env, startIP, &st)...,
- )...,
+ true,
+ SetupConfigRepoTask(env, &st),
+ SetupZoneTask(env, startIP, &st),
+ SetupInfra(env, startIP, &st),
)
rctx, done := context.WithCancel(context.Background())
t.OnDone(func(_ error) {
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index 0216f84..0685a6d 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -13,8 +13,8 @@
var initGroups = []string{"admin"}
-func SetupInfra(env Env, startIP net.IP, st *state) []Task {
- t := newLeafTask("Create client", func() error {
+func CreateRepoClient(env Env, st *state) Task {
+ t := newLeafTask("Create repo client", func() error {
repo, err := st.ssClient.GetRepo("config")
if err != nil {
return err
@@ -30,25 +30,25 @@
st.emptySuffixGen = installer.NewEmptySuffixGenerator()
return nil
})
- return []Task{
- CommitEnvironmentConfiguration(env, st),
- ConfigureFirstAccount(env, st),
- &t,
- newConcurrentParentTask(
- "Core services",
- SetupNetwork(env, startIP, st),
- SetupCertificateIssuers(env, st),
- SetupAuth(env, st),
- SetupGroupMemberships(env, st),
- SetupHeadscale(env, startIP, st),
- SetupWelcome(env, st),
- SetupAppStore(env, st),
- ),
- }
+ return &t
+}
+
+func SetupInfra(env Env, startIP net.IP, st *state) Task {
+ return newConcurrentParentTask(
+ "Setup core services",
+ true,
+ SetupNetwork(env, startIP, st),
+ SetupCertificateIssuers(env, st),
+ SetupAuth(env, st),
+ SetupGroupMemberships(env, st),
+ SetupHeadscale(env, startIP, st),
+ SetupWelcome(env, st),
+ SetupAppStore(env, st),
+ )
}
func CommitEnvironmentConfiguration(env Env, st *state) Task {
- t := newLeafTask("Configure environment infrastructure", func() error {
+ t := newLeafTask("commit config", func() error {
repo, err := st.ssClient.GetRepo("config")
if err != nil {
return err
@@ -129,7 +129,7 @@
}
func SetupNetwork(env Env, startIP net.IP, st *state) Task {
- t := newLeafTask("Setup network", func() error {
+ t := newLeafTask("Setup private and public networks", func() error {
startAddr, err := netip.ParseAddr(startIP.String())
if err != nil {
return err
@@ -232,7 +232,7 @@
}
return nil
})
- return newSequentialParentTask("Configure TLS certificate issuers", &pub, &priv)
+ return newSequentialParentTask("Configure TLS certificate issuers", false, &pub, &priv)
}
func SetupAuth(env Env, st *state) Task {
@@ -250,6 +250,7 @@
})
return newSequentialParentTask(
"Authentication services",
+ false,
&t,
waitForAddr(fmt.Sprintf("https://accounts-ui.%s", env.Domain)),
)
@@ -269,7 +270,8 @@
return nil
})
return newSequentialParentTask(
- "Group Membership",
+ "Group membership",
+ false,
&t,
waitForAddr(fmt.Sprintf("https://memberships.p.%s", env.Domain)),
)
@@ -290,7 +292,8 @@
return nil
})
return newSequentialParentTask(
- "Headscale service",
+ "Setup mesh VPN",
+ false,
&t,
waitForAddr(fmt.Sprintf("https://headscale.%s/apple", env.Domain)),
)
@@ -323,6 +326,7 @@
})
return newSequentialParentTask(
"Welcome service",
+ false,
&t,
waitForAddr(fmt.Sprintf("https://welcome.%s", env.Domain)),
)
diff --git a/core/installer/tasks/init.go b/core/installer/tasks/init.go
index f898a1f..e6dd47e 100644
--- a/core/installer/tasks/init.go
+++ b/core/installer/tasks/init.go
@@ -11,10 +11,23 @@
func SetupConfigRepoTask(env Env, st *state) Task {
return newSequentialParentTask(
- "Configuration repository",
- NewCreateConfigRepoTask(env, st),
- CreateGitClientTask(env, st),
+ "Configure Git repository for new environment",
+ true,
+ newSequentialParentTask(
+ "Start up Git server",
+ false,
+ NewCreateConfigRepoTask(env, st),
+ CreateGitClientTask(env, st),
+ ),
NewInitConfigRepoTask(env, st),
+ NewActivateEnvTask(env, st),
+ newSequentialParentTask(
+ "Create initial commit",
+ false,
+ CreateRepoClient(env, st),
+ CommitEnvironmentConfiguration(env, st),
+ ConfigureFirstAccount(env, st),
+ ),
)
}
@@ -85,7 +98,7 @@
}
func NewInitConfigRepoTask(env Env, st *state) Task {
- t := newLeafTask("Create Git repository for environment configuration", func() error {
+ t := newLeafTask("Configure access control lists", func() error {
st.fluxUserName = fmt.Sprintf("flux-%s", env.Name)
keys, err := installer.NewSSHKeyPair(st.fluxUserName)
if err != nil {
@@ -100,7 +113,23 @@
return err
}
repoIO := installer.NewRepoIO(repo, st.ssClient.Signer)
- if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", env.Name), "readme"); err != nil {
+ if err := func() error {
+ w, err := repoIO.Writer("README.md")
+ if err != nil {
+ return err
+ }
+ defer w.Close()
+ if _, err := fmt.Fprintf(w, "# %s PCloud environment", env.Name); err != nil {
+ return err
+ }
+ return nil
+ }(); err != nil {
+ return err
+ }
+ if err := repoIO.WriteKustomization("kustomization.yaml", installer.NewKustomization()); err != nil {
+ return err
+ }
+ if err := repoIO.CommitAndPush("init"); err != nil {
return err
}
if err := st.ssClient.AddUser(st.fluxUserName, keys.AuthorizedKey()); err != nil {
diff --git a/core/installer/tasks/tasks.go b/core/installer/tasks/tasks.go
index 75e8a68..6d0ec01 100644
--- a/core/installer/tasks/tasks.go
+++ b/core/installer/tasks/tasks.go
@@ -86,25 +86,31 @@
type parentTask struct {
leafTask
- subtasks []Task
+ subtasks []Task
+ showChildren bool
}
-func newParentTask(title string, start func() error, subtasks ...Task) parentTask {
+func newParentTask(title string, showChildren bool, start func() error, subtasks ...Task) parentTask {
return parentTask{
- leafTask: newLeafTask(title, start),
- subtasks: subtasks,
+ leafTask: newLeafTask(title, start),
+ subtasks: subtasks,
+ showChildren: showChildren,
}
}
func (t *parentTask) Subtasks() []Task {
- return t.subtasks
+ if t.showChildren {
+ return t.subtasks
+ } else {
+ return make([]Task, 0)
+ }
}
type sequentialParentTask struct {
parentTask
}
-func newSequentialParentTask(title string, subtasks ...Task) *sequentialParentTask {
+func newSequentialParentTask(title string, showChildren bool, subtasks ...Task) *sequentialParentTask {
start := func() error {
errCh := make(chan error)
for i := range subtasks[:len(subtasks)-1] {
@@ -124,7 +130,7 @@
return <-errCh
}
return &sequentialParentTask{
- parentTask: newParentTask(title, start, subtasks...),
+ parentTask: newParentTask(title, showChildren, start, subtasks...),
}
}
@@ -132,7 +138,7 @@
parentTask
}
-func newConcurrentParentTask(title string, subtasks ...Task) *concurrentParentTask {
+func newConcurrentParentTask(title string, showChildren bool, subtasks ...Task) *concurrentParentTask {
start := func() error {
errCh := make(chan error)
for i := range subtasks {
@@ -151,6 +157,6 @@
return nil
}
return &concurrentParentTask{
- parentTask: newParentTask(title, start, subtasks...),
+ parentTask: newParentTask(title, showChildren, start, subtasks...),
}
}
diff --git a/core/installer/tasks/tasks_test.go b/core/installer/tasks/tasks_test.go
index 7aa78f3..194be44 100644
--- a/core/installer/tasks/tasks_test.go
+++ b/core/installer/tasks/tasks_test.go
@@ -27,7 +27,7 @@
two := newLeafTask("two", func() error {
return nil
})
- l := newSequentialParentTask("parent", &one, &two)
+ l := newSequentialParentTask("parent", true, &one, &two)
done := make(chan error)
l.OnDone(func(err error) {
done <- err
@@ -46,7 +46,7 @@
two := newLeafTask("two", func() error {
return nil
})
- l := newSequentialParentTask("parent", &one, &two)
+ l := newSequentialParentTask("parent", true, &one, &two)
done := make(chan error)
l.OnDone(func(err error) {
done <- err
@@ -67,7 +67,7 @@
fmt.Println("two")
return fmt.Errorf("two")
})
- l := newSequentialParentTask("parent", &one, &two)
+ l := newSequentialParentTask("parent", true, &one, &two)
done := make(chan error)
l.OnDone(func(err error) {
done <- err
diff --git a/core/installer/welcome/create-env.html b/core/installer/welcome/create-env.html
deleted file mode 100644
index f3d0f65..0000000
--- a/core/installer/welcome/create-env.html
+++ /dev/null
@@ -1,86 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" data-theme="light">
- <head>
- <link rel="stylesheet" href="/static/pico.min.css">
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css">
- <link rel="stylesheet" href="/static/main.css">
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- </head>
- <body>
- <nav id="menu" class="container-fluid">
- <ul>
- <li class="dodo"><span style="color: #ffffff;">do</span><span class="highlight">do:</span></li>
- </ul>
- <ul>
- <li><a href="#">register</a></li>
- <li><a href="#">apps</a></li>
- <li><a href="#" style="border-right: none;">about</a></li>
- </ul>
- </nav>
- <main class="container">
- <div class="grid contents-header">
- <div style="border-width: 1px; border-right-style: solid;">
- attention
- </div>
- <div>
- take <span class="highlight">control</span>
- </div>
- </div>
- <div id="contents" class="grid">
- <div style="border-width: 1px; border-right-style: solid;">
- As part of provisioning new dodo instance you will have to update DNS records at your domain registrar, so that it points to the nameservers running on your newly created dodo. Please first get familiar with your domain registrar documentation, and only then proceed with provisioning.
- <label for="accept" style="padding-top: 1rem;">
- <input type="checkbox" name="accept" id="accept" form="create-form" required tabindex="5">
- <strong>I understand</strong>
- </label>
- </div>
- <div id="create-instance-form">
- <form action="" method="POST" id="create-form">
- <label for="domain">
- domain
- <input
- type="text"
- id="domain"
- name="domain"
- required
- autofocus
- tabindex="1"
- />
- </label>
- <label for="contact-email">
- contact email
- <input
- type="email"
- id="contact-email"
- name="contact-email"
- required
- tabindex="2"
- />
- </label>
- <label for="admin-public-key">
- admin ssh public key
- <input
- type="string"
- id="admin-public-key"
- name="admin-public-key"
- required
- tabindex="3"
- /> <!-- TODO(gio): remove-->
- </label>
- <label for="secret-token">
- invitation code
- <textarea
- id="secret-token"
- name="secret-token"
- required
- tabindex="4"
- ></textarea>
- </label>
- <button type="submit" tabindex="6">provision</button>
- </form>
- </div>
- </div>
- </main>
- </body>
-</html>
diff --git a/core/installer/welcome/env-created.html b/core/installer/welcome/env-created.html
deleted file mode 100644
index c50457e..0000000
--- a/core/installer/welcome/env-created.html
+++ /dev/null
@@ -1,69 +0,0 @@
-{{ define "task" }}
-{{ range . }}
-<li aria-busy="{{ eq .Status 0 }}">
- {{ .Title }}{{ if .Err }} - {{ .Err.Error }} {{ end }}
- {{ if .Subtasks }}
- <ul>
- {{ template "task" .Subtasks }}
- </ul>
- {{ end }}
-</li>
-{{ end }}
-{{ end }}
-
-<!DOCTYPE html>
-<html lang="en" data-theme="light">
- <head>
- <link rel="stylesheet" href="/static/pico.min.css">
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css">
- <link rel="stylesheet" href="/static/main.css">
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- {{ if not (or (eq .Root.Status 2) (eq .Root.Status 3))}}
- <meta http-equiv="refresh" content="60">
- {{ end }}
- </head>
- <body>
- <nav id="menu" class="container-fluid">
- <ul>
- <li class="dodo">do<span class="highlight">do:</span></li>
- </ul>
- <ul>
- <li><a href="#">register</a></li>
- <li><a href="#">apps</a></li>
- <li><a href="#" style="border-right: none;">about</a></li>
- </ul>
- </nav>
- <main class="container">
- <article>
- <ul>
- {{ template "task" .Root.Subtasks }}
- </ul>
- </article>
- {{ if .DNSRecords }}
- <div>
- <form action="" method="POST">
- You will have to publish following DNS records via your domain registrar.
- <textarea rows="7">{{ .DNSRecords }}</textarea>
- <label for="domain-registrar">Domain Registrar</label>
- <select id="domain-registrar" required tabindex="1">
- <option value="" selected>Select registrar</option>
- <option value="gandi">Gandi</option>
- <option value="namecheap">Namecheap</option>
- </select>
- <label for="api-token">API Token</label>
- <input
- type="text"
- id="api-token"
- name="api-token"
- required
- autofocus
- tabindex="2"
- />
- <button type="submit" tabindex="3">Update</button>
- </form>
- </div>
- {{ end }}
- </main>
- </body>
-</html>
diff --git a/core/installer/welcome/env-manager-tmpl/base.html b/core/installer/welcome/env-manager-tmpl/base.html
new file mode 100644
index 0000000..a9ca048
--- /dev/null
+++ b/core/installer/welcome/env-manager-tmpl/base.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en" data-theme="light">
+ <head>
+ <link rel="stylesheet" href="/static/pico.2.0.6.min.css">
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css">
+ <link rel="stylesheet" href="/static/main.css">
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>dodo:</title>
+ </head>
+ <body>
+ <nav id="menu" class="container-fluid">
+ <ul>
+ <li class="dodo"><span style="color: #ffffff;">do</span><span class="highlight">do:</span></li>
+ </ul>
+ <ul id="links">
+ <li><a href="#">register</a></li>
+ <li><a href="#">apps</a></li>
+ <li><a href="#" style="border-right: none;">about</a></li>
+ </ul>
+ </nav>
+ <main class="container">
+ {{ block "main" . }}{{ end }}
+ </main>
+ </body>
+</html>
diff --git a/core/installer/welcome/env-manager-tmpl/form.html b/core/installer/welcome/env-manager-tmpl/form.html
new file mode 100644
index 0000000..c0f0d4a
--- /dev/null
+++ b/core/installer/welcome/env-manager-tmpl/form.html
@@ -0,0 +1,64 @@
+{{ define "main" }}
+<div class="grid contents-header">
+ <div style="border-width: 1px; border-right-style: solid;">
+ attention
+ </div>
+ <div>
+ take <span class="highlight">control</span>
+ </div>
+</div>
+<div id="contents" class="grid">
+ <div style="border-width: 1px; border-right-style: solid;">
+ As part of provisioning new dodo instance you will have to update DNS records at your domain registrar, so that it points to the nameservers running on your newly created dodo. Please first get familiar with your domain registrar documentation, and only then proceed with provisioning.
+ <label for="accept" style="padding-top: 1rem;">
+ <input type="checkbox" name="accept" id="accept" form="create-form" required tabindex="5">
+ <strong>I understand</strong>
+ </label>
+ </div>
+ <div id="create-instance-form">
+ <form action="" method="POST" id="create-form">
+ <label for="domain">
+ domain
+ <input
+ type="text"
+ id="domain"
+ name="domain"
+ required
+ autofocus
+ tabindex="1"
+ />
+ </label>
+ <label for="contact-email">
+ contact email
+ <input
+ type="email"
+ id="contact-email"
+ name="contact-email"
+ required
+ tabindex="2"
+ />
+ </label>
+ <label for="admin-public-key">
+ admin ssh public key
+ <input
+ type="string"
+ id="admin-public-key"
+ name="admin-public-key"
+ required
+ tabindex="3"
+ /> <!-- TODO(gio): remove-->
+ </label>
+ <label for="secret-token">
+ invitation code
+ <textarea
+ id="secret-token"
+ name="secret-token"
+ required
+ tabindex="4"
+ ></textarea>
+ </label>
+ <button type="submit" tabindex="6">provision</button>
+ </form>
+ </div>
+</div>
+{{ end }}
diff --git a/core/installer/welcome/env-manager-tmpl/status.html b/core/installer/welcome/env-manager-tmpl/status.html
new file mode 100644
index 0000000..3a54edf
--- /dev/null
+++ b/core/installer/welcome/env-manager-tmpl/status.html
@@ -0,0 +1,44 @@
+{{ define "task" }}
+{{ range . }}
+<li aria-busy="{{ eq .Status 0 }}">
+ {{ .Title }}{{ if .Err }} - {{ .Err.Error }} {{ end }}
+ {{ if .Subtasks }}
+ <ul>
+ {{ template "task" .Subtasks }}
+ </ul>
+ {{ end }}
+</li>
+{{ end }}
+{{ end }}
+
+{{ define "main" }}
+<article>
+ <ul>
+ {{ template "task" .Root.Subtasks }}
+ </ul>
+</article>
+{{ if .DNSRecords }}
+<div>
+ <form action="" method="POST">
+ You will have to publish following DNS records via your domain registrar.
+ <textarea rows="7">{{ .DNSRecords }}</textarea>
+ <label for="domain-registrar">Domain Registrar</label>
+ <select id="domain-registrar" required tabindex="1">
+ <option value="" selected>Select registrar</option>
+ <option value="gandi">Gandi</option>
+ <option value="namecheap">Namecheap</option>
+ </select>
+ <label for="api-token">API Token</label>
+ <input
+ type="text"
+ id="api-token"
+ name="api-token"
+ required
+ autofocus
+ tabindex="2"
+ />
+ <button type="submit" tabindex="3">Update</button>
+ </form>
+</div>
+{{ end }}
+{{ end }}
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,
diff --git a/core/installer/welcome/static/main.css b/core/installer/welcome/static/main.css
index 1b6ec68..2a01ae8 100644
--- a/core/installer/welcome/static/main.css
+++ b/core/installer/welcome/static/main.css
@@ -1,26 +1,29 @@
[data-theme="light"],
:root:not([data-theme="dark"]) {
- --font-family: Hack, monospace;
- --font-size: 14px;
- --background-color: #d6d6d6;
- --border-radius: 0;
- --form-element-border-color: #ffffff;
- --form-element-active-border-color: #7f9f7f;
- --primary: #7f9f7f;
- --primary-hover: #d4888d;
- --grid-spacing-horizontal: 0;
+ --pico-font-family: Hack, monospace;
+ --pico-font-size: 14px;
+ --pico-background-color: #d6d6d6;
+ --pico-border-radius: 0;
+ --pico-form-element-border-color: #ffffff;
+ --pico-form-element-active-border-color: #7f9f7f;
+ --pico-primary: #7f9f7f;
+ --pico-primary-background: #7f9f7f;
+ --pico-primary-hover: #d4888d;
+ --pico-primary-hover-background: #d4888d;
+ --pico-grid-spacing-horizontal: 0;
}
input[type="checkbox"] {
- --form-element-border-color: #3a3a3a;
+ --pico-form-element-border-color: #3a3a3a;
}
main.container {
max-width: 850px;
+ margin-top: 13rem;
}
.dodo {
- font-size: 2.5rem;
+ font-size: 1.6rem;
font-weight: bold;
background-color: #3a3a3a;
border-left: 1px dashed #3a3a3a;
@@ -32,12 +35,13 @@
#menu {
border-bottom: 1px dashed #3a3a3a;
+ padding-left: 5rem;
+ padding-right: 5rem;
}
#menu a {
- font-size: 1.4rem;
+ /* font-size: 1.2rem; */
color: #3a3a3a;
- border-right: 1px dashed #3a3a3a;
}
#menu li {
@@ -45,6 +49,14 @@
padding-bottom: 0;
}
+#links {
+ --pico-nav-element-spacing-horizontal: 3rem;
+}
+
+#menu li {
+ border-right: 1px dashed #3a3a3a;
+}
+
div.contents-header {
font-size: 1.2rem;
border-width: 1px;
@@ -56,6 +68,10 @@
border-style: none solid solid solid;
}
+main > div.grid {
+ --pico-grid-column-gap: 0;
+}
+
div.contents-header > div {
padding: 1rem 3rem;
}
@@ -65,7 +81,8 @@
}
#create-instance-form {
- --spacing: 10px;
+ --pico-spacing: 10px;
+ /* TODO(gio): figure out why overriding --pico-background-color does not work */
background-color: #3a3a3a;
}
@@ -75,6 +92,7 @@
#create-instance-form input, textarea, button {
border-width: 0.5px !important;
+ background-color: #3a3a3a;
}
#create-instance-form input {