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