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