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