luci-app-olsr: fix null pointer exception (#6778)
[project/luci.git] / applications / luci-app-olsr / htdocs / luci-static / resources / view / olsr / status-olsr / neighbors.js
1 'use strict';
2 'require uci';
3 'require view';
4 'require poll';
5 'require rpc';
6 'require ui';
7 'require network';
8
9 function etx_color(etx) {
10 let color = '#bb3333';
11 if (etx === 0) {
12 color = '#bb3333';
13 } else if (etx < 2) {
14 color = '#00cc00';
15 } else if (etx < 4) {
16 color = '#ffcb05';
17 } else if (etx < 10) {
18 color = '#ff6600';
19 }
20 return color;
21 }
22
23 function snr_colors(snr) {
24 let color = '#bb3333';
25 if (snr === 0) {
26 color = '#bb3333';
27 } else if (snr > 30) {
28 color = '#00cc00';
29 } else if (snr > 20) {
30 color = '#ffcb05';
31 } else if (snr > 5) {
32 color = '#ff6600';
33 }
34 return color;
35 }
36
37 return view.extend({
38 callGetJsonStatus: rpc.declare({
39 object: 'olsrinfo',
40 method: 'getjsondata',
41 params: ['otable', 'v4_port', 'v6_port'],
42 }),
43
44 callGetHosts: rpc.declare({
45 object: 'olsrinfo',
46 method: 'hosts',
47 }),
48
49 fetch_jsoninfo: function (otable) {
50 var jsonreq4 = '';
51 var jsonreq6 = '';
52 var v4_port = parseInt(uci.get('olsrd', 'olsrd_jsoninfo', 'port') || '') || 9090;
53 var v6_port = parseInt(uci.get('olsrd6', 'olsrd_jsoninfo', 'port') || '') || 9090;
54 var json;
55 var self = this;
56 return new Promise(function (resolve, reject) {
57 L.resolveDefault(self.callGetJsonStatus(otable, v4_port, v6_port), {})
58 .then(function (res) {
59 json = res;
60
61 jsonreq4 = JSON.parse(json.jsonreq4);
62 jsonreq6 = json.jsonreq6 !== '' ? JSON.parse(json.jsonreq6) : [];
63 var jsondata4 = {};
64 var jsondata6 = {};
65 var data4 = [];
66 var data6 = [];
67 var has_v4 = false;
68 var has_v6 = false;
69
70 if (jsonreq4 === '' && jsonreq6 === '') {
71 window.location.href = 'error_olsr';
72 reject([null, 0, 0, true]);
73 return;
74 }
75
76 if (jsonreq4 !== '') {
77 has_v4 = true;
78 jsondata4 = jsonreq4 || {};
79 if (otable === 'status') {
80 data4 = jsondata4;
81 } else {
82 data4 = jsondata4[otable] || [];
83 }
84
85 for (var i = 0; i < data4.length; i++) {
86 data4[i]['proto'] = '4';
87 }
88 }
89
90 if (jsonreq6 !== '') {
91 has_v6 = true;
92 jsondata6 = jsonreq6 || {};
93 if (otable === 'status') {
94 data6 = jsondata6;
95 } else {
96 data6 = jsondata6[otable] || [];
97 }
98
99 for (var j = 0; j < data6.length; j++) {
100 data6[j]['proto'] = '6';
101 }
102 }
103
104 for (var k = 0; k < data6.length; k++) {
105 data4.push(data6[k]);
106 }
107
108 resolve([data4, has_v4, has_v6, false]);
109 })
110 .catch(function (err) {
111 console.error(err);
112 reject([null, 0, 0, true]);
113 });
114 });
115 },
116
117 action_neigh: async function () {
118 try {
119 const [data, has_v4, has_v6, error] = await this.fetch_jsoninfo('links');
120
121 if (error) {
122 throw error;
123 }
124
125 function compare(a, b) {
126 if (a.proto === b.proto) {
127 return a.linkCost < b.linkCost;
128 } else {
129 return a.proto < b.proto;
130 }
131 }
132
133 var assoclist = [];
134 var resolveVal = uci.get('luci_olsr', 'general', 'resolve');
135 var devices;
136 var defaultgw;
137
138 devices = await network.getWifiDevices();
139 var rts = await network.getWANNetworks();
140
141 rts.forEach(function (rt) {
142 defaultgw = rt.getGatewayAddr() || '0.0.0.0';
143 });
144
145 var networkPromises = devices.map(async function (dev) {
146 var networks = await dev.getWifiNetworks();
147
148 var promiseArr = networks.map(async function (net) {
149 var radio = await net.getDevice();
150 var [ifname, devnetwork, device, list] = await Promise.all([net.getIfname(), net.getNetworkNames(), radio ? radio.getName() : null, net.getAssocList()]);
151
152 assoclist.push({
153 ifname: ifname,
154 network: devnetwork[0],
155 device: device,
156 list: list,
157 });
158 });
159
160 await Promise.all(promiseArr);
161 });
162
163 await Promise.all(networkPromises);
164 var res = '';
165 var self = this;
166 await (async function () {
167 try {
168 res = await self.callGetHosts();
169 }
170 catch (e) {
171 console.error(e);
172 }
173 })();
174
175 function matchHostnames(ip) {
176 var lines = res.hosts.split('\n');
177 for (var i = 0; i < lines.length; i++) {
178 var ipandhostname = lines[i].trim().split(/\s+/);
179 if (ipandhostname[0] === ip) {
180 return ipandhostname[1];
181 }
182 }
183 return null;
184 }
185 var modifiedData = await Promise.all(
186 data.map(async function (v) {
187 var snr = 0;
188 var signal = 0;
189 var noise = 0;
190 var mac = '';
191 var ip;
192 var neihgt = [];
193
194 if (resolveVal === '1') {
195 var hostname = matchHostnames(v.remoteIP);
196 if (hostname) {
197 v.hostname = hostname;
198 }
199 }
200 var hosthints = await network.getHostHints();
201 var networkStatus = await network.getStatusByAddress(v.localIP);
202 var lmac = await hosthints.getMACAddrByIPAddr(v.localIP);
203 var rmac = await hosthints.getMACAddrByIPAddr(v.remoteIP);
204
205 for (let i = 0; i < assoclist.length; i++) {
206 var val = assoclist[i];
207 if (networkStatus != undefined && val.network === networkStatus.interface && val.list) {
208 for (var assocmac in val.list) {
209 var assot = val.list[assocmac];
210 if (rmac == assot.mac) {
211 signal = parseInt(assot.signal);
212 noise = parseInt(assot.noise);
213 snr = noise * -1 - signal * -1;
214 }
215 }
216 }
217 }
218
219 if (networkStatus) {
220 v.interface = networkStatus;
221 }
222 v.snr = snr || null;
223 v.signal = signal || null;
224 v.noise = noise || null;
225 if (rmac) {
226 v.remoteMAC = rmac;
227 }
228 if (lmac) {
229 v.localMAC = lmac;
230 }
231
232 if (defaultgw === v.remoteIP) {
233 v.defaultgw = 1;
234 }
235 return v;
236 })
237 );
238
239 modifiedData.sort(compare);
240
241 var result = { links: modifiedData, has_v4: has_v4, has_v6: has_v6 };
242 return result;
243 } catch (err) {
244 console.error(err);
245 throw err;
246 }
247 },
248
249 load: function () {
250 var self = this;
251 poll.add(function () {
252 self.render();
253 }, 5);
254 return Promise.all([uci.load('olsrd'), uci.load('luci_olsr')]);
255 },
256 render: function () {
257 var neigh_res;
258 var has_v4;
259 var has_v6;
260 var self = this;
261
262 return this.action_neigh()
263 .then(function (result) {
264 neigh_res = result.links;
265 has_v4 = result.has_v4;
266 has_v6 = result.has_v6;
267
268 var table = E('div', { 'class': 'table cbi-section-table', 'id': 'olsr_neigh_table' }, [
269 E('div', { 'class': 'tr cbi-section-table-cell' }, [
270 E('div', { 'class': 'th cbi-section-table-cell' }, _('Neighbour IP')),
271 E('div', { 'class': 'th cbi-section-table-cell' }, _('Hostname')),
272 E('div', { 'class': 'th cbi-section-table-cell' }, _('Interface')),
273 E('div', { 'class': 'th cbi-section-table-cell' }, _('Local interface IP')),
274 E('div', { 'class': 'th cbi-section-table-cell' }, 'LQ'),
275 E('div', { 'class': 'th cbi-section-table-cell' }, 'NLQ'),
276 E('div', { 'class': 'th cbi-section-table-cell' }, 'ETX'),
277 E('div', { 'class': 'th cbi-section-table-cell' }, 'SNR'),
278 ]),
279 ]);
280
281 var rv = [];
282 for (var k = 0; k < neigh_res.length; k++) {
283 var link = neigh_res[k];
284 link.linkCost = (link.linkCost).toFixed(3) || 0;
285 if (link.linkCost === 4194304) {
286 link.linkCost = 0;
287 }
288 var color = etx_color(link.linkCost);
289 var snr_color = snr_colors(link.snr);
290 var defaultgw_color = '';
291 if (link.defaultgw === 1) {
292 defaultgw_color = '#ffff99';
293 }
294
295 rv.push({
296 rip: link.remoteIP,
297 hn: link.hostname,
298 lip: link.localIP,
299 ifn: link.interface,
300 lq: link.linkQuality.toFixed(3),
301 nlq: link.neighborLinkQuality.toFixed(3),
302 cost: link.linkCost,
303 snr: link.snr,
304 signal: link.signal,
305 noise: link.noise,
306 color: color,
307 snr_color: snr_color,
308 dfgcolor: defaultgw_color,
309 proto: link.proto,
310 });
311 }
312
313 var nt = document.getElementById('olsr_neigh_table');
314 if (nt) {
315 var s =
316 '<div class="tr cbi-section-table-cell">' +
317 '<div class="th cbi-section-table-cell">Neighbour IP</div>' +
318 '<div class="th cbi-section-table-cell">Hostname</div>' +
319 '<div class="th cbi-section-table-cell">Interface</div>' +
320 '<div class="th cbi-section-table-cell">Local interface IP</div>' +
321 '<div class="th cbi-section-table-cell">LQ</div>' +
322 '<div class="th cbi-section-table-cell">NLQ</div>' +
323 '<div class="th cbi-section-table-cell">ETX</div>' +
324 '<div class="th cbi-section-table-cell">SNR</div>' +
325 '</div>';
326
327 for (var idx = 0; idx < rv.length; idx++) {
328 var neigh = rv[idx];
329
330 if (neigh.proto == '6') {
331 s +=
332 '<div class="tr cbi-section-table-row cbi-rowstyle-' +
333 (1 + (idx % 2)) +
334 ' proto-' +
335 neigh.proto +
336 '">' +
337 '<div class="td cbi-section-table-cell left" style="background-color:' +
338 neigh.dfgcolor +
339 '"><a href="http://[' +
340 neigh.rip +
341 ']/cgi-bin-status.html">' +
342 neigh.rip +
343 '</a></div>';
344 } else {
345 s +=
346 '<div class="tr cbi-section-table-row cbi-rowstyle-' +
347 (1 + (idx % 2)) +
348 ' proto-' +
349 neigh.proto +
350 '">' +
351 '<div class="td cbi-section-table-cell left" style="background-color:' +
352 neigh.dfgcolor +
353 '"><a href="http://' +
354 neigh.rip +
355 '/cgi-bin-status.html">' +
356 neigh.rip +
357 '</a></div>';
358 }
359 if (neigh.hn) {
360 s += '<div class="td cbi-section-table-cell left" style="background-color:' + neigh.dfgcolor + '"><a href="http://' + neigh.hn + '/cgi-bin-status.html">' + neigh.hn + '</a></div>';
361 } else {
362 s += '<div class="td cbi-section-table-cell left" style="background-color:' + neigh.dfgcolor + '">?</div>';
363 }
364 s +=
365 '<div class="td cbi-section-table-cell left" style="background-color:' +
366 neigh.dfgcolor +
367 '">' +
368 (neigh?.ifn?.interface ?? '?') +
369 '</div>' +
370 '<div class="td cbi-section-table-cell left" style="background-color:' +
371 neigh.dfgcolor +
372 '">' +
373 neigh.lip +
374 '</div>' +
375 '<div class="td cbi-section-table-cell left" style="background-color:' +
376 neigh.dfgcolor +
377 '">' +
378 neigh.lq +
379 '</div>' +
380 '<div class="td cbi-section-table-cell left" style="background-color:' +
381 neigh.dfgcolor +
382 '">' +
383 neigh.nlq +
384 '</div>' +
385 '<div class="td cbi-section-table-cell left" style="background-color:' +
386 neigh.color +
387 '">' +
388 neigh.cost +
389 '</div>' +
390 '<div class="td cbi-section-table-cell left" style="background-color:' +
391 neigh.snr_color +
392 '" title="Signal: ' +
393 neigh.signal +
394 ' Noise: ' +
395 neigh.noise +
396 '">' +
397 (neigh.snr || '?') +
398 '</div>' +
399 '</div>';
400 }
401
402 nt.innerHTML = s;
403 }
404
405 var i = 1;
406
407 for (var k = 0; k < neigh_res.length; k++) {
408 var link = neigh_res[k];
409 link.linkCost = Number(link.linkCost).toFixed(3) || 0;
410 if (link.linkCost === 4194304) {
411 link.linkCost = 0;
412 }
413
414 color = etx_color(link.linkCost);
415 snr_color = snr_colors(link.snr);
416
417 if (link.snr === 0) {
418 link.snr = '?';
419 }
420
421 var defaultgw_color = '';
422 if (link.defaultgw === 1) {
423 defaultgw_color = '#ffff99';
424 }
425
426 var tr = E(
427 'div',
428 {
429 'class': 'tr cbi-section-table-row cbi-rowstyle-' + i + ' proto-' + link.proto,
430 },
431 [
432 link.proto === '6'
433 ? E(
434 'div',
435 {
436 'class': 'td cbi-section-table-cell left',
437 'style': 'background-color:' + defaultgw_color,
438 },
439 [
440 E(
441 'a',
442 {
443 'href': 'http://[' + link.remoteIP + ']/cgi-bin-status.html',
444 },
445 link.remoteIP
446 ),
447 ]
448 )
449 : E(
450 'div',
451 {
452 'class': 'td cbi-section-table-cell left',
453 'style': 'background-color:' + defaultgw_color,
454 },
455 [
456 E(
457 'a',
458 {
459 'href': 'http://' + link.remoteIP + '/cgi-bin-status.html',
460 },
461 link.remoteIP
462 ),
463 ]
464 ),
465 E(
466 'div',
467 {
468 'class': 'td cbi-section-table-cell left',
469 'style': 'background-color:' + defaultgw_color,
470 },
471 [E('a', { 'href': 'http://%q/cgi-bin-status.html'.format(link.hostname) }, '%h'.format(link.hostname))]
472 ),
473 E(
474 'div',
475 {
476 'class': 'td cbi-section-table-cell left',
477 'style': 'background-color:' + defaultgw_color,
478 },
479 link?.interface?.interface ?? '?'
480 ),
481 E(
482 'div',
483 {
484 'class': 'td cbi-section-table-cell left',
485 'style': 'background-color:' + defaultgw_color,
486 },
487 link.localIP
488 ),
489 E(
490 'div',
491 {
492 'class': 'td cbi-section-table-cell left',
493 'style': 'background-color:' + defaultgw_color,
494 },
495 [E('div', {}, link.linkQuality.toFixed(3))]
496 ),
497 E(
498 'div',
499 {
500 'class': 'td cbi-section-table-cell left',
501 'style': 'background-color:' + defaultgw_color,
502 },
503 [E('div', {}, link.neighborLinkQuality.toFixed(3))]
504 ),
505 E(
506 'div',
507 {
508 'class': 'td cbi-section-table-cell left',
509 'style': 'background-color:' + color,
510 },
511 [E('div', {}, link.linkCost)]
512 ),
513 E(
514 'div',
515 {
516 'class': 'td cbi-section-table-cell left',
517 'style': 'background-color:' + snr_color,
518 'title': 'Signal: ' + link.signal + ' Noise: ' + link.noise,
519 },
520 link.snr
521 ),
522 ]
523 );
524
525 table.appendChild(tr);
526 i = (i % 2) + 1;
527 }
528
529 var fieldset = E('fieldset', { 'class': 'cbi-section' }, [E('legend', {}, _('Overview of currently established OLSR connections')), table]);
530
531 var h2 = E('h2', { 'name': 'content' }, _('OLSR connections'));
532 var divToggleButtons = E('div', { 'id': 'togglebuttons' });
533 var statusOlsrLegend = E('div', {}, [
534 E('h3', {}, [_('Legend') + ':']),
535 E('ul', {}, [
536 E('li', {}, [E('strong', {}, [_('LQ: ')]), _('Success rate of packages received from the neighbour')]),
537 E('li', {}, [E('strong', {}, [_('NLQ: ')]), _('Success rate of packages sent to the neighbour')]),
538 E('li', {}, [E('strong', {}, [_('ETX: ')]), _('Expected retransmission count')]),
539 E('li', { 'style': 'list-style: none' }, [
540 E('ul', {}, [
541 E('li', {}, [E('strong', { 'style': 'color:#00cc00' }, [_('Green')]), ':', _('Very good (ETX < 2)')]),
542 E('li', {}, [E('strong', { 'style': 'color:#ffcb05' }, [_('Yellow')]), ':', _('Good (2 < ETX < 4)')]),
543 E('li', {}, [E('strong', { 'style': 'color:#ff6600' }, [_('Orange')]), ':', _('Still usable (4 < ETX < 10)')]),
544 E('li', {}, [E('strong', { 'style': 'color:#bb3333' }, [_('Red')]), ':', _('Bad (ETX > 10)')]),
545 ]),
546 ]),
547 E('li', {}, [E('strong', {}, [_('SNR: ')]), _('Signal Noise Ratio in dB')]),
548 E('li', { 'style': 'list-style: none' }, [
549 E('ul', {}, [
550 E('li', {}, [E('strong', { 'style': 'color:#00cc00' }, [_('Green')]), ':', _('Very good (SNR > 30)')]),
551 E('li', {}, [E('strong', { 'style': 'color:#ffcb05' }, [_('Yellow')]), ':', _('Good (30 > SNR > 20)')]),
552 E('li', {}, [E('strong', { 'style': 'color:#ff6600' }, [_('Orange')]), ':', _('Still usable (20 > SNR > 5)')]),
553 E('li', {}, [E('strong', { 'style': 'color:#bb3333' }, [_('Red')]), ':', _('Bad (SNR < 5)')]),
554 ]),
555 ]),
556 ]),
557 ]);
558
559 var statusOlsrCommonJs = null;
560
561 if (has_v4 && has_v6) {
562 statusOlsrCommonJs = E('script', {
563 type: 'text/javascript',
564 src: L.resource('common/common_js.js'),
565 });
566 }
567
568 var result = E([], {}, [h2, divToggleButtons, fieldset, statusOlsrLegend, statusOlsrCommonJs]);
569
570 return result;
571 })
572 .catch(function (error) {
573 console.error(error);
574 });
575 },
576 handleSaveApply: null,
577 handleSave: null,
578 handleReset: null,
579 });