luci-app-attendedsysupgrade: introduce rebuilders
This adds automatic verification builds to shift trust on multiple server and multiple entities. Signed-off-by: Paul Spooren <mail@aparcar.org>
This commit is contained in:
parent
a97ff279ce
commit
fa9fb2f955
2 changed files with 425 additions and 210 deletions
|
@ -3,28 +3,53 @@
|
|||
'require form';
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
var m, s, o;
|
||||
render: function () {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('attendedsysupgrade', _('Attended Sysupgrade'),
|
||||
_('Attendedsysupgrade Configuration.'));
|
||||
m = new form.Map(
|
||||
'attendedsysupgrade',
|
||||
_('Attended Sysupgrade'),
|
||||
_('Attendedsysupgrade Configuration.')
|
||||
);
|
||||
|
||||
s = m.section(form.TypedSection, 'server', _('Server'));
|
||||
s.anonymous = true;
|
||||
|
||||
s.option(form.Value, 'url', _('Address'),
|
||||
_('Address of the sysupgrade server'));
|
||||
s.option(
|
||||
form.Value,
|
||||
'url',
|
||||
_('Address'),
|
||||
_('Address of the sysupgrade server')
|
||||
);
|
||||
|
||||
s.option(
|
||||
form.DynamicList,
|
||||
'rebuilder',
|
||||
_('Rebuilders'),
|
||||
_(
|
||||
'Other ASU server instances that rebuild a requested image. ' +
|
||||
'Allows to compare checksums and verify that the results are the same.'
|
||||
)
|
||||
);
|
||||
|
||||
s = m.section(form.TypedSection, 'client', _('Client'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.Flag, 'auto_search', _('Search on opening'),
|
||||
_('Search for new sysupgrades on opening the tab'));
|
||||
o = s.option(
|
||||
form.Flag,
|
||||
'auto_search',
|
||||
_('Search on opening'),
|
||||
_('Search for new sysupgrades on opening the tab')
|
||||
);
|
||||
o.default = '1';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Flag, 'advanced_mode', _('Advanced Mode'),
|
||||
_('Show advanced options like package list modification'));
|
||||
o = s.option(
|
||||
form.Flag,
|
||||
'advanced_mode',
|
||||
_('Advanced Mode'),
|
||||
_('Show advanced options like package list modification')
|
||||
);
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
|
|
|
@ -9,17 +9,17 @@
|
|||
'require dom';
|
||||
'require fs';
|
||||
|
||||
var callPackagelist = rpc.declare({
|
||||
let callPackagelist = rpc.declare({
|
||||
object: 'rpc-sys',
|
||||
method: 'packagelist',
|
||||
});
|
||||
|
||||
var callSystemBoard = rpc.declare({
|
||||
let callSystemBoard = rpc.declare({
|
||||
object: 'system',
|
||||
method: 'board',
|
||||
});
|
||||
|
||||
var callUpgradeStart = rpc.declare({
|
||||
let callUpgradeStart = rpc.declare({
|
||||
object: 'rpc-sys',
|
||||
method: 'upgrade_start',
|
||||
params: ['keep'],
|
||||
|
@ -60,17 +60,19 @@ function get_revision_count(revision) {
|
|||
|
||||
return view.extend({
|
||||
steps: {
|
||||
init: _('10% Received build request'),
|
||||
download_imagebuilder: _('20% Downloading ImageBuilder archive'),
|
||||
unpack_imagebuilder: _('40% Setup ImageBuilder'),
|
||||
calculate_packages_hash: _('60% Validate package selection'),
|
||||
building_image: _('80% Generating firmware image')
|
||||
init: [10, _('Received build request')],
|
||||
download_imagebuilder: [20, _('Downloading ImageBuilder archive')],
|
||||
unpack_imagebuilder: [40, _('Setup ImageBuilder')],
|
||||
calculate_packages_hash: [60, _('Validate package selection')],
|
||||
building_image: [80, _('Generating firmware image')],
|
||||
},
|
||||
|
||||
data: {
|
||||
url: '',
|
||||
revision: '',
|
||||
advanced_mode: 0,
|
||||
rebuilder: [],
|
||||
sha256_unsigned: '',
|
||||
},
|
||||
|
||||
firmware: {
|
||||
|
@ -82,74 +84,107 @@ return view.extend({
|
|||
filesystem: '',
|
||||
},
|
||||
|
||||
handle200: function (response) {
|
||||
response = response.json();
|
||||
var image;
|
||||
for (image of response.images) {
|
||||
selectImage: function (images) {
|
||||
let image;
|
||||
for (image of images) {
|
||||
if (this.firmware.filesystem == image.filesystem) {
|
||||
if (this.data.efi) {
|
||||
if (image.type == 'combined-efi') {
|
||||
break;
|
||||
return image;
|
||||
}
|
||||
} else {
|
||||
if (image.type == 'sysupgrade' || image.type == 'combined') {
|
||||
break;
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
handle200: function (response) {
|
||||
response = response.json();
|
||||
let image = this.selectImage(response.images);
|
||||
|
||||
if (image.name != undefined) {
|
||||
var sysupgrade_url = `${this.data.url}/store/${response.bin_dir}/${image.name}`;
|
||||
this.data.sha256_unsigned = image.sha256_unsigned;
|
||||
let sysupgrade_url = `${this.data.url}/store/${response.bin_dir}/${image.name}`;
|
||||
|
||||
var keep = E('input', { type: 'checkbox' });
|
||||
let keep = E('input', { type: 'checkbox' });
|
||||
keep.checked = true;
|
||||
|
||||
var fields = [
|
||||
_('Version'), `${response.version_number} ${response.version_code}`,
|
||||
_('SHA256'), image.sha256,
|
||||
let fields = [
|
||||
_('Version'),
|
||||
`${response.version_number} ${response.version_code}`,
|
||||
_('SHA256'),
|
||||
image.sha256,
|
||||
];
|
||||
|
||||
if (this.data.advanced_mode == 1) {
|
||||
fields.push(
|
||||
_('Profile'), response.id,
|
||||
_('Target'), response.target,
|
||||
_('Build Date'), response.build_at,
|
||||
_('Filename'), image.name,
|
||||
_('Filesystem'), image.filesystem,
|
||||
)
|
||||
_('Profile'),
|
||||
response.id,
|
||||
_('Target'),
|
||||
response.target,
|
||||
_('Build Date'),
|
||||
response.build_at,
|
||||
_('Filename'),
|
||||
image.name,
|
||||
_('Filesystem'),
|
||||
image.filesystem
|
||||
);
|
||||
}
|
||||
|
||||
fields.push('', E('a', { href: sysupgrade_url }, _('Download firmware image')))
|
||||
fields.push(
|
||||
'',
|
||||
E('a', { href: sysupgrade_url }, _('Download firmware image'))
|
||||
);
|
||||
if (this.data.rebuilder) {
|
||||
fields.push(_('Rebuilds'), E('div', { id: 'rebuilder_status' }));
|
||||
}
|
||||
|
||||
var table = E('div', { class: 'table' });
|
||||
let table = E('div', { class: 'table' });
|
||||
|
||||
for (var i = 0; i < fields.length; i += 2) {
|
||||
table.appendChild(E('tr', { class: 'tr' }, [
|
||||
for (let i = 0; i < fields.length; i += 2) {
|
||||
table.appendChild(
|
||||
E('tr', { class: 'tr' }, [
|
||||
E('td', { class: 'td left', width: '33%' }, [fields[i]]),
|
||||
E('td', { class: 'td left' }, [fields[i + 1]]),
|
||||
]));
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
var modal_body = [
|
||||
let modal_body = [
|
||||
table,
|
||||
E('p', { class: 'mt-2' },
|
||||
E(
|
||||
'p',
|
||||
{ class: 'mt-2' },
|
||||
E('label', { class: 'btn' }, [
|
||||
keep, ' ',
|
||||
_('Keep settings and retain the current configuration')
|
||||
])),
|
||||
keep,
|
||||
' ',
|
||||
_('Keep settings and retain the current configuration'),
|
||||
])
|
||||
),
|
||||
E('div', { class: 'right' }, [
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Cancel')), ' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive important',
|
||||
'click': ui.createHandlerFn(this, function () {
|
||||
this.handleInstall(sysupgrade_url, keep.checked, image.sha256)
|
||||
})
|
||||
}, _('Install firmware image')),
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E(
|
||||
'button',
|
||||
{
|
||||
class: 'btn cbi-button cbi-button-positive important',
|
||||
click: ui.createHandlerFn(this, function () {
|
||||
this.handleInstall(sysupgrade_url, keep.checked, image.sha256);
|
||||
}),
|
||||
},
|
||||
_('Install firmware image')
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
ui.showModal(_('Successfully created firmware image'), modal_body);
|
||||
if (this.data.rebuilder) {
|
||||
this.handleRebuilder();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -159,20 +194,37 @@ return view.extend({
|
|||
|
||||
if ('queue_position' in response) {
|
||||
ui.showModal(_('Queued...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Request in build queue position %s').format(response.queue_position))
|
||||
E(
|
||||
'p',
|
||||
{ class: 'spinning' },
|
||||
_('Request in build queue position %s').format(
|
||||
response.queue_position
|
||||
)
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
ui.showModal(_('Building Firmware...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Progress: %s').format(this.steps[response.imagebuilder_status]))
|
||||
E(
|
||||
'p',
|
||||
{ class: 'spinning' },
|
||||
_('Progress: %s%% %s').format(
|
||||
this.steps[response.imagebuilder_status][0],
|
||||
this.steps[response.imagebuilder_status][1]
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
handleError: function (response) {
|
||||
response = response.json();
|
||||
var body = [
|
||||
let body = [
|
||||
E('p', {}, _('Server response: %s').format(response.detail)),
|
||||
E('a', { href: 'https://github.com/openwrt/asu/issues' }, _('Please report the error message and request')),
|
||||
E(
|
||||
'a',
|
||||
{ href: 'https://github.com/openwrt/asu/issues' },
|
||||
_('Please report the error message and request')
|
||||
),
|
||||
E('p', {}, _('Request Data:')),
|
||||
E('pre', {}, JSON.stringify({ ...this.data, ...this.firmware }, null, 4)),
|
||||
];
|
||||
|
@ -196,61 +248,118 @@ return view.extend({
|
|||
ui.showModal(_('Error building the firmware image'), body);
|
||||
},
|
||||
|
||||
handleRequest: function () {
|
||||
var request_url = `${this.data.url}/api/v1/build`;
|
||||
var method = "POST"
|
||||
var content = this.firmware;
|
||||
handleRequest: function (server, main) {
|
||||
let request_url = `${server}/api/v1/build`;
|
||||
let method = 'POST';
|
||||
let content = this.firmware;
|
||||
|
||||
/**
|
||||
* If `request_hash` is available use a GET request instead of
|
||||
* sending the entire object.
|
||||
*/
|
||||
if (this.data.request_hash) {
|
||||
if (this.data.request_hash && main == true) {
|
||||
request_url += `/${this.data.request_hash}`;
|
||||
content = {};
|
||||
method = "GET"
|
||||
method = 'GET';
|
||||
}
|
||||
|
||||
request.request(request_url, { method: method, content: content })
|
||||
request
|
||||
.request(request_url, { method: method, content: content })
|
||||
.then((response) => {
|
||||
switch (response.status) {
|
||||
case 202:
|
||||
if (main) {
|
||||
this.handle202(response);
|
||||
} else {
|
||||
response = response.json();
|
||||
|
||||
let view = document.getElementById(server);
|
||||
view.innerText = `⏳ (${
|
||||
this.steps[response.imagebuilder_status][0]
|
||||
}%) ${server}`;
|
||||
}
|
||||
break;
|
||||
case 200:
|
||||
poll.stop();
|
||||
if (main == true) {
|
||||
poll.remove(this.pollFn);
|
||||
this.handle200(response);
|
||||
} else {
|
||||
poll.remove(this.rebuilder_polls[server]);
|
||||
response = response.json();
|
||||
let view = document.getElementById(server);
|
||||
let image = this.selectImage(response.images);
|
||||
if (image.sha256_unsigned == this.data.sha256_unsigned) {
|
||||
view.innerText = '✅ %s'.format(server);
|
||||
} else {
|
||||
view.innerHTML = `⚠️ ${server} (<a href="${server}/store/${
|
||||
response.bin_dir
|
||||
}/${image.name}">${_('Download')}</a>)`;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 400: // bad request
|
||||
case 422: // bad package
|
||||
case 500: // build failed
|
||||
poll.stop();
|
||||
if (main == true) {
|
||||
poll.remove(this.pollFn);
|
||||
this.handleError(response);
|
||||
break;
|
||||
} else {
|
||||
poll.remove(this.rebuilder_polls[server]);
|
||||
document.getElementById(server).innerText = '🚫 %s'.format(
|
||||
server
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleRebuilder: function () {
|
||||
this.rebuilder_polls = {};
|
||||
for (let rebuilder of this.data.rebuilder) {
|
||||
this.rebuilder_polls[rebuilder] = L.bind(
|
||||
this.handleRequest,
|
||||
this,
|
||||
rebuilder,
|
||||
false
|
||||
);
|
||||
poll.add(this.rebuilder_polls[rebuilder], 5);
|
||||
document.getElementById(
|
||||
'rebuilder_status'
|
||||
).innerHTML += `<p id="${rebuilder}">⏳ ${rebuilder}</p>`;
|
||||
}
|
||||
poll.start();
|
||||
},
|
||||
|
||||
handleInstall: function (url, keep, sha256) {
|
||||
ui.showModal(_('Downloading...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Downloading firmware from server to browser'))
|
||||
E(
|
||||
'p',
|
||||
{ class: 'spinning' },
|
||||
_('Downloading firmware from server to browser')
|
||||
),
|
||||
]);
|
||||
|
||||
request.get(url, {
|
||||
request
|
||||
.get(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
responseType: 'blob',
|
||||
})
|
||||
.then((response) => {
|
||||
var form_data = new FormData();
|
||||
let 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());
|
||||
|
||||
ui.showModal(_('Uploading...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Uploading firmware from browser to device'))
|
||||
E(
|
||||
'p',
|
||||
{ class: 'spinning' },
|
||||
_('Uploading firmware from browser to device')
|
||||
),
|
||||
]);
|
||||
|
||||
request
|
||||
|
@ -261,18 +370,23 @@ return view.extend({
|
|||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
if (response.sha256sum != sha256) {
|
||||
|
||||
ui.showModal(_('Wrong checksum'), [
|
||||
E('p', _('Error during download of firmware. Please try again')),
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Close'))
|
||||
E(
|
||||
'p',
|
||||
_('Error during download of firmware. Please try again')
|
||||
),
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Close')),
|
||||
]);
|
||||
} else {
|
||||
ui.showModal(_('Installing...'), [
|
||||
E('p', { class: 'spinning' }, _('Installing the sysupgrade. Do not unpower device!'))
|
||||
E(
|
||||
'p',
|
||||
{ class: 'spinning' },
|
||||
_('Installing the sysupgrade. Do not unpower device!')
|
||||
),
|
||||
]);
|
||||
|
||||
L.resolveDefault(callUpgradeStart(keep), {})
|
||||
.then((response) => {
|
||||
L.resolveDefault(callUpgradeStart(keep), {}).then((response) => {
|
||||
if (keep) {
|
||||
ui.awaitReconnect(window.location.host);
|
||||
} else {
|
||||
|
@ -285,42 +399,54 @@ return view.extend({
|
|||
},
|
||||
|
||||
handleCheck: function () {
|
||||
var { url, revision } = this.data
|
||||
var { version, target } = this.firmware
|
||||
var candidates = [];
|
||||
var response;
|
||||
var request_url = `${url}/api/overview`;
|
||||
let { url, revision } = this.data;
|
||||
let { version, target } = this.firmware;
|
||||
let candidates = [];
|
||||
let request_url = `${url}/api/overview`;
|
||||
if (version.endsWith('SNAPSHOT')) {
|
||||
request_url = `${url}/api/v1/revision/${version}/${target}`;
|
||||
}
|
||||
|
||||
ui.showModal(_('Searching...'), [
|
||||
E('p', { 'class': 'spinning' },
|
||||
_('Searching for an available sysupgrade of %s - %s').format(version, revision))
|
||||
E(
|
||||
'p',
|
||||
{ class: 'spinning' },
|
||||
_('Searching for an available sysupgrade of %s - %s').format(
|
||||
version,
|
||||
revision
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
L.resolveDefault(request.get(request_url))
|
||||
.then(response => {
|
||||
L.resolveDefault(request.get(request_url)).then((response) => {
|
||||
if (!response.ok) {
|
||||
ui.showModal(_('Error connecting to upgrade server'), [
|
||||
E('p', {}, _('Could not reach API at "%s". Please try again later.').format(response.url)),
|
||||
E(
|
||||
'p',
|
||||
{},
|
||||
_('Could not reach API at "%s". Please try again later.').format(
|
||||
response.url
|
||||
)
|
||||
),
|
||||
E('pre', {}, response.responseText),
|
||||
E('div', { class: 'right' }, [
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Close'))
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Close')),
|
||||
]),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
if (version.endsWith('SNAPSHOT')) {
|
||||
const remote_revision = response.json().revision;
|
||||
if (get_revision_count(revision) < get_revision_count(remote_revision)) {
|
||||
if (
|
||||
get_revision_count(revision) < get_revision_count(remote_revision)
|
||||
) {
|
||||
candidates.push([version, remote_revision]);
|
||||
}
|
||||
} else {
|
||||
const latest = response.json().latest;
|
||||
|
||||
for (let remote_version of latest) {
|
||||
var remote_branch = get_branch(remote_version);
|
||||
let remote_branch = get_branch(remote_version);
|
||||
|
||||
// already latest version installed
|
||||
if (version == remote_version) {
|
||||
|
@ -328,7 +454,10 @@ return view.extend({
|
|||
}
|
||||
|
||||
// skip branch upgrades outside the advanced mode
|
||||
if (this.data.branch != remote_branch && this.data.advanced_mode == 0) {
|
||||
if (
|
||||
this.data.branch != remote_branch &&
|
||||
this.data.advanced_mode == 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -343,13 +472,13 @@ return view.extend({
|
|||
|
||||
// allow to re-install running firmware in advanced mode
|
||||
if (this.data.advanced_mode == 1) {
|
||||
candidates.unshift([version, revision])
|
||||
candidates.unshift([version, revision]);
|
||||
}
|
||||
|
||||
if (candidates.length) {
|
||||
var m, s, o;
|
||||
let s, o;
|
||||
|
||||
var mapdata = {
|
||||
let mapdata = {
|
||||
request: {
|
||||
profile: this.firmware.profile,
|
||||
version: candidates[0][0],
|
||||
|
@ -357,16 +486,31 @@ return view.extend({
|
|||
},
|
||||
};
|
||||
|
||||
var map = new form.JSONMap(mapdata, '');
|
||||
let map = new form.JSONMap(mapdata, '');
|
||||
|
||||
s = map.section(form.NamedSection, 'request', '', '', 'Use defaults for the safest update');
|
||||
s = map.section(
|
||||
form.NamedSection,
|
||||
'request',
|
||||
'',
|
||||
'',
|
||||
'Use defaults for the safest update'
|
||||
);
|
||||
o = s.option(form.ListValue, 'version', 'Select firmware version');
|
||||
for (let candidate of candidates) {
|
||||
if (candidate[0] == version && candidate[1] == revision) {
|
||||
o.value(candidate[0], _('[installed] %s')
|
||||
.format(candidate[1] ? `${candidate[0]} - ${candidate[1]}` : candidate[0]));
|
||||
o.value(
|
||||
candidate[0],
|
||||
_('[installed] %s').format(
|
||||
candidate[1]
|
||||
? `${candidate[0]} - ${candidate[1]}`
|
||||
: candidate[0]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
o.value(candidate[0], candidate[1] ? `${candidate[0]} - ${candidate[1]}` : candidate[0]);
|
||||
o.value(
|
||||
candidate[0],
|
||||
candidate[1] ? `${candidate[0]} - ${candidate[1]}` : candidate[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,36 +519,55 @@ return view.extend({
|
|||
o = s.option(form.DynamicList, 'packages', _('Packages'));
|
||||
}
|
||||
|
||||
L.resolveDefault(map.render()).
|
||||
then(form_rendered => {
|
||||
L.resolveDefault(map.render()).then((form_rendered) => {
|
||||
ui.showModal(_('New firmware upgrade available'), [
|
||||
E('p', _('Currently running: %s - %s').format(this.firmware.version, this.data.revision)),
|
||||
E(
|
||||
'p',
|
||||
_('Currently running: %s - %s').format(
|
||||
this.firmware.version,
|
||||
this.data.revision
|
||||
)
|
||||
),
|
||||
form_rendered,
|
||||
E('div', { class: 'right' }, [
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Cancel')), ' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive important',
|
||||
'click': ui.createHandlerFn(this, function () {
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E(
|
||||
'button',
|
||||
{
|
||||
class: 'btn cbi-button cbi-button-positive important',
|
||||
click: ui.createHandlerFn(this, function () {
|
||||
map.save().then(() => {
|
||||
this.firmware.packages = mapdata.request.packages;
|
||||
this.firmware.version = mapdata.request.version;
|
||||
this.firmware.profile = mapdata.request.profile;
|
||||
poll.add(L.bind(this.handleRequest, this), 5);
|
||||
this.pollFn = L.bind(function () {
|
||||
this.handleRequest(this.data.url, true);
|
||||
}, this);
|
||||
poll.add(this.pollFn, 5);
|
||||
poll.start();
|
||||
});
|
||||
})
|
||||
}, _('Request firmware image')),
|
||||
}),
|
||||
},
|
||||
_('Request firmware image')
|
||||
),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
ui.showModal(_('No upgrade available'), [
|
||||
E('p', _('The device runs the latest firmware version %s - %s').format(version, revision)),
|
||||
E(
|
||||
'p',
|
||||
_('The device runs the latest firmware version %s - %s').format(
|
||||
version,
|
||||
revision
|
||||
)
|
||||
),
|
||||
E('div', { class: 'right' }, [
|
||||
E('div', { class: 'btn', click: ui.hideModal }, _('Close')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -412,13 +575,14 @@ return view.extend({
|
|||
return Promise.all([
|
||||
L.resolveDefault(callPackagelist(), {}),
|
||||
L.resolveDefault(callSystemBoard(), {}),
|
||||
L.resolveDefault(fs.stat("/sys/firmware/efi"), null),
|
||||
L.resolveDefault(fs.stat('/sys/firmware/efi'), null),
|
||||
uci.load('attendedsysupgrade'),
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (response) {
|
||||
this.firmware.client = 'luci/' + response[0].packages['luci-app-attendedsysupgrade'];
|
||||
this.firmware.client =
|
||||
'luci/' + response[0].packages['luci-app-attendedsysupgrade'];
|
||||
this.firmware.packages = response[0].packages;
|
||||
|
||||
this.firmware.profile = response[1].board_name;
|
||||
|
@ -431,20 +595,46 @@ return view.extend({
|
|||
this.data.efi = response[2];
|
||||
|
||||
this.data.url = uci.get_first('attendedsysupgrade', 'server', 'url');
|
||||
this.data.advanced_mode = uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0
|
||||
this.data.advanced_mode =
|
||||
uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
|
||||
this.data.rebuilder = uci.get_first(
|
||||
'attendedsysupgrade',
|
||||
'server',
|
||||
'rebuilder'
|
||||
);
|
||||
|
||||
return E('p', [
|
||||
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.')),
|
||||
E('p', _('Currently running: %s - %s').format(this.firmware.version, this.data.revision)),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive important',
|
||||
'click': ui.createHandlerFn(this, this.handleCheck)
|
||||
}, _('Search for firmware upgrade'))
|
||||
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.'
|
||||
)
|
||||
),
|
||||
E(
|
||||
'p',
|
||||
_('Currently running: %s - %s').format(
|
||||
this.firmware.version,
|
||||
this.data.revision
|
||||
)
|
||||
),
|
||||
E(
|
||||
'button',
|
||||
{
|
||||
class: 'btn cbi-button cbi-button-positive important',
|
||||
click: ui.createHandlerFn(this, this.handleCheck),
|
||||
},
|
||||
_('Search for firmware upgrade')
|
||||
),
|
||||
]);
|
||||
},
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
handleReset: null,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue