In JavaScript (other than in Python) an empty array is considered `true` within if statements. Fix this by checking for the array length rather than its existence. This fixes the issue of an empty dropdown menu in case the user is running the latest release. Signed-off-by: Paul Spooren <mail@aparcar.org>
376 lines
9.2 KiB
JavaScript
376 lines
9.2 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 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/build/" + data.request_hash)
|
|
} else {
|
|
req = request.post(server_url + "/api/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:
|
|
var 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;
|
|
switch (res.status) {
|
|
case "queued":
|
|
displayStatus("notice spinning", E('p', _('Request in build queue')));
|
|
break;
|
|
case "started":
|
|
displayStatus("notice spinning", E('p', _('Building the sysupgrade image')));
|
|
break;
|
|
}
|
|
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.message)),
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
|
|
function check_sysupgrade(server_url, current_version, target, board_name, packages) {
|
|
displayStatus("notice spinning", E('p', _('Searching for an available sysupgrade')));
|
|
var current_branch = get_branch(current_version);
|
|
var advanced_mode = uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
|
|
var candidates = [];
|
|
|
|
fetch(server_url + "/api/latest")
|
|
.then(response => response.json())
|
|
.then(response => {
|
|
if (current_version == "SNAPSHOT") {
|
|
candidates.push("SNAPSHOT");
|
|
} else {
|
|
for (let version of response["latest"]) {
|
|
var branch = get_branch(version);
|
|
|
|
// already latest version installed
|
|
if (current_version == version) {
|
|
break;
|
|
}
|
|
|
|
// skip branch upgrades outside the advanced mode
|
|
if (current_branch != branch && advanced_mode == 0) {
|
|
continue;
|
|
}
|
|
|
|
candidates.unshift(version);
|
|
|
|
// don't offer branches older than the current
|
|
if (current_branch == 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")),
|
|
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 current_version = res[1].release.version;
|
|
var target = res[1].release.target;
|
|
var board_name = res[1].board_name;
|
|
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, current_version, target, board_name, packages)
|
|
}
|
|
|
|
view.push(E('p', {
|
|
'class': 'btn cbi-button-positive',
|
|
'click': function() {
|
|
check_sysupgrade(server_url, current_version, target, board_name, packages)
|
|
}
|
|
}, _('Search for sysupgrade')));
|
|
|
|
return view;
|
|
},
|
|
|
|
});
|