Network.jsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. // @flow
  2. // Copyright 2018 The go-ethereum Authors
  3. // This file is part of the go-ethereum library.
  4. //
  5. // The go-ethereum library is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Lesser General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // The go-ethereum library is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Lesser General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Lesser General Public License
  16. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  17. import React, {Component} from 'react';
  18. import withStyles from '@material-ui/core/styles/withStyles';
  19. import Table from '@material-ui/core/Table';
  20. import TableHead from '@material-ui/core/TableHead';
  21. import TableBody from '@material-ui/core/TableBody';
  22. import TableRow from '@material-ui/core/TableRow';
  23. import TableCell from '@material-ui/core/TableCell';
  24. import Grid from '@material-ui/core/Grid/Grid';
  25. import Typography from '@material-ui/core/Typography';
  26. import {AreaChart, Area, Tooltip, YAxis} from 'recharts';
  27. import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
  28. import {faCircle as fasCircle} from '@fortawesome/free-solid-svg-icons'; // More icons at fontawesome.com/icons
  29. import {faCircle as farCircle, faClipboard as farClipboard} from '@fortawesome/free-regular-svg-icons';
  30. import convert from 'color-convert';
  31. import {Scrollbars} from 'react-custom-scrollbars';
  32. import CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';
  33. import type {Network as NetworkType, PeerEvent} from '../types/content';
  34. import {chartStrokeWidth, hues, hueScale} from '../common';
  35. // Peer chart dimensions.
  36. const trafficChartHeight = 15;
  37. const trafficChartWidth = 200;
  38. // attemptSeparator separates the peer connection attempts
  39. // such as the peers from the addresses with more attempts
  40. // go to the beginning of the table, and the rest go to the end.
  41. const attemptSeparator = 9;
  42. // setMaxIngress adjusts the peer chart's gradient values based on the given value.
  43. const setMaxIngress = (peer, value) => {
  44. peer.maxIngress = value;
  45. peer.ingressGradient = [];
  46. peer.ingressGradient.push({offset: hueScale[0], color: hues[0]});
  47. let i = 1;
  48. for (; i < hues.length && value > hueScale[i]; i++) {
  49. peer.ingressGradient.push({offset: Math.floor(hueScale[i] * 100 / value), color: hues[i]});
  50. }
  51. i--;
  52. if (i < hues.length - 1) {
  53. // Usually the maximum value gets between two points on the predefined
  54. // color scale (e.g. 123KB is somewhere between 100KB (#FFFF00) and
  55. // 1MB (#FF0000)), and the charts need to be comparable by the colors,
  56. // so we have to calculate the last hue using the maximum value and the
  57. // surrounding hues in order to avoid the uniformity of the top colors
  58. // on the charts. For this reason the two hues are translated into the
  59. // CIELAB color space, and the top color will be their weighted average
  60. // (CIELAB is perceptually uniform, meaning that any point on the line
  61. // between two pure color points is also a pure color, so the weighted
  62. // average will not lose from the saturation).
  63. //
  64. // In case the maximum value is greater than the biggest predefined
  65. // scale value, the top of the chart will have uniform color.
  66. const lastHue = convert.hex.lab(hues[i]);
  67. const proportion = (value - hueScale[i]) * 100 / (hueScale[i + 1] - hueScale[i]);
  68. convert.hex.lab(hues[i + 1]).forEach((val, j) => {
  69. lastHue[j] = (lastHue[j] * proportion + val * (100 - proportion)) / 100;
  70. });
  71. peer.ingressGradient.push({offset: 100, color: `#${convert.lab.hex(lastHue)}`});
  72. }
  73. };
  74. // setMaxEgress adjusts the peer chart's gradient values based on the given value.
  75. // In case of the egress the chart is upside down, so the gradients need to be
  76. // calculated inversely compared to the ingress.
  77. const setMaxEgress = (peer, value) => {
  78. peer.maxEgress = value;
  79. peer.egressGradient = [];
  80. peer.egressGradient.push({offset: 100 - hueScale[0], color: hues[0]});
  81. let i = 1;
  82. for (; i < hues.length && value > hueScale[i]; i++) {
  83. peer.egressGradient.unshift({offset: 100 - Math.floor(hueScale[i] * 100 / value), color: hues[i]});
  84. }
  85. i--;
  86. if (i < hues.length - 1) {
  87. // Calculate the last hue.
  88. const lastHue = convert.hex.lab(hues[i]);
  89. const proportion = (value - hueScale[i]) * 100 / (hueScale[i + 1] - hueScale[i]);
  90. convert.hex.lab(hues[i + 1]).forEach((val, j) => {
  91. lastHue[j] = (lastHue[j] * proportion + val * (100 - proportion)) / 100;
  92. });
  93. peer.egressGradient.unshift({offset: 0, color: `#${convert.lab.hex(lastHue)}`});
  94. }
  95. };
  96. // setIngressChartAttributes searches for the maximum value of the ingress
  97. // samples, and adjusts the peer chart's gradient values accordingly.
  98. const setIngressChartAttributes = (peer) => {
  99. let max = 0;
  100. peer.ingress.forEach(({value}) => {
  101. if (value > max) {
  102. max = value;
  103. }
  104. });
  105. setMaxIngress(peer, max);
  106. };
  107. // setEgressChartAttributes searches for the maximum value of the egress
  108. // samples, and adjusts the peer chart's gradient values accordingly.
  109. const setEgressChartAttributes = (peer) => {
  110. let max = 0;
  111. peer.egress.forEach(({value}) => {
  112. if (value > max) {
  113. max = value;
  114. }
  115. });
  116. setMaxEgress(peer, max);
  117. };
  118. // shortName adds some heuristics to the node name in order to make it look meaningful.
  119. const shortName = (name: string) => {
  120. const parts = name.split('/');
  121. if (parts[0].substring(0, 'parity'.length).toLowerCase() === 'parity') {
  122. // Merge Parity and Parity-Ethereum under the same name.
  123. parts[0] = 'Parity';
  124. }
  125. if (parts.length < 2) {
  126. console.error('Incorrect node name', name);
  127. return parts[0];
  128. }
  129. const versionRE = RegExp(/^v?\d+\.\d+\.\d+.*/);
  130. // Drop optional custom identifier.
  131. if (!versionRE.test(parts[1])) {
  132. if (parts.length < 3 || !versionRE.test(parts[2])) {
  133. console.error('Incorrect node name', name);
  134. return parts[0];
  135. }
  136. parts[1] = parts[2];
  137. }
  138. // Cutting anything from the version after the first - or +.
  139. parts[1] = parts[1].split('-')[0].split('+')[0];
  140. return `${parts[0]}/${parts[1]}`;
  141. };
  142. // shortLocation returns a shortened version of the given location object.
  143. const shortLocation = (location: Object) => {
  144. if (!location) {
  145. return '';
  146. }
  147. return `${location.city ? `${location.city}/` : ''}${location.country ? location.country : ''}`;
  148. };
  149. // protocol returns a shortened version of the eth protocol values.
  150. const protocol = (p: Object) => {
  151. if (!p) {
  152. return '';
  153. }
  154. if (typeof p === 'string') {
  155. return p;
  156. }
  157. if (!(p instanceof Object)) {
  158. console.error('Wrong protocol type', p, typeof p);
  159. return '';
  160. }
  161. if (!p.hasOwnProperty('version') || !p.hasOwnProperty('difficulty') || !p.hasOwnProperty('head')) {
  162. console.error('Missing protocol attributes', p);
  163. return '';
  164. }
  165. return `h=${p.head.substring(0, 10)} v=${p.version} td=${p.difficulty}`;
  166. };
  167. // inserter is a state updater function for the main component, which handles the peers.
  168. export const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {
  169. // The first message contains the metered peer history.
  170. if (update.peers && update.peers.bundles) {
  171. prev.peers = update.peers;
  172. Object.values(prev.peers.bundles).forEach((bundle) => {
  173. if (bundle.knownPeers) {
  174. Object.values(bundle.knownPeers).forEach((peer) => {
  175. if (!peer.maxIngress) {
  176. setIngressChartAttributes(peer);
  177. }
  178. if (!peer.maxEgress) {
  179. setEgressChartAttributes(peer);
  180. }
  181. if (!peer.name) {
  182. peer.name = '';
  183. peer.shortName = '';
  184. } else if (!peer.shortName) {
  185. peer.shortName = shortName(peer.name);
  186. }
  187. if (!peer.enode) {
  188. peer.enode = '';
  189. }
  190. if (!peer.protocols) {
  191. peer.protocols = {};
  192. }
  193. peer.eth = protocol(peer.protocols.eth);
  194. peer.les = protocol(peer.protocols.les);
  195. });
  196. }
  197. bundle.shortLocation = shortLocation(bundle.location);
  198. });
  199. }
  200. if (Array.isArray(update.diff)) {
  201. update.diff.forEach((event: PeerEvent) => {
  202. if (!event.addr) {
  203. console.error('Peer event without TCP address', event);
  204. return;
  205. }
  206. switch (event.remove) {
  207. case 'bundle': {
  208. delete prev.peers.bundles[event.addr];
  209. return;
  210. }
  211. case 'known': {
  212. if (!event.enode) {
  213. console.error('Remove known peer event without node URL', event.addr);
  214. return;
  215. }
  216. const bundle = prev.peers.bundles[event.addr];
  217. if (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.enode]) {
  218. console.error('No known peer to remove', event.addr, event.enode);
  219. return;
  220. }
  221. delete bundle.knownPeers[event.enode];
  222. return;
  223. }
  224. }
  225. if (!prev.peers.bundles[event.addr]) {
  226. prev.peers.bundles[event.addr] = {
  227. location: {
  228. country: '',
  229. city: '',
  230. latitude: 0,
  231. longitude: 0,
  232. },
  233. shortLocation: '',
  234. knownPeers: {},
  235. attempts: 0,
  236. };
  237. }
  238. const bundle = prev.peers.bundles[event.addr];
  239. if (event.location) {
  240. bundle.location = event.location;
  241. bundle.shortLocation = shortLocation(bundle.location);
  242. return;
  243. }
  244. if (!event.enode) {
  245. bundle.attempts++;
  246. return;
  247. }
  248. if (!bundle.knownPeers) {
  249. bundle.knownPeers = {};
  250. }
  251. if (!bundle.knownPeers[event.enode]) {
  252. bundle.knownPeers[event.enode] = {
  253. connected: [],
  254. disconnected: [],
  255. ingress: [],
  256. egress: [],
  257. active: false,
  258. name: '',
  259. shortName: '',
  260. enode: '',
  261. protocols: {},
  262. eth: '',
  263. les: '',
  264. };
  265. }
  266. const peer = bundle.knownPeers[event.enode];
  267. if (event.name) {
  268. peer.name = event.name;
  269. peer.shortName = shortName(event.name);
  270. }
  271. if (event.enode) {
  272. peer.enode = event.enode;
  273. }
  274. if (event.protocols) {
  275. peer.protocols = event.protocols;
  276. peer.eth = protocol(peer.protocols.eth);
  277. peer.les = protocol(peer.protocols.les);
  278. }
  279. if (!peer.maxIngress) {
  280. setIngressChartAttributes(peer);
  281. }
  282. if (!peer.maxEgress) {
  283. setEgressChartAttributes(peer);
  284. }
  285. if (event.connected) {
  286. if (!peer.connected) {
  287. console.warn('peer.connected should exist');
  288. peer.connected = [];
  289. }
  290. peer.connected.push(event.connected);
  291. }
  292. if (event.disconnected) {
  293. if (!peer.disconnected) {
  294. console.warn('peer.disconnected should exist');
  295. peer.disconnected = [];
  296. }
  297. peer.disconnected.push(event.disconnected);
  298. }
  299. switch (event.activity) {
  300. case 'active':
  301. peer.active = true;
  302. break;
  303. case 'inactive':
  304. peer.active = false;
  305. break;
  306. }
  307. if (Array.isArray(event.ingress) && Array.isArray(event.egress)) {
  308. if (event.ingress.length !== event.egress.length) {
  309. console.error('Different traffic sample length', event);
  310. return;
  311. }
  312. // Check if there is a new maximum value, and reset the colors in case.
  313. let maxIngress = peer.maxIngress;
  314. event.ingress.forEach(({value}) => {
  315. if (value > maxIngress) {
  316. maxIngress = value;
  317. }
  318. });
  319. if (maxIngress > peer.maxIngress) {
  320. setMaxIngress(peer, maxIngress);
  321. }
  322. // Push the new values.
  323. peer.ingress.splice(peer.ingress.length, 0, ...event.ingress);
  324. const ingressDiff = peer.ingress.length - sampleLimit;
  325. if (ingressDiff > 0) {
  326. // Check if the maximum value is in the beginning.
  327. let i = 0;
  328. while (i < ingressDiff && peer.ingress[i].value < peer.maxIngress) {
  329. i++;
  330. }
  331. // Remove the old values from the beginning.
  332. peer.ingress.splice(0, ingressDiff);
  333. if (i < ingressDiff) {
  334. // Reset the colors if the maximum value leaves the chart.
  335. setIngressChartAttributes(peer);
  336. }
  337. }
  338. // Check if there is a new maximum value, and reset the colors in case.
  339. let maxEgress = peer.maxEgress;
  340. event.egress.forEach(({value}) => {
  341. if (value > maxEgress) {
  342. maxEgress = value;
  343. }
  344. });
  345. if (maxEgress > peer.maxEgress) {
  346. setMaxEgress(peer, maxEgress);
  347. }
  348. // Push the new values.
  349. peer.egress.splice(peer.egress.length, 0, ...event.egress);
  350. const egressDiff = peer.egress.length - sampleLimit;
  351. if (egressDiff > 0) {
  352. // Check if the maximum value is in the beginning.
  353. let i = 0;
  354. while (i < egressDiff && peer.egress[i].value < peer.maxEgress) {
  355. i++;
  356. }
  357. // Remove the old values from the beginning.
  358. peer.egress.splice(0, egressDiff);
  359. if (i < egressDiff) {
  360. // Reset the colors if the maximum value leaves the chart.
  361. setEgressChartAttributes(peer);
  362. }
  363. }
  364. }
  365. });
  366. }
  367. prev.activePeerCount = 0;
  368. Object.entries(prev.peers.bundles).forEach(([addr, bundle]) => {
  369. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  370. return;
  371. }
  372. Object.entries(bundle.knownPeers).forEach(([enode, peer]) => {
  373. if (peer.active === true) {
  374. prev.activePeerCount++;
  375. }
  376. });
  377. });
  378. return prev;
  379. };
  380. // styles contains the constant styles of the component.
  381. const styles = {
  382. title: {
  383. marginLeft: 5,
  384. },
  385. table: {
  386. borderCollapse: 'unset',
  387. padding: 5,
  388. },
  389. tableHead: {
  390. height: 'auto',
  391. },
  392. tableRow: {
  393. height: 'auto',
  394. },
  395. tableCell: {
  396. paddingTop: 0,
  397. paddingRight: 5,
  398. paddingBottom: 0,
  399. paddingLeft: 5,
  400. border: 'none',
  401. fontFamily: 'monospace',
  402. fontSize: 10,
  403. },
  404. content: {
  405. height: '800px',
  406. },
  407. };
  408. // themeStyles returns the styles generated from the theme for the component.
  409. const themeStyles = theme => ({
  410. title: {
  411. color: theme.palette.common.white,
  412. },
  413. table: {
  414. background: theme.palette.grey[900],
  415. },
  416. });
  417. // limitedWidthStyle returns a style object which cuts the long text with three dots.
  418. const limitedWidthStyle = (width) => {
  419. return {
  420. textOverflow: 'ellipsis',
  421. maxWidth: width,
  422. overflow: 'hidden',
  423. whiteSpace: 'nowrap',
  424. };
  425. };
  426. export type Props = {
  427. classes: Object, // injected by withStyles()
  428. container: Object,
  429. content: NetworkType,
  430. shouldUpdate: Object,
  431. };
  432. type State = {};
  433. // Network renders the network page.
  434. class Network extends Component<Props, State> {
  435. componentDidMount() {
  436. const {container} = this.props;
  437. if (typeof container === 'undefined') {
  438. return;
  439. }
  440. container.scrollTop = 0;
  441. }
  442. formatTime = (t: string) => {
  443. const time = new Date(t);
  444. if (isNaN(time)) {
  445. return '';
  446. }
  447. const month = `0${time.getMonth() + 1}`.slice(-2);
  448. const date = `0${time.getDate()}`.slice(-2);
  449. const hours = `0${time.getHours()}`.slice(-2);
  450. const minutes = `0${time.getMinutes()}`.slice(-2);
  451. const seconds = `0${time.getSeconds()}`.slice(-2);
  452. return `${month}/${date}/${hours}:${minutes}:${seconds}`;
  453. };
  454. copyToClipboard = (text: string) => (event) => {
  455. event.preventDefault();
  456. navigator.clipboard.writeText(text).then(() => {}, () => {
  457. console.error("Failed to copy", text);
  458. });
  459. };
  460. lesList = () => {
  461. const list = [];
  462. Object.values(this.props.content.peers.bundles).forEach((bundle) => {
  463. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  464. return;
  465. }
  466. Object.entries(bundle.knownPeers).forEach(([enode, peer]) => {
  467. if (peer.les === '' || peer.eth !== '') {
  468. return;
  469. }
  470. list.push({enode, name: peer.name, location: bundle.location, protocols: peer.protocols});
  471. });
  472. });
  473. return list;
  474. };
  475. ethList = () => {
  476. const list = [];
  477. Object.values(this.props.content.peers.bundles).forEach((bundle) => {
  478. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  479. return;
  480. }
  481. Object.entries(bundle.knownPeers).forEach(([enode, peer]) => {
  482. if (peer.eth === '' && peer.les !== '') {
  483. return;
  484. }
  485. list.push({enode, name: peer.name, location: bundle.location, protocols: peer.protocols});
  486. });
  487. });
  488. return list;
  489. };
  490. attemptList = () => {
  491. const list = [];
  492. Object.entries(this.props.content.peers.bundles).forEach(([addr, bundle]) => {
  493. if (!bundle.attempts) {
  494. return;
  495. }
  496. list.push({addr, location: bundle.location, attempts: bundle.attempts});
  497. });
  498. return list;
  499. };
  500. knownPeerTableRow = (addr, enode, bundle, peer, showTraffic, proto) => {
  501. const ingressValues = peer.ingress.map(({value}) => ({ingress: value || 0.001}));
  502. const egressValues = peer.egress.map(({value}) => ({egress: -value || -0.001}));
  503. return (
  504. <TableRow key={`known_${addr}_${enode}`} style={styles.tableRow}>
  505. <TableCell style={styles.tableCell}>
  506. {peer.active
  507. ? <FontAwesomeIcon icon={fasCircle} color='green' />
  508. : <FontAwesomeIcon icon={farCircle} />
  509. }
  510. </TableCell>
  511. <TableCell
  512. style={{
  513. cursor: 'copy',
  514. ...styles.tableCell,
  515. ...limitedWidthStyle(80),
  516. }}
  517. onClick={this.copyToClipboard(enode)}
  518. >
  519. {enode.substring(8)}
  520. </TableCell>
  521. <TableCell
  522. style={{
  523. cursor: 'copy',
  524. ...styles.tableCell,
  525. ...limitedWidthStyle(80),
  526. }}
  527. onClick={this.copyToClipboard(peer.name)}
  528. >
  529. {peer.shortName}
  530. </TableCell>
  531. <TableCell
  532. style={{
  533. cursor: 'copy',
  534. ...styles.tableCell,
  535. ...limitedWidthStyle(100),
  536. }}
  537. onClick={this.copyToClipboard(JSON.stringify(bundle.location))}
  538. >
  539. {bundle.shortLocation}
  540. </TableCell>
  541. <TableCell style={styles.tableCell}>
  542. {showTraffic ? (
  543. <>
  544. <AreaChart
  545. width={trafficChartWidth}
  546. height={trafficChartHeight}
  547. data={ingressValues}
  548. margin={{top: 5, right: 5, bottom: 0, left: 5}}
  549. syncId={`peerIngress_${addr}_${enode}`}
  550. >
  551. <defs>
  552. <linearGradient id={`ingressGradient_${addr}_${enode}`} x1='0' y1='1' x2='0' y2='0'>
  553. {peer.ingressGradient
  554. && peer.ingressGradient.map(({offset, color}, i) => (
  555. <stop
  556. key={`ingressStop_${addr}_${enode}_${i}`}
  557. offset={`${offset}%`}
  558. stopColor={color}
  559. />
  560. ))}
  561. </linearGradient>
  562. </defs>
  563. <Tooltip cursor={false} content={<CustomTooltip tooltip={bytePlotter('Download')} />} />
  564. <YAxis hide scale='sqrt' domain={[0.001, dataMax => Math.max(dataMax, 0)]} />
  565. <Area
  566. dataKey='ingress'
  567. isAnimationActive={false}
  568. type='monotone'
  569. fill={`url(#ingressGradient_${addr}_${enode})`}
  570. stroke={peer.ingressGradient[peer.ingressGradient.length - 1].color}
  571. strokeWidth={chartStrokeWidth}
  572. />
  573. </AreaChart>
  574. <AreaChart
  575. width={trafficChartWidth}
  576. height={trafficChartHeight}
  577. data={egressValues}
  578. margin={{top: 0, right: 5, bottom: 5, left: 5}}
  579. syncId={`peerIngress_${addr}_${enode}`}
  580. >
  581. <defs>
  582. <linearGradient id={`egressGradient_${addr}_${enode}`} x1='0' y1='1' x2='0' y2='0'>
  583. {peer.egressGradient
  584. && peer.egressGradient.map(({offset, color}, i) => (
  585. <stop
  586. key={`egressStop_${addr}_${enode}_${i}`}
  587. offset={`${offset}%`}
  588. stopColor={color}
  589. />
  590. ))}
  591. </linearGradient>
  592. </defs>
  593. <Tooltip cursor={false} content={<CustomTooltip tooltip={bytePlotter('Upload', multiplier(-1))} />} />
  594. <YAxis hide scale='sqrt' domain={[dataMin => Math.min(dataMin, 0), -0.001]} />
  595. <Area
  596. dataKey='egress'
  597. isAnimationActive={false}
  598. type='monotone'
  599. fill={`url(#egressGradient_${addr}_${enode})`}
  600. stroke={peer.egressGradient[0].color}
  601. strokeWidth={chartStrokeWidth}
  602. />
  603. </AreaChart>
  604. </>
  605. ) : null}
  606. </TableCell>
  607. {typeof proto === 'object' ? (
  608. <>
  609. <TableCell
  610. style={{
  611. cursor: 'copy',
  612. ...styles.tableCell,
  613. ...limitedWidthStyle(80),
  614. }}
  615. onClick={this.copyToClipboard(JSON.stringify(proto.head))}
  616. >
  617. {proto.head}
  618. </TableCell>
  619. <TableCell
  620. style={{
  621. cursor: 'copy',
  622. ...styles.tableCell,
  623. }}
  624. onClick={this.copyToClipboard(JSON.stringify(proto.difficulty))}
  625. >
  626. {proto.difficulty}
  627. </TableCell>
  628. <TableCell
  629. style={{
  630. cursor: 'copy',
  631. ...styles.tableCell,
  632. }}
  633. onClick={this.copyToClipboard(JSON.stringify(proto.version))}
  634. >
  635. {proto.version}
  636. </TableCell>
  637. </>
  638. ) : null }
  639. </TableRow>
  640. );
  641. };
  642. connectionAttemptTableRow = (addr, bundle) => (
  643. <TableRow key={`attempt_${addr}`} style={styles.tableRow}>
  644. <TableCell
  645. style={{cursor: 'copy', ...styles.tableCell}}
  646. onClick={this.copyToClipboard(addr)}
  647. >
  648. {addr}
  649. </TableCell>
  650. <TableCell
  651. style={{cursor: 'copy', ...limitedWidthStyle(100), ...styles.tableCell}}
  652. onClick={this.copyToClipboard(JSON.stringify(bundle.location))}
  653. >
  654. {bundle.shortLocation}
  655. </TableCell>
  656. <TableCell style={styles.tableCell}>
  657. {bundle.attempts}
  658. </TableCell>
  659. </TableRow>
  660. );
  661. render() {
  662. const {classes} = this.props;
  663. return (
  664. <Grid container direction='row' spacing={3}>
  665. <Grid item style={{width: '40%'}}>
  666. <div className={classes.table} style={styles.table}>
  667. <Typography variant='subtitle1' gutterBottom className={classes.title} style={styles.title}>
  668. Full peers
  669. <FontAwesomeIcon
  670. icon={farClipboard}
  671. onClick={this.copyToClipboard(JSON.stringify(this.ethList()))}
  672. style={{float: 'right'}}
  673. />
  674. </Typography>
  675. <Scrollbars style={styles.content}>
  676. <Table>
  677. <TableHead style={styles.tableHead}>
  678. <TableRow style={styles.tableRow}>
  679. <TableCell style={styles.tableCell} />
  680. <TableCell style={styles.tableCell}>Node URL</TableCell>
  681. <TableCell style={styles.tableCell}>Name</TableCell>
  682. <TableCell style={styles.tableCell}>Location</TableCell>
  683. <TableCell style={styles.tableCell}>Traffic</TableCell>
  684. <TableCell style={styles.tableCell}>Head</TableCell>
  685. <TableCell style={styles.tableCell}>TD</TableCell>
  686. <TableCell style={styles.tableCell}>V</TableCell>
  687. </TableRow>
  688. </TableHead>
  689. <TableBody>
  690. {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
  691. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  692. return null;
  693. }
  694. return Object.entries(bundle.knownPeers).map(([enode, peer]) => {
  695. if (peer.active === false) {
  696. return null;
  697. }
  698. if (peer.eth === '' && peer.les !== '') {
  699. return null;
  700. }
  701. return this.knownPeerTableRow(addr, enode, bundle, peer, true, peer.protocols.eth);
  702. });
  703. })}
  704. </TableBody>
  705. <TableBody>
  706. {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
  707. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  708. return null;
  709. }
  710. return Object.entries(bundle.knownPeers).map(([enode, peer]) => {
  711. if (peer.active === true) {
  712. return null;
  713. }
  714. if (peer.eth === '' && peer.les !== '') {
  715. return null;
  716. }
  717. return this.knownPeerTableRow(addr, enode, bundle, peer, false, peer.protocols.eth);
  718. });
  719. })}
  720. </TableBody>
  721. </Table>
  722. </Scrollbars>
  723. </div>
  724. </Grid>
  725. <Grid item style={{width: '40%'}}>
  726. <div className={classes.table} style={styles.table}>
  727. <Typography variant='subtitle1' gutterBottom className={classes.title} style={styles.title}>
  728. Light peers
  729. <FontAwesomeIcon
  730. icon={farClipboard}
  731. onClick={this.copyToClipboard(JSON.stringify(this.lesList()))}
  732. style={{float: 'right'}}
  733. />
  734. </Typography>
  735. <Scrollbars style={styles.content}>
  736. <Table>
  737. <TableHead style={styles.tableHead}>
  738. <TableRow style={styles.tableRow}>
  739. <TableCell style={styles.tableCell} />
  740. <TableCell style={styles.tableCell}>Node URL</TableCell>
  741. <TableCell style={styles.tableCell}>Name</TableCell>
  742. <TableCell style={styles.tableCell}>Location</TableCell>
  743. <TableCell style={styles.tableCell}>Traffic</TableCell>
  744. <TableCell style={styles.tableCell}>Head</TableCell>
  745. <TableCell style={styles.tableCell}>TD</TableCell>
  746. <TableCell style={styles.tableCell}>V</TableCell>
  747. </TableRow>
  748. </TableHead>
  749. <TableBody>
  750. {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
  751. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  752. return null;
  753. }
  754. return Object.entries(bundle.knownPeers).map(([enode, peer]) => {
  755. if (peer.active === false) {
  756. return null;
  757. }
  758. if (peer.les === '' || peer.eth !== '') {
  759. return null;
  760. }
  761. return this.knownPeerTableRow(addr, enode, bundle, peer, true, peer.protocols.les);
  762. });
  763. })}
  764. </TableBody>
  765. <TableBody>
  766. {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
  767. if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
  768. return null;
  769. }
  770. return Object.entries(bundle.knownPeers).map(([enode, peer]) => {
  771. if (peer.active === true) {
  772. return null;
  773. }
  774. if (peer.les === '' || peer.eth !== '') {
  775. return null;
  776. }
  777. return this.knownPeerTableRow(addr, enode, bundle, peer, false, peer.protocols.les);
  778. });
  779. })}
  780. </TableBody>
  781. </Table>
  782. </Scrollbars>
  783. </div>
  784. </Grid>
  785. <Grid item xs>
  786. <div className={classes.table} style={styles.table}>
  787. <Typography variant='subtitle1' gutterBottom className={classes.title} style={styles.title}>
  788. Connection attempts
  789. <FontAwesomeIcon
  790. icon={farClipboard}
  791. onClick={this.copyToClipboard(JSON.stringify(this.attemptList()))}
  792. style={{float: 'right'}}
  793. />
  794. </Typography>
  795. <Scrollbars style={styles.content}>
  796. <Table>
  797. <TableHead style={styles.tableHead}>
  798. <TableRow style={styles.tableRow}>
  799. <TableCell style={styles.tableCell}>TCP address</TableCell>
  800. <TableCell style={styles.tableCell}>Location</TableCell>
  801. <TableCell style={styles.tableCell}>Nr</TableCell>
  802. </TableRow>
  803. </TableHead>
  804. <TableBody>
  805. {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
  806. if (!bundle.attempts || bundle.attempts <= attemptSeparator) {
  807. return null;
  808. }
  809. return this.connectionAttemptTableRow(addr, bundle);
  810. })}
  811. </TableBody>
  812. <TableBody>
  813. {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
  814. if (!bundle.attempts || bundle.attempts < 1 || bundle.attempts > attemptSeparator) {
  815. return null;
  816. }
  817. return this.connectionAttemptTableRow(addr, bundle);
  818. })}
  819. </TableBody>
  820. </Table>
  821. </Scrollbars>
  822. </div>
  823. </Grid>
  824. </Grid>
  825. );
  826. }
  827. }
  828. export default withStyles(themeStyles)(Network);