847ad5cfb34ceae228ea980adc9ca14c1874d47d
[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%>