luci-app-attendedsysupgrade: rework check-for-new-firmware
authorEric Fahlgren <redacted>
Fri, 23 Jan 2026 00:12:33 +0000 (16:12 -0800)
committerPaul Donald <redacted>
Fri, 23 Jan 2026 03:18:35 +0000 (04:18 +0100)
Do a complete overhaul of the firmware check:

When the Status -> Overview page is loaded, we check if the
configuration is set.  If not, we ask the user to choose between
enabled or disabled.  Once this is done, it never appears again
(much like the "set password" logic in the shell).

As a result, there is no longer a persistent section on the Overview
page with a simple toggle eating real estate and playing havoc with
the Hide/Show button scheme.

When the setting is enabled, then every time Status -> Overview
is loaded, we do the firmware check and display an alert notice
as before.  But, the alert notice now contains a button to disable
the alerts, so navigation is still simple, you don't have to dig
around to figure out how to turn it off.

The logic for version comparisons was cleaned up and simplified.

Fixes: #7925
Fixes: #8226
Signed-off-by: Eric Fahlgren <redacted>
applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js
applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/status/include/11_upgrades.js
applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/95-luci-app-attendedsysupgrade-housekeeping [new file with mode: 0755]

index d5d1ddfbf4c108415f958d586c68a8e1c33a04ff..5dcfe68d97daa3ec4531c89a799561ab2c6dac5a 100644 (file)
@@ -26,20 +26,28 @@ return view.extend({
                        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.'
+                       _('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,
+                       'login_check_for_upgrades',
+                       _('Check for upgrades'),
+                       _('Check for upgrades whenever the System -> Overview page is loaded.')
+               );
+               o.default = '0';
+               o.rmempty = false;
+
                o = s.option(
                        form.Flag,
                        'auto_search',
                        _('Search on opening'),
-                       _('Search for new sysupgrades on opening the tab')
+                       _('Search for upgrades when opening the Attended Sysupgrade tab')
                );
                o.default = '1';
                o.rmempty = false;
index aefc80a25c508a55fa585f5828e2ef8e2d81a23e..c4edcad15806f4ff6896bc570320854f5453cade 100644 (file)
@@ -11,13 +11,25 @@ const callSystemBoard = rpc.declare({
        method: 'board'
 });
 
-function showUpgradeNotification(type, boardinfo, new_version, upgrade_info)
+const check_setting = [ 'attendedsysupgrade', 'client', 'login_check_for_upgrades' ];
+
+function setSetUpgradeCheck(pref) {
+       uci.set(...check_setting, pref);
+       return uci.save()
+               .then(L.bind(L.ui.changes.init, L.ui.changes))
+               .then(L.bind(L.ui.changes.displayChanges, L.ui.changes));
+}
+
+function showUpgradeNotification(type, boardinfo, version, upgrade_info)
 {
+
+       // TODO show the toggle for _('Look online for upgrades upon status page load')...
+
        const table_rows = [
                // Title                Current                     Available
                [_('Firmware Version'), boardinfo.release.version,  upgrade_info.version_number        ],
                [_('Revision'),         boardinfo.release.revision, upgrade_info.version_code          ],
-               [_('Kernel Version'),   boardinfo?.kernel,          upgrade_info.linux_kernel?.version ],
+               [_('Kernel Version'),   boardinfo.kernel,           upgrade_info.linux_kernel?.version ],
        ];
 
        const table = E('table', { 'class': 'table' });
@@ -30,54 +42,55 @@ function showUpgradeNotification(type, boardinfo, new_version, upgrade_info)
                ])
        );
 
-       table_rows.forEach((cols) => {
+       table_rows.forEach(([c1, c2, c3]) => {
                table.appendChild(E('tr', { 'class': 'tr' }, [
-                       E('td', { 'class': 'td left', 'width': '33%' }, [ cols[0] ]),
-                       E('td', { 'class': 'td left'                 }, [ cols[1] ?? '?' ]),
-                       E('td', { 'class': 'td left'                 }, [ cols[2] ?? '?' ]),
+                       E('td', { 'class': 'td left', 'width': '33%' }, [ c1 ]),
+                       E('td', { 'class': 'td left'                 }, [ c2 ?? '?' ]),
+                       E('td', { 'class': 'td left'                 }, [ c3 ?? '?' ]),
                ]));
        });
 
+       const branch = version.split('.').slice(0, 2).join('.');
        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')),
+                       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')),
+                       E('a', {href: `https://openwrt.org/releases/${branch}/notes-${version}`}, _('release notes')),
                ]),
+               E('div', { class: 'btn', click: () => setSetUpgradeCheck(false) }, _('Stop showing upgrade alerts')),
        ], 60000, 'notice');
-};
+}
 
 function shouldUpgrade(installed, available)
 {
        // If installed is any snapshot (release or main), don't upgrade.
 
        if (! available) return false;
-       if (! installed || installed.includes('SNAPSHOT')) return false
-
-       const parse = (v) => v.split(/[-+]/)[0]?.split('.').map(Number);
-       const parseRC = (v) => v.split(/[-+]/)[1]?.split('').map(Number);
-       const isPrerelease = (v) => /-/.test(v);
+       if (! installed) return false;
+       if (installed.includes('SNAPSHOT')) return false;
+
+       // At this point we know the versions are in one of two forms:
+       //    MM.mm.rr
+       //    MM.mm.rr-rcN
+       // so partition them up into a 4-element array, with a value of
+       // 99 for the "release candidate" part of any release.
+       const parse = (v) => [
+               ...v.split('-')[0].split('.').map(Number),
+               Number(v.split(/rc/)[1] || 99)
+       ];
 
-       const [aParts, bParts] = [parse(available), parse(installed)];
+       const [aParts, iParts] = [parse(available), parse(installed)];
 
-       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;
+       for (let i = 0; i < iParts.length; i++) {
+               const aVal = aParts[i];
+               const iVal = iParts[i];
+               if (aVal > iVal) return true;
+               if (aVal < iVal) return false;
        }
 
-       const [aRC, bRC] = [parseRC(available), parseRC(installed)];
-
-       if (aRC > bRC) return true;
-       if (aRC < bRC) return false;
-
-       // If numeric parts are equal, handle release candidates
-       // if (isPrerelease(available) && !isPrerelease(installed)) return false;
-       if (!isPrerelease(available) && isPrerelease(installed)) return true;
        return false;
 }
 
@@ -116,24 +129,14 @@ return baseclass.extend({
        load: function() {
                return Promise.all([
                        L.resolveDefault(callSystemBoard(), {}),
-                       uci.load('luci')
+                       uci.load(check_setting[0]),
                ]);
        },
 
-       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');
+               const check_upgrades = uci.get_bool(...check_setting);
 
                if (check_upgrades) {
                        fetch('https://downloads.openwrt.org/.versions.json')
@@ -177,14 +180,32 @@ return baseclass.extend({
        },
 
        render: function(data) {
-               const check_upgrades = uci.get_bool('luci', 'main', 'check_for_newer_firmwares') ?? false;
-               const isReadonlyView = !L.hasViewPermission();
+               const isReadOnlyView = !L.hasViewPermission();
+               if (isReadOnlyView)
+                       return null;
+
+               let check_upgrades = uci.get(...check_setting);
+               if (check_upgrades != null)
+                       return null;
+
+               let modal_body = [
+                       E('p',  _('Checking for firmware upgrades requires access to several files ' +
+                         'on the downloads site, so requires internet access.')),
+
+                       E('p',  _('The check will be performed every time the Status -> Overview page is loaded.')),
 
-               let perform_check_pref = E('input', { type: 'checkbox', 'click': L.bind(this.handleSetUpgradeCheck, this, !check_upgrades), });
-               perform_check_pref.checked = check_upgrades;
+                       E('p', _('You have not yet specified a preference for this setting. ' +
+                         'Once set, this dialog will not be shown again, but you can go to ' +
+                         'System -> Attended Sysupgrade configuration to change the setting.')),
 
-               let perform_check_pref_p = E('div', [_('Look online for upgrades upon status page load') + ' ', perform_check_pref]);
+                       E('div', { class: 'right' }, [
+                               E('div', { class: 'btn', click: () => setSetUpgradeCheck(true)  }, _('Yes, enable checking')),
+                               E('div', { class: 'btn', click: () => setSetUpgradeCheck(false) }, _('No, disable checking')),
+                               E('div', { class: 'btn', click: ui.hideModal }, _('Close')),
+                       ]),
+               ];
+               ui.showModal(_('Check online for firmware upgrades'), modal_body);
 
-               return E('div', [!isReadonlyView ? perform_check_pref_p : '']);
+               return null;
        }
 });
diff --git a/applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/95-luci-app-attendedsysupgrade-housekeeping b/applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/95-luci-app-attendedsysupgrade-housekeeping
new file mode 100755 (executable)
index 0000000..5ad127b
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+check="$(uci -q get luci.main.check_for_newer_firmwares)"
+if [ -n "$check" ]; then
+        if ! uci -q get attendedsysupgrade.client.login_check_for_upgrades >/dev/null; then
+                uci set attendedsysupgrade.client.login_check_for_upgrades="$check"
+        fi
+        uci -q delete luci.main.check_for_newer_firmwares
+fi
+exit 0
git clone https://git.99rst.org/PROJECT