c4edcad15806f4ff6896bc570320854f5453cade
[openwrt-luci.git] /
1 'use strict';
2 'require baseclass';
3 'require fs';
4 'require rpc';
5 'require uci'
6 'require ui';
7
8
9 const callSystemBoard = rpc.declare({
10         object: 'system',
11         method: 'board'
12 });
13
14 const check_setting = [ 'attendedsysupgrade', 'client', 'login_check_for_upgrades' ];
15
16 function setSetUpgradeCheck(pref) {
17         uci.set(...check_setting, pref);
18         return uci.save()
19                 .then(L.bind(L.ui.changes.init, L.ui.changes))
20                 .then(L.bind(L.ui.changes.displayChanges, L.ui.changes));
21 }
22
23 function showUpgradeNotification(type, boardinfo, version, upgrade_info)
24 {
25
26         // TODO show the toggle for _('Look online for upgrades upon status page load')...
27
28         const table_rows = [
29                 // Title                Current                     Available
30                 [_('Firmware Version'), boardinfo.release.version,  upgrade_info.version_number        ],
31                 [_('Revision'),         boardinfo.release.revision, upgrade_info.version_code          ],
32                 [_('Kernel Version'),   boardinfo.kernel,           upgrade_info.linux_kernel?.version ],
33         ];
34
35         const table = E('table', { 'class': 'table' });
36
37         table.appendChild(
38                 E('tr', { 'class': 'tr' }, [
39                         E('th', { 'class': 'th' }, [  ]),
40                         E('th', { 'class': 'th' }, [ _('Current') ]),
41                         E('th', { 'class': 'th' }, [ _('Available') ])
42                 ])
43         );
44
45         table_rows.forEach(([c1, c2, c3]) => {
46                 table.appendChild(E('tr', { 'class': 'tr' }, [
47                         E('td', { 'class': 'td left', 'width': '33%' }, [ c1 ]),
48                         E('td', { 'class': 'td left'                 }, [ c2 ?? '?' ]),
49                         E('td', { 'class': 'td left'                 }, [ c3 ?? '?' ]),
50                 ]));
51         });
52
53         const branch = version.split('.').slice(0, 2).join('.');
54         ui.addTimeLimitedNotification(_('New Firmware Available'), [
55                 E('p', _('A new %s version of OpenWrt is available:').format(type)),
56                 table,
57                 E('p', [
58                         _('Check') + ' ',
59                         E('a', {href: '/cgi-bin/luci/admin/system/attendedsysupgrade'}, _('Attended Sysupgrade')),
60                         ' ' + _('and') + ' ',
61                         E('a', {href: `https://openwrt.org/releases/${branch}/notes-${version}`}, _('release notes')),
62                 ]),
63                 E('div', { class: 'btn', click: () => setSetUpgradeCheck(false) }, _('Stop showing upgrade alerts')),
64         ], 60000, 'notice');
65 }
66
67 function shouldUpgrade(installed, available)
68 {
69         // If installed is any snapshot (release or main), don't upgrade.
70
71         if (! available) return false;
72         if (! installed) return false;
73         if (installed.includes('SNAPSHOT')) return false;
74
75         // At this point we know the versions are in one of two forms:
76         //    MM.mm.rr
77         //    MM.mm.rr-rcN
78         // so partition them up into a 4-element array, with a value of
79         // 99 for the "release candidate" part of any release.
80         const parse = (v) => [
81                 ...v.split('-')[0].split('.').map(Number),
82                 Number(v.split(/rc/)[1] || 99)
83         ];
84
85         const [aParts, iParts] = [parse(available), parse(installed)];
86
87         for (let i = 0; i < iParts.length; i++) {
88                 const aVal = aParts[i];
89                 const iVal = iParts[i];
90                 if (aVal > iVal) return true;
91                 if (aVal < iVal) return false;
92         }
93
94         return false;
95 }
96
97 async function checkDeviceAvailable(boardinfo, new_version)
98 {
99         const profile_url = `https://downloads.openwrt.org/releases/${new_version}/targets/${boardinfo?.release?.target}/profiles.json`;
100         return fetch(profile_url)
101                 .then(response => response.json())
102                 .then(data => {
103                         // special case for x86, armsr and loongarch
104                         if (Object.keys(data?.profiles).length == 1 && Object.keys(data?.profiles)[0] == "generic") {
105                                 return [true, data];
106                         }
107
108                         for (const profileName in data?.profiles) {
109                                 if (profileName === boardinfo?.board_name) {
110                                         return [true, data];
111                                 }
112                                 const profile = data?.profiles[profileName];
113                                 if (profile.supported_devices?.includes(boardinfo?.board_name)) {
114                                         return [true, data];
115                                 }
116                         }
117
118                         return [false, null];
119                 })
120                 .catch(error => {
121                         console.error('Failed to fetch firmware upgrade profile information:', error);
122                         return [false, null];
123                 });
124 };
125
126 return baseclass.extend({
127         title: '',
128
129         load: function() {
130                 return Promise.all([
131                         L.resolveDefault(callSystemBoard(), {}),
132                         uci.load(check_setting[0]),
133                 ]);
134         },
135
136
137         oneshot: function(data) {
138                 var boardinfo = data[0];
139                 const check_upgrades = uci.get_bool(...check_setting);
140
141                 if (check_upgrades) {
142                         fetch('https://downloads.openwrt.org/.versions.json')
143                         .then(response => response.json())
144                         .then(async (versions) => {
145                                 var label = null;
146                                 var new_version = null;
147
148                                 const installed_version = boardinfo?.release?.version;
149                                 const prev_version = versions?.oldstable_version;
150                                 const curr_version = versions?.stable_version;
151                                 const next_version = versions?.upcoming_version;  // Only available during "rc".
152
153                                 if (shouldUpgrade(installed_version, prev_version)) {
154                                         // On old branch, and a newer 'old stable' is available.
155                                         label = 'old stable';
156                                         new_version = prev_version;
157                                 } else if (shouldUpgrade(installed_version, curr_version)) {
158                                         // On old stable or current branch, and newer stable is available.
159                                         label = 'stable';
160                                         new_version = curr_version;
161                                 } else if (shouldUpgrade(installed_version, next_version)) {
162                                         // On current stable or rc branch, a newer rc is available.
163                                         label = 'release candidate';
164                                         new_version = next_version;
165                                 }
166
167                                 if (new_version) {
168                                         (async function(label, new_version) {
169                                                 const [available, upgrade_info] = await checkDeviceAvailable(boardinfo, new_version);
170                                                 if (available && upgrade_info)
171                                                         showUpgradeNotification(label, boardinfo, new_version, upgrade_info);
172                                         })(label, new_version);
173                                 }
174
175                         })
176                         .catch(error => {
177                                         console.error('Failed to fetch firmware upgrade version information:', error);
178                         });
179                 }
180         },
181
182         render: function(data) {
183                 const isReadOnlyView = !L.hasViewPermission();
184                 if (isReadOnlyView)
185                         return null;
186
187                 let check_upgrades = uci.get(...check_setting);
188                 if (check_upgrades != null)
189                         return null;
190
191                 let modal_body = [
192                         E('p',  _('Checking for firmware upgrades requires access to several files ' +
193                           'on the downloads site, so requires internet access.')),
194
195                         E('p',  _('The check will be performed every time the Status -> Overview page is loaded.')),
196
197                         E('p', _('You have not yet specified a preference for this setting. ' +
198                           'Once set, this dialog will not be shown again, but you can go to ' +
199                           'System -> Attended Sysupgrade configuration to change the setting.')),
200
201                         E('div', { class: 'right' }, [
202                                 E('div', { class: 'btn', click: () => setSetUpgradeCheck(true)  }, _('Yes, enable checking')),
203                                 E('div', { class: 'btn', click: () => setSetUpgradeCheck(false) }, _('No, disable checking')),
204                                 E('div', { class: 'btn', click: ui.hideModal }, _('Close')),
205                         ]),
206                 ];
207                 ui.showModal(_('Check online for firmware upgrades'), modal_body);
208
209                 return null;
210         }
211 });
git clone https://git.99rst.org/PROJECT