1e423b49f0b53e7fda0fc6ad1ed38f4143bcc9a9
[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%>