36b7c852cbc071acec0e2bd9735d04c058c109e5
12 let callPackagelist
= rpc
.declare({
14 method
: 'packagelist',
17 let callSystemBoard
= rpc
.declare({
22 let callUpgradeStart
= rpc
.declare({
24 method
: 'upgrade_start',
29 * Returns the branch of a given version. This helps to offer upgrades
30 * for point releases (aka within the branch).
33 * SNAPSHOT -> SNAPSHOT
34 * 21.02-SNAPSHOT -> 21.02
35 * 21.02.0-rc1 -> 21.02
38 * @param {string} version
39 * Input version from which to determine the branch
41 * The determined branch
43 function get_branch(version
) {
44 return version
.replace('-SNAPSHOT', '').split('.').slice(0, 2).join('.');
48 * The OpenWrt revision string contains both a hash as well as the number
49 * commits since the OpenWrt/LEDE reboot. It helps to determine if a
50 * snapshot is newer than another.
52 * @param {string} revision
53 * Revision string of a OpenWrt device
55 * The number of commits since OpenWrt/LEDE reboot
57 function get_revision_count(revision
) {
58 return parseInt(revision
.substring(1).split('-')[0]);
63 init
: [10, _('Received build request')],
64 download_imagebuilder
: [20, _('Downloading ImageBuilder archive')],
65 unpack_imagebuilder
: [40, _('Setting Up ImageBuilder')],
66 calculate_packages_hash
: [60, _('Validate package selection')],
67 building_image
: [80, _('Generating firmware image')],
73 selectImage: function (images
, data
, firmware
) {
74 var filesystemFilter = function(e
) {
75 return (e
.filesystem
== firmware
.filesystem
);
77 var typeFilter = function(e
) {
78 if (firmware
.target
.indexOf("x86") != -1) {
79 // x86 images can be combined-efi (EFI) or combined (BIOS)
81 return (e
.type
== 'combined-efi');
83 return (e
.type
== 'combined');
86 return (e
.type
== 'sysupgrade' || e
.type
== 'combined');
89 return images
.filter(filesystemFilter
).filter(typeFilter
)[0];
92 handle200: function (response
, content
, data
, firmware
) {
93 response
= response
.json();
94 let image
= this.selectImage(response
.images
, data
, firmware
);
96 if (image
.name
!= undefined) {
97 this.sha256_unsigned
= image
.sha256_unsigned
;
98 let sysupgrade_url
= `${data.url}/store/${response.bin_dir}/${image.name}`;
100 let keep
= E('input', { type
: 'checkbox' });
105 `${response.version_number} ${response.version_code}`,
110 if (data
.advanced_mode
== 1) {
127 E('a', { href
: sysupgrade_url
}, _('Download firmware image'))
129 if (data
.rebuilder
) {
130 fields
.push(_('Rebuilds'), E('div', { id
: 'rebuilder_status' }));
133 let table
= E('div', { class: 'table' });
135 for (let i
= 0; i
< fields
.length
; i
+= 2) {
137 E('tr', { class: 'tr' }, [
138 E('td', { class: 'td left', width
: '33%' }, [fields
[i
]]),
139 E('td', { class: 'td left' }, [fields
[i
+ 1]]),
149 E('label', { class: 'btn' }, [
152 _('Keep settings and retain the current configuration'),
155 E('div', { class: 'right' }, [
156 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Cancel')),
161 class: 'btn cbi-button cbi-button-positive important',
162 click
: ui
.createHandlerFn(this, function () {
163 this.handleInstall(sysupgrade_url
, keep
.checked
, image
.sha256
);
166 _('Install firmware image')
171 ui
.showModal(_('Successfully created firmware image'), modal_body
);
172 if (data
.rebuilder
) {
173 this.handleRebuilder(content
, data
, firmware
);
178 handle202: function (response
) {
179 response
= response
.json();
180 this.request_hash
= response
.request_hash
;
182 if ('queue_position' in response
) {
183 ui
.showModal(_('Queued...'), [
186 { class: 'spinning' },
187 _('Request in build queue position %s').format(
188 response
.queue_position
193 ui
.showModal(_('Building Firmware...'), [
196 { class: 'spinning' },
197 _('Progress: %s%% %s').format(
198 this.steps
[response
.imagebuilder_status
][0],
199 this.steps
[response
.imagebuilder_status
][1]
206 handleError: function (response
, data
, firmware
) {
207 response
= response
.json();
208 const request_data
= {
210 request_hash
: this.request_hash
,
211 sha256_unsigned
: this.sha256_unsigned
,
215 E('p', {}, _('Server response: %s').format(response
.detail
)),
218 { href
: 'https://github.com/openwrt/asu/issues' },
219 _('Please report the error message and request')
221 E('p', {}, _('Request Data:')),
222 E('pre', {}, JSON
.stringify({ ...request_data
}, null, 4)),
225 if (response
.stdout
) {
226 body
.push(E('b', {}, 'STDOUT:'));
227 body
.push(E('pre', {}, response
.stdout
));
230 if (response
.stderr
) {
231 body
.push(E('b', {}, 'STDERR:'));
232 body
.push(E('pre', {}, response
.stderr
));
236 E('div', { class: 'right' }, [
237 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close')),
241 ui
.showModal(_('Error building the firmware image'), body
);
244 handleRequest: function (server
, main
, content
, data
, firmware
) {
245 let request_url
= `${server}/api/v1/build`;
247 let local_content
= content
;
250 * If `request_hash` is available use a GET request instead of
251 * sending the entire object.
253 if (this.request_hash
&& main
== true) {
254 request_url
+= `/${this.request_hash}`;
260 .request(request_url
, { method
: method
, content
: local_content
})
261 .then((response
) => {
262 switch (response
.status
) {
265 this.handle202(response
);
267 response
= response
.json();
269 let view
= document
.getElementById(server
);
270 view
.innerText
= `⏳ (${
271 this.steps[response.imagebuilder_status][0]
277 poll
.remove(this.pollFn
);
278 this.handle200(response
, content
, data
, firmware
);
280 poll
.remove(this.rebuilder_polls
[server
]);
281 response
= response
.json();
282 let view
= document
.getElementById(server
);
283 let image
= this.selectImage(response
.images
, data
, firmware
);
284 if (image
.sha256_unsigned
== this.sha256_unsigned
) {
285 view
.innerText
= '✅ %s'.format(server
);
287 view
.innerHTML
= `⚠️ ${server} (<a href="${server}/store/${
289 }/${image.name}">${_('Download')}</a>)`;
293 case 400: // bad request
294 case 422: // bad package
295 case 500: // build failed
297 poll
.remove(this.pollFn
);
298 this.handleError(response
, data
, firmware
);
301 poll
.remove(this.rebuilder_polls
[server
]);
302 document
.getElementById(server
).innerText
= '🚫 %s'.format(
310 handleRebuilder: function (content
, data
, firmware
) {
311 this.rebuilder_polls
= {};
312 for (let rebuilder
of data
.rebuilder
) {
313 this.rebuilder_polls
[rebuilder
] = L
.bind(
322 poll
.add(this.rebuilder_polls
[rebuilder
], 5);
323 document
.getElementById(
325 ).innerHTML
+= `<p id="${rebuilder}">⏳ ${rebuilder}</p>`;
330 handleInstall: function (url
, keep
, sha256
) {
331 ui
.showModal(_('Downloading...'), [
334 { class: 'spinning' },
335 _('Downloading firmware from server to browser')
342 'Content-Type': 'application/x-www-form-urlencoded',
344 responseType
: 'blob',
346 .then((response
) => {
347 let form_data
= new FormData();
348 form_data
.append('sessionid', rpc
.getSessionID());
349 form_data
.append('filename', '/tmp/firmware.bin');
350 form_data
.append('filemode', 600);
351 form_data
.append('filedata', response
.blob());
353 ui
.showModal(_('Uploading...'), [
356 { class: 'spinning' },
357 _('Uploading firmware from browser to device')
362 .get(`${L.env.cgi_base}/cgi-upload`, {
366 .then((response
) => response
.json())
367 .then((response
) => {
368 if (response
.sha256sum
!= sha256
) {
369 ui
.showModal(_('Wrong checksum'), [
372 _('Error during download of firmware. Please try again')
374 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close')),
377 ui
.showModal(_('Installing...'), [
380 { class: 'spinning' },
381 _('Installing the sysupgrade. Do not unpower device!')
385 L
.resolveDefault(callUpgradeStart(keep
), {}).then((response
) => {
387 ui
.awaitReconnect(window
.location
.host
);
389 ui
.awaitReconnect('192.168.1.1', 'openwrt.lan');
397 handleCheck: function (data
, firmware
) {
398 let { url
, revision
, advanced_mode
, branch
} = data
;
399 let { version
, target
, profile
, packages
} = firmware
;
401 let request_url
= `${url}/api/overview`;
402 if (version
.endsWith('SNAPSHOT')) {
403 request_url
= `${url}/api/v1/revision/${version}/${target}`;
406 ui
.showModal(_('Searching...'), [
409 { class: 'spinning' },
410 _('Searching for an available sysupgrade of %s - %s').format(
417 L
.resolveDefault(request
.get(request_url
)).then((response
) => {
419 ui
.showModal(_('Error connecting to upgrade server'), [
423 _('Could not reach API at "%s". Please try again later.').format(
427 E('pre', {}, response
.responseText
),
428 E('div', { class: 'right' }, [
429 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close')),
434 if (version
.endsWith('SNAPSHOT')) {
435 const remote_revision
= response
.json().revision
;
437 get_revision_count(revision
) < get_revision_count(remote_revision
)
439 candidates
.push([version
, remote_revision
]);
442 const latest
= response
.json().latest
;
444 for (let remote_version
of latest
) {
445 let remote_branch
= get_branch(remote_version
);
447 // already latest version installed
448 if (version
== remote_version
) {
452 // skip branch upgrades outside the advanced mode
453 if (branch
!= remote_branch
&& advanced_mode
== 0) {
457 candidates
.unshift([remote_version
, null]);
459 // don't offer branches older than the current
460 if (branch
== remote_branch
) {
466 // allow to re-install running firmware in advanced mode
467 if (advanced_mode
== 1) {
468 candidates
.unshift([version
, revision
]);
471 if (candidates
.length
) {
477 version
: candidates
[0][0],
478 packages
: Object
.keys(packages
).sort(),
482 let map
= new form
.JSONMap(mapdata
, '');
489 'Use defaults for the safest update'
491 o
= s
.option(form
.ListValue
, 'version', 'Select firmware version');
492 for (let candidate
of candidates
) {
493 if (candidate
[0] == version
&& candidate
[1] == revision
) {
496 _('[installed] %s').format(
498 ? `${candidate[0]} - ${candidate[1]}`
505 candidate
[1] ? `${candidate[0]} - ${candidate[1]}` : candidate
[0]
510 if (advanced_mode
== 1) {
511 o
= s
.option(form
.Value
, 'profile', _('Board Name / Profile'));
512 o
= s
.option(form
.DynamicList
, 'packages', _('Packages'));
515 L
.resolveDefault(map
.render()).then((form_rendered
) => {
516 ui
.showModal(_('New firmware upgrade available'), [
519 _('Currently running: %s - %s').format(
525 E('div', { class: 'right' }, [
526 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Cancel')),
531 class: 'btn cbi-button cbi-button-positive important',
532 click
: ui
.createHandlerFn(this, function () {
533 map
.save().then(() => {
536 packages
: mapdata
.request
.packages
,
537 version
: mapdata
.request
.version
,
538 profile
: mapdata
.request
.profile
540 this.pollFn
= L
.bind(function () {
541 this.handleRequest(url
, true, content
, data
, firmware
);
543 poll
.add(this.pollFn
, 5);
548 _('Request firmware image')
554 ui
.showModal(_('No upgrade available'), [
557 _('The device runs the latest firmware version %s - %s').format(
562 E('div', { class: 'right' }, [
563 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close')),
572 L
.resolveDefault(callPackagelist(), {}),
573 L
.resolveDefault(callSystemBoard(), {}),
574 L
.resolveDefault(fs
.stat('/sys/firmware/efi'), null),
575 uci
.load('attendedsysupgrade'),
579 render: function (response
) {
581 url
: uci
.get_first('attendedsysupgrade', 'server', 'url'),
582 branch
: get_branch(response
[1].release
.version
),
583 revision
: response
[1].release
.revision
,
585 advanced_mode
: uci
.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0,
586 rebuilder
: uci
.get_first('attendedsysupgrade', 'server', 'rebuilder')
590 client
: 'luci/' + response
[0].packages
['luci-app-attendedsysupgrade'],
591 packages
: response
[0].packages
,
592 profile
: response
[1].board_name
,
593 target
: response
[1].release
.target
,
594 version
: response
[1].release
.version
,
596 filesystem
: response
[1].rootfs_type
600 E('h2', _('Attended Sysupgrade')),
604 'The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.'
610 'This is done by building a new firmware on demand via an online service.'
615 _('Currently running: %s - %s').format(
623 class: 'btn cbi-button cbi-button-positive important',
624 click
: ui
.createHandlerFn(this, this.handleCheck
, data
, firmware
),
626 _('Search for firmware upgrade')
630 handleSaveApply
: null,