Installer: Implement multi network selector
Change-Id: I52227a0f0e964ac48cb378ead077fad941c3315c
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 2cc29ed..dda7f8b 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -897,6 +897,8 @@
return ret
case KindNetwork:
return []string{}
+ case KindMultiNetwork:
+ return []string{}
case KindAuth:
return []string{}
case KindSSHKey:
diff --git a/core/installer/app_test.go b/core/installer/app_test.go
index 7e64d50..5051cc9 100644
--- a/core/installer/app_test.go
+++ b/core/installer/app_test.go
@@ -373,3 +373,19 @@
t.Log(string(r))
}
}
+
+func TestDodoApp(t *testing.T) {
+ contents, err := valuesTmpls.ReadFile("values-tmpl/dodo-app.cue")
+ if err != nil {
+ t.Fatal(err)
+ }
+ app, err := NewCueEnvApp(CueAppData{
+ "base.cue": []byte(cueBaseConfig),
+ "app.cue": []byte(contents),
+ "global.cue": []byte(cueEnvAppGlobal),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Log(app.Schema())
+}
diff --git a/core/installer/derived.go b/core/installer/derived.go
index e4756be..7a5e5f3 100644
--- a/core/installer/derived.go
+++ b/core/installer/derived.go
@@ -92,11 +92,33 @@
}
ret[k] = a
case KindNetwork:
- n, err := findNetwork(networks, v.(string)) // TODO(giolekva): validate
+ name, ok := v.(string)
+ if !ok {
+ return nil, fmt.Errorf("not a string")
+ }
+ n, err := findNetwork(networks, name)
if err != nil {
return nil, err
}
ret[k] = n
+ case KindMultiNetwork:
+ vv, ok := v.([]any)
+ if !ok {
+ return nil, fmt.Errorf("not an array")
+ }
+ picked := []Network{}
+ for _, nn := range vv {
+ name, ok := nn.(string)
+ if !ok {
+ return nil, fmt.Errorf("not a string")
+ }
+ n, err := findNetwork(networks, name)
+ if err != nil {
+ return nil, err
+ }
+ picked = append(picked, n)
+ }
+ ret[k] = picked
case KindAuth:
r, err := deriveValues(v, AuthSchema, networks)
if err != nil {
@@ -157,6 +179,24 @@
return nil, fmt.Errorf("expected network name")
}
ret[k] = name
+ case KindMultiNetwork:
+ nl, ok := v.([]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ names := []string{}
+ for _, n := range nl {
+ i, ok := n.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ name, ok := i["name"]
+ if !ok {
+ return nil, fmt.Errorf("expected network name")
+ }
+ names = append(names, name.(string))
+ }
+ ret[k] = names
case KindAuth:
vm, ok := v.(map[string]any)
if !ok {
diff --git a/core/installer/schema.go b/core/installer/schema.go
index 943ef8a..8b49905 100644
--- a/core/installer/schema.go
+++ b/core/installer/schema.go
@@ -11,16 +11,17 @@
type Kind int
const (
- KindBoolean Kind = 0
- KindInt = 7
- KindString = 1
- KindStruct = 2
- KindNetwork = 3
- KindAuth = 5
- KindSSHKey = 6
- KindNumber = 4
- KindArrayString = 8
- KindPort = 9
+ KindBoolean Kind = 0
+ KindInt = 7
+ KindString = 1
+ KindStruct = 2
+ KindNetwork = 3
+ KindMultiNetwork = 10
+ KindAuth = 5
+ KindSSHKey = 6
+ KindNumber = 4
+ KindArrayString = 8
+ KindPort = 9
)
type Field struct {
@@ -82,6 +83,37 @@
return false
}
+const multiNetworkSchema = `
+#Network: {
+ name: string
+ ingressClass: string
+ certificateIssuer: string | *""
+ domain: string
+ allocatePortAddr: string
+ reservePortAddr: string
+ deallocatePortAddr: string
+}
+
+#Networks: [...#Network]
+
+value: %s
+`
+
+func isMultiNetwork(v cue.Value) bool {
+ if v.Value().IncompleteKind() != cue.ListKind {
+ return false
+ }
+ s := fmt.Sprintf(multiNetworkSchema, fmt.Sprintf("%#v", v))
+ c := cuecontext.New()
+ u := c.CompileString(s)
+ networks := u.LookupPath(cue.ParsePath("#Networks"))
+ vv := u.LookupPath(cue.ParsePath("value"))
+ if err := networks.Subsume(vv); err == nil {
+ return true
+ }
+ return false
+}
+
const authSchema = `
#Auth: {
enabled: bool | false
@@ -198,6 +230,9 @@
return basicSchema{name, KindInt, false}, nil
}
case cue.ListKind:
+ if isMultiNetwork(v) {
+ return basicSchema{name, KindMultiNetwork, false}, nil
+ }
return basicSchema{name, KindArrayString, false}, nil
case cue.StructKind:
if isNetwork(v) {
diff --git a/core/installer/values-tmpl/dodo-app.cue b/core/installer/values-tmpl/dodo-app.cue
index ee96044..7e2a1a7 100644
--- a/core/installer/values-tmpl/dodo-app.cue
+++ b/core/installer/values-tmpl/dodo-app.cue
@@ -8,7 +8,7 @@
network: #Network @name(Network)
subdomain: string @name(Subdomain)
sshPort: int @name(SSH Port) @role(port)
- allowedNetworks: string | *"" @name(Allowed Networks)
+ allowedNetworks: [...#Network] | *[] @name(Allowed Networks)
external: bool | *false @name(External)
// TODO(gio): auto generate
@@ -127,7 +127,7 @@
envConfig: base64.Encode(null, json.Marshal(global))
gitRepoPublicKey: input.ssKeys.public
persistentVolumeClaimName: volumes.db.name
- allowedNetworks: input.allowedNetworks
+ allowedNetworks: strings.Join([for n in input.allowedNetworks { n.name }], ",")
external: input.external
}
}
diff --git a/core/installer/welcome/appmanager-tmpl/app.html b/core/installer/welcome/appmanager-tmpl/app.html
index 2967bd0..e65c612 100644
--- a/core/installer/welcome/appmanager-tmpl/app.html
+++ b/core/installer/welcome/appmanager-tmpl/app.html
@@ -42,12 +42,37 @@
<label {{ if $schema.Advanced }}hidden{{ end }}>
{{ $schema.Name }}
<select name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} >
- {{ if not $readonly }}<option disabled selected value>Available networks</option>{{ end }}
+ {{ if not $readonly }}<option disabled selected value></option>{{ end }}
{{ range $networks }}
<option {{if eq .Name (index $data $name) }}selected{{ end }}>{{ .Name }}</option>
{{ end }}
</select>
</label>
+ {{ else if eq $schema.Kind 10 }}
+ <label {{ if $schema.Advanced }}hidden{{ end }}>
+ {{ $schema.Name }}
+ <details class="dropdown">
+ {{ $selectedNetworks := index $data $name }}
+ <summary id="{{ $name }}">{{ $selectedNetworks | join "," }}</summary>
+ <ul>
+ {{ range $networks }}
+ {{ $networkName := .Name }}
+ {{ $selected := false }}
+ {{ range $selectedNetworks }}
+ {{ if eq . $networkName }}
+ {{ $selected = true }}
+ {{ end }}
+ {{ end }}
+ <li>
+ <label>
+ <input type="checkbox" name="{{ $networkName }}" oninput="multiNetworkSelected('{{ $name }}', '{{ $networkName }}', this.checked)" {{ if $selected }}checked{{ end }} />
+ {{ .Name }}
+ </label>
+ </li>
+ {{ end }}
+ </ul>
+ </details>
+ </label>
{{ else if eq $schema.Kind 5 }}
{{ $auth := index $data $name }}
{{ $authEnabled := false }}
@@ -146,11 +171,37 @@
config = config[items[i]];
}
config[items[items.length - 1]] = value;
-}
+ }
+
+ function getValue(name, value) {
+ let items = name.split(".")
+ for (let i = 0; i < items.length - 1; i++) {
+ if (!(items[i] in config)) {
+ config[items[i]] = {}
+ }
+ config = config[items[i]];
+ }
+ return config[items[items.length - 1]];
+ }
+
function valueChanged(name, value) {
setValue(name, value, config);
}
+ function multiNetworkSelected(name, network, selected) {
+ let v = getValue(name, config);
+ if (v === undefined) {
+ v = [];
+ }
+ if (selected) {
+ v.push(network);
+ } else {
+ v = v.filter((n) => n != network);
+ }
+ setValue(name, v, config);
+ document.getElementById(name).innerHTML = v.join(",");
+ }
+
function disableForm() {
document.querySelectorAll("#config-form input").forEach((i) => i.setAttribute("disabled", ""));
document.querySelectorAll("#config-form select").forEach((i) => i.setAttribute("disabled", ""));
diff --git a/core/installer/welcome/appmanager-tmpl/base.html b/core/installer/welcome/appmanager-tmpl/base.html
index a32809f..efa4a35 100644
--- a/core/installer/welcome/appmanager-tmpl/base.html
+++ b/core/installer/welcome/appmanager-tmpl/base.html
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="/static/pico.2.0.6.min.css">
- <link rel="stylesheet" type="text/css" href="/static/appmanager.css?v=0.0.11">
+ <link rel="stylesheet" type="text/css" href="/static/appmanager.css?v=0.0.12">
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
diff --git a/core/installer/welcome/static/appmanager.css b/core/installer/welcome/static/appmanager.css
index 5bd3dc4..26bce1a 100644
--- a/core/installer/welcome/static/appmanager.css
+++ b/core/installer/welcome/static/appmanager.css
@@ -11,6 +11,9 @@
--pico-form-element-background-color: #d6d6d6;
--pico-form-element-active-background-color: #d6d6d6;
--pico-form-element-selected-background-color: #d6d6d6;
+ --pico-dropdown-background-color: #d6d6d6;
+ --pico-dropdown-border-color: #7f9f7f;
+ --pico-dropdown-hover-background-color: #7f9f7f;
--pico-primary: #7f9f7f;
--pico-primary-background: #7f9f7f;
--pico-primary-hover: #d4888d;