12 var callPackagelist
= rpc
.declare({
14 method
: 'packagelist',
17 var callSystemBoard
= rpc
.declare({
22 var 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% Setup ImageBuilder'),
66 calculate_packages_hash
: _('60% Validate package selection'),
67 building_image
: _('80% Generating firmware image')
85 handle200: function (response
) {
86 res
= response
.json();
88 for (image
of res
.images
) {
89 if (this.firmware
.filesystem
== image
.filesystem
) {
91 if (image
.type
== 'combined-efi') {
95 if (image
.type
== 'sysupgrade' || image
.type
== 'combined') {
102 if (image
.name
!= undefined) {
103 var sysupgrade_url
= `${this.data.url}/store/${res.bin_dir}/${image.name}`;
105 var keep
= E('input', { type
: 'checkbox' });
109 _('Version'), `${res.version_number} ${res.version_code}`,
110 _('SHA256'), image
.sha256
,
113 if (this.data
.advanced_mode
== 1) {
115 _('Profile'), res
.id
,
116 _('Target'), res
.target
,
117 _('Build Date'), res
.build_at
,
118 _('Filename'), image
.name
,
119 _('Filesystem'), image
.filesystem
,
123 fields
.push('', E('a', { href
: sysupgrade_url
}, _('Download firmware image')))
125 var table
= E('div', { class: 'table' });
127 for (var i
= 0; i
< fields
.length
; i
+= 2) {
128 table
.appendChild(E('tr', { class: 'tr' }, [
129 E('td', { class: 'td left', width
: '33%' }, [fields
[i
]]),
130 E('td', { class: 'td left' }, [fields
[i
+ 1]]),
136 E('p', { class: 'mt-2' },
137 E('label', { class: 'btn' }, [
139 _('Keep settings and retain the current configuration')
141 E('div', { class: 'right' }, [
142 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Cancel')), ' ',
144 'class': 'btn cbi-button cbi-button-positive important',
145 'click': ui
.createHandlerFn(this, function () {
146 this.handleInstall(sysupgrade_url
, keep
.checked
, image
.sha256
)
148 }, _('Install firmware image')),
152 ui
.showModal(_('Successfully created firmware image'), modal_body
);
156 handle202: function (response
) {
157 response
= response
.json();
158 this.data
.request_hash
= res
.request_hash
;
160 if ('queue_position' in response
) {
161 ui
.showModal(_('Queued...'), [
162 E('p', { 'class': 'spinning' }, _('Request in build queue position %s').format(response
.queue_position
))
165 ui
.showModal(_('Building Firmware...'), [
166 E('p', { 'class': 'spinning' }, _('Progress: %s').format(this.steps
[response
.imagebuilder_status
]))
171 handleError: function (response
) {
172 response
= response
.json();
174 E('p', {}, _('Server response: %s').format(response
.detail
)),
175 E('a', { href
: 'https://github.com/openwrt/asu/issues' }, _('Please report the error message and request')),
176 E('p', {}, _('Request Data:')),
177 E('pre', {}, JSON
.stringify({ ...this.data
, ...this.firmware
}, null, 4)),
180 if (response
.stdout
) {
181 body
.push(E('b', {}, 'STDOUT:'));
182 body
.push(E('pre', {}, response
.stdout
));
185 if (response
.stderr
) {
186 body
.push(E('b', {}, 'STDERR:'));
187 body
.push(E('pre', {}, response
.stderr
));
191 E('div', { class: 'right' }, [
192 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close')),
196 ui
.showModal(_('Error building the firmware image'), body
);
199 handleRequest: function () {
200 var request_url
= `${this.data.url}/api/v1/build`;
202 var content
= this.firmware
;
205 * If `request_hash` is available use a GET request instead of
206 * sending the entire object.
208 if (this.data
.request_hash
) {
209 request_url
+= `/${this.data.request_hash}`;
214 request
.request(request_url
, { method
: method
, content
: content
})
215 .then((response
) => {
216 switch (response
.status
) {
218 this.handle202(response
);
222 this.handle200(response
);
224 case 400: // bad request
225 case 422: // bad package
226 case 500: // build failed
228 this.handleError(response
);
234 handleInstall: function (url
, keep
, sha256
) {
235 ui
.showModal(_('Downloading...'), [
236 E('p', { 'class': 'spinning' }, _('Downloading firmware from server to browser'))
241 'Content-Type': 'application/x-www-form-urlencoded',
243 responseType
: 'blob',
245 .then((response
) => {
246 var form_data
= new FormData();
247 form_data
.append('sessionid', rpc
.getSessionID());
248 form_data
.append('filename', '/tmp/firmware.bin');
249 form_data
.append('filemode', 600);
250 form_data
.append('filedata', response
.blob());
252 ui
.showModal(_('Uploading...'), [
253 E('p', { 'class': 'spinning' }, _('Uploading firmware from browser to device'))
257 .get(`${L.env.cgi_base}/cgi-upload`, {
261 .then((response
) => response
.json())
262 .then((response
) => {
263 if (response
.sha256sum
!= sha256
) {
265 ui
.showModal(_('Wrong checksum'), [
266 E('p', _('Error during download of firmware. Please try again')),
267 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close'))
270 ui
.showModal(_('Installing...'), [
271 E('p', { class: 'spinning' }, _('Installing the sysupgrade. Do not unpower device!'))
274 L
.resolveDefault(callUpgradeStart(keep
), {})
275 .then((response
) => {
277 ui
.awaitReconnect(window
.location
.host
);
279 ui
.awaitReconnect('192.168.1.1', 'openwrt.lan');
287 handleCheck: function () {
288 var { url
, revision
} = this.data
289 var { version
, target
} = this.firmware
292 var request_url
= `${url}/api/overview`;
293 if (version
.endsWith('SNAPSHOT')) {
294 request_url
= `${url}/api/v1/revision/${version}/${target}`;
297 ui
.showModal(_('Searching...'), [
298 E('p', { 'class': 'spinning' },
299 _('Searching for an available sysupgrade of %s - %s').format(version
, revision
))
302 L
.resolveDefault(request
.get(request_url
))
305 ui
.showModal(_('Error connecting to upgrade server'), [
306 E('p', {}, _('Could not reach API at "%s". Please try again later.').format(response
.url
)),
307 E('pre', {}, response
.responseText
),
308 E('div', { class: 'right' }, [
309 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close'))
314 if (version
.endsWith('SNAPSHOT')) {
315 const remote_revision
= response
.json().revision
;
316 if (get_revision_count(revision
) < get_revision_count(remote_revision
)) {
317 candidates
.push([version
, remote_revision
]);
320 const latest
= response
.json().latest
;
322 for (let remote_version
of latest
) {
323 var remote_branch
= get_branch(remote_version
);
325 // already latest version installed
326 if (version
== remote_version
) {
330 // skip branch upgrades outside the advanced mode
331 if (this.data
.branch
!= remote_branch
&& this.data
.advanced_mode
== 0) {
335 candidates
.unshift([remote_version
, null]);
337 // don't offer branches older than the current
338 if (this.data
.branch
== remote_branch
) {
344 // allow to re-install running firmware in advanced mode
345 if (this.data
.advanced_mode
== 1) {
346 candidates
.unshift([version
, revision
])
349 if (candidates
.length
) {
354 profile
: this.firmware
.profile
,
355 version
: candidates
[0][0],
356 packages
: Object
.keys(this.firmware
.packages
).sort(),
360 var map
= new form
.JSONMap(mapdata
, '');
362 s
= map
.section(form
.NamedSection
, 'request', '', '', 'Use defaults for the safest update');
363 o
= s
.option(form
.ListValue
, 'version', 'Select firmware version');
364 for (let candidate
of candidates
) {
365 if (candidate
[0] == version
&& candidate
[1] == revision
) {
366 o
.value(candidate
[0], _('[installed] %s')
367 .format(candidate
[1] ? `${candidate[0]} - ${candidate[1]}` : candidate
[0]));
369 o
.value(candidate
[0], candidate
[1] ? `${candidate[0]} - ${candidate[1]}` : candidate
[0]);
373 if (this.data
.advanced_mode
== 1) {
374 o
= s
.option(form
.Value
, 'profile', _('Board Name / Profile'));
375 o
= s
.option(form
.DynamicList
, 'packages', _('Packages'));
378 L
.resolveDefault(map
.render()).
379 then(form_rendered
=> {
380 ui
.showModal(_('New firmware upgrade available'), [
381 E('p', _('Currently running: %s - %s').format(this.firmware
.version
, this.data
.revision
)),
383 E('div', { class: 'right' }, [
384 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Cancel')), ' ',
386 'class': 'btn cbi-button cbi-button-positive important',
387 'click': ui
.createHandlerFn(this, function () {
388 map
.save().then(() => {
389 this.firmware
.packages
= mapdata
.request
.packages
;
390 this.firmware
.version
= mapdata
.request
.version
;
391 this.firmware
.profile
= mapdata
.request
.profile
;
392 poll
.add(L
.bind(this.handleRequest
, this), 5);
395 }, _('Request firmware image')),
400 ui
.showModal(_('No upgrade available'), [
401 E('p', _('The device runs the latest firmware version %s - %s').format(version
, revision
)),
402 E('div', { class: 'right' }, [
403 E('div', { class: 'btn', click
: ui
.hideModal
}, _('Close')),
413 L
.resolveDefault(callPackagelist(), {}),
414 L
.resolveDefault(callSystemBoard(), {}),
415 L
.resolveDefault(fs
.stat("/sys/firmware/efi"), null),
416 uci
.load('attendedsysupgrade'),
420 render: function (res
) {
421 this.data
.app_version
= res
[0].packages
['luci-app-attendedsysupgrade'];
422 this.firmware
.packages
= res
[0].packages
;
424 this.firmware
.profile
= res
[1].board_name
;
425 this.firmware
.target
= res
[1].release
.target
;
426 this.firmware
.version
= res
[1].release
.version
;
427 this.data
.branch
= get_branch(res
[1].release
.version
);
428 this.firmware
.filesystem
= res
[1].rootfs_type
;
429 this.data
.revision
= res
[1].release
.revision
;
431 this.data
.efi
= res
[2];
433 this.data
.url
= uci
.get_first('attendedsysupgrade', 'server', 'url');
434 this.data
.advanced_mode
= uci
.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0
437 E('h2', _('Attended Sysupgrade')),
438 E('p', _('The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.')),
439 E('p', _('This is done by building a new firmware on demand via an online service.')),
440 E('p', _('Currently running: %s - %s').format(this.firmware
.version
, this.data
.revision
)),
442 'class': 'btn cbi-button cbi-button-positive important',
443 'click': ui
.createHandlerFn(this, this.handleCheck
)
444 }, _('Search for firmware upgrade'))
447 handleSaveApply
: null,