blob: 088a4c5f38d07fb8412ff29ad9c1c8872e45d19b [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 Lekveishvilie009a5d2024-01-05 14:10:11 +040011 {{ else if eq $schema.Kind 1 }}
12 <label for="{{ $name }}">
13 <span>{{ $name }}</span>
14 </label>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040015 <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 +040016 {{ else if eq $schema.Kind 4 }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040017 <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 Lekveishvili7c427602024-01-04 00:13:55 +040021 {{ else if eq $schema.Kind 3 }}
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040022 <label for="{{ $name }}">
23 <span>{{ $name }}</span>
24 </label>
25 <select name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} >
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040026 {{ if not $readonly }}<option disabled selected value> -- select an option -- </option>{{ end }}
27 {{ range $networks }}
28 <option {{if eq .Name (index $data $name) }}selected{{ end }}>{{ .Name }}</option>
29 {{ end }}
30 </select>
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040031 {{ else if eq $schema.Kind 5 }}
32 <label for="authEnabled">
33 <span>Require authentication</span>
34 </label>
35 {{ $auth := index $data $name }}
36 {{ $authEnabled := false }}
37 {{ $authGroups := "" }}
38 {{ if and $auth (index $auth "enabled") }}{{ $authEnabled = true }}{{ end }}
39 {{ if and $auth (index $auth "groups") }}{{ $authGroups = index $auth "groups" }}{{ end }}
40 <input type="checkbox" role="swtich" name="authEnabled" oninput="valueChanged('{{- $name -}}.enabled', this.checked)" {{ if $readonly }}disabled{{ end }} {{ if $authEnabled }}checked{{ end }} />
41 <label for="authGroups">
42 <span>Authentication Groups</span>
43 </label>
44 <input type="text" name="authGroups" oninput="valueChanged('{{- $name -}}.groups', this.value)" {{ if $readonly }}disabled{{ end }} value="{{ $authGroups }}" />
45 {{ end }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040046 {{ end }}
47{{ end }}
48
49{{ define "main" }}
50{{ $instance := .Instance }}
51<h1>{{ .App.Icon }}{{ .App.Name }}</h1>
52<pre id="readme"></pre>
53
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040054{{ $schema := .App.Schema }}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040055{{ $networks := .AvailableNetworks }}
56
57<form id="config-form">
58 {{ if $instance }}
59 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" $instance.Config) }}
60 {{ else }}
61 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" (dict)) }}
62 {{ end }}
63 {{ if $instance }}
64 <div class="grid">
65 <button type="submit" id="submit" name="update">Update</button>
66 <button type="submit" id="uninstall" name="remove">Uninstall</button>
67 </div>
68 {{ else }}
69 <button type="submit" id="submit">{{ if $instance }}Update{{ else }}Install{{ end }}</button>
70 {{ end }}
71</form>
72
73{{ range .Instances }}
74 {{ if or (not $instance) (ne $instance.Id .Id)}}
75 <details>
76 <summary>{{ .Id }}</summary>
77 {{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" true "Data" .Config ) }}
78 <a href="/instance/{{ .Id }}" role="button" class="secondary">View</a>
79 </details>
80 {{ end }}
81{{ end }}
82
83
84<div id="toast-success" class="toast hidden">
85 <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}}
86</div>
87
88<div id="toast-failure" class="toast hidden">
89 <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 }}
90</div>
91
92<div id="toast-uninstall-success" class="toast hidden">
93 <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
94</div>
95
96<div id="toast-uninstall-failure" class="toast hidden">
97 <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
98</div>
99
100<style>
101 pre {
102 white-space: pre-wrap; /* Since CSS 2.1 */
103 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
104 white-space: -pre-wrap; /* Opera 4-6 */
105 white-space: -o-pre-wrap; /* Opera 7 */
106 word-wrap: break-word; /* Internet Explorer 5.5+ */
107 background-color: transparent;
108 }
109
110 .hidden {
111 visibility: hidden;
112 }
113
114 .toast {
115 position: fixed;
116 z-index: 999;
117 bottom: 10px;
118 }
119</style>
120
121<script>
122 let readme = "";
123 let config = {{ if $instance }}JSON.parse({{ toJson $instance.Config }}){{ else }}{}{{ end }};
124
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400125 function setValue(name, value, config) {
126 let items = name.split(".")
127 for (let i = 0; i < items.length - 1; i++) {
128 if (!(items[i] in config)) {
129 config[items[i]] = {}
130 }
131 config = config[items[i]];
132 }
133 config[items[items.length - 1]] = value;
134}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400135 function valueChanged(name, value) {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400136 setValue(name, value, config);
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400137 renderReadme();
138 }
139
140 async function renderReadme() {
141 const resp = await fetch("/api/app/{{ .App.Name }}/render", {
142 method: "POST",
143 headers: {
144 "Content-Type": "application/json",
145 "Accept": "application/json",
146 },
147 body: JSON.stringify(config),
148 });
149 const app = await resp.json();
150 document.getElementById("readme").innerHTML = app.readme;
151 }
152
153 {{ if $instance }}renderReadme();{{ end }}
154
155 function disableForm() {
156 document.querySelectorAll("#config-form input").forEach((i) => i.setAttribute("disabled", ""));
157 document.querySelectorAll("#config-form select").forEach((i) => i.setAttribute("disabled", ""));
158 document.querySelectorAll("#config-form button").forEach((i) => i.setAttribute("disabled", ""));
159 }
160
161 function enableForm() {
162 document.querySelectorAll("[aria-busy]").forEach((i) => i.removeAttribute("aria-busy"));
163 document.querySelectorAll("#config-form input").forEach((i) => i.removeAttribute("disabled"));
164 document.querySelectorAll("#config-form select").forEach((i) => i.removeAttribute("disabled"));
165 document.querySelectorAll("#config-form button").forEach((i) => i.removeAttribute("disabled"));
166 }
167
168 function installStarted() {
169 const submit = document.getElementById("submit");
170 submit.setAttribute("aria-busy", true);
171 submit.innerHTML = {{ if $instance }}"Updating ..."{{ else }}"Installing ..."{{ end }};
172 disableForm();
173 }
174
175 function uninstallStarted() {
176 const submit = document.getElementById("uninstall");
177 submit.setAttribute("aria-busy", true);
178 submit.innerHTML = "Uninstalling ...";
179 disableForm();
180 }
181
182 function actionFinished(toast) {
183 enableForm();
184 toast.classList.remove("hidden");
185 setTimeout(
186 () => toast.classList.add("hidden"),
187 2000,
188 );
189 }
190
191 function installSucceeded() {
192 actionFinished(document.getElementById("toast-success"));
193 }
194
195 function installFailed() {
196 actionFinished(document.getElementById("toast-failure"));
197 }
198
199 function uninstallSucceeded() {
200 actionFinished(document.getElementById("toast-uninstall-success"));
201 }
202
203 function uninstallFailed() {
204 actionFinished(document.getElementById("toast-uninstall-failure"));
205 }
206
207 const submitAddr = {{ if $instance }}"/api/instance/{{ $instance.Id }}/update"{{ else }}"/api/app/{{ .App.Name }}/install"{{ end }};
208
209 async function install() {
210 installStarted();
211 const resp = await fetch(submitAddr, {
212 method: "POST",
213 headers: {
214 "Content-Type": "application/json",
215 "Accept": "application/json",
216 },
217 body: JSON.stringify(config),
218 });
219 if (resp.status === 200) {
220 installSucceeded();
221 } else {
222 installFailed();
223 }
224 }
225
226 async function uninstall() {
227 {{ if $instance }}
228 uninstallStarted();
229 const resp = await fetch("/api/instance/{{ $instance.Id }}/remove", {
230 method: "POST",
231 });
232 if (resp.status === 200) {
233 uninstallSucceeded();
234 } else {
235 uninstallFailed();
236 }
237 {{ end }}
238 }
239
240 document.getElementById("config-form").addEventListener("submit", (event) => {
241 event.preventDefault();
242 if (event.submitter.id === "submit") {
243 install();
244 } if (event.submitter.id === "uninstall") {
245 uninstall();
246 }
247 });
248</script>
249{{ end }}