luci/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js
Paul Spooren f799d550b6 luci-app-attendedsysupgrade: support revision checks
SNAPSHOTS are not real releases and therefore the app always offers an
upgrade, even if running the latest build. To prevent that all SNAPSHOTS
now check for the running revision and if a newer one is available.

Also do a bunch of refactoring based on JavaScript I learned over the
last week.

Signed-off-by: Paul Spooren <mail@aparcar.org>
2021-08-30 10:20:12 -10:00

420 lines
19 KiB
JavaScript

'use strict';
'require view';
'require form';
'require uci';
'require rpc';
'require ui';
'require poll';
'require request';
'require dom';
var callPackagelist = rpc.declare({
object: 'rpc-sys',
method: 'packagelist',
});
var callSystemBoard = rpc.declare({
object: 'system',
method: 'board'
});
var callUpgradeStart = rpc.declare({
object: 'rpc-sys',
method: 'upgrade_start',
params: ["keep"]
});
function get_branch(version) {
// determine branch of a version
// SNAPSHOT -> SNAPSHOT
// 21.02-SNAPSHOT -> 21.02
// 21.02.0-rc1 -> 21.02
// 19.07.8 -> 19.07
return version.replace("-SNAPSHOT", "").split(".").slice(0, 2).join(".");
}
function get_revision_count(revision) {
return parseInt(revision.substring(1).split("-")[0])
}
function error_api_connect(response) {
console.log(response)
ui.showModal(_('Error connecting to upgrade server'), [
E('p', {}, _(`Could not reach API at "${response.url}. Please try again later.`)),
E('pre', {}, response.responseText),
E('div', {
'class': 'right'
}, [
E('div', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
]);
}
function install_sysupgrade(url, keep, sha256) {
displayStatus("notice spinning", E('p', _('Downloading firmware from server to browser')));
request.get(url, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
responseType: 'blob'
})
.then(response => {
var form_data = new FormData();
form_data.append("sessionid", rpc.getSessionID());
form_data.append("filename", "/tmp/firmware.bin");
form_data.append("filemode", 600);
form_data.append("filedata", response.blob());
displayStatus("notice spinning", E('p', _('Uploading firmware from browser to device')));
request.get(`${L.env.cgi_base}/cgi-upload`, {
method: 'PUT',
content: form_data
})
.then(response => response.json())
.then(response => {
if (response.sha256sum != sha256) {
displayStatus("warning", [
E('b', _('Wrong checksum')),
E('p', _('Error during download of firmware. Please try again')),
E('div', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
]);
} else {
displayStatus('warning spinning', E('p', _('Installing the sysupgrade. Do not unpower device!')));
L.resolveDefault(callUpgradeStart(keep), {}).then(response => {
if (keep) {
ui.awaitReconnect(window.location.host);
} else {
ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
}
});
}
});
});
}
function request_sysupgrade(server_url, data) {
var res, req;
if (data.request_hash) {
req = request.get(`${server_url}/api/v1/build/${data.request_hash}`)
} else {
req = request.post(`${server_url}/api/v1/build`, {
profile: data.board_name,
target: data.target,
version: data.version,
packages: data.packages,
diff_packages: true,
})
}
req.then(response => {
switch (response.status) {
case 200:
res = response.json()
var image;
for (image of res.images) {
if (image.type == "sysupgrade") {
break;
}
}
if (image.name != undefined) {
var sysupgrade_url = `${server_url}/store/${res.bin_dir}/${image.name}`;
var keep = E('input', {
type: 'checkbox'
})
keep.checked = true;
var fields = [
_('Version'), `${res.version_number} ${res.version_code}`,
_('File'), E('a', {
'href': sysupgrade_url
}, image.name),
_('SHA256'), image.sha256,
_('Build Date'), res.build_at,
_('Target'), res.target,
];
var table = E('div', {
'class': 'table'
});
for (var i = 0; i < fields.length; i += 2) {
table.appendChild(E('div', {
'class': 'tr'
}, [
E('div', {
'class': 'td left',
'width': '33%'
}, [fields[i]]),
E('div', {
'class': 'td left'
}, [(fields[i + 1] != null) ? fields[i + 1] : '?'])
]));
}
var modal_body = [
table,
E('p', {}, E('label', {
'class': 'btn'
}, [
keep, ' ', _('Keep settings and retain the current configuration')
])),
E('div', {
'class': 'right'
}, [
E('div', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('div', {
'class': 'btn cbi-button-action',
'click': function() {
install_sysupgrade(sysupgrade_url, keep.checked, image.sha256)
}
}, _('Install Sysupgrade'))
])
]
ui.showModal(_('Successfully created sysupgrade image'), modal_body);
}
break;
case 202:
res = response.json()
data.request_hash = res.request_hash;
if ("queue_position" in res)
displayStatus("notice spinning", E('p', _('Request in build queue position %d'.format(res.queue_position))));
else
displayStatus("notice spinning", E('p', _('Building firmware sysupgrade image')));
setTimeout(function() {
request_sysupgrade(server_url, data);
}, 5000);
break;
case 400: // bad request
case 422: // bad package
case 500: // build failed
res = response.json()
var body = [
E('p', {}, res.detail),
E('p', {}, _("Please report the error message and request")),
E('b', {}, _("Request to server:")),
E('pre', {}, JSON.stringify(data, null, 4)),
]
if (res.stdout) {
body.push(E('b', {}, "STDOUT:"))
body.push(E('pre', {}, res.stdout))
}
if (res.stderr) {
body.push(E('b', {}, "STDERR:"))
body.push(E('pre', {}, res.stderr))
}
body = body.concat([
E('div', {
'class': 'right'
}, [
E('div', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
]);
ui.showModal(_('Error building the sysupgrade'), body);
break;
}
});
}
async function check_sysupgrade(server_url, system_board, packages) {
var {
board_name
} = system_board;
var {
target,
version,
revision
} = system_board.release;
var current_branch = get_branch(version);
var advanced_mode = uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
var candidates = [];
var response;
displayStatus("notice spinning", E('p', _(`Searching for an available sysupgrade of ${version} - ${revision}`)));
if (version.endsWith("SNAPSHOT")) {
response = await request.get(`${server_url}/api/v1/revision/${version}/${target}`)
if (!response.ok) {
error_api_connect(response);
return;
}
const remote_revision = response.json().revision;
if (get_revision_count(revision) < get_revision_count(remote_revision)) {
candidates.push(version);
}
} else {
response = await request.get(`${server_url}/api/overview`, {
timeout: 8000
});
if (!response.ok) {
error_api_connect(response);
return;
}
const latest = response.json().latest
for (let remote_version of latest) {
var remote_branch = get_branch(remote_version);
// already latest version installed
if (version == remote_version) {
break;
}
// skip branch upgrades outside the advanced mode
if (current_branch != remote_branch && advanced_mode == 0) {
continue;
}
candidates.unshift(remote_version);
// don't offer branches older than the current
if (current_branch == remote_branch) {
break;
}
}
}
if (candidates.length) {
var m, s, o;
var mapdata = {
request: {
board_name: board_name,
target: target,
version: candidates[0],
packages: Object.keys(packages).sort(),
}
}
m = new form.JSONMap(mapdata, '');
s = m.section(form.NamedSection, 'request', 'example', '',
'Use defaults for the safest update');
o = s.option(form.ListValue, 'version', 'Select firmware version');
for (let candidate of candidates) {
o.value(candidate, candidate);
}
if (advanced_mode == 1) {
o = s.option(form.Value, 'board_name', 'Board Name / Profile');
o = s.option(form.DynamicList, 'packages', 'Packages');
}
m.render()
.then(function(form_rendered) {
ui.showModal(_('New upgrade available'), [
form_rendered,
E('div', {
'class': 'right'
}, [
E('div', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('div', {
'class': 'btn cbi-button-action',
'click': function() {
m.save().then(foo => {
request_sysupgrade(
server_url, mapdata.request
)
});
}
}, _('Request Sysupgrade'))
])
]);
});
} else {
ui.showModal(_('No upgrade available'), [
E('p', {}, _(`The device runs the latest firmware version ${version} - ${revision}`)),
E('div', {
'class': 'right'
}, [
E('div', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
]);
}
}
function displayStatus(type, content) {
if (type) {
var message = ui.showModal('', '');
message.classList.add('alert-message');
DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/));
if (content)
dom.content(message, content);
} else {
ui.hideModal();
}
}
return view.extend({
load: function() {
return Promise.all([
L.resolveDefault(callPackagelist(), {}),
L.resolveDefault(callSystemBoard(), {}),
uci.load('attendedsysupgrade')
]);
},
render: function(res) {
var packages = res[0].packages;
var system_board = res[1];
var auto_search = uci.get_first('attendedsysupgrade', 'client', 'auto_search') || 1;
var server_url = uci.get_first('attendedsysupgrade', 'server', 'url');
var view = [
E('h2', _("Attended Sysupgrade")),
E('p', _('The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.')),
E('p', _('This is done by building a new firmware on demand via an online service.'))
];
if (auto_search == 1) {
check_sysupgrade(server_url, system_board, packages)
}
view.push(E('p', {
'class': 'btn cbi-button-positive',
'click': function() {
check_sysupgrade(server_url, system_board, packages)
}
}, _('Search for sysupgrade')));
return view;
},
});