blob: 6f956f32187fe891603b680babb8cb68dd6e1552 [file] [log] [blame]
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04001{{ define "schema-form" }}
2 {{ $readonly := .ReadOnly }}
3 {{ $networks := .AvailableNetworks }}
4 {{ $data := .Data }}
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04005 {{ range $name, $schema := .Schema.Fields }}
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +04006 {{ if eq $schema.Kind 0 }}
7 <label for="{{ $name }}">
8 <span>{{ $name }}</span>
9 </label>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040010 <input type="checkbox" role="swtich" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.checked)" {{ if $readonly }}disabled{{ end }} {{ if index $data $name }}checked{{ end }} />
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040011 {{ else if eq $schema.Kind 7 }}
12 <label for="{{ $name }}">
13 <span>{{ $name }}</span>
14 </label>
15 <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, parseInt(this.value))" {{ if $readonly }}disabled{{ end }} {{ if index $data $name }}checked{{ end }} />
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040016 {{ else if eq $schema.Kind 1 }}
17 <label for="{{ $name }}">
18 <span>{{ $name }}</span>
19 </label>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040020 <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 +040021 {{ else if eq $schema.Kind 4 }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040022 <label for="{{ $name }}">
23 <span>{{ $name }}</span>
24 </label>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040025 <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040026 {{ else if eq $schema.Kind 3 }}
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040027 <label for="{{ $name }}">
28 <span>{{ $name }}</span>
29 </label>
30 <select name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} >
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040031 {{ if not $readonly }}<option disabled selected value> -- select an option -- </option>{{ end }}
32 {{ range $networks }}
33 <option {{if eq .Name (index $data $name) }}selected{{ end }}>{{ .Name }}</option>
34 {{ end }}
35 </select>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040036 {{ else if eq $schema.Kind 5 }}
37 <label for="authEnabled">
38 <span>Require authentication</span>
39 </label>
40 {{ $auth := index $data $name }}
41 {{ $authEnabled := false }}
42 {{ $authGroups := "" }}
43 {{ if and $auth (index $auth "enabled") }}{{ $authEnabled = true }}{{ end }}
44 {{ if and $auth (index $auth "groups") }}{{ $authGroups = index $auth "groups" }}{{ end }}
45 <input type="checkbox" role="swtich" name="authEnabled" oninput="valueChanged('{{- $name -}}.enabled', this.checked)" {{ if $readonly }}disabled{{ end }} {{ if $authEnabled }}checked{{ end }} />
46 <label for="authGroups">
47 <span>Authentication Groups</span>
48 </label>
49 <input type="text" name="authGroups" oninput="valueChanged('{{- $name -}}.groups', this.value)" {{ if $readonly }}disabled{{ end }} value="{{ $authGroups }}" />
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040050 {{ else if eq $schema.Kind 6 }}
51 {{ $sshKey := index $data $name }}
52 {{ $public := "" }}
53 {{ $private := "" }}
54 {{ if $sshKey }}{{ $public = index $sshKey "public" }}{{ end }}
55 {{ if $sshKey }}{{ $private = index $sshKey "private" }}{{ end }}
56 <label for="{{ $name }}-public">
57 <span>Public Key</span>
58 </label>
59 <textarea name="{{ $name }}-public" disabled>{{ $public }}</textarea>
60 <label for="{{ $name }}-private">
61 <span>Private Key</span>
62 </label>
63 <textarea name="{{ $name }}-private" disabled>{{ $private }}</textarea>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040064 {{ end }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040065 {{ end }}
66{{ end }}
67
68{{ define "main" }}
69{{ $instance := .Instance }}
70<h1>{{ .App.Icon }}{{ .App.Name }}</h1>
71<pre id="readme"></pre>
72
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040073{{ $schema := .App.Schema }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040074{{ $networks := .AvailableNetworks }}
75
76<form id="config-form">
77 {{ if $instance }}
78 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" $instance.Config) }}
79 {{ else }}
80 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" (dict)) }}
81 {{ end }}
82 {{ if $instance }}
83 <div class="grid">
84 <button type="submit" id="submit" name="update">Update</button>
85 <button type="submit" id="uninstall" name="remove">Uninstall</button>
86 </div>
87 {{ else }}
88 <button type="submit" id="submit">{{ if $instance }}Update{{ else }}Install{{ end }}</button>
89 {{ end }}
90</form>
91
92{{ range .Instances }}
93 {{ if or (not $instance) (ne $instance.Id .Id)}}
94 <details>
95 <summary>{{ .Id }}</summary>
96 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" true "Data" .Config ) }}
97 <a href="/instance/{{ .Id }}" role="button" class="secondary">View</a>
98 </details>
99 {{ end }}
100{{ end }}
101
102
103<div id="toast-success" class="toast hidden">
104 <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><path fill="currentColor" d="M18 2a16 16 0 1 0 16 16A16 16 0 0 0 18 2Zm0 30a14 14 0 1 1 14-14a14 14 0 0 1-14 14Z" class="clr-i-outline clr-i-outline-path-1"/><path fill="currentColor" d="M28 12.1a1 1 0 0 0-1.41 0l-11.1 11.05l-6-6A1 1 0 0 0 8 18.53L15.49 26L28 13.52a1 1 0 0 0 0-1.42Z" class="clr-i-outline clr-i-outline-path-2"/><path fill="none" d="M0 0h36v36H0z"/></svg> {{ if $instance }}Update succeeded{{ else }}Install succeeded{{ end}}
105</div>
106
107<div id="toast-failure" class="toast hidden">
108 <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 }}
109</div>
110
111<div id="toast-uninstall-success" class="toast hidden">
112 <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><path fill="currentColor" d="M18 2a16 16 0 1 0 16 16A16 16 0 0 0 18 2Zm0 30a14 14 0 1 1 14-14a14 14 0 0 1-14 14Z" class="clr-i-outline clr-i-outline-path-1"/><path fill="currentColor" d="M28 12.1a1 1 0 0 0-1.41 0l-11.1 11.05l-6-6A1 1 0 0 0 8 18.53L15.49 26L28 13.52a1 1 0 0 0 0-1.42Z" class="clr-i-outline clr-i-outline-path-2"/><path fill="none" d="M0 0h36v36H0z"/></svg> Uninstalled application
113</div>
114
115<div id="toast-uninstall-failure" class="toast hidden">
116 <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
117</div>
118
119<style>
120 pre {
121 white-space: pre-wrap; /* Since CSS 2.1 */
122 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
123 white-space: -pre-wrap; /* Opera 4-6 */
124 white-space: -o-pre-wrap; /* Opera 7 */
125 word-wrap: break-word; /* Internet Explorer 5.5+ */
126 background-color: transparent;
127 }
128
129 .hidden {
130 visibility: hidden;
131 }
132
133 .toast {
134 position: fixed;
135 z-index: 999;
136 bottom: 10px;
137 }
138</style>
139
140<script>
141 let readme = "";
142 let config = {{ if $instance }}JSON.parse({{ toJson $instance.Config }}){{ else }}{}{{ end }};
143
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400144 function setValue(name, value, config) {
145 let items = name.split(".")
146 for (let i = 0; i < items.length - 1; i++) {
147 if (!(items[i] in config)) {
148 config[items[i]] = {}
149 }
150 config = config[items[i]];
151 }
152 config[items[items.length - 1]] = value;
153}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400154 function valueChanged(name, value) {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400155 setValue(name, value, config);
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400156 renderReadme();
157 }
158
159 async function renderReadme() {
160 const resp = await fetch("/api/app/{{ .App.Name }}/render", {
161 method: "POST",
162 headers: {
163 "Content-Type": "application/json",
164 "Accept": "application/json",
165 },
166 body: JSON.stringify(config),
167 });
168 const app = await resp.json();
169 document.getElementById("readme").innerHTML = app.readme;
170 }
171
172 {{ if $instance }}renderReadme();{{ end }}
173
174 function disableForm() {
175 document.querySelectorAll("#config-form input").forEach((i) => i.setAttribute("disabled", ""));
176 document.querySelectorAll("#config-form select").forEach((i) => i.setAttribute("disabled", ""));
177 document.querySelectorAll("#config-form button").forEach((i) => i.setAttribute("disabled", ""));
178 }
179
180 function enableForm() {
181 document.querySelectorAll("[aria-busy]").forEach((i) => i.removeAttribute("aria-busy"));
182 document.querySelectorAll("#config-form input").forEach((i) => i.removeAttribute("disabled"));
183 document.querySelectorAll("#config-form select").forEach((i) => i.removeAttribute("disabled"));
184 document.querySelectorAll("#config-form button").forEach((i) => i.removeAttribute("disabled"));
185 }
186
187 function installStarted() {
188 const submit = document.getElementById("submit");
189 submit.setAttribute("aria-busy", true);
190 submit.innerHTML = {{ if $instance }}"Updating ..."{{ else }}"Installing ..."{{ end }};
191 disableForm();
192 }
193
194 function uninstallStarted() {
195 const submit = document.getElementById("uninstall");
196 submit.setAttribute("aria-busy", true);
197 submit.innerHTML = "Uninstalling ...";
198 disableForm();
199 }
200
201 function actionFinished(toast) {
202 enableForm();
203 toast.classList.remove("hidden");
204 setTimeout(
205 () => toast.classList.add("hidden"),
206 2000,
207 );
208 }
209
210 function installSucceeded() {
211 actionFinished(document.getElementById("toast-success"));
212 }
213
214 function installFailed() {
215 actionFinished(document.getElementById("toast-failure"));
216 }
217
218 function uninstallSucceeded() {
219 actionFinished(document.getElementById("toast-uninstall-success"));
220 }
221
222 function uninstallFailed() {
223 actionFinished(document.getElementById("toast-uninstall-failure"));
224 }
225
226 const submitAddr = {{ if $instance }}"/api/instance/{{ $instance.Id }}/update"{{ else }}"/api/app/{{ .App.Name }}/install"{{ end }};
227
228 async function install() {
229 installStarted();
230 const resp = await fetch(submitAddr, {
231 method: "POST",
232 headers: {
233 "Content-Type": "application/json",
234 "Accept": "application/json",
235 },
236 body: JSON.stringify(config),
237 });
238 if (resp.status === 200) {
239 installSucceeded();
240 } else {
241 installFailed();
242 }
243 }
244
245 async function uninstall() {
246 {{ if $instance }}
247 uninstallStarted();
248 const resp = await fetch("/api/instance/{{ $instance.Id }}/remove", {
249 method: "POST",
250 });
251 if (resp.status === 200) {
252 uninstallSucceeded();
253 } else {
254 uninstallFailed();
255 }
256 {{ end }}
257 }
258
259 document.getElementById("config-form").addEventListener("submit", (event) => {
260 event.preventDefault();
261 if (event.submitter.id === "submit") {
262 install();
263 } if (event.submitter.id === "uninstall") {
264 uninstall();
265 }
266 });
267</script>
268{{ end }}