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