GL-7 Part 2: install-command generator on the customer page
Replaces the static Option-1/2 Setup Command blocks with an interactive, client-side builder: mode (required radio), cores/memory (required for byo, optional for appliance), vmid, node, acl-storages (quote-wrapped), operator-pubkey-file, preserve-state-from, and --dry-run/--preflight-only/ --skip-provision/--allow-new-leaf checkboxes. genFlags()/genUpdate() assemble a live-updating download-then-run command (never curl|bash) + a local-run variant, enforcing the script's own rules client-side (mode required; byo requires caps → shows a warning + no runnable command; appliance hides the caps requirement; allow-new-leaf shows its leaf-regen warning). Emits ONLY real host-install v1.12.0 flags; the dangerous/operator-only set (--force/--rotate-recovery/--enable-oob/--remove-golden/--uninstall/ --adopt-pool/--rescope-acl) is never offered. Graceful static fallback: the server-rendered Option-1/2 commands keep --customer-id + a --mode placeholder when JS is off. No framework/CDN/network; ScriptVersion (const, in sync with SCRIPT_VERSION) drives the header. Render/structure test covers the control ids, version, fallback, and the excluded-flag absence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -174,3 +174,57 @@ func TestTemplates_PassphraseHardened(t *testing.T) {
|
||||
t.Errorf("raw secret is the retrieval-pw node's visible default text (must be masked)")
|
||||
}
|
||||
}
|
||||
|
||||
// GL-7 Part 2/3: the install-command generator renders its control surface, targets the right
|
||||
// script version, keeps a JS-off static fallback command, and NEVER offers the dangerous/operator-
|
||||
// only flags as controls.
|
||||
func TestTemplates_InstallGenerator(t *testing.T) {
|
||||
st, err := store.New(filepath.Join(t.TempDir(), "t.db"), log.New(io.Discard, "", 0))
|
||||
if err != nil {
|
||||
t.Fatalf("store.New: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
if err := st.SaveCustomerConfig(&store.CustomerConfig{
|
||||
CustomerID: "peti-felhom", CustomerName: "Peti", Domain: "sajatfelhom.hu",
|
||||
RetrievalPassword: "pw", APIKey: "k", Status: "active",
|
||||
}); err != nil {
|
||||
t.Fatalf("SaveCustomerConfig: %v", err)
|
||||
}
|
||||
s := New(st, "", "", "test", time.Hour, log.New(io.Discard, "", 0))
|
||||
rr := httptest.NewRecorder()
|
||||
s.handleCustomerUnified(rr, httptest.NewRequest("GET", "/configs/peti-felhom", nil), "peti-felhom")
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("status = %d", rr.Code)
|
||||
}
|
||||
html := rr.Body.String()
|
||||
|
||||
// control surface present (curated subset — all real v1.12.0 flags)
|
||||
for _, id := range []string{
|
||||
`name="gen-mode" value="appliance"`, `name="gen-mode" value="byo"`,
|
||||
`id="gen-cores"`, `id="gen-memory"`, `id="gen-vmid"`, `id="gen-node"`, `id="gen-acl"`,
|
||||
`id="gen-pubkey"`, `id="gen-preserve"`, `id="gen-dry"`, `id="gen-preflight"`,
|
||||
`id="gen-skip"`, `id="gen-leaf"`,
|
||||
} {
|
||||
if !strings.Contains(html, id) {
|
||||
t.Errorf("generator control missing: %s", id)
|
||||
}
|
||||
}
|
||||
// targets the right script version + carries the client-side customer id
|
||||
if !strings.Contains(html, hostInstallVersion) {
|
||||
t.Errorf("ScriptVersion %s not rendered", hostInstallVersion)
|
||||
}
|
||||
if !strings.Contains(html, `data-customer-id="peti-felhom"`) {
|
||||
t.Errorf("generator missing data-customer-id")
|
||||
}
|
||||
// JS-off static fallback: the Option-1/2 commands still show --customer-id + a mode placeholder
|
||||
if !strings.Contains(html, "--customer-id peti-felhom --mode") {
|
||||
t.Errorf("static fallback command missing customer-id + mode")
|
||||
}
|
||||
// the dangerous/operator-only flags are NEVER offered as generator controls
|
||||
for _, f := range []string{"--force", "--rotate-recovery", "--enable-oob", "--remove-golden",
|
||||
"--uninstall", "--adopt-pool", "--rescope-acl"} {
|
||||
if strings.Contains(html, f) {
|
||||
t.Errorf("excluded flag %s must not appear on the customer page", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user