Merge pull request #5008 from mpratt14/vlan-default
[project/luci.git] / applications / luci-app-attendedsysupgrade / htdocs / luci-static / resources / view / attendedsysupgrade / overview.js
1 'use strict';
2 'require view';
3 'require form';
4 'require uci';
5 'require rpc';
6 'require ui';
7 'require poll';
8 'require request';
9 'require dom';
10
11 var callPackagelist = rpc.declare({
12 object: 'rpc-sys',
13 method: 'packagelist',
14 });
15
16 var callSystemBoard = rpc.declare({
17 object: 'system',
18 method: 'board'
19 });
20
21 var callUpgradeStart = rpc.declare({
22 object: 'rpc-sys',
23 method: 'upgrade_start',
24 params: ["keep"]
25 });
26
27 function get_branch(version) {
28 // determine branch of a version
29 // SNAPSHOT -> SNAPSHOT
30 // 21.02-SNAPSHOT -> 21.02
31 // 21.02.0-rc1 -> 21.02
32 // 19.07.8 -> 19.07
33 return version.replace("-SNAPSHOT", "").split(".").slice(0, 2).join(".");
34 }
35
36 function get_revision_count(revision) {
37 return parseInt(revision.substring(1).split("-")[0])
38 }
39
40 function error_api_connect(response) {
41 console.log(response)
42 ui.showModal(_('Error connecting to upgrade server'), [
43 E('p', {}, _(`Could not reach API at "${response.url}. Please try again later.`)),
44 E('pre', {}, response.responseText),
45 E('div', {
46 'class': 'right'
47 }, [
48 E('div', {
49 'class': 'btn',
50 'click': ui.hideModal
51 }, _('Close'))
52 ])
53 ]);
54 }
55
56 function install_sysupgrade(url, keep, sha256) {
57 displayStatus("notice spinning", E('p', _('Downloading firmware from server to browser')));
58 request.get(url, {
59 headers: {
60 'Content-Type': 'application/x-www-form-urlencoded'
61 },
62 responseType: 'blob'
63 })
64 .then(response => {
65 var form_data = new FormData();
66 form_data.append("sessionid", rpc.getSessionID());
67 form_data.append("filename", "/tmp/firmware.bin");
68 form_data.append("filemode", 600);
69 form_data.append("filedata", response.blob());
70
71 displayStatus("notice spinning", E('p', _('Uploading firmware from browser to device')));
72 request.get(`${L.env.cgi_base}/cgi-upload`, {
73 method: 'PUT',
74 content: form_data
75 })
76 .then(response => response.json())
77 .then(response => {
78 if (response.sha256sum != sha256) {
79 displayStatus("warning", [
80 E('b', _('Wrong checksum')),
81 E('p', _('Error during download of firmware. Please try again')),
82 E('div', {
83 'class': 'btn',
84 'click': ui.hideModal
85 }, _('Close'))
86 ]);
87 } else {
88 displayStatus('warning spinning', E('p', _('Installing the sysupgrade. Do not unpower device!')));
89 L.resolveDefault(callUpgradeStart(keep), {}).then(response => {
90 if (keep) {
91 ui.awaitReconnect(window.location.host);
92 } else {
93 ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
94 }
95 });
96 }
97 });
98 });
99 }
100
101 function request_sysupgrade(server_url, data) {
102 var res, req;
103
104 if (data.request_hash) {
105 req = request.get(`${server_url}/api/v1/build/${data.request_hash}`)
106 } else {
107 req = request.post(`${server_url}/api/v1/build`, {
108 profile: data.board_name,
109 target: data.target,
110 version: data.version,
111 packages: data.packages,
112 diff_packages: true,
113 })
114 }
115
116 req.then(response => {
117 switch (response.status) {
118 case 200:
119 res = response.json()
120 var image;
121 for (image of res.images) {
122 if (image.type == "sysupgrade") {
123 break;
124 }
125 }
126 if (image.name != undefined) {
127 var sysupgrade_url = `${server_url}/store/${res.bin_dir}/${image.name}`;
128
129 var keep = E('input', {
130 type: 'checkbox'
131 })
132 keep.checked = true;
133
134 var fields = [
135 _('Version'), `${res.version_number} ${res.version_code}`,
136 _('File'), E('a', {
137 'href': sysupgrade_url
138 }, image.name),
139 _('SHA256'), image.sha256,
140 _('Build Date'), res.build_at,
141 _('Target'), res.target,
142 ];
143
144 var table = E('div', {
145 'class': 'table'
146 });
147
148 for (var i = 0; i < fields.length; i += 2) {
149 table.appendChild(E('div', {
150 'class': 'tr'
151 }, [
152 E('div', {
153 'class': 'td left',
154 'width': '33%'
155 }, [fields[i]]),
156 E('div', {
157 'class': 'td left'
158 }, [(fields[i + 1] != null) ? fields[i + 1] : '?'])
159 ]));
160 }
161
162 var modal_body = [
163 table,
164 E('p', {}, E('label', {
165 'class': 'btn'
166 }, [
167 keep, ' ', _('Keep settings and retain the current configuration')
168 ])),
169 E('div', {
170 'class': 'right'
171 }, [
172 E('div', {
173 'class': 'btn',
174 'click': ui.hideModal
175 }, _('Cancel')),
176 ' ',
177 E('div', {
178 'class': 'btn cbi-button-action',
179 'click': function() {
180 install_sysupgrade(sysupgrade_url, keep.checked, image.sha256)
181 }
182 }, _('Install Sysupgrade'))
183 ])
184 ]
185
186 ui.showModal(_('Successfully created sysupgrade image'), modal_body);
187 }
188
189 break;
190 case 202:
191 res = response.json()
192 data.request_hash = res.request_hash;
193
194 if ("queue_position" in res)
195 displayStatus("notice spinning", E('p', _('Request in build queue position %d'.format(res.queue_position))));
196 else
197 displayStatus("notice spinning", E('p', _('Building firmware sysupgrade image')));
198
199 setTimeout(function() {
200 request_sysupgrade(server_url, data);
201 }, 5000);
202 break;
203 case 400: // bad request
204 case 422: // bad package
205 case 500: // build failed
206 res = response.json()
207 var body = [
208 E('p', {}, res.detail),
209 E('p', {}, _("Please report the error message and request")),
210 E('b', {}, _("Request to server:")),
211 E('pre', {}, JSON.stringify(data, null, 4)),
212
213 ]
214
215 if (res.stdout) {
216 body.push(E('b', {}, "STDOUT:"))
217 body.push(E('pre', {}, res.stdout))
218
219 }
220
221 if (res.stderr) {
222 body.push(E('b', {}, "STDERR:"))
223 body.push(E('pre', {}, res.stderr))
224
225 }
226
227 body = body.concat([
228 E('div', {
229 'class': 'right'
230 }, [
231 E('div', {
232 'class': 'btn',
233 'click': ui.hideModal
234 }, _('Close'))
235 ])
236 ]);
237 ui.showModal(_('Error building the sysupgrade'), body);
238 break;
239 }
240 });
241 }
242
243 async function check_sysupgrade(server_url, system_board, packages) {
244 var {
245 board_name
246 } = system_board;
247 var {
248 target,
249 version,
250 revision
251 } = system_board.release;
252 var current_branch = get_branch(version);
253 var advanced_mode = uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
254 var candidates = [];
255 var response;
256
257 displayStatus("notice spinning", E('p', _(`Searching for an available sysupgrade of ${version} - ${revision}`)));
258
259 if (version.endsWith("SNAPSHOT")) {
260 response = await request.get(`${server_url}/api/v1/revision/${version}/${target}`)
261 if (!response.ok) {
262 error_api_connect(response);
263 return;
264 }
265
266 const remote_revision = response.json().revision;
267
268 if (get_revision_count(revision) < get_revision_count(remote_revision)) {
269 candidates.push(version);
270 }
271 } else {
272 response = await request.get(`${server_url}/api/overview`, {
273 timeout: 8000
274 });
275
276 if (!response.ok) {
277 error_api_connect(response);
278 return;
279 }
280
281 const latest = response.json().latest
282
283 for (let remote_version of latest) {
284 var remote_branch = get_branch(remote_version);
285
286 // already latest version installed
287 if (version == remote_version) {
288 break;
289 }
290
291 // skip branch upgrades outside the advanced mode
292 if (current_branch != remote_branch && advanced_mode == 0) {
293 continue;
294 }
295
296 candidates.unshift(remote_version);
297
298 // don't offer branches older than the current
299 if (current_branch == remote_branch) {
300 break;
301 }
302 }
303 }
304
305 if (candidates.length) {
306 var m, s, o;
307
308 var mapdata = {
309 request: {
310 board_name: board_name,
311 target: target,
312 version: candidates[0],
313 packages: Object.keys(packages).sort(),
314 }
315 }
316
317 m = new form.JSONMap(mapdata, '');
318
319 s = m.section(form.NamedSection, 'request', 'example', '',
320 'Use defaults for the safest update');
321 o = s.option(form.ListValue, 'version', 'Select firmware version');
322 for (let candidate of candidates) {
323 o.value(candidate, candidate);
324 }
325
326 if (advanced_mode == 1) {
327 o = s.option(form.Value, 'board_name', 'Board Name / Profile');
328 o = s.option(form.DynamicList, 'packages', 'Packages');
329 }
330
331
332 m.render()
333 .then(function(form_rendered) {
334 ui.showModal(_('New upgrade available'), [
335 form_rendered,
336 E('div', {
337 'class': 'right'
338 }, [
339 E('div', {
340 'class': 'btn',
341 'click': ui.hideModal
342 }, _('Cancel')),
343 ' ',
344 E('div', {
345 'class': 'btn cbi-button-action',
346 'click': function() {
347 m.save().then(foo => {
348 request_sysupgrade(
349 server_url, mapdata.request
350 )
351 });
352 }
353 }, _('Request Sysupgrade'))
354 ])
355 ]);
356 });
357 } else {
358 ui.showModal(_('No upgrade available'), [
359 E('p', {}, _(`The device runs the latest firmware version ${version} - ${revision}`)),
360 E('div', {
361 'class': 'right'
362 }, [
363 E('div', {
364 'class': 'btn',
365 'click': ui.hideModal
366 }, _('Close'))
367 ])
368 ]);
369 }
370 }
371
372 function displayStatus(type, content) {
373 if (type) {
374 var message = ui.showModal('', '');
375
376 message.classList.add('alert-message');
377 DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/));
378
379 if (content)
380 dom.content(message, content);
381 } else {
382 ui.hideModal();
383 }
384 }
385
386 return view.extend({
387 load: function() {
388 return Promise.all([
389 L.resolveDefault(callPackagelist(), {}),
390 L.resolveDefault(callSystemBoard(), {}),
391 uci.load('attendedsysupgrade')
392 ]);
393 },
394 render: function(res) {
395 var packages = res[0].packages;
396 var system_board = res[1];
397 var auto_search = uci.get_first('attendedsysupgrade', 'client', 'auto_search') || 1;
398 var server_url = uci.get_first('attendedsysupgrade', 'server', 'url');
399
400 var view = [
401 E('h2', _("Attended Sysupgrade")),
402 E('p', _('The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.')),
403 E('p', _('This is done by building a new firmware on demand via an online service.'))
404 ];
405
406 if (auto_search == 1) {
407 check_sysupgrade(server_url, system_board, packages)
408 }
409
410 view.push(E('p', {
411 'class': 'btn cbi-button-positive',
412 'click': function() {
413 check_sysupgrade(server_url, system_board, packages)
414 }
415 }, _('Search for sysupgrade')));
416
417 return view;
418 },
419
420 });