From f746a52979141df8ffbf7c3774002fead7817724 Mon Sep 17 00:00:00 2001 From: Paul Spooren Date: Thu, 30 Jan 2025 23:36:34 +0000 Subject: [PATCH] luci-app-attendedsysupgrade: add firmware upgrade check status include The status include is named '11' so as to come immediately after '10_system' (which displays the system properties). Its only permanent addition to the status page is a checkbox for the user preference to check for updates. If the UCI luci core section contains the option: `check_for_newer_firmwares` then upon load, the status page checks if the download server offers a new(er) firmware compatible with the current device. Signed-off-by: Paul Spooren Signed-off-by: Paul Donald --- .../view/status/include/11_upgrades.js | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/status/include/11_upgrades.js diff --git a/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/status/include/11_upgrades.js b/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/status/include/11_upgrades.js new file mode 100644 index 0000000000..fac4f57126 --- /dev/null +++ b/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/status/include/11_upgrades.js @@ -0,0 +1,184 @@ +'use strict'; +'require baseclass'; +'require fs'; +'require rpc'; +'require uci' +'require ui'; + + +const callSystemBoard = rpc.declare({ + object: 'system', + method: 'board' +}); + +function showUpgradeNotification(type, boardinfo, new_version, upgrade_info) { + + const table_fields = [ + // Row/property, + // Current, + // Available + _('Firmware Version'), + boardinfo?.release?.version, // '24.10.0' + (L.isObject(upgrade_info) ? upgrade_info?.version_number : ''), + _('Revision'), + boardinfo?.release?.revision, // r28427-6df0e3d02a + (L.isObject(upgrade_info) ? upgrade_info?.version_code : ''), + _('Kernel Version'), + boardinfo.kernel, // 6.6.73 + upgrade_info?.linux_kernel?.version, + ]; + + const table = E('table', { 'class': 'table' }); + + table.appendChild( + E('tr', { 'class': 'tr' }, [ + E('th', { 'class': 'th' }, [ ]), + E('th', { 'class': 'th' }, [ _('Current') ]), + E('th', { 'class': 'th' }, [ _('Available') ]) + ]) + ); + + for (var i = 0; i < table_fields.length; i += 3) { + table.appendChild(E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td left', 'width': '33%' }, [ table_fields[i] ]), + E('td', { 'class': 'td left' }, [ (table_fields[i + 1] != null) ? table_fields[i + 1] : '?' ]), + E('td', { 'class': 'td left' }, [ (table_fields[i + 2] != null) ? table_fields[i + 2] : '?' ]), + ])); + } + + ui.addTimeLimitedNotification(_('New Firmware Available'), [ + E('p', _('A new %s version of OpenWrt is available:').format(type)), + table, + E('p', [ + _('Check') + ' ', + E('a', {href: `/cgi-bin/luci/admin/system/attendedsysupgrade`}, _('Attended Sysupgrade')), + ' ' + _('and') + ' ', + E('a', {href: `https://openwrt.org/releases/${new_version?.split('.').slice(0, 2).join('.')}/notes-${new_version}`}, _('release notes')), + ] + ), + ], 60000, 'notice'); +}; + +function compareVersions(a, b) { + const parse = (v) => v.split(/[-+]/)[0]?.split('.').map(Number); + const parseRC = (v) => v.split(/[-+]/)[1]?.split('').map(Number); + const isPrerelease = (v) => /-/.test(v); + + const [aParts, bParts] = [parse(a), parse(b)]; + + for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) { + const numA = aParts[i] || 0; + const numB = bParts[i] || 0; + if (numA > numB) return true; + if (numA < numB) return false; + } + + const [aRC, bRC] = [parseRC(a), parseRC(b)]; + + if (aRC > bRC) return true; + if (aRC < bRC) return false; + + // If numeric parts are equal, handle release candidates + // if (isPrerelease(a) && !isPrerelease(b)) return false; + if (!isPrerelease(a) && isPrerelease(b)) return true; + return false; +} + +async function checkDeviceAvailable(boardinfo, new_version) { + const profile_url = `https://downloads.openwrt.org/releases/${new_version}/targets/${boardinfo?.release?.target}/profiles.json`; + return fetch(profile_url) + .then(response => response.json()) + .then(data => { + // special case for x86 and armsr + if (Object.keys(data?.profiles).length == 1 && Object.keys(data?.profiles)[0] == "generic") { + return [true, data]; + } + + for (const profileName in data?.profiles) { + if (profileName === boardinfo?.board_name) { + return [true, data]; + } + const profile = data?.profiles[profileName]; + if (profile.supported_devices?.includes(boardinfo?.board_name)) { + return [true, data]; + } + } + + return [false, null] + }) + .catch(error => { + console.error('Failed to fetch firmware upgrade profile information:', error); + return [false, null]; + }); +}; + +return baseclass.extend({ + title: '', + + load: function() { + return Promise.all([ + L.resolveDefault(callSystemBoard(), {}), + uci.load('luci') + ]); + }, + + handleSetUpgradeCheck: function(pref, ev) { + ev.currentTarget.classList.add('spinning'); + ev.currentTarget.blur(); + + uci.set('luci', 'main', 'check_for_newer_firmwares', pref); + + return uci.save() + .then(L.bind(L.ui.changes.init, L.ui.changes)) + .then(L.bind(L.ui.changes.displayChanges, L.ui.changes)); + }, + + oneshot: function(data) { + var boardinfo = data[0]; + const check_upgrades = uci.get_bool('luci', 'main', 'check_for_newer_firmwares') ?? false; + + if (check_upgrades) { + fetch('https://downloads.openwrt.org/.versions.json') + .then(response => response.json()) + .then(async data => { + if (data?.oldstable_version && compareVersions(data?.oldstable_version, boardinfo?.release?.version) ) { + (async function () { + const [available, upgrade_info] = await checkDeviceAvailable(boardinfo, data?.oldstable_version); + if (available) showUpgradeNotification("oldstable", boardinfo, data?.oldstable_version, upgrade_info); + })(); + + } else if (data?.stable_version && compareVersions(data?.stable_version, boardinfo?.release?.version) ) { + (async function () { + const [available, upgrade_info] = await checkDeviceAvailable(boardinfo, data?.stable_version) + + if (available) showUpgradeNotification("stable", boardinfo, data?.stable_version, upgrade_info); + })(); + } else if (data?.upcoming_version && data?.stable_version + && compareVersions(boardinfo?.release?.version, data?.stable_version) + && compareVersions(data?.upcoming_version > boardinfo?.release?.version) ) { + (async function () { + const [available, upgrade_info] = await checkDeviceAvailable(boardinfo, data?.upcoming_version); + + if (available) showUpgradeNotification("release candidate", boardinfo, data?.upcoming_version, upgrade_info); + })(); + } + }) + .catch(error => { + console.error('Failed to fetch firmware upgrade version information:', error); + }); + } + + }, + + render: function(data) { + const check_upgrades = uci.get_bool('luci', 'main', 'check_for_newer_firmwares') ?? false; + const isReadonlyView = !L.hasViewPermission(); + + let perform_check_pref = E('input', { type: 'checkbox', 'click': L.bind(this.handleSetUpgradeCheck, this, !check_upgrades), }); + perform_check_pref.checked = check_upgrades; + + let perform_check_pref_p = E('div', [_('Look online for upgrades upon status page load') + ' ', perform_check_pref]); + + return E('div', [!isReadonlyView ? perform_check_pref_p : '']); + } +}); -- 2.30.2