b7cbdc2231fbfad83991160a598713105e6a9bf8
[openwrt-luci.git] /
1 'use strict';
2 'require view';
3 'require form';
4 'require uci';
5 'require rpc';
6 'require ui';
7 'require poll';
8 'require request';
9 'require dom';
10
11 var callPackagelist = rpc.declare({
12         object: 'rpc-sys',
13         method: 'packagelist',
14 });
15
16 var callSystemBoard = rpc.declare({
17         object: 'system',
18         method: 'board',
19 });
20
21 var callUpgradeStart = rpc.declare({
22         object: 'rpc-sys',
23         method: 'upgrade_start',
24         params: [ 'keep' ],
25 });
26
27 function get_branch(version) {
28         // determine branch of a version
29         // SNAPSHOT -> SNAPSHOT
30         // 21.02-SNAPSHOT -> 21.02
31         // 21.02.0-rc1 -> 21.02
32         // 19.07.8 -> 19.07
33         return version.replace('-SNAPSHOT', '').split('.').slice(0, 2).join('.');
34 }
35
36 function get_revision_count(revision) {
37         return parseInt(revision.substring(1).split('-')[0]);
38 }
39
40 function error_api_connect(response) {
41         ui.showModal(_('Error connecting to upgrade server'), [
42                 E('p', {},
43                         _('Could not reach API at "%s". Please try again later.')
44                 .format(response.url)),
45                 E('pre', {}, response.responseText),
46                 E('div', {
47                         class: 'right',
48                 },
49                         [
50         E('div', {
51                 class: 'btn',
52                 click: ui.hideModal,
53         },
54                 _('Close')),
55                         ]),
56         ]);
57 }
58
59 function install_sysupgrade(url, keep, sha256) {
60         displayStatus('notice spinning',
61                 E('p', _('Downloading firmware from server to browser')));
62         request
63                         .get(url, {
64         headers: {
65                 'Content-Type': 'application/x-www-form-urlencoded',
66         },
67         responseType: 'blob',
68                         })
69                         .then((response) => {
70         var form_data = new FormData();
71         form_data.append('sessionid', rpc.getSessionID());
72         form_data.append('filename', '/tmp/firmware.bin');
73         form_data.append('filemode', 600);
74         form_data.append('filedata', response.blob());
75
76         displayStatus('notice spinning',
77                                         E('p', _('Uploading firmware from browser to device')));
78         request
79                         .get(`${L.env.cgi_base}/cgi-upload`, {
80                                 method: 'PUT',
81                                 content: form_data,
82                         })
83                         .then((response) => response.json())
84                         .then((response) => {
85                                 if (response.sha256sum != sha256) {
86                 displayStatus('warning', [
87                         E('b', _('Wrong checksum')),
88                         E('p',
89                                 _('Error during download of firmware. Please try again')),
90                         E('div', {
91                                 class: 'btn',
92                                 click: ui.hideModal,
93                         },
94                                 _('Close')),
95                 ]);
96                                 } else {
97                 displayStatus(
98                                 'warning spinning',
99                                 E('p',
100                                         _('Installing the sysupgrade. Do not unpower device!')));
101                 L.resolveDefault(callUpgradeStart(keep), {})
102                                 .then((response) => {
103                                         if (keep) {
104                         ui.awaitReconnect(window.location.host);
105                                         } else {
106                         ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
107                                         }
108                                 });
109                                 }
110                         });
111                         });
112 }
113
114 function request_sysupgrade(server_url, data) {
115         var res, req;
116
117         if (data.request_hash) {
118                 req = request.get(`${server_url}/api/v1/build/${data.request_hash}`);
119         } else {
120                 req = request.post(`${server_url}/api/v1/build`, {
121                         profile: data.board_name,
122                         target: data.target,
123                         version: data.version,
124                         packages: data.packages,
125                         diff_packages: true,
126                 });
127         }
128
129         req.then((response) => {
130                 switch (response.status) {
131                 case 200:
132                         res = response.json();
133                         var image;
134                         for (image of res.images) {
135         if (image.type == 'sysupgrade') {
136                 break;
137         }
138                         }
139                         if (image.name != undefined) {
140         var sysupgrade_url = `${server_url}/store/${res.bin_dir}/${image.name}`;
141
142         var keep = E('input', {
143                 type: 'checkbox',
144         });
145         keep.checked = true;
146
147         var fields = [
148                 _('Version'),
149                 `${res.version_number} ${res.version_code}`,
150                 _('File'),
151                 E('a', {
152                         href: sysupgrade_url,
153                 },
154                         image.name),
155                 _('SHA256'),
156                 image.sha256,
157                 _('Build Date'),
158                 res.build_at,
159                 _('Target'),
160                 res.target,
161         ];
162
163         var table = E('div', {
164                 class: 'table',
165         });
166
167         for (var i = 0; i < fields.length; i += 2) {
168                 table.appendChild(E('tr', {
169                         class: 'tr',
170                 },
171                                                 [
172                                 E('td', {
173                                         class: 'td left',
174                                         width: '33%',
175                                 },
176                                         [ fields[i] ]),
177                                 E('td', {
178                                         class: 'td left',
179                                 },
180                                         [ fields[i + 1] ]),
181                                                 ]));
182         }
183
184         var modal_body = [
185                 table,
186                 E('p', {class: 'mt-2'},
187                         E('label', {
188                                 class: 'btn',
189                         },
190                                 [
191                 keep, ' ',
192                 _('Keep settings and retain the current configuration')
193                                 ])),
194                 E('div', {
195                         class: 'right',
196                 },
197                         [
198                                 E('div', {
199                 class: 'btn',
200                 click: ui.hideModal,
201                                 },
202                 _('Cancel')),
203                                 ' ',
204                                 E('div', {
205                 class: 'btn cbi-button-action',
206                 click: function() {
207                         install_sysupgrade(sysupgrade_url, keep.checked,
208                                                  image.sha256);
209                 },
210                                 },
211                 _('Install Sysupgrade')),
212                         ]),
213         ];
214
215         ui.showModal(_('Successfully created sysupgrade image'), modal_body);
216                         }
217
218                         break;
219                 case 202:
220                         res = response.json();
221                         data.request_hash = res.request_hash;
222
223                         if ('queue_position' in res)
224         displayStatus('notice spinning',
225                                         E('p', _('Request in build queue position %s')
226                                  .format(res.queue_position)));
227                         else
228         displayStatus('notice spinning',
229                                         E('p', _('Building firmware sysupgrade image')));
230
231                         setTimeout(function() { request_sysupgrade(server_url, data); }, 5000);
232                         break;
233                 case 400: // bad request
234                 case 422: // bad package
235                 case 500: // build failed
236                         res = response.json();
237                         var body = [
238         E('p', {}, res.detail),
239         E('p', {}, _('Please report the error message and request')),
240         E('b', {}, _('Request to server:')),
241         E('pre', {}, JSON.stringify(data, null, 4)),
242                         ];
243
244                         if (res.stdout) {
245         body.push(E('b', {}, 'STDOUT:'));
246         body.push(E('pre', {}, res.stdout));
247                         }
248
249                         if (res.stderr) {
250         body.push(E('b', {}, 'STDERR:'));
251         body.push(E('pre', {}, res.stderr));
252                         }
253
254                         body = body.concat([
255         E('div', {
256                 class: 'right',
257         },
258                 [
259                         E('div', {
260                                 class: 'btn',
261                                 click: ui.hideModal,
262                         },
263                                 _('Close')),
264                 ]),
265                         ]);
266                         ui.showModal(_('Error building the sysupgrade'), body);
267                         break;
268                 }
269         });
270 }
271
272 async function check_sysupgrade(server_url, system_board, packages) {
273         var {board_name} = system_board;
274         var {target, version, revision} = system_board.release;
275         var current_branch = get_branch(version);
276         var advanced_mode =
277                         uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
278         var candidates = [];
279         var response;
280
281         displayStatus('notice spinning',
282                 E('p', _('Searching for an available sysupgrade of %s - %s')
283                                  .format(version, revision)));
284
285         if (version.endsWith('SNAPSHOT')) {
286                 response =
287         await request.get(`${server_url}/api/v1/revision/${version}/${target}`);
288                 if (!response.ok) {
289                         error_api_connect(response);
290                         return;
291                 }
292
293                 const remote_revision = response.json().revision;
294
295                 if (get_revision_count(revision) < get_revision_count(remote_revision)) {
296                         candidates.push(version);
297                 }
298         } else {
299                 response = await request.get(`${server_url}/api/overview`, {
300                         timeout: 8000,
301                 });
302
303                 if (!response.ok) {
304                         error_api_connect(response);
305                         return;
306                 }
307
308                 const latest = response.json().latest;
309
310                 for (let remote_version of latest) {
311                         var remote_branch = get_branch(remote_version);
312
313                         // already latest version installed
314                         if (version == remote_version) {
315         break;
316                         }
317
318                         // skip branch upgrades outside the advanced mode
319                         if (current_branch != remote_branch && advanced_mode == 0) {
320         continue;
321                         }
322
323                         candidates.unshift(remote_version);
324
325                         // don't offer branches older than the current
326                         if (current_branch == remote_branch) {
327         break;
328                         }
329                 }
330         }
331
332         if (candidates.length) {
333                 var m, s, o;
334
335                 var mapdata = {
336                         request: {
337         board_name: board_name,
338         target: target,
339         version: candidates[0],
340         packages: Object.keys(packages).sort(),
341                         },
342                 };
343
344                 m = new form.JSONMap(mapdata, '');
345
346                 s = m.section(form.NamedSection, 'request', 'example', '',
347                         'Use defaults for the safest update');
348                 o = s.option(form.ListValue, 'version', 'Select firmware version');
349                 for (let candidate of candidates) {
350                         o.value(candidate, candidate);
351                 }
352
353                 if (advanced_mode == 1) {
354                         o = s.option(form.Value, 'board_name', 'Board Name / Profile');
355                         o = s.option(form.DynamicList, 'packages', 'Packages');
356                 }
357
358                 m.render().then(function(form_rendered) {
359                         ui.showModal(_('New upgrade available'), [
360         form_rendered,
361         E('div', {
362                 class: 'right',
363         },
364                 [
365                         E('div', {
366                                 class: 'btn',
367                                 click: ui.hideModal,
368                         },
369                                 _('Cancel')),
370                         ' ',
371                         E('div', {
372                                 class: 'btn cbi-button-action',
373                                 click: function() {
374                 m.save().then((foo) => {
375                         request_sysupgrade(server_url, mapdata.request);
376                 });
377                                 },
378                         },
379                                 _('Request Sysupgrade')),
380                 ]),
381                         ]);
382                 });
383         } else {
384                 ui.showModal(_('No upgrade available'), [
385                         E('p', {},
386         _('The device runs the latest firmware version %s - %s')
387                         .format(version, revision)),
388                         E('div', {
389         class: 'right',
390                         },
391         [
392                 E('div', {
393                         class: 'btn',
394                         click: ui.hideModal,
395                 },
396                         _('Close')),
397         ]),
398                 ]);
399         }
400 }
401
402 function displayStatus(type, content) {
403         if (type) {
404                 var message = ui.showModal('', '');
405
406                 message.classList.add('alert-message');
407                 DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/));
408
409                 if (content)
410                         dom.content(message, content);
411         } else {
412                 ui.hideModal();
413         }
414 }
415
416 return view.extend({
417         load: function() {
418                 return Promise.all([
419                         L.resolveDefault(callPackagelist(), {}),
420                         L.resolveDefault(callSystemBoard(), {}),
421                         uci.load('attendedsysupgrade'),
422                 ]);
423         },
424         render: function(res) {
425                 var packages = res[0].packages;
426                 var system_board = res[1];
427                 var auto_search =
428         uci.get_first('attendedsysupgrade', 'client', 'auto_search') || 1;
429                 var server_url = uci.get_first('attendedsysupgrade', 'server', 'url');
430
431                 var view = [
432                         E('h2', _('Attended Sysupgrade')),
433                         E('p',
434         _('The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.')),
435                         E('p',
436         _('This is done by building a new firmware on demand via an online service.')),
437                 ];
438
439                 if (auto_search == 1) {
440                         check_sysupgrade(server_url, system_board, packages);
441                 }
442
443                 view.push(E('p', {
444                         class: 'btn cbi-button-positive',
445                         click:
446                 function() { check_sysupgrade(server_url, system_board, packages); },
447                 },
448                 _('Search for sysupgrade')));
449
450                 return view;
451         },
452 });
git clone https://git.99rst.org/PROJECT