neighbors.js 16 KB

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