luci-mod-network: steering flow - permit only integer value
[project/luci.git] / applications / luci-app-attendedsysupgrade / htdocs / luci-static / resources / view / attendedsysupgrade / overview.js
index 5d5d4bd9ba8f01dd171f06359f35f741a682e1f7..0e6ae9d20cca100e8f9e8a6b7c2bfabb893d82cd 100644 (file)
 'require poll';
 'require request';
 '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'
+       method: 'board',
 });
 
-var callUpgradeStart = rpc.declare({
+let callUpgradeStart = rpc.declare({
        object: 'rpc-sys',
        method: 'upgrade_start',
-       params: ["keep"]
+       params: ['keep'],
 });
 
+/**
+ * Returns the branch of a given version. This helps to offer upgrades
+ * for point releases (aka within the branch).
+ *
+ * Logic:
+ * SNAPSHOT -> SNAPSHOT
+ * 21.02-SNAPSHOT -> 21.02
+ * 21.02.0-rc1 -> 21.02
+ * 19.07.8 -> 19.07
+ *
+ * @param {string} version
+ * Input version from which to determine the branch
+ * @returns {string}
+ * The determined branch
+ */
 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(".");
+       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');
-                                                       }
-                                               });
-                                       }
-                               });
-               });
+/**
+ * The OpenWrt revision string contains both a hash as well as the number
+ * commits since the OpenWrt/LEDE reboot. It helps to determine if a
+ * snapshot is newer than another.
+ *
+ * @param {string} revision
+ * Revision string of a OpenWrt device
+ * @returns {integer}
+ * The number of commits since OpenWrt/LEDE reboot
+ */
+function get_revision_count(revision) {
+       return parseInt(revision.substring(1).split('-')[0]);
 }
 
-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;
-                                       }
+return view.extend({
+       steps: {
+               init: [10, _('Received build request')],
+               download_imagebuilder: [20, _('Downloading ImageBuilder archive')],
+               unpack_imagebuilder: [40, _('Setting Up ImageBuilder')],
+               calculate_packages_hash: [60, _('Validate package selection')],
+               building_image: [80, _('Generating firmware image')],
+       },
+
+       request_hash: '',
+       sha256_unsigned: '',
+
+       selectImage: function (images, data, firmware) {
+               var filesystemFilter = function(e) {
+                       return (e.filesystem == firmware.filesystem);
+               }
+               var typeFilter = function(e) {
+                       if (firmware.target.indexOf("x86") != -1) {
+                               // x86 images can be combined-efi (EFI) or combined (BIOS)
+                               if (data.efi) {
+                                       return (e.type == 'combined-efi');
+                               } else {
+                                       return (e.type == 'combined');
                                }
-                               if (image.name != undefined) {
-                                       var sysupgrade_url = server_url + "/store/" + res.bin_dir + "/" + image.name;
+                       } else {
+                               return (e.type == 'sysupgrade' || e.type == 'combined');
+                       }
+               }
+               return images.filter(filesystemFilter).filter(typeFilter)[0];
+       },
 
-                                       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'
-                                       });
+       handle200: function (response, content, data, firmware) {
+               response = response.json();
+               let image = this.selectImage(response.images, data, firmware);
+
+               if (image.name != undefined) {
+                       this.sha256_unsigned = image.sha256_unsigned;
+                       let sysupgrade_url = `${data.url}/store/${response.bin_dir}/${image.name}`;
+
+                       let keep = E('input', { type: 'checkbox' });
+                       keep.checked = true;
+
+                       let fields = [
+                               _('Version'),
+                               `${response.version_number} ${response.version_code}`,
+                               _('SHA256'),
+                               image.sha256,
+                       ];
+
+                       if (data.advanced_mode == 1) {
+                               fields.push(
+                                       _('Profile'),
+                                       response.id,
+                                       _('Target'),
+                                       response.target,
+                                       _('Build Date'),
+                                       response.build_at,
+                                       _('Filename'),
+                                       image.name,
+                                       _('Filesystem'),
+                                       image.filesystem
+                               );
+                       }
 
-                                       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] : '?'])
-                                               ]));
-                                       }
+                       fields.push(
+                               '',
+                               E('a', { href: sysupgrade_url }, _('Download firmware image'))
+                       );
+                       if (data.rebuilder) {
+                               fields.push(_('Rebuilds'), E('div', { id: 'rebuilder_status' }));
+                       }
 
-                                       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'))
-                                               ])
-                                       ]
+                       let table = E('div', { class: 'table' });
 
-                                       ui.showModal(_('Successfully created sysupgrade image'), modal_body);
-                               }
+                       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]]),
+                                       ])
+                               );
+                       }
 
-                               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.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))
+                       let modal_body = [
+                               table,
+                               E(
+                                       'p',
+                                       { class: 'mt-2' },
+                                       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(
+                                               '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 (data.rebuilder) {
+                               this.handleRebuilder(content, data, firmware);
+                       }
+               }
+       },
 
-                               }
+       handle202: function (response) {
+               response = response.json();
+               this.request_hash = response.request_hash;
+
+               if ('queue_position' in response) {
+                       ui.showModal(_('Queued...'), [
+                               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%% %s').format(
+                                               this.steps[response.imagebuilder_status][0],
+                                               this.steps[response.imagebuilder_status][1]
+                                       )
+                               ),
+                       ]);
+               }
+       },
 
-                               if (res.stderr) {
-                                       body.push(E('b', {}, "STDERR:"))
-                                       body.push(E('pre', {}, res.stderr))
+       handleError: function (response, data, firmware) {
+               response = response.json();
+               const request_data = {
+                       ...data,
+                       request_hash: this.request_hash,
+                       sha256_unsigned: this.sha256_unsigned,
+                       ...firmware
+               };
+               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('p', {}, _('Request Data:')),
+                       E('pre', {}, JSON.stringify({ ...request_data }, null, 4)),
+               ];
 
+               if (response.stdout) {
+                       body.push(E('b', {}, 'STDOUT:'));
+                       body.push(E('pre', {}, response.stdout));
+               }
+
+               if (response.stderr) {
+                       body.push(E('b', {}, 'STDERR:'));
+                       body.push(E('pre', {}, response.stderr));
+               }
+
+               body = body.concat([
+                       E('div', { class: 'right' }, [
+                               E('div', { class: 'btn', click: ui.hideModal }, _('Close')),
+                       ]),
+               ]);
+
+               ui.showModal(_('Error building the firmware image'), body);
+       },
+
+       handleRequest: function (server, main, content, data, firmware) {
+               let request_url = `${server}/api/v1/build`;
+               let method = 'POST';
+               let local_content = content;
+
+               /**
+                * If `request_hash` is available use a GET request instead of
+                * sending the entire object.
+                */
+               if (this.request_hash && main == true) {
+                       request_url += `/${this.request_hash}`;
+                       local_content = {};
+                       method = 'GET';
+               }
+
+               request
+                       .request(request_url, { method: method, content: local_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:
+                                               if (main == true) {
+                                                       poll.remove(this.pollFn);
+                                                       this.handle200(response, content, data, firmware);
+                                               } else {
+                                                       poll.remove(this.rebuilder_polls[server]);
+                                                       response = response.json();
+                                                       let view = document.getElementById(server);
+                                                       let image = this.selectImage(response.images, data, firmware);
+                                                       if (image.sha256_unsigned == this.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
+                                               if (main == true) {
+                                                       poll.remove(this.pollFn);
+                                                       this.handleError(response, data, firmware);
+                                                       break;
+                                               } else {
+                                                       poll.remove(this.rebuilder_polls[server]);
+                                                       document.getElementById(server).innerText = '🚫 %s'.format(
+                                                               server
+                                                       );
+                                               }
                                }
+                       });
+       },
 
-                               body = body.concat([
-                                       E('div', {
-                                               'class': 'right'
-                                       }, [
-                                               E('div', {
-                                                       'class': 'btn',
-                                                       'click': ui.hideModal
-                                               }, _('Close'))
-                                       ])
+       handleRebuilder: function (content, data, firmware) {
+               this.rebuilder_polls = {};
+               for (let rebuilder of data.rebuilder) {
+                       this.rebuilder_polls[rebuilder] = L.bind(
+                               this.handleRequest,
+                               this,
+                               rebuilder,
+                               false,
+                               content,
+                               data,
+                               firmware
+                       );
+                       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')
+                       ),
+               ]);
+
+               request
+                       .get(url, {
+                               headers: {
+                                       'Content-Type': 'application/x-www-form-urlencoded',
+                               },
+                               responseType: 'blob',
+                       })
+                       .then((response) => {
+                               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')
+                                       ),
                                ]);
-                               ui.showModal(_('Error building the sysupgrade'), body);
-                               break;
+
+                               request
+                                       .get(`${L.env.cgi_base}/cgi-upload`, {
+                                               method: 'PUT',
+                                               content: form_data,
+                                       })
+                                       .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')),
+                                                       ]);
+                                               } else {
+                                                       ui.showModal(_('Installing...'), [
+                                                               E(
+                                                                       'p',
+                                                                       { class: 'spinning' },
+                                                                       _('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');
+                                                               }
+                                                       });
+                                               }
+                                       });
+                       });
+       },
+
+       handleCheck: function (data, firmware) {
+               this.request_hash = '';
+               let { url, revision, advanced_mode, branch } = data;
+               let { version, target, profile, packages } = firmware;
+               let candidates = [];
+               let request_url = `${url}/api/overview`;
+               if (version.endsWith('SNAPSHOT')) {
+                       request_url = `${url}/api/v1/revision/${version}/${target}`;
                }
-       });
-}
 
-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 = [];
-
-       request.get(server_url + "/api/latest", {
-                       timeout: 8000
-               })
-               .then(response => response.json())
-               .then(response => {
-                       if (current_version == "SNAPSHOT") {
-                               candidates.push("SNAPSHOT");
+               ui.showModal(_('Searching...'), [
+                       E(
+                               'p',
+                               { class: 'spinning' },
+                               _('Searching for an available sysupgrade of %s - %s').format(
+                                       version,
+                                       revision
+                               )
+                       ),
+               ]);
+
+               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('pre', {}, response.responseText),
+                                       E('div', { class: 'right' }, [
+                                               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)
+                               ) {
+                                       candidates.push([version, remote_revision]);
+                               }
                        } else {
-                               for (let version of response["latest"]) {
-                                       var branch = get_branch(version);
+                               const latest = response.json().latest;
+
+                               for (let remote_version of latest) {
+                                       let remote_branch = get_branch(remote_version);
 
                                        // already latest version installed
-                                       if (current_version == version) {
+                                       if (version == remote_version) {
                                                break;
                                        }
 
                                        // skip branch upgrades outside the advanced mode
-                                       if (current_branch != branch && advanced_mode == 0) {
+                                       if (branch != remote_branch && advanced_mode == 0) {
                                                continue;
                                        }
 
-                                       candidates.unshift(version);
+                                       candidates.unshift([remote_version, null]);
 
                                        // don't offer branches older than the current
-                                       if (current_branch == branch) {
+                                       if (branch == remote_branch) {
                                                break;
                                        }
                                }
                        }
+
+                       // allow to re-install running firmware in advanced mode
+                       if (advanced_mode == 1) {
+                               candidates.unshift([version, revision]);
+                       }
+
                        if (candidates.length) {
-                               var m, s, o;
+                               let s, o;
 
-                               var mapdata = {
+                               let mapdata = {
                                        request: {
-                                               board_name: board_name,
-                                               target: target,
-                                               version: candidates[0],
+                                               profile,
+                                               version: candidates[0][0],
                                                packages: Object.keys(packages).sort(),
-                                       }
-                               }
-
-                               m = new form.JSONMap(mapdata, '');
-
-                               s = m.section(form.NamedSection, 'request', 'example', '',
-                                       'Use defaults for the safest update');
+                                       },
+                               };
+
+                               let map = new form.JSONMap(mapdata, '');
+
+                               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) {
-                                       o.value(candidate, candidate);
+                                       if (candidate[0] == version && candidate[1] == revision) {
+                                               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]
+                                               );
+                                       }
                                }
 
                                if (advanced_mode == 1) {
-                                       o = s.option(form.Value, 'board_name', 'Board Name / Profile');
-                                       o = s.option(form.DynamicList, 'packages', 'Packages');
+                                       o = s.option(form.Value, 'profile', _('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
-                                                                                       )
+                               L.resolveDefault(map.render()).then((form_rendered) => {
+                                       ui.showModal(_('New firmware upgrade available'), [
+                                               E(
+                                                       'p',
+                                                       _('Currently running: %s - %s').format(
+                                                               version,
+                                                               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 () {
+                                                                               map.save().then(() => {
+                                                                                       const content = {
+                                                                                               ...firmware,
+                                                                                               packages: mapdata.request.packages,
+                                                                                               version: mapdata.request.version,
+                                                                                               profile: mapdata.request.profile
+                                                                                       };
+                                                                                       this.pollFn = L.bind(function () {
+                                                                                               this.handleRequest(url, true, content, data, firmware);
+                                                                                       }, this);
+                                                                                       poll.add(this.pollFn, 5);
+                                                                                       poll.start();
                                                                                });
-                                                                       }
-                                                               }, _('Request Sysupgrade'))
-                                                       ])
-                                               ]);
-                                       });
+                                                                       }),
+                                                               },
+                                                               _('Request firmware image')
+                                                       ),
+                                               ]),
+                                       ]);
+                               });
                        } 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'))
-                                       ])
+                                       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')),
+                                       ]),
                                ]);
                        }
-               })
-               .catch(error => {
-                       ui.showModal(_('Error connecting to upgrade server'), [
-                               E('p', {}, _('Could not reach API at "%s". Please try again later.'.format(server_url))),
-                               E('pre', {}, error),
-                               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([
+       load: async function () {
+               const promises = await Promise.all([
                        L.resolveDefault(callPackagelist(), {}),
                        L.resolveDefault(callSystemBoard(), {}),
-                       uci.load('attendedsysupgrade')
+                       L.resolveDefault(fs.stat('/sys/firmware/efi'), null),
+                       uci.load('attendedsysupgrade'),
                ]);
+               const data = {
+                       url: uci.get_first('attendedsysupgrade', 'server', 'url'),
+                       branch: get_branch(promises[1].release.version),
+                       revision: promises[1].release.revision,
+                       efi: promises[2],
+                       advanced_mode: uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0,
+                       rebuilder: uci.get_first('attendedsysupgrade', 'server', 'rebuilder')
+               };
+               const firmware = {
+                       client: 'luci/' + promises[0].packages['luci-app-attendedsysupgrade'],
+                       packages: promises[0].packages,
+                       profile: promises[1].board_name,
+                       target: promises[1].release.target,
+                       version: promises[1].release.version,
+                       diff_packages: true,
+                       filesystem: promises[1].rootfs_type
+               };
+               return [data, firmware];
        },
-       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;
+       render: function (response) {
+               const data = response[0];
+               const firmware = response[1];
+
+               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(
+                                       firmware.version,
+                                       data.revision
+                               )
+                       ),
+                       E(
+                               'button',
+                               {
+                                       class: 'btn cbi-button cbi-button-positive important',
+                                       click: ui.createHandlerFn(this, this.handleCheck, data, firmware),
+                               },
+                               _('Search for firmware upgrade')
+                       ),
+               ]);
        },
-
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null,
 });