blob: 64c14581bd7cbbedf530f65f37c31eec86996419 [file] [log] [blame]
gio778577f2024-04-29 09:44:38 +04001{{ define "task" }}
2{{ range . }}
3<li aria-busy="{{ eq .Status 1 }}">
4 {{ 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 }}
5 {{ if .Subtasks }}
6 <ul>
7 {{ template "task" .Subtasks }}
8 </ul>
9 {{ end }}
10</li>
11{{ end }}
12{{ end }}
13
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040014{{ define "schema-form" }}
15 {{ $readonly := .ReadOnly }}
16 {{ $networks := .AvailableNetworks }}
17 {{ $data := .Data }}
gio44f621b2024-04-29 09:44:38 +040018 {{ range $f := .Schema.Fields }}
19 {{ $name := $f.Name }}
20 {{ $schema := $f.Schema }}
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040021 {{ if eq $schema.Kind 0 }}
gio44f621b2024-04-29 09:44:38 +040022 <label {{ if $schema.Advanced }}hidden{{ end }}>
23 <input type="checkbox" role="swtich" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.checked)" {{ if $readonly }}disabled{{ end }} {{ if index $data $name }}checked{{ end }} />
24 {{ $schema.Name }}
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040025 </label>
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040026 {{ else if eq $schema.Kind 7 }}
gio44f621b2024-04-29 09:44:38 +040027 <label {{ if $schema.Advanced }}hidden{{ end }}>
28 {{ $schema.Name }}
29 <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, parseInt(this.value))" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040030 </label>
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040031 {{ else if eq $schema.Kind 1 }}
gio44f621b2024-04-29 09:44:38 +040032 <label {{ if $schema.Advanced }}hidden{{ end }}>
33 {{ $schema.Name }}
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040034 <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040035 {{ else if eq $schema.Kind 4 }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040036 </label>
gio44f621b2024-04-29 09:44:38 +040037 <label {{ if $schema.Advanced }}hidden{{ end }}>
38 {{ $schema.Name }}
39 <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
40 </label>
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040041 {{ else if eq $schema.Kind 3 }}
gio44f621b2024-04-29 09:44:38 +040042 <label {{ if $schema.Advanced }}hidden{{ end }}>
43 {{ $schema.Name }}
44 <select name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} >
45 {{ if not $readonly }}<option disabled selected value>Available networks</option>{{ end }}
46 {{ range $networks }}
47 <option {{if eq .Name (index $data $name) }}selected{{ end }}>{{ .Name }}</option>
48 {{ end }}
49 </select>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040050 </label>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040051 {{ else if eq $schema.Kind 5 }}
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040052 {{ $auth := index $data $name }}
53 {{ $authEnabled := false }}
54 {{ $authGroups := "" }}
55 {{ if and $auth (index $auth "enabled") }}{{ $authEnabled = true }}{{ end }}
56 {{ if and $auth (index $auth "groups") }}{{ $authGroups = index $auth "groups" }}{{ end }}
gio44f621b2024-04-29 09:44:38 +040057 <label {{ if $schema.Advanced }}hidden{{ end }}>
58 <input type="checkbox" role="swtich" name="authEnabled" oninput="valueChanged('{{- $name -}}.enabled', this.checked)" {{ if $readonly }}disabled{{ end }} {{ if $authEnabled }}checked{{ end }} />
59 <span>Require authentication</span>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040060 </label>
gio44f621b2024-04-29 09:44:38 +040061 <label for="authGroups">
62 <span>Authentication groups</span>
63 <input type="text" name="authGroups" oninput="valueChanged('{{- $name -}}.groups', this.value)" {{ if $readonly }}disabled{{ end }} value="{{ $authGroups }}" />
64 </label>
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040065 {{ else if eq $schema.Kind 6 }}
66 {{ $sshKey := index $data $name }}
67 {{ $public := "" }}
68 {{ $private := "" }}
69 {{ if $sshKey }}{{ $public = index $sshKey "public" }}{{ end }}
70 {{ if $sshKey }}{{ $private = index $sshKey "private" }}{{ end }}
gio44f621b2024-04-29 09:44:38 +040071 <label {{ if $schema.Advanced }}hidden{{ end }}>
72 <span>Public Key</span>
73 <textarea name="{{ $name }}-public" disabled>{{ $public }}</textarea>
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040074 </label>
gio44f621b2024-04-29 09:44:38 +040075 <label {{ if $schema.Advanced }}hidden{{ end }}>
76 <span>Private Key</span>
77 <textarea name="{{ $name }}-private" disabled>{{ $private }}</textarea>
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040078 </label>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040079 {{ end }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040080 {{ end }}
81{{ end }}
82
83{{ define "main" }}
84{{ $instance := .Instance }}
gio44f621b2024-04-29 09:44:38 +040085<h1 style="margin-bottom: 20px">{{ .App.Icon }}{{ .App.Name }}</h1>
86<pre id="readme" style="margin-bottom: 50px">{{ .App.Description }}</pre>
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040087
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040088{{ $schema := .App.Schema }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040089{{ $networks := .AvailableNetworks }}
90
gio778577f2024-04-29 09:44:38 +040091{{ $renderForm := true }}
92
93{{ if .Task }}
94 {{if or (eq .Task.Status 0) (eq .Task.Status 1) }}
95 {{ $renderForm = false }}
96 Waiting for resources:
97 <ul class="progress">
98 {{ template "task" .Task.Subtasks }}
99 </ul>
100 <script>setTimeout(() => location.reload(), 3000);</script>
101 {{ end }}
102{{ end }}
103
104{{ if $renderForm }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400105<form id="config-form">
106 {{ if $instance }}
gio3cdee592024-04-17 10:15:56 +0400107 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" ($instance.InputToValues $schema)) }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400108 {{ else }}
109 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" (dict)) }}
110 {{ end }}
111 {{ if $instance }}
112 <div class="grid">
113 <button type="submit" id="submit" name="update">Update</button>
114 <button type="submit" id="uninstall" name="remove">Uninstall</button>
115 </div>
116 {{ else }}
117 <button type="submit" id="submit">{{ if $instance }}Update{{ else }}Install{{ end }}</button>
118 {{ end }}
119</form>
120
121{{ range .Instances }}
gio778577f2024-04-29 09:44:38 +0400122 {{ $r := true}}
123 {{ if $instance }}
124 {{ if eq $instance.Id .Id }}
125 {{ $r = false}}
126 {{ end }}
127 {{ end }}
128 {{ if $r }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400129 <details>
130 <summary>{{ .Id }}</summary>
gio3cdee592024-04-17 10:15:56 +0400131 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" true "Data" (.InputToValues $schema)) }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400132 <a href="/instance/{{ .Id }}" role="button" class="secondary">View</a>
133 </details>
134 {{ end }}
135{{ end }}
gio778577f2024-04-29 09:44:38 +0400136{{ end }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400137
138<div id="toast-failure" class="toast hidden">
139 <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 }}
140</div>
141
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400142<div id="toast-uninstall-failure" class="toast hidden">
143 <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
144</div>
145
146<style>
147 pre {
148 white-space: pre-wrap; /* Since CSS 2.1 */
149 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
150 white-space: -pre-wrap; /* Opera 4-6 */
151 white-space: -o-pre-wrap; /* Opera 7 */
152 word-wrap: break-word; /* Internet Explorer 5.5+ */
153 background-color: transparent;
154 }
155
156 .hidden {
157 visibility: hidden;
158 }
159
160 .toast {
161 position: fixed;
162 z-index: 999;
163 bottom: 10px;
164 }
165</style>
166
167<script>
gio3cdee592024-04-17 10:15:56 +0400168 let config = {{ if $instance }}JSON.parse({{ toJson ($instance.InputToValues $schema) }}){{ else }}{}{{ end }};
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400169
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400170 function setValue(name, value, config) {
171 let items = name.split(".")
172 for (let i = 0; i < items.length - 1; i++) {
173 if (!(items[i] in config)) {
174 config[items[i]] = {}
175 }
176 config = config[items[i]];
177 }
178 config[items[items.length - 1]] = value;
179}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400180 function valueChanged(name, value) {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400181 setValue(name, value, config);
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400182 }
183
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400184 function disableForm() {
185 document.querySelectorAll("#config-form input").forEach((i) => i.setAttribute("disabled", ""));
186 document.querySelectorAll("#config-form select").forEach((i) => i.setAttribute("disabled", ""));
187 document.querySelectorAll("#config-form button").forEach((i) => i.setAttribute("disabled", ""));
188 }
189
190 function enableForm() {
191 document.querySelectorAll("[aria-busy]").forEach((i) => i.removeAttribute("aria-busy"));
192 document.querySelectorAll("#config-form input").forEach((i) => i.removeAttribute("disabled"));
193 document.querySelectorAll("#config-form select").forEach((i) => i.removeAttribute("disabled"));
194 document.querySelectorAll("#config-form button").forEach((i) => i.removeAttribute("disabled"));
195 }
196
197 function installStarted() {
198 const submit = document.getElementById("submit");
199 submit.setAttribute("aria-busy", true);
200 submit.innerHTML = {{ if $instance }}"Updating ..."{{ else }}"Installing ..."{{ end }};
201 disableForm();
202 }
203
204 function uninstallStarted() {
205 const submit = document.getElementById("uninstall");
206 submit.setAttribute("aria-busy", true);
207 submit.innerHTML = "Uninstalling ...";
208 disableForm();
209 }
210
211 function actionFinished(toast) {
212 enableForm();
213 toast.classList.remove("hidden");
214 setTimeout(
215 () => toast.classList.add("hidden"),
216 2000,
217 );
218 }
219
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400220 function installFailed() {
221 actionFinished(document.getElementById("toast-failure"));
222 }
223
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400224 function uninstallFailed() {
225 actionFinished(document.getElementById("toast-uninstall-failure"));
226 }
227
gio44f621b2024-04-29 09:44:38 +0400228 const submitAddr = {{ if $instance }}"/api/instance/{{ $instance.Id }}/update"{{ else }}"/api/app/{{ .App.Slug }}/install"{{ end }};
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400229
230 async function install() {
231 installStarted();
232 const resp = await fetch(submitAddr, {
gio778577f2024-04-29 09:44:38 +0400233 method: "POST",
234 headers: {
235 "Content-Type": "application/json",
236 "Accept": "application/json",
237 },
238 body: JSON.stringify(config),
239 });
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400240 if (resp.status === 200) {
gio778577f2024-04-29 09:44:38 +0400241 window.location = await resp.text();
242 } else {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400243 installFailed();
244 }
245 }
246
247 async function uninstall() {
248 {{ if $instance }}
249 uninstallStarted();
250 const resp = await fetch("/api/instance/{{ $instance.Id }}/remove", {
251 method: "POST",
252 });
253 if (resp.status === 200) {
gio778577f2024-04-29 09:44:38 +0400254 window.location = await resp.text();
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400255 } else {
256 uninstallFailed();
257 }
258 {{ end }}
259 }
260
261 document.getElementById("config-form").addEventListener("submit", (event) => {
262 event.preventDefault();
263 if (event.submitter.id === "submit") {
264 install();
265 } if (event.submitter.id === "uninstall") {
266 uninstall();
267 }
268 });
269</script>
270{{ end }}