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