Merge pull request #980 from NvrBst/pull-request-upnp_description
[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="cbi-map-descr">
79         Easily search and install new releases and package upgrades. Sysupgrade images are created on demand based on locally installed packages.
80 </div>
81 <div style="display: none" id="upgrade_info" class="alert-message info"></div>
82 <div style="display: none" id="upgrade_error" class="alert-message danger"></div>
83 <div style="display: none" id="packages" class="alert-message success"></div>
84 <p>
85 <textarea style="display: none; width: 100%;" id="edit_packages" rows="15"></textarea>
86 </p>
87 <fieldset class="cbi-section">
88         <form method="post" action="">
89                 <div class="cbi-selection-node">
90                         <div class="cbi-value" id="keep_container" style="display: none">
91                                 <div class="cbi-section-descr">
92                                         Check "Keep settings" to retain the current configuration (requires a compatible firmware image).
93                                 </div>
94                                 <label class="cbi-value-title" for="keep">Keep settings:</label>
95                                 <div class="cbi-value-field">
96                                         <input name="keep" id="keep" checked="checked" type="checkbox">
97                                 </div>
98                         </div>
99                         <div class="cbi-value" id="edit_button" style="display: none">
100                                 <div class="cbi-value-field">
101                                         <input class="cbi-button" value="edit installed packages" onclick="edit_packages()" type="button">
102                                 </div>
103                         </div>
104                         <div class="cbi-value cbi-value-last">
105                                 <div class="cbi-value-field">
106                                         <input class="cbi-button cbi-input-apply" value="Search for upgrades" style="display: none" onclick="upgrade_check()" type="button" id="upgrade_button">
107                                 </div>
108                         </div>
109                 </div>
110         </form>
111 </fieldset>
112 <script type="text/javascript">
113 latest_version = "";
114 data = {};
115 origin = document.location.href.replace(location.pathname, "")
116 ubus_url = origin + "/ubus/"
117
118 function edit_packages() {
119         data.edit_packages = true
120         document.getElementById("edit_button").style.display = "none";
121         document.getElementById("edit_packages").value = data.packages.join("\n");
122         document.getElementById("edit_packages").style.display = "block";
123 }
124
125 // requests to the upgrade server
126 function server_request(request_dict, path, callback) {
127         request_dict.distro = data.release.distribution;
128         request_dict.target = data.release.target.split("\/")[0];
129         request_dict.subtarget = data.release.target.split("\/")[1];
130         var request = new XMLHttpRequest();
131         request.open("POST", data.url + "/" + path, true);
132         request.setRequestHeader("Content-type", "application/json");
133         request.send(JSON.stringify(request_dict));
134         request.onerror = function(e) {
135                 upgrade_error("upgrade server down")
136         }
137         request.addEventListener('load', function(event) {
138                 callback(request)
139         });
140 }
141
142 // initial setup, get system information
143 function setup() {
144         data["ubus_rpc_session"] = "<%=luci.dispatcher.context.authsession%>"
145         ubus_call("packagelist", "list", {}, "packagelist");
146         ubus_call("system", "board", {}, "release");
147         ubus_call("system", "board", {}, "board_name");
148         ubus_call("system", "board", {}, "model");
149         uci_call({ "config": "attendedsysupgrade", "section": "server", "option": "url" })
150         uci_call({ "config": "attendedsysupgrade", "section": "client", "option": "upgrade_packages" })
151         uci_call({ "config": "attendedsysupgrade", "section": "client", "option": "advanced_mode" })
152         uci_call({ "config": "attendedsysupgrade", "section": "client", "option": "auto_search" })
153         setup_ready();
154 }
155
156 function setup_ready() {
157         if(ubus_counter != ubus_closed) {
158                 setTimeout(setup_ready, 300)
159         } else {
160                 if(data.auto_search == 1) {
161                         upgrade_check();
162                 } else {
163                         document.getElementById("upgrade_button").style.display = "block";
164                 }
165         }
166 }
167
168 function uci_call(option) {
169         ubus_call("uci", "get", option, option["option"])
170 }
171
172 ubus_counter = 0;
173 ubus_closed = 0;
174 function ubus_call(command, argument, params, variable) {
175         request_data = {};
176         request_data.jsonrpc = "2.0";
177         request_data.id = ubus_counter;
178         request_data.method = "call";
179         request_data.params = [ data.ubus_rpc_session, command, argument, params ]
180         ubus_counter++;
181         var request = new XMLHttpRequest();
182         request.open("POST", ubus_url, true);
183         request.setRequestHeader("Content-type", "application/json");
184         request.addEventListener('load', function(event) {
185                 if(request.status === 200) {
186                         request_json = JSON.parse(request.responseText).result[1]
187                         if(command === "uci") {
188                                 data[variable] = request_json.value
189                         } else {
190                                 if (variable == "release") {
191                                         latest_version = request_json.release.version
192                                 }
193                                 data[variable] = request_json[variable]
194                         }
195                         ubus_closed++;
196                 }
197         });
198         request.send(JSON.stringify(request_data));
199 }
200
201 // shows notification if upgrade is available
202 function upgrade_info(info_output, loading) {
203         document.getElementById("upgrade_info").style.display = "block";
204         if(loading) {
205                 loading_image = '<img src="/luci-static/resources/icons/loading.gif">'
206         } else {
207                 loading_image = ''
208         }
209         document.getElementById("upgrade_info").innerHTML = loading_image + info_output;
210 }
211
212 function upgrade_error(error_output) {
213         document.getElementById("upgrade_error").style.display = "block";
214         document.getElementById("upgrade_error").innerHTML = error_output;
215         document.getElementById("upgrade_info").style.display = "none";
216 }
217
218 // asks server for news upgrades, actually only based on relesae not packages
219 function upgrade_check() {
220         upgrade_info("Searching for upgrades", true);
221         request_dict = {}
222         request_dict.version = data.release.version;
223         request_dict.packages = data.packagelist;
224         // not only search for new release, but for new package versions as well
225         request_dict.upgrade_packages = data.upgrade_packages
226         server_request(request_dict, "api/upgrade-check", upgrade_check_callback)
227 }
228
229 // request the image, need merge with upgrade_request
230 function upgrade_request() {
231         console.log("upgrade_request")
232         document.getElementById("upgrade_button").disabled = true;
233         document.getElementById("edit_packages").style.display = "none";
234         document.getElementById("edit_button").style.display = "none";
235         document.getElementById("keep_container").style.display = "none";
236         request_dict = {}
237         request_dict.version = latest_version;
238         request_dict.board = data.board_name
239
240         if(data.edit_packages == true) {
241                 request_dict.packages = document.getElementById("edit_packages").value.split("\n")
242         } else {
243                 request_dict.packages = data.packages;
244         }
245         request_dict.model = data.model
246         server_request(request_dict, "api/upgrade-request", upgrade_request_callback)
247 }
248
249 function upgrade_request_callback(response) {
250         if (response.status === 400) {
251                 response_content = JSON.parse(response.responseText)
252                 upgrade_error(response_content.error)
253         } else if (response.status === 500) {
254                 response_content = JSON.parse(response.responseText)
255                 upgrade_error(response_content.error)
256                 if(response_content.log != undefined) {
257                         data.log_url = response_content.log
258                 }
259         } else if (response.status === 503) {
260                 upgrade_error("please wait. server overloaded")
261                 // handle overload
262                 setTimeout(upgrade_request, 30000)
263         } else if (response.status === 201) {
264                 response_content = JSON.parse(response.responseText)
265                 if(response_content.queue != undefined) {
266                         // in queue
267                         upgrade_info("please wait. you are in queue position " + response_content.queue, true)
268                         console.log("queued")
269                 } else {
270                         upgrade_info("imagebuilder not ready, please wait", true)
271                         console.log("setting up imagebuilder")
272                 }
273                 setTimeout(upgrade_request, 5000)
274         } else if (response.status === 206) {
275                 // building
276                 console.log("building")
277                 upgrade_info("building image", true)
278                 setTimeout(upgrade_request, 5000)
279         } else if (response.status === 200) {
280                 // ready to download
281                 response_content = JSON.parse(response.responseText);
282                 data.sysupgrade_url = response_content.sysupgrade;
283
284                 info_output = "Image created"
285                 if(data.advanced_mode == 1) {
286                         build_log = '</br><a target="_blank" href="' + data.sysupgrade_url + '.log">Build log</a>'
287                         info_output += build_log
288                 }
289                 upgrade_info(info_output);
290
291                 document.getElementById("keep_container").style.display = "block";
292                 document.getElementById("upgrade_button").disabled = false;
293                 document.getElementById("upgrade_button").style.display = "block";
294                 document.getElementById("upgrade_button").value = "Sysupgrade";
295                 document.getElementById("upgrade_button").onclick = download_image;
296         }
297 }
298
299 // uploads received blob data to the server using cgi-io
300 function upload_image(blob) {
301         var upload_request = new XMLHttpRequest();
302         var form_data  = new FormData();
303
304         form_data.append("sessionid", data.ubus_rpc_session)
305         form_data.append("filename", "/tmp/sysupgrade.bin")
306         form_data.append("filemode", 755) // insecure?
307         form_data.append("filedata", blob)
308
309         upload_request.addEventListener('load', function(event) {
310                 // this checksum should be parsed
311                 upgrade_info("Flashing firmware", true)
312                 ubus_call("attendedsysupgrade", "sysupgrade", { "keep_settings": document.getElementById("keep").checked }, 'message');
313                 setTimeout(ping_ubus, 5000)
314                 console.log(data.message);
315         });
316
317         upload_request.addEventListener('error', function(event) {
318                 upgrade_info("uploading failed, please retry")
319         });
320
321         upload_request.open('POST', origin + '/cgi-bin/cgi-upload');
322         upload_request.send(form_data);
323 }
324
325 function ping_ubus() {
326         var request = new XMLHttpRequest();
327         request.open("GET", ubus_url, true);
328         request.addEventListener('error', function(event) {
329                 upgrade_info("Rebooting", true);
330                 setTimeout(ping_ubus, 1000)
331         });
332         request.addEventListener('load', function(event) {
333                 upgrade_info("Success! Please reload web interface");
334                 document.getElementById("upgrade_button").value = "reload page";
335                 document.getElementById("upgrade_button").style.display = "block";
336                 document.getElementById("upgrade_button").disabled = false;
337                 document.getElementById("upgrade_button").onclick = function() { location.reload(); }
338         });
339         request.send();
340 }
341
342 // download image from server once the url was received by upgrade_request
343 function download_image() {
344         console.log("download_image")
345         document.getElementById("keep_container").style.display = "none";
346         document.getElementById("upgrade_button").style.display = "none";
347         var download_request = new XMLHttpRequest();
348         download_request.open("GET", data.sysupgrade_url);
349         download_request.responseType = "arraybuffer";
350
351         download_request.onload = function () {
352                 if (this.status === 200) {
353                         var blob = new Blob([download_request.response], {type: "application/octet-stream"});
354                         upload_image(blob)
355                 }
356         };
357         upgrade_info("downloading image", true);
358         download_request.send();
359 }
360
361 function upgrade_check_callback(response_object) {
362         if (response_object.status === 500) {
363                 // python crashed
364                 upgrade_error("internal server error, please try again later")
365                 console.log("upgrade server issue")
366         } else if (response_object.status === 502) {
367                 // python part offline
368                 upgrade_error("internal server error, please try again later")
369                 console.log("upgrade server issue")
370         } else if (response_object.status === 503) {
371                 // handle overload
372                 upgrade_error("server overloaded, retry in 5 minutes")
373                 console.log("server overloaded")
374                 setTimeout(upgrade_request, 300000)
375         } else if (response_object.status === 201) {
376                 upgrade_info("Setting up ImageBuilder", true)
377                 console.log("setting up imagebuilder")
378                 setTimeout(upgrade_request, 5000)
379         } else if (response_object.status === 204) {
380                 // no upgrades
381                 upgrade_info("No upgrades available")
382         } else if (response_object.status === 400) {
383                 // bad request
384                 console.log(response_object.responseText)
385                 response_object_content = JSON.parse(response_object.responseText)
386                 upgrade_error(response_object_content.error)
387         } else if (response_object.status === 200) {
388                 // new release/upgrades
389                 response_content = JSON.parse(response_object.responseText)
390
391                 // create simple output to tell user whats going to be upgrade (release/packages)
392                 info_output = ""
393                 if(response_content.version != undefined) {
394                         info_output += "<h3>new upgrade available</h3>"
395                         info_output += data.release.version + " to " + response_content.version
396                         latest_version = response_content.version;
397                 }
398                 if(response_content.upgrades != undefined) {
399                         info_output += "<h3>package upgrades available</h3>"
400                         for (upgrade in response_content.upgrades) {
401                                 info_output += "<b>" + upgrade + "</b>: " + response_content.upgrades[upgrade][1] + " to " + response_content.upgrades[upgrade][0] + "</br>"
402                         }
403                 }
404                 data.packages = response_content.packages
405                 upgrade_info(info_output)
406
407                 // directly request image if not in advanced mode
408                 if(data.advanced_mode == 1) {
409                         document.getElementById("edit_button").style.display = "block";
410                         document.getElementById("upgrade_button").value = "request image";
411                         document.getElementById("upgrade_button").style.display = "block";
412                         document.getElementById("upgrade_button").disabled = false;
413                         document.getElementById("upgrade_button").onclick = upgrade_request;
414                 } else {
415                         upgrade_request();
416                 }
417         }
418 }
419 document.onload = setup()
420 </script>
421
422 <%+footer%>