d04745abd14e62bf80580ba3c4d29649ea0ca859
[project/luci.git] / applications / luci-app-attendedsysupgrade / luasrc / view / attendedsysupgrade.htm
1 <%
2 -- all lua code provided by https://github.com/jow-/
3 -- thank you very much!
4
5     function apply_acls(filename, session)
6         local json = require "luci.jsonc"
7         local util = require "luci.util"
8         local fs   = require "nixio.fs"
9
10         local grants = { }
11
12         local acl = json.parse(fs.readfile(filename))
13         if type(acl) ~= "table" then
14             return
15         end
16
17         local group, perms
18         for group, perms in pairs(acl) do
19             local perm, scopes
20             for perm, scopes in pairs(perms) do
21                 if type(scopes) == "table" then
22                     local scope, objects
23                     for scope, objects in pairs(scopes) do
24                         if type(objects) == "table" then
25                             if not grants[scope] then
26                                 grants[scope] = { }
27                             end
28
29                             if next(objects) == 1 then
30                                 local _, object
31                                 for _, object in ipairs(objects) do
32                                     if not grants[scope][object] then
33                                         grants[scope][object] = { }
34                                     end
35                                     table.insert(grants[scope][object], perm)
36                                 end
37                             else
38                                 local object, funcs
39                                 for object, funcs in pairs(objects) do
40                                     if type(funcs) == "table" then
41                                         local _, func
42                                         for _, func in ipairs(funcs) do
43                                             if not grants[scope][object] then
44                                                 grants[scope][object] = { }
45                                             end
46                                             table.insert(grants[scope][object], func)
47                                         end
48                                     end
49                                 end
50                             end
51                         end
52                     end
53                 end
54             end
55         end
56
57         local _, scope, object, func
58         for scope, _ in pairs(grants) do
59             local objects = { }
60             for object, _ in pairs(_) do
61                 for _, func in ipairs(_) do
62                     table.insert(objects, { object, func })
63                 end
64             end
65
66             util.ubus("session", "grant", {
67                 ubus_rpc_session = session,
68                 scope = scope, objects = objects
69             })
70         end
71     end
72
73     apply_acls("/usr/share/rpcd/acl.d/attendedsysupgrade.json", luci.dispatcher.context.authsession)
74     apply_acls("/usr/share/rpcd/acl.d/packagelist.json", luci.dispatcher.context.authsession)
75 %>
76 <%+header%>
77 <h2 name="content"><%:Attended Sysupgrade%></h2>
78 <div class="container">
79         <div style="display: none" id="update_info" class="alert-message info"></div>
80         <div style="display: none" id="update_error" class="alert-message danger"></div>
81 </div>
82 <p>
83         <input class="cbi-button" style="display: none;" value="edit installed packages" onclick="edit_packages()" type="button" id="edit_button">
84         <textarea style="display: none; width: 100%;" id="edit_packages" rows="15"></textarea>
85 </P>
86 <p>
87         <input class="cbi-button" value="search for updates" onclick="update_request()" type="button" id="update_button">
88 </p>
89 <p>
90         <input style="display: none" class="cbi-button" value="show build log" onclick="window.open(data.log_url);" type="button" id="log_button">
91 </p>
92 <div style="display: none" id="packages" class="alert-message success"></div>
93 <div class="cbi-value" id="update_packages_container" style="display: block">
94         <label class="cbi-value-title" for="update_packages">
95                         <input type="checkbox" name="update_packages" id="update_packages" />
96                 Search for package updates
97         </label>
98 </div>
99 <div class="cbi-value" id="keep_container" style="display: none">
100         <label class="cbi-value-title" for="keep">
101                         <input type="checkbox" name="keep" id="keep" checked="checked" />
102                 Keep settings
103         </label>
104 </div>
105
106 <script type="text/javascript">
107
108 latest_version = "";
109 data = {};
110 ubus_counter = 1
111 origin = document.location.href.replace(location.pathname, "")
112 ubus_url = origin + "/ubus/"
113
114 function edit_packages() {
115         data.edit_packages = true
116         document.getElementById("edit_button").style.display = "none";
117         document.getElementById("edit_packages").value = data.packages.join("\n");
118         document.getElementById("edit_packages").style.display = "block";
119 }
120
121 // requests to the update server
122 function server_request(request_dict, path, callback) {
123         url = data.update_server + "/" + path
124         request_dict.distro = data.release.distribution;
125         request_dict.target = data.release.target.split("\/")[0];
126         request_dict.subtarget = data.release.target.split("\/")[1];
127         var xmlhttp = new XMLHttpRequest();
128         xmlhttp.open("POST", url, true);
129         xmlhttp.setRequestHeader("Content-type", "application/json");
130         xmlhttp.send(JSON.stringify(request_dict));
131         xmlhttp.onerror = function(e) {
132                 update_error("update server down")
133         }
134         xmlhttp.addEventListener('load', function(event) {
135                 callback(xmlhttp)
136         });
137 }
138
139 // requests ubus via rpcd
140 function ubus_request(command, argument, params, callback) {
141         request_data = {};
142         request_data.jsonrpc = "2.0";
143         request_data.id = ubus_counter;
144         request_data.method = "call";
145         request_data.params = [ data.ubus_rpc_session, command, argument, params ]
146         ubus_counter++
147         var xmlhttp = new XMLHttpRequest();
148         xmlhttp.open("POST", ubus_url, true);
149         xmlhttp.setRequestHeader("Content-type", "application/json");
150         xmlhttp.onerror = function(e) {
151                 setTimeout(back_online, 5000)
152         }
153         xmlhttp.addEventListener('load', function(event) {
154                 if(command === "uci") {
155                         ubus_request_callback_uci(xmlhttp, callback)
156                 } else {
157                         ubus_request_callback(xmlhttp, callback)
158                 }
159         });
160         xmlhttp.send(JSON.stringify(request_data));
161 }
162
163 // handle ubus_requests, set variables or perform functions
164 function ubus_request_callback(response_object, callback) {
165         if(response_object.status === 200) {
166                 console.log(callback)
167                 if(typeof callback === "string") {
168                         response_json = JSON.parse(response_object.responseText).result[1]
169                         if (callback == "release") {
170                                 latest_version = response_json.release.version
171                         }
172                         data[callback] = response_json[callback]
173                 } else {
174                         callback(response_object)
175                 }
176         } else {
177                 console.log(respons_object.responseText)
178         }
179 }
180
181 function ubus_request_callback_uci(response_object, callback) {
182         if(response_object.status === 200) {
183                 console.log(callback)
184                 response_json = JSON.parse(response_object.responseText).result[1].value
185                 data[callback] = response_json
186
187                 document.getElementById("update_packages").checked = data.update_packages;
188         } else {
189                 console.log(respons_object.responseText)
190         }
191 }
192
193 // initial setup, get system information
194 function setup() {
195         data["ubus_rpc_session"] = "<%=luci.dispatcher.context.authsession%>"
196         ubus_request("packagelist", "list", {}, "packagelist");
197         ubus_request("system", "board", {}, "release");
198         ubus_request("system", "board", {}, "board_name");
199         ubus_request("system", "board", {}, "model");
200         ubus_request("uci", "get", { "config": "attendedsysupgrade", "section": "updateserver", "option": "url" }, "update_server")
201         ubus_request("uci", "get", { "config": "attendedsysupgrade", "section": "updateclient", "option": "update_packages" }, "update_packages")
202 }
203
204 // shows notification if update is available
205 function update_info(info_output) {
206         document.getElementById("update_info").style.display = "block";
207         document.getElementById("update_info").innerHTML = info_output;
208 }
209
210 function update_error(error_output) {
211         document.getElementById("update_error").style.display = "block";
212         document.getElementById("update_error").innerHTML = error_output;
213         document.getElementById("update_info").style.display = "none";
214 }
215
216 // asks server for news updates, actually only based on relesae not packages
217 function update_request() {
218         console.log("update_request")
219         request_dict = {}
220         request_dict.version = data.release.version;
221         request_dict.packages = data.packagelist;
222         if (document.getElementById("update_packages").checked == 1) {
223                 request_dict.update_packages = 1
224         }
225         server_request(request_dict, "update-request", update_request_callback)
226 }
227
228 function update_request_callback(response_object) {
229         if (response_object.status === 500) {
230                 // python crashed
231                 update_error("internal server error, please try again later")
232                 console.log("update server issue")
233         } else if (response_object.status === 502) {
234                 // python part offline
235                 update_error("internal server error, please try again later")
236                 console.log("update server issue")
237         } else if (response_object.status === 503) {
238                 // handle overload
239                 update_error("server overloaded, retry in 5 minutes")
240                 console.log("server overloaded")
241                 setTimeout(update_request, 300000)
242         } else if (response_object.status === 201) {
243                 update_info("imagebuilder not ready, please wait")
244                 console.log("setting up imagebuilder")
245                 setTimeout(update_request, 5000)
246         } else if (response_object.status === 204) {
247                 // no updates
248                 update_info("no updates available")
249         } else if (response_object.status === 400) {
250                 // bad request
251                 console.log(response_object.responseText)
252                 response_object_content = JSON.parse(response_object.responseText)
253                 update_error(response_object_content.error)
254         } else if (response_object.status === 200) {
255                 // new release/updates
256                 response_object_content = JSON.parse(response_object.responseText)
257                 document.getElementById("edit_button").style.display = "block";
258                 document.getElementById("update_button").disabled = false;
259                 update_request_200(response_object_content)
260         }
261 }
262
263 function back_online() {
264         ubus_request("session", "login", {}, back_online_callback)
265 }
266
267 function back_online_callback(response_object) {
268         if (response_object.status != 200) {
269                 setTimeout(back_online, 5000)
270         } else {
271                 update_info("upgrade successfull!")
272                 document.getElementById("update_button").value = "reload page";
273                 document.getElementById("update_button").onclick = function() { location.reload(); }
274         }
275
276 }
277
278 function update_request_200(response_content) {
279         info_output = ""
280         if(response_content.version != undefined) {
281                 info_output += "<h3>new update available</h3>"
282                 info_output += data.release.version + " to " + response_content.version
283                 latest_version = response_content.version;
284         }
285         if(response_content.updates != undefined) {
286                 info_output += "<h3>package updates available</h3>"
287                 for (update in response_content.updates) {
288                         info_output += "<b>" + update + "</b>: " + response_content.updates[update][1] + " to " + response_content.updates[update][0] + "</br>"
289                 }
290         }
291         data.packages = response_content.packages
292         update_info(info_output)
293         document.getElementById("update_button").value = "request image";
294         document.getElementById("update_packages_container").style.display = "none";
295         document.getElementById("update_button").onclick = image_request;
296 }
297
298 // request the image, need merge with update_request
299 function image_request() {
300         console.log("image_request")
301         document.getElementById("update_button").disabled = true;
302         document.getElementById("update_packages_container").style.display = "none";
303         document.getElementById("edit_packages").style.display = "none";
304         document.getElementById("edit_button").style.display = "none";
305         request_dict = {}
306         request_dict.version = latest_version;
307         request_dict.board = data.board_name
308         if(data.edit_packages == true) {
309                 request_dict.packages = document.getElementById("edit_packages").value.split("\n")
310         } else {
311                 request_dict.packages = data.packages;
312         }
313         request_dict.model = data.model
314         server_request(request_dict, "image-request", image_request_handler)
315 }
316
317 function image_request_handler(response) {
318         if (response.status === 400) {
319                 response_content = JSON.parse(response.responseText)
320                 update_error(response_content.error)
321         } else if (response.status === 500) {
322                 response_content = JSON.parse(response.responseText)
323                 update_error(response_content.error)
324                 if(response_content.log != undefined) {
325                         data.log_url = response_content.log
326                         document.getElementById("log_button").style.display = "block";
327                 }
328         } else if (response.status === 503) {
329                 update_error("please wait. server overloaded")
330                 // handle overload
331                 setTimeout(image_request, 30000)
332         } else if (response.status === 201) {
333                 response_content = JSON.parse(response.responseText)
334                 if(response_content.queue != undefined) {
335                         // in queue
336                         update_info("please wait. you are in queue position " + response_content.queue)
337                         console.log("queued")
338                 } else {
339                         update_info("imagebuilder not ready, please wait")
340                         console.log("setting up imagebuilder")
341                 }
342                 setTimeout(image_request, 5000)
343         } else if (response.status === 206) {
344                 // building
345                 console.log("building")
346                 update_info("building image")
347                 setTimeout(image_request, 5000)
348         } else if (response.status === 200) {
349                 // ready to download
350                 response_content = JSON.parse(response.responseText);
351                 data.image_url = response_content.url;
352                 data.log_url = data.image_url + ".log";
353                 update_info("image created");
354                 document.getElementById("log_button").style.display = "block";
355                 document.getElementById("update_button").disabled = false;
356                 document.getElementById("update_button").value = "sysupgrade";
357                 document.getElementById("update_button").onclick = download_image;
358                 document.getElementById("keep_container").style.display = "block";
359         }
360 }
361
362
363 // uploads received blob data to the server using cgi-io
364 function upload_image(blob) {
365         var upload_request = new XMLHttpRequest();
366         var form_data  = new FormData();
367
368         form_data.append("sessionid", data.ubus_rpc_session)
369         form_data.append("filename", "/tmp/sysupgrade.bin")
370         form_data.append("filemode", 755) // insecure?
371         form_data.append("filedata", blob)
372
373         upload_request.addEventListener('load', function(event) {
374                 // this checksum should be parsed
375                 document.getElementById("update_info").innerHTML = "flashing... please wait"; // show fancy indicator http://www.ajaxload.info/
376
377                 ubus_request("attendedsysupgrade", "sysupgrade", { "keep_settings": document.getElementById("keep").checked }, 'done');
378         });
379
380         upload_request.addEventListener('error', function(event) {
381                 document.getElementById("update_info").innerHTML = "uploading failed, please retry"
382         });
383
384         upload_request.open('POST', origin + '/cgi-bin/cgi-upload');
385         upload_request.send(form_data);
386 }
387
388 // download image from server once the url was received by image_request
389 function download_image() {
390         console.log("download_image")
391         document.getElementById("update_button").value = "downloading image";
392         document.getElementById("update_button").disabled = true;
393         var download_request = new XMLHttpRequest();
394         download_request.open("GET", data.image_url);
395         download_request.responseType = "arraybuffer";
396
397         download_request.onload = function () {
398                 if (this.status === 200) {
399                         var blob = new Blob([download_request.response], {type: "application/octet-stream"});
400                         upload_image(blob)
401                 }
402         };
403         document.getElementById("update_info").innerHTML = "downloading image";
404         download_request.send();
405 }
406
407 document.onload = setup()
408 </script>
409
410 <%+footer%>