Merge pull request #1314 from aparcar/acld
[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 <input class="cbi-button" value="search for updates" onclick="update_request()" type="button" id="update_button">
83 <div style="display: none" id="packages" class="alert-message success"></div>
84 <div class="cbi-value" id="update_packages_container" style="display: block">
85         <label class="cbi-value-title" for="keep">search for package updates:</label>
86         <div class="cbi-value-field">
87                 <input type="checkbox" name="update_packages" id="update_packages" />
88         </div>
89 </div>
90 <div class="cbi-value" id="keep_container" style="display: none">
91         <label class="cbi-value-title" for="keep">keep settings:</label>
92         <div class="cbi-value-field">
93                 <input type="checkbox" name="keep" id="keep" checked="checked" />
94         </div>
95 </div>
96
97 <script type="text/javascript">
98
99 latest_version = "";
100 data = {};
101 ubus_counter = 1
102 origin = document.location.href.replace(location.pathname, "")
103 ubus_url = origin + "/ubus/"
104
105 // requests to the update server
106 function server_request(request_dict, path, callback) {
107         url = data.update_server + "/" + path
108         request_dict.distro = data.release.distribution;
109         request_dict.target = data.release.target.split("\/")[0];
110         request_dict.subtarget = data.release.target.split("\/")[1];
111         var xmlhttp = new XMLHttpRequest();
112         xmlhttp.open("POST", url, true);
113         xmlhttp.setRequestHeader("Content-type", "application/json");
114         xmlhttp.send(JSON.stringify(request_dict));
115         xmlhttp.onerror = function(e) {
116                 update_error("update server down")
117         }
118         xmlhttp.addEventListener('load', function(event) {
119                 callback(xmlhttp)
120         });
121 }
122
123 // requests ubus via rpcd
124 function ubus_request(command, argument, params, callback) {
125         request_data = {};
126         request_data.jsonrpc = "2.0";
127         request_data.id = ubus_counter;
128         request_data.method = "call";
129         request_data.params = [ data.ubus_rpc_session, command, argument, params ]
130         ubus_counter++
131         var xmlhttp = new XMLHttpRequest();
132         xmlhttp.open("POST", ubus_url, true);
133         xmlhttp.setRequestHeader("Content-type", "application/json");
134         xmlhttp.onerror = function(e) {
135                 setTimeout(back_online, 5000)
136         }
137         xmlhttp.addEventListener('load', function(event) {
138                 if(command === "uci") {
139                         ubus_request_callback_uci(xmlhttp, callback)
140                 } else {
141                         ubus_request_callback(xmlhttp, callback)
142                 }
143         });
144         xmlhttp.send(JSON.stringify(request_data));
145 }
146
147 // handle ubus_requests, set variables or perform functions
148 function ubus_request_callback(response_object, callback) {
149         if(response_object.status === 200) {
150                 console.log(callback)
151                 if(typeof callback === "string") {
152                         response_json = JSON.parse(response_object.responseText).result[1]
153                         if (callback == "release") {
154                                 latest_version = response_json.release.version
155                         }
156                         data[callback] = response_json[callback]
157                 } else {
158                         callback(response_object)
159                 }
160         } else {
161                 console.log(respons_object.responseText)
162         }
163 }
164
165 function ubus_request_callback_uci(response_object, callback) {
166         if(response_object.status === 200) {
167                 console.log(callback)
168                 response_json = JSON.parse(response_object.responseText).result[1].value
169                 data[callback] = response_json
170
171                 document.getElementById("update_packages").checked = data.update_packages;
172         } else {
173                 console.log(respons_object.responseText)
174         }
175 }
176
177 // initial setup, get system information
178 function setup() {
179         data["ubus_rpc_session"] = "<%=luci.dispatcher.context.authsession%>"
180         ubus_request("packagelist", "list", {}, "packagelist");
181         ubus_request("system", "board", {}, "release");
182         ubus_request("system", "board", {}, "board_name");
183         ubus_request("system", "board", {}, "model");
184         ubus_request("uci", "get", { "config": "attendedsysupgrade", "section": "updateserver", "option": "url" }, "update_server")
185         ubus_request("uci", "get", { "config": "attendedsysupgrade", "section": "updateclient", "option": "update_packages" }, "update_packages")
186 }
187
188 // shows notification if update is available
189 function update_info(info_output) {
190         document.getElementById("update_info").style.display = "block";
191         document.getElementById("update_info").innerHTML = info_output;
192 }
193
194 function update_error(error_output) {
195         document.getElementById("update_error").style.display = "block";
196         document.getElementById("update_error").innerHTML = error_output;
197         document.getElementById("update_info").style.display = "none";
198 }
199
200 // asks server for news updates, actually only based on relesae not packages
201 function update_request() {
202         console.log("update_request")
203         request_dict = {}
204         request_dict.version = data.release.version;
205         request_dict.packages = data.packagelist;
206         if (document.getElementById("update_packages").checked == 1) {
207                 request_dict.update_packages = 1
208         }
209         server_request(request_dict, "update-request", update_request_callback)
210 }
211
212 function update_request_callback(response_object) {
213         if (response_object.status === 500) {
214                 // python crashed
215                 update_error("internal server error, please try again later")
216                 console.log("update server issue")
217         } else if (response_object.status === 502) {
218                 // python part offline
219                 update_error("internal server error, please try again later")
220                 console.log("update server issue")
221         } else if (response_object.status === 503) {
222                 // handle overload
223                 update_error("server overloaded, retry in 5 minutes")
224                 console.log("server overloaded")
225                 setTimeout(update_request, 300000)
226         } else if (response_object.status === 201) {
227                 update_info("imagebuilder not ready, please wait")
228                 console.log("setting up imagebuilder")
229                 setTimeout(update_request, 5000)
230         } else if (response_object.status === 204) {
231                 // no updates
232                 update_info("no updates available")
233         } else if (response_object.status === 400) {
234                 // bad request
235                 console.log(response_object.responseText)
236                 response_object_content = JSON.parse(response_object.responseText)
237                 update_error(response_object_content.error)
238         } else if (response_object.status === 200) {
239                 // new release/updates
240                 response_object_content = JSON.parse(response_object.responseText)
241                 update_request_200(response_object_content)
242         }
243 }
244
245 function back_online() {
246         ubus_request("session", "login", {}, back_online_callback)
247 }
248
249 function back_online_callback(response_object) {
250         if (response_object.status != 200) {
251                 setTimeout(back_online, 5000)
252         } else {
253                 update_info("upgrade successfull!")
254                 document.getElementById("update_button").value = "reload page";
255                 document.getElementById("update_button").onclick = function() { location.reload(); }
256         }
257
258 }
259
260 function update_request_200(response_content) {
261         info_output = ""
262         if(response_content.version != undefined) {
263                 info_output += "<h3>new update available</h3>"
264                 info_output += data.release.version + " to " + response_content.version
265                 latest_version = response_content.version;
266         }
267         if(response_content.updates != undefined) {
268                 info_output += "<h3>package updates available</h3>"
269                 for (update in response_content.updates) {
270                         info_output += "<b>" + update + "</b>: " + response_content.updates[update][1] + " to " + response_content.updates[update][0] + "</br>"
271                 }
272         }
273         data.packages = response_content.packages
274         update_info(info_output)
275         document.getElementById("update_button").value = "request image";
276         document.getElementById("update_packages_container").style.display = "none";
277         document.getElementById("update_button").onclick = image_request;
278 }
279
280 // request the image, need merge with update_request
281 function image_request() {
282         console.log("image_request")
283         document.getElementById("update_packages_container").style.display = "none";
284         request_dict = {}
285         request_dict.version = latest_version;
286         request_dict.board = data.board_name
287         request_dict.packages = data.packages;
288         request_dict.model = data.model
289         server_request(request_dict, "image-request", image_request_handler)
290 }
291
292 function image_request_handler(response) {
293         if (response.status === 400) {
294                 response_content = JSON.parse(response.responseText)
295                 update_error(response_content.error)
296         } else if (response.status === 500) {
297                 image_request_500()
298         } else if (response.status === 503) {
299                 update_error("please wait. server overloaded")
300                 // handle overload
301                 setTimeout(image_request, 30000)
302         } else if (response.status === 201) {
303                 response_content = JSON.parse(response.responseText)
304                 if(response_content.queue != undefined) {
305                         // in queue
306                         update_info("please wait. you are in queue position " + response_content.queue)
307                         console.log("queued")
308                 } else {
309                         update_info("imagebuilder not ready, please wait")
310                         console.log("setting up imagebuilder")
311                 }
312                 setTimeout(image_request, 5000)
313         } else if (response.status === 206) {
314                 // building
315                 console.log("building")
316                 update_info("building image")
317                 setTimeout(image_request, 5000)
318         } else if (response.status === 200) {
319                 // ready to download
320                 response_content = JSON.parse(response.responseText)
321                 update_info("image created")
322                 document.getElementById("update_button").value = "sysupgrade"
323                 document.getElementById("update_button").onclick = function() {download_image(response_content.url); }
324                 document.getElementById("keep_container").style.display = "block";
325         }
326 }
327
328
329 // uploads received blob data to the server using cgi-io
330 function upload_image(blob) {
331         var upload_request = new XMLHttpRequest();
332         var form_data  = new FormData();
333
334         form_data.append("sessionid", data.ubus_rpc_session)
335         form_data.append("filename", "/tmp/sysupgrade.bin")
336         form_data.append("filemode", 755) // insecure?
337         form_data.append("filedata", blob)
338
339         upload_request.addEventListener('load', function(event) {
340                 // this checksum should be parsed
341                 document.getElementById("update_info").innerHTML = "flashing... please wait" // show fancy indicator http://www.ajaxload.info/
342
343                 ubus_request("attendedsysupgrade", "sysupgrade", { "keep_settings": document.getElementById("keep").checked }, 'done');
344         });
345
346         upload_request.addEventListener('error', function(event) {
347                 document.getElementById("update_info").innerHTML = "uploading failed, please retry"
348         });
349
350         upload_request.open('POST', origin + '/cgi-bin/cgi-upload');
351         upload_request.send(form_data);
352 }
353
354 // download image from server once the url was received by image_request
355 function download_image(url) {
356         console.log("download_image")
357         document.getElementById("update_button").value = "flashing..."
358         document.getElementById("update_button").disabled = true;
359         var download_request = new XMLHttpRequest();
360         download_request.open("GET", url);
361         download_request.responseType = "arraybuffer";
362
363         download_request.onload = function () {
364                 if (this.status === 200) {
365                         var blob = new Blob([download_request.response], {type: "application/octet-stream"});
366                         upload_image(blob)
367                 }
368         };
369         document.getElementById("update_info").innerHTML = "downloading image"
370         download_request.send();
371 }
372
373 document.onload = setup()
374 </script>
375
376 <%+footer%>