| {{ define "task" }} |
| {{ range . }} |
| <li aria-busy="{{ eq .Status 1 }}"> |
| {{ if eq .Status 3 }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><circle cx="12" cy="12" r="8" fill="green" fill-opacity="0.25"/><path stroke="green" stroke-width="1.2" d="m8.5 11l2.894 2.894a.15.15 0 0 0 .212 0L19.5 6"/><path stroke="green" stroke-linecap="round" d="M19.358 10.547a7.5 7.5 0 1 1-3.608-5.042"/></g></svg>{{ end }}{{ .Title }}{{ if .Err }} - {{ .Err.Error }} {{ end }} |
| {{ if .Subtasks }} |
| <ul> |
| {{ template "task" .Subtasks }} |
| </ul> |
| {{ end }} |
| </li> |
| {{ end }} |
| {{ end }} |
| |
| {{ define "schema-form" }} |
| {{ $readonly := .ReadOnly }} |
| {{ $networks := .AvailableNetworks }} |
| {{ $data := .Data }} |
| {{ range $f := .Schema.Fields }} |
| {{ $name := $f.Name }} |
| {{ $schema := $f.Schema }} |
| {{ if eq $schema.Kind 0 }} |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| <input type="checkbox" role="swtich" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.checked)" {{ if $readonly }}disabled{{ end }} {{ if index $data $name }}checked{{ end }} /> |
| {{ $schema.Name }} |
| </label> |
| {{ else if eq $schema.Kind 7 }} |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| {{ $schema.Name }} |
| <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, parseInt(this.value))" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" /> |
| </label> |
| {{ else if eq $schema.Kind 1 }} |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| {{ $schema.Name }} |
| <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" /> |
| {{ else if eq $schema.Kind 4 }} |
| </label> |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| {{ $schema.Name }} |
| <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" /> |
| </label> |
| {{ else if eq $schema.Kind 3 }} |
| <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 }} |
| {{ range $networks }} |
| <option {{if eq .Name (index $data $name) }}selected{{ end }}>{{ .Name }}</option> |
| {{ end }} |
| </select> |
| </label> |
| {{ else if eq $schema.Kind 5 }} |
| {{ $auth := index $data $name }} |
| {{ $authEnabled := false }} |
| {{ $authGroups := "" }} |
| {{ if and $auth (index $auth "enabled") }}{{ $authEnabled = true }}{{ end }} |
| {{ if and $auth (index $auth "groups") }}{{ $authGroups = index $auth "groups" }}{{ end }} |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| <input type="checkbox" role="swtich" name="authEnabled" oninput="valueChanged('{{- $name -}}.enabled', this.checked)" {{ if $readonly }}disabled{{ end }} {{ if $authEnabled }}checked{{ end }} /> |
| <span>Require authentication</span> |
| </label> |
| <label for="authGroups"> |
| <span>Authentication groups</span> |
| <input type="text" name="authGroups" oninput="valueChanged('{{- $name -}}.groups', this.value)" {{ if $readonly }}disabled{{ end }} value="{{ $authGroups }}" /> |
| </label> |
| {{ else if eq $schema.Kind 6 }} |
| {{ $sshKey := index $data $name }} |
| {{ $public := "" }} |
| {{ $private := "" }} |
| {{ if $sshKey }}{{ $public = index $sshKey "public" }}{{ end }} |
| {{ if $sshKey }}{{ $private = index $sshKey "private" }}{{ end }} |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| <span>Public Key</span> |
| <textarea name="{{ $name }}-public" disabled>{{ $public }}</textarea> |
| </label> |
| <label {{ if $schema.Advanced }}hidden{{ end }}> |
| <span>Private Key</span> |
| <textarea name="{{ $name }}-private" disabled>{{ $private }}</textarea> |
| </label> |
| {{ end }} |
| {{ end }} |
| {{ end }} |
| |
| {{ define "header" }} |
| <h1>{{ .App.Icon }}{{ .App.Name }}</h1> |
| {{ end }} |
| |
| {{ define "extra_menu" }} |
| <li><a href="/app/{{ .App.Slug }}" class="{{ if eq $.CurrentPage .App.Name }}outline{{ else }}secondary{{ end }}">{{ .App.Name }}</a></li> |
| {{ range .Instances }} |
| <li><a href="/instance/{{ .Id }}" class="{{ if eq $.CurrentPage .Id }}outline{{ else }}secondary{{ end }}">{{ .Id }}</a></li> |
| {{ end }} |
| {{ end }} |
| |
| {{ define "content"}} |
| {{ $schema := .App.Schema }} |
| {{ $networks := .AvailableNetworks }} |
| {{ $instance := .Instance }} |
| {{ $renderForm := true }} |
| |
| {{ if .Task }} |
| {{if or (eq .Task.Status 0) (eq .Task.Status 1) }} |
| {{ $renderForm = false }} |
| Waiting for resources: |
| <ul class="progress"> |
| {{ template "task" .Task.Subtasks }} |
| </ul> |
| <script>setTimeout(() => location.reload(), 3000);</script> |
| {{ end }} |
| {{ end }} |
| |
| {{ if $renderForm }} |
| <form id="config-form"> |
| {{ if $instance }} |
| {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" ($instance.InputToValues $schema)) }} |
| {{ else }} |
| {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" (dict)) }} |
| {{ end }} |
| {{ if $instance }} |
| <div class="grid"> |
| <button type="submit" id="submit" name="update">Update</button> |
| <button type="submit" id="uninstall" name="remove">Uninstall</button> |
| </div> |
| {{ else }} |
| <button type="submit" id="submit">{{ if $instance }}Update{{ else }}Install{{ end }}</button> |
| {{ end }} |
| </form> |
| {{ end }} |
| |
| <div id="toast-failure" class="toast hidden"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10Zm3-6L9 8m0 8l6-8"/></svg> {{ if $instance }}Update failed{{ else}}Install failed{{ end }} |
| </div> |
| |
| <div id="toast-uninstall-failure" class="toast hidden"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10Zm3-6L9 8m0 8l6-8"/></svg> Failed to uninstall application |
| </div> |
| |
| <script> |
| let config = {{ if $instance }}JSON.parse({{ toJson ($instance.InputToValues $schema) }}){{ else }}{}{{ end }}; |
| |
| function setValue(name, value, config) { |
| 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]]; |
| } |
| config[items[items.length - 1]] = value; |
| } |
| function valueChanged(name, value) { |
| setValue(name, value, config); |
| } |
| |
| function disableForm() { |
| document.querySelectorAll("#config-form input").forEach((i) => i.setAttribute("disabled", "")); |
| document.querySelectorAll("#config-form select").forEach((i) => i.setAttribute("disabled", "")); |
| document.querySelectorAll("#config-form button").forEach((i) => i.setAttribute("disabled", "")); |
| } |
| |
| function enableForm() { |
| document.querySelectorAll("[aria-busy]").forEach((i) => i.removeAttribute("aria-busy")); |
| document.querySelectorAll("#config-form input").forEach((i) => i.removeAttribute("disabled")); |
| document.querySelectorAll("#config-form select").forEach((i) => i.removeAttribute("disabled")); |
| document.querySelectorAll("#config-form button").forEach((i) => i.removeAttribute("disabled")); |
| } |
| |
| function installStarted() { |
| const submit = document.getElementById("submit"); |
| submit.setAttribute("aria-busy", true); |
| submit.innerHTML = {{ if $instance }}"Updating ..."{{ else }}"Installing ..."{{ end }}; |
| disableForm(); |
| } |
| |
| function uninstallStarted() { |
| const submit = document.getElementById("uninstall"); |
| submit.setAttribute("aria-busy", true); |
| submit.innerHTML = "Uninstalling ..."; |
| disableForm(); |
| } |
| |
| function actionFinished(toast) { |
| enableForm(); |
| toast.classList.remove("hidden"); |
| setTimeout( |
| () => toast.classList.add("hidden"), |
| 2000, |
| ); |
| } |
| |
| function installFailed() { |
| actionFinished(document.getElementById("toast-failure")); |
| } |
| |
| function uninstallFailed() { |
| actionFinished(document.getElementById("toast-uninstall-failure")); |
| } |
| |
| const submitAddr = {{ if $instance }}"/api/instance/{{ $instance.Id }}/update"{{ else }}"/api/app/{{ .App.Slug }}/install"{{ end }}; |
| |
| async function install() { |
| installStarted(); |
| const resp = await fetch(submitAddr, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| "Accept": "application/json", |
| }, |
| body: JSON.stringify(config), |
| }); |
| if (resp.status === 200) { |
| window.location = await resp.text(); |
| } else { |
| installFailed(); |
| } |
| } |
| |
| async function uninstall() { |
| {{ if $instance }} |
| uninstallStarted(); |
| const resp = await fetch("/api/instance/{{ $instance.Id }}/remove", { |
| method: "POST", |
| }); |
| if (resp.status === 200) { |
| window.location = await resp.text(); |
| } else { |
| uninstallFailed(); |
| } |
| {{ end }} |
| } |
| |
| document.getElementById("config-form").addEventListener("submit", (event) => { |
| event.preventDefault(); |
| if (event.submitter.id === "submit") { |
| install(); |
| } if (event.submitter.id === "uninstall") { |
| uninstall(); |
| } |
| }); |
| </script> |
| |
| {{end}} |