{\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.container = React.createRef();\n\t\tthis.content = React.createRef();\n\t}\n\n\tcomponentDidUpdate(prevProps, prevState, snapshot) {\n\t\tif (this.content && typeof this.content.didUpdate === 'function') {\n\t\t\tthis.content.didUpdate(prevProps, prevState, snapshot);\n\t\t}\n\t}\n\n\tonScroll = () => {\n\t\tif (this.content && typeof this.content.onScroll === 'function') {\n\t\t\tthis.content.onScroll();\n\t\t}\n\t};\n\n\tgetSnapshotBeforeUpdate(prevProps: Readonly, prevState: Readonly) {\n\t\tif (this.content && typeof this.content.beforeUpdate === 'function') {\n\t\t\treturn this.content.beforeUpdate();\n\t\t}\n\t\treturn null;\n\t}\n\n\trender() {\n\t\tconst {\n\t\t\tclasses, active, content, shouldUpdate,\n\t\t} = this.props;\n\n\t\tlet children = null;\n\t\tswitch (active) {\n\t\tcase MENU.get('home').id:\n\t\t\tchildren =
Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('chain').id:\n\t\t\tchildren = Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('txpool').id:\n\t\t\tchildren = Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('network').id:\n\t\t\tchildren = ;\n\t\t\tbreak;\n\t\tcase MENU.get('system').id:\n\t\t\tchildren = Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('logs').id:\n\t\t\tchildren = (\n\t\t\t\t { this.content = ref; }}\n\t\t\t\t\tcontainer={this.container}\n\t\t\t\t\tsend={this.props.send}\n\t\t\t\t\tcontent={this.props.content}\n\t\t\t\t\tshouldUpdate={shouldUpdate}\n\t\t\t\t/>\n\t\t\t);\n\t\t}\n\n\t\treturn (\n\t\t\t\n\t\t\t\t
{ this.container = ref; }}\n\t\t\t\t\tonScroll={this.onScroll}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t);\n\t}\n}\n\nexport default withStyles(themeStyles)(Main);\n",
- "// @flow\n\n// Copyright 2018 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React, {Component} from 'react';\n\nimport Table from '@material-ui/core/Table';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableRow from '@material-ui/core/TableRow';\nimport TableCell from '@material-ui/core/TableCell';\nimport Grid from '@material-ui/core/Grid/Grid';\nimport AreaChart from 'recharts/es6/chart/AreaChart';\nimport Tooltip from 'recharts/es6/component/Tooltip';\nimport Area from 'recharts/es6/cartesian/Area';\nimport CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';\nimport type {Network as NetworkType, PeerEvent} from '../types/content';\n\n// inserter is a state updater function for the main component, which handles the peers.\nexport const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {\n\t// The first message contains the metered peer history,\n\t// which has a valid peer state JSON format per se.\n\tif (update.peers && update.peers.bundles) {\n\t\tprev.peers = update.peers;\n\t}\n\tif (Array.isArray(update.diff)) {\n\t\tupdate.diff.forEach((event: PeerEvent) => {\n\t\t\tif (!event.ip) {\n\t\t\t\tconsole.error('Peer event without IP', event);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tswitch (event.remove) {\n\t\t\tcase 'bundle':\n\t\t\t\tdelete prev.peers.bundles[event.ip];\n\t\t\t\treturn;\n\t\t\tcase 'known': {\n\t\t\t\tif (!event.id) {\n\t\t\t\t\tconsole.error('Remove known peer event without ID', event.ip);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\t\tif (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.id]) {\n\t\t\t\t\tconsole.error('No known peer to remove', event.ip, event.id);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdelete bundle.knownPeers[event.id];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'attempt': {\n\t\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\t\tif (!bundle || !Array.isArray(bundle.attempts) || bundle.attempts.length < 1) {\n\t\t\t\t\tconsole.error('No unknown peer to remove', event.ip);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbundle.attempts.splice(0, 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t}\n\t\t\tif (!prev.peers.bundles[event.ip]) {\n\t\t\t\tprev.peers.bundles[event.ip] = {\n\t\t\t\t\tlocation: {\n\t\t\t\t\t\tcountry: '',\n\t\t\t\t\t\tcity: '',\n\t\t\t\t\t\tlatitude: 0,\n\t\t\t\t\t\tlongitude: 0,\n\t\t\t\t\t},\n\t\t\t\t\tknownPeers: {},\n\t\t\t\t\tattempts: [],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\tif (event.location) {\n\t\t\t\tbundle.location = event.location;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!event.id) {\n\t\t\t\tif (!bundle.attempts) {\n\t\t\t\t\tbundle.attempts = [];\n\t\t\t\t}\n\t\t\t\tbundle.attempts.push({\n\t\t\t\t\tconnected: event.connected,\n\t\t\t\t\tdisconnected: event.disconnected,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!bundle.knownPeers || !bundle.knownPeers[event.id]) {\n\t\t\t\tbundle.knownPeers[event.id] = {\n\t\t\t\t\tconnected: [],\n\t\t\t\t\tdisconnected: [],\n\t\t\t\t\tingress: [],\n\t\t\t\t\tegress: [],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst peer = bundle.knownPeers[event.id];\n\t\t\tif (event.connected) {\n\t\t\t\tpeer.connected.push(event.connected);\n\t\t\t}\n\t\t\tif (event.disconnected) {\n\t\t\t\tpeer.disconnected.push(event.disconnected);\n\t\t\t}\n\t\t\tif (Array.isArray(event.ingress) && Array.isArray(event.egress)) {\n\t\t\t\tif (event.ingress.length !== event.egress.length) {\n\t\t\t\t\tconsole.error('Different traffic sample length', event);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tpeer.ingress.splice(peer.ingress.length, 0, ...event.ingress);\n\t\t\t\tpeer.egress.splice(peer.egress.length, 0, ...event.egress);\n\t\t\t\tif (peer.ingress.length > sampleLimit) {\n\t\t\t\t\tpeer.ingress.splice(0, peer.ingress.length - sampleLimit);\n\t\t\t\t}\n\t\t\t\tif (peer.egress.length > sampleLimit) {\n\t\t\t\t\tpeer.egress.splice(0, peer.egress.length - sampleLimit);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\treturn prev;\n};\n\n// styles contains the constant styles of the component.\nconst styles = {};\n\nexport type Props = {\n container: Object,\n content: NetworkType,\n shouldUpdate: Object,\n};\n\ntype State = {};\n\n// Network renders the network page.\nclass Network extends Component {\n\tformatTime = (t: string) => {\n\t\tconst time = new Date(t);\n\t\tif (isNaN(time)) {\n\t\t\treturn '';\n\t\t}\n\t\tconst month = `))) + ((("`" + `0${time.getMonth() + 1}`) + ("`" + `.slice(-2);\n\t\tconst date = `)) + (("`" + `0${time.getDate()}`) + ("`" + `.slice(-2);\n\t\tconst hours = `)))))) + ((((("`" + (`0${time.getHours()}` + "`")) + ((`.slice(-2);\n\t\tconst minutes = ` + "`") + (`0${time.getMinutes()}` + "`"))) + (((`.slice(-2);\n\t\tconst seconds = ` + "`") + (`0${time.getSeconds()}` + "`")) + ((`.slice(-2);\n\t\treturn ` + "`") + (`${month}/${date}/${hours}:${minutes}:${seconds}` + "`")))) + (((`;\n\t};\n\n\trender() {\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tNode ID\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tTraffic\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([id, peer]) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{id.substring(0, 10)}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{bundle.location ? (() => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst l = bundle.location;\n\t\t\t\t\t\t\t\t\t\t\t\treturn `) + ("`" + `${l.country ? l.country : ''}${l.city ? `))) + ((("`" + `/${l.city}`) + ("`" + ` : ''}`)) + (("`" + `;\n\t\t\t\t\t\t\t\t\t\t\t})() : ''}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t ({egress: value || 0}))}\n\t\t\t\t\t\t\t\t\t\t\t\tmargin={{top: 5, right: 5, bottom: 0, left: 5}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t} />\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t ({ingress: -value || 0}))}\n\t\t\t\t\t\t\t\t\t\t\t\tmargin={{top: 0, right: 5, bottom: 5, left: 5}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t} />\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t));\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tIP\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tAttempts\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.attempts || bundle.attempts.length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{ip}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{bundle.location ? (() => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst l = bundle.location;\n\t\t\t\t\t\t\t\t\t\t\t\treturn ` + "`")) + ((`${l.country ? l.country : ''}${l.city ? ` + "`") + (`/${l.city}` + "`"))) + (((` : ''}` + "`") + (`;\n\t\t\t\t\t\t\t\t\t\t\t})() : ''}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{Object.values(bundle.attempts).length}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n}\n\nexport default Network;\n",
+ "// @flow\n\n// Copyright 2018 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React, {Component} from 'react';\n\nimport Table from '@material-ui/core/Table';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableRow from '@material-ui/core/TableRow';\nimport TableCell from '@material-ui/core/TableCell';\nimport Grid from '@material-ui/core/Grid/Grid';\nimport Typography from '@material-ui/core/Typography';\nimport AreaChart from 'recharts/es6/chart/AreaChart';\nimport Tooltip from 'recharts/es6/component/Tooltip';\nimport Area from 'recharts/es6/cartesian/Area';\nimport {Icon as FontAwesome} from 'react-fa';\n\nimport CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';\nimport type {Network as NetworkType, PeerEvent} from '../types/content';\nimport {styles as commonStyles} from '../common';\n\n// inserter is a state updater function for the main component, which handles the peers.\nexport const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {\n\t// The first message contains the metered peer history,\n\t// which has a valid peer state JSON format per se.\n\tif (update.peers && update.peers.bundles) {\n\t\tprev.peers = update.peers;\n\t}\n\tif (Array.isArray(update.diff)) {\n\t\tupdate.diff.forEach((event: PeerEvent) => {\n\t\t\tif (!event.ip) {\n\t\t\t\tconsole.error('Peer event without IP', event);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tswitch (event.remove) {\n\t\t\tcase 'bundle': {\n\t\t\t\tdelete prev.peers.bundles[event.ip];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'known': {\n\t\t\t\tif (!event.id) {\n\t\t\t\t\tconsole.error('Remove known peer event without ID', event.ip);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\t\tif (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.id]) {\n\t\t\t\t\tconsole.error('No known peer to remove', event.ip, event.id);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdelete bundle.knownPeers[event.id];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'attempt': {\n\t\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\t\tif (!bundle || !Array.isArray(bundle.attempts) || bundle.attempts.length < 1) {\n\t\t\t\t\tconsole.error('No unknown peer to remove', event.ip);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbundle.attempts.splice(0, 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t}\n\t\t\tif (!prev.peers.bundles[event.ip]) {\n\t\t\t\tprev.peers.bundles[event.ip] = {\n\t\t\t\t\tlocation: {\n\t\t\t\t\t\tcountry: '',\n\t\t\t\t\t\tcity: '',\n\t\t\t\t\t\tlatitude: 0,\n\t\t\t\t\t\tlongitude: 0,\n\t\t\t\t\t},\n\t\t\t\t\tknownPeers: {},\n\t\t\t\t\tattempts: [],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\tif (event.location) {\n\t\t\t\tbundle.location = event.location;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!event.id) {\n\t\t\t\tif (!bundle.attempts) {\n\t\t\t\t\tbundle.attempts = [];\n\t\t\t\t}\n\t\t\t\tbundle.attempts.push({\n\t\t\t\t\tconnected: event.connected,\n\t\t\t\t\tdisconnected: event.disconnected,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!bundle.knownPeers) {\n\t\t\t\tbundle.knownPeers = {};\n\t\t\t}\n\t\t\tif (!bundle.knownPeers[event.id]) {\n\t\t\t\tbundle.knownPeers[event.id] = {\n\t\t\t\t\tconnected: [],\n\t\t\t\t\tdisconnected: [],\n\t\t\t\t\tingress: [],\n\t\t\t\t\tegress: [],\n\t\t\t\t\tactive: false,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst peer = bundle.knownPeers[event.id];\n\t\t\tif (event.connected) {\n\t\t\t\tif (!peer.connected) {\n\t\t\t\t\tconsole.warn('peer.connected should exist');\n\t\t\t\t\tpeer.connected = [];\n\t\t\t\t}\n\t\t\t\tpeer.connected.push(event.connected);\n\t\t\t}\n\t\t\tif (event.disconnected) {\n\t\t\t\tif (!peer.disconnected) {\n\t\t\t\t\tconsole.warn('peer.disconnected should exist');\n\t\t\t\t\tpeer.disconnected = [];\n\t\t\t\t}\n\t\t\t\tpeer.disconnected.push(event.disconnected);\n\t\t\t}\n\t\t\tswitch (event.activity) {\n\t\t\tcase 'active':\n\t\t\t\tpeer.active = true;\n\t\t\t\tbreak;\n\t\t\tcase 'inactive':\n\t\t\t\tpeer.active = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (Array.isArray(event.ingress) && Array.isArray(event.egress)) {\n\t\t\t\tif (event.ingress.length !== event.egress.length) {\n\t\t\t\t\tconsole.error('Different traffic sample length', event);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tpeer.ingress.splice(peer.ingress.length, 0, ...event.ingress);\n\t\t\t\tpeer.egress.splice(peer.egress.length, 0, ...event.egress);\n\t\t\t\tif (peer.ingress.length > sampleLimit) {\n\t\t\t\t\tpeer.ingress.splice(0, peer.ingress.length - sampleLimit);\n\t\t\t\t}\n\t\t\t\tif (peer.egress.length > sampleLimit) {\n\t\t\t\t\tpeer.egress.splice(0, peer.egress.length - sampleLimit);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\treturn prev;\n};\n\n// styles contains the constant styles of the component.\nconst styles = {\n\ttableHead: {\n\t\theight: 'auto',\n\t},\n\ttableRow: {\n\t\theight: 'auto',\n\t},\n\ttableCell: {\n\t\tpaddingTop: 0,\n\t\tpaddingRight: 5,\n\t\tpaddingBottom: 0,\n\t\tpaddingLeft: 5,\n\t\tborder: 'none',\n\t},\n};\n\nexport type Props = {\n container: Object,\n content: NetworkType,\n shouldUpdate: Object,\n};\n\ntype State = {};\n\n// Network renders the network page.\nclass Network extends Component {\n\tformatTime = (t: string) => {\n\t\tconst time = new Date(t);\n\t\tif (isNaN(time)) {\n\t\t\treturn '';\n\t\t}\n\t\tconst month = `))) + ((("`" + `0${time.getMonth() + 1}`) + ("`" + `.slice(-2);\n\t\tconst date = `)) + (("`" + `0${time.getDate()}`) + ("`" + `.slice(-2);\n\t\tconst hours = `)))))) + ((((("`" + (`0${time.getHours()}` + "`")) + ((`.slice(-2);\n\t\tconst minutes = ` + "`") + (`0${time.getMinutes()}` + "`"))) + (((`.slice(-2);\n\t\tconst seconds = ` + "`") + (`0${time.getSeconds()}` + "`")) + ((`.slice(-2);\n\t\treturn ` + "`") + (`${month}/${date}/${hours}:${minutes}:${seconds}` + "`")))) + (((`;\n\t};\n\n\tcopyToClipboard = (id) => (event) => {\n\t\tevent.preventDefault();\n\t\tnavigator.clipboard.writeText(id).then(() => {}, () => {\n\t\t\tconsole.error(\"Failed to copy node id\", id);\n\t\t});\n\t};\n\n\t// TODO (kurkomisi): add single tooltip and move it to the mouse position on copy button click.\n\t// Tried with TooltipTrigger components for each button, but it seems to be a big load.\n\tpeerTableRow = (ip, id, bundle, peer) => (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t{id.substring(0, 10) + ' '}\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t{bundle.location ? (() => {\n\t\t\t\t\tconst l = bundle.location;\n\t\t\t\t\treturn `) + ("`" + `${l.country ? l.country : ''}${l.city ? `))) + ((("`" + `/${l.city}`) + ("`" + ` : ''}`)) + (("`" + `;\n\t\t\t\t})() : ''}\n\t\t\t\n\t\t\t\n\t\t\t\t ({ingress: value || 0}))}\n\t\t\t\t\tmargin={{top: 5, right: 5, bottom: 0, left: 5}}\n\t\t\t\t>\n\t\t\t\t\t} />\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t ({egress: -value || 0}))}\n\t\t\t\t\tmargin={{top: 0, right: 5, bottom: 5, left: 5}}\n\t\t\t\t>\n\t\t\t\t\t} />\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t);\n\n\trender() {\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tKnown peers\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tNode ID\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tTraffic\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([id, peer]) => {\n\t\t\t\t\t\t\t\t\tif (peer.active === false) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.peerTableRow(ip, id, bundle, peer);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([id, peer]) => {\n\t\t\t\t\t\t\t\t\tif (peer.active === true) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.peerTableRow(ip, id, bundle, peer);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tConnection attempts\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tIP\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tNr\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.attempts || bundle.attempts.length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{ip}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{bundle.location ? (() => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst l = bundle.location;\n\t\t\t\t\t\t\t\t\t\t\t\treturn ` + "`")) + ((`${l.country ? l.country : ''}${l.city ? ` + "`") + (`/${l.city}` + "`"))) + (((` : ''}` + "`") + (`;\n\t\t\t\t\t\t\t\t\t\t\t})() : ''}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{Object.values(bundle.attempts).length}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n}\n\nexport default Network;\n",
"// @flow\n\n// Copyright 2017 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React, {Component} from 'react';\n\nimport withStyles from '@material-ui/core/styles/withStyles';\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport Icon from '@material-ui/core/Icon';\nimport Transition from 'react-transition-group/Transition';\nimport {Icon as FontAwesome} from 'react-fa';\n\nimport {MENU, DURATION} from '../common';\n\n// styles contains the constant styles of the component.\nconst styles = {\n\tmenu: {\n\t\tdefault: {\n\t\t\ttransition: ` + "`")) + ((`margin-left ${DURATION}ms` + "`") + (`,\n\t\t},\n\t\ttransition: {\n\t\t\tentered: {marginLeft: -200},\n\t\t},\n\t},\n};\n\n// themeStyles returns the styles generated from the theme for the component.\nconst themeStyles = theme => ({\n\tlist: {\n\t\tbackground: theme.palette.grey[900],\n\t},\n\tlistItem: {\n\t\tminWidth: theme.spacing.unit * 7,\n\t},\n\ticon: {\n\t\tfontSize: theme.spacing.unit * 3,\n\t\toverflow: 'unset',\n\t},\n});\n\nexport type Props = {\n\tclasses: Object, // injected by withStyles()\n\topened: boolean,\n\tchangeContent: string => void,\n};\n\ntype State = {}\n\n// SideBar renders the sidebar of the dashboard.\nclass SideBar extends Component {\n\tshouldComponentUpdate(nextProps: Readonly, nextState: Readonly, nextContext: any) {\n\t\treturn nextProps.opened !== this.props.opened;\n\t}\n\n\t// clickOn returns a click event handler function for the given menu item.\n\tclickOn = menu => (event) => {\n\t\tevent.preventDefault();\n\t\tthis.props.changeContent(menu);\n\t};\n\n\t// menuItems returns the menu items corresponding to the sidebar state.\n\tmenuItems = (transitionState) => {\n\t\tconst {classes} = this.props;\n\t\tconst children = [];\n\t\tMENU.forEach((menu) => {\n\t\t\tchildren.push((\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t));\n\t\t});\n\t\treturn children;\n\t};\n\n\t// menu renders the list of the menu items.\n\tmenu = (transitionState: Object) => (\n\t\t\n\t\t\t\n\t\t\t\t{this.menuItems(transitionState)}\n\t\t\t
\n\t\t
\n\t);\n\n\trender() {\n\t\treturn (\n\t\t\t\n\t\t\t\t{this.menu}\n\t\t\t\n\t\t);\n\t}\n}\n\nexport default withStyles(themeStyles)(SideBar);\n",
"// @flow\n\n// Copyright 2017 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React from 'react';\nimport {render} from 'react-dom';\n\nimport MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';\nimport createMuiTheme from '@material-ui/core/styles/createMuiTheme';\n\nimport Dashboard from './components/Dashboard';\n\nconst theme: Object = createMuiTheme({\n\t// typography: {\n\t// \tuseNextVariants: true,\n\t// },\n\tpalette: {\n\t\ttype: 'dark',\n\t},\n});\nconst dashboard = document.getElementById('dashboard');\nif (dashboard) {\n\t// Renders the whole dashboard.\n\trender(\n\t\t\n\t\t\t\n\t\t,\n\t\tdashboard,\n\t);\n}\n",
"function _arrayWithoutHoles(arr) {\n if (Array.isArray(arr)) {\n for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n }\n}\n\nmodule.exports = _arrayWithoutHoles;",
@@ -36569,7 +36681,7 @@ func bundleJsMap() (*asset, error) {
}
info := bindataFileInfo{name: "bundle.js.map", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
- a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x50, 0xb4, 0x42, 0x10, 0xa4, 0xac, 0x46, 0x45, 0x39, 0x21, 0x0, 0x84, 0xc8, 0xd2, 0x21, 0x13, 0x59, 0x63, 0x21, 0xda, 0x36, 0x37, 0xf4, 0xd1, 0xf6, 0x89, 0xb, 0x19, 0x9e, 0xe8, 0x89, 0x24}}
+ a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb1, 0xd7, 0xb9, 0xa6, 0xbd, 0x4d, 0xc9, 0x97, 0x91, 0x92, 0x90, 0x4a, 0xc5, 0xf4, 0xf1, 0x19, 0xe7, 0x1f, 0x16, 0x4c, 0xac, 0x6f, 0x29, 0x28, 0x7f, 0x60, 0xba, 0x2b, 0x3d, 0x33, 0x5c, 0xc}}
return a, nil
}
diff --git a/dashboard/assets/components/Network.jsx b/dashboard/assets/components/Network.jsx
index c8128a60cf..389fd2abf1 100644
--- a/dashboard/assets/components/Network.jsx
+++ b/dashboard/assets/components/Network.jsx
@@ -24,11 +24,15 @@ import TableBody from '@material-ui/core/TableBody';
import TableRow from '@material-ui/core/TableRow';
import TableCell from '@material-ui/core/TableCell';
import Grid from '@material-ui/core/Grid/Grid';
+import Typography from '@material-ui/core/Typography';
import AreaChart from 'recharts/es6/chart/AreaChart';
import Tooltip from 'recharts/es6/component/Tooltip';
import Area from 'recharts/es6/cartesian/Area';
+import {Icon as FontAwesome} from 'react-fa';
+
import CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';
import type {Network as NetworkType, PeerEvent} from '../types/content';
+import {styles as commonStyles} from '../common';
// inserter is a state updater function for the main component, which handles the peers.
export const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {
@@ -44,9 +48,10 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
return;
}
switch (event.remove) {
- case 'bundle':
+ case 'bundle': {
delete prev.peers.bundles[event.ip];
return;
+ }
case 'known': {
if (!event.id) {
console.error('Remove known peer event without ID', event.ip);
@@ -97,21 +102,41 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
});
return;
}
- if (!bundle.knownPeers || !bundle.knownPeers[event.id]) {
+ if (!bundle.knownPeers) {
+ bundle.knownPeers = {};
+ }
+ if (!bundle.knownPeers[event.id]) {
bundle.knownPeers[event.id] = {
connected: [],
disconnected: [],
ingress: [],
egress: [],
+ active: false,
};
}
const peer = bundle.knownPeers[event.id];
if (event.connected) {
+ if (!peer.connected) {
+ console.warn('peer.connected should exist');
+ peer.connected = [];
+ }
peer.connected.push(event.connected);
}
if (event.disconnected) {
+ if (!peer.disconnected) {
+ console.warn('peer.disconnected should exist');
+ peer.disconnected = [];
+ }
peer.disconnected.push(event.disconnected);
}
+ switch (event.activity) {
+ case 'active':
+ peer.active = true;
+ break;
+ case 'inactive':
+ peer.active = false;
+ break;
+ }
if (Array.isArray(event.ingress) && Array.isArray(event.egress)) {
if (event.ingress.length !== event.egress.length) {
console.error('Different traffic sample length', event);
@@ -132,7 +157,21 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
};
// styles contains the constant styles of the component.
-const styles = {};
+const styles = {
+ tableHead: {
+ height: 'auto',
+ },
+ tableRow: {
+ height: 'auto',
+ },
+ tableCell: {
+ paddingTop: 0,
+ paddingRight: 5,
+ paddingBottom: 0,
+ paddingLeft: 5,
+ border: 'none',
+ },
+};
export type Props = {
container: Object,
@@ -157,16 +196,67 @@ class Network extends Component {
return `${month}/${date}/${hours}:${minutes}:${seconds}`;
};
+ copyToClipboard = (id) => (event) => {
+ event.preventDefault();
+ navigator.clipboard.writeText(id).then(() => {}, () => {
+ console.error("Failed to copy node id", id);
+ });
+ };
+
+ // TODO (kurkomisi): add single tooltip and move it to the mouse position on copy button click.
+ // Tried with TooltipTrigger components for each button, but it seems to be a big load.
+ peerTableRow = (ip, id, bundle, peer) => (
+
+
+
+
+
+ {id.substring(0, 10) + ' '}
+
+
+
+ {bundle.location ? (() => {
+ const l = bundle.location;
+ return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
+ })() : ''}
+
+
+ ({ingress: value || 0}))}
+ margin={{top: 5, right: 5, bottom: 0, left: 5}}
+ >
+ } />
+
+
+ ({egress: -value || 0}))}
+ margin={{top: 0, right: 5, bottom: 5, left: 5}}
+ >
+ } />
+
+
+
+
+ );
+
render() {
return (
-
-
+
+
+
+ Known peers
+
-
-
- Node ID
- Location
- Traffic
+
+
+
+ Node ID
+ Location
+ Traffic
@@ -174,50 +264,39 @@ class Network extends Component {
if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
return null;
}
- return Object.entries(bundle.knownPeers).map(([id, peer]) => (
-
-
- {id.substring(0, 10)}
-
-
- {bundle.location ? (() => {
- const l = bundle.location;
- return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
- })() : ''}
-
-
- ({egress: value || 0}))}
- margin={{top: 5, right: 5, bottom: 0, left: 5}}
- >
- } />
-
-
- ({ingress: -value || 0}))}
- margin={{top: 0, right: 5, bottom: 5, left: 5}}
- >
- } />
-
-
-
-
- ));
+ return Object.entries(bundle.knownPeers).map(([id, peer]) => {
+ if (peer.active === false) {
+ return null;
+ }
+ return this.peerTableRow(ip, id, bundle, peer);
+ });
+ })}
+
+
+ {Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
+ if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
+ return null;
+ }
+ return Object.entries(bundle.knownPeers).map(([id, peer]) => {
+ if (peer.active === true) {
+ return null;
+ }
+ return this.peerTableRow(ip, id, bundle, peer);
+ });
})}
-
+
+
+ Connection attempts
+
-
-
- IP
- Location
- Attempts
+
+
+ IP
+ Location
+ Nr
@@ -226,15 +305,15 @@ class Network extends Component {
return null;
}
return (
-
- {ip}
-
+
+ {ip}
+
{bundle.location ? (() => {
const l = bundle.location;
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
})() : ''}
-
+
{Object.values(bundle.attempts).length}
diff --git a/dashboard/assets/types/content.jsx b/dashboard/assets/types/content.jsx
index 0e7f27e8a2..3086d44244 100644
--- a/dashboard/assets/types/content.jsx
+++ b/dashboard/assets/types/content.jsx
@@ -63,6 +63,7 @@ export type PeerEvent = {
disconnected: Date,
ingress: ChartEntries,
egress: ChartEntries,
+ activity: string,
};
export type Peers = {
@@ -80,6 +81,7 @@ export type KnownPeer = {
disconnected: Array,
ingress: Array,
egress: Array,
+ active: boolean,
};
export type UnknownPeer = {
diff --git a/dashboard/assets/webpack.config.prod.js b/dashboard/assets/webpack.config.prod.js
index 9b65069754..b80cb452f2 100644
--- a/dashboard/assets/webpack.config.prod.js
+++ b/dashboard/assets/webpack.config.prod.js
@@ -29,8 +29,8 @@ module.exports = merge(common, {
uglifyOptions: {
compress: true,
output: {
- comments: false,
- beautify: true,
+ comments: false,
+ beautify: true,
},
// warnings: true,
},
diff --git a/dashboard/peers.go b/dashboard/peers.go
index 253cbad1a4..e591bda779 100644
--- a/dashboard/peers.go
+++ b/dashboard/peers.go
@@ -30,7 +30,7 @@ import (
const (
eventBufferLimit = 128 // Maximum number of buffered peer events.
knownPeerLimit = 100 // Maximum number of stored peers, which successfully made the handshake.
- attemptLimit = 100 // Maximum number of stored peers, which failed to make the handshake.
+ attemptLimit = 200 // Maximum number of stored peers, which failed to make the handshake.
// eventLimit is the maximum number of the dashboard's custom peer events,
// that are collected between two metering period and sent to the clients
@@ -78,8 +78,7 @@ type peerContainer struct {
// Bundles is the outer map using the peer's IP address as key.
Bundles map[string]*peerBundle `json:"bundles,omitempty"`
- // activePeers contains the peers with opened connection in random order.
- activePeers *list.List
+ activeCount int // Number of the still connected peers
// inactivePeers contains the peers with closed connection in chronological order.
inactivePeers *list.List
@@ -100,7 +99,6 @@ type peerContainer struct {
func newPeerContainer(geodb *geoDB) *peerContainer {
return &peerContainer{
Bundles: make(map[string]*peerBundle),
- activePeers: list.New(),
inactivePeers: list.New(),
attemptOrder: make([]string, 0, attemptLimit),
geodb: geodb,
@@ -142,21 +140,37 @@ func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
if first := len(peer.Connected) - sampleLimit; first > 0 {
peer.Connected = peer.Connected[first:]
}
+ peer.Active = true
+ events = append(events, &peerEvent{
+ Activity: Active,
+ IP: peer.ip,
+ ID: peer.id,
+ })
+ pc.activeCount++
+ if peer.listElement != nil {
+ _ = pc.inactivePeers.Remove(peer.listElement)
+ peer.listElement = nil
+ }
case event.Disconnected != nil:
peer.Disconnected = append(peer.Disconnected, event.Disconnected)
if first := len(peer.Disconnected) - sampleLimit; first > 0 {
peer.Disconnected = peer.Disconnected[first:]
}
+ peer.Active = false
+ events = append(events, &peerEvent{
+ Activity: Inactive,
+ IP: peer.ip,
+ ID: peer.id,
+ })
+ pc.activeCount--
+ if peer.listElement != nil {
+ // If the peer is already in the list, remove and reinsert it.
+ _ = pc.inactivePeers.Remove(peer.listElement)
+ }
+ // Insert the peer into the list.
+ peer.listElement = pc.inactivePeers.PushBack(peer)
}
- if peer.listElement != nil {
- // If the peer is already in the list, remove and reinsert it.
- _ = pc.activePeers.Remove(peer.listElement)
- _ = pc.inactivePeers.Remove(peer.listElement)
- peer.listElement = nil
- }
- // Insert the peer into the list.
- peer.listElement = pc.activePeers.PushBack(peer)
- for pc.activePeers.Len()+pc.inactivePeers.Len() > knownPeerLimit {
+ for pc.inactivePeers.Len() > 0 && pc.activeCount+pc.inactivePeers.Len() > knownPeerLimit {
// While the count of the known peers is greater than the limit,
// remove the first element from the inactive peer list and from the map.
if removedPeer, ok := pc.inactivePeers.Remove(pc.inactivePeers.Front()).(*knownPeer); ok {
@@ -165,12 +179,8 @@ func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
log.Warn("Failed to parse the removed peer")
}
}
- for pc.activePeers.Len() > knownPeerLimit {
- if removedPeer, ok := pc.activePeers.Remove(pc.activePeers.Front()).(*knownPeer); ok {
- events = append(events, pc.removeKnown(removedPeer.ip, removedPeer.id)...)
- } else {
- log.Warn("Failed to parse the removed peer")
- }
+ if pc.activeCount > knownPeerLimit {
+ log.Warn("Number of active peers is greater than the limit")
}
return events
}
@@ -194,28 +204,6 @@ func (pc *peerContainer) handleAttempt(event *peerEvent) []*peerEvent {
return events
}
-// setActive pushes the peer denoted by the given IP address and node ID
-// into the active peer list. Takes no effect if the peer doesn't exist.
-func (pc *peerContainer) setActive(ip, id string) {
- if bundle, ok := pc.Bundles[ip]; ok {
- if peer, ok := bundle.KnownPeers[id]; ok {
- if peer.listElement != nil {
- _ = pc.inactivePeers.Remove(peer.listElement)
- _ = pc.activePeers.Remove(peer.listElement)
- }
- peer.listElement = pc.activePeers.PushBack(peer)
- }
- }
-}
-
-// resetActive pushes the active peers to the end of the inactive peer
-// list, denoting that active peers are not considered active anymore.
-func (pc *peerContainer) resetActive() {
- for pc.activePeers.Front() != nil {
- pc.inactivePeers.PushBack(pc.activePeers.Remove(pc.activePeers.Front()))
- }
-}
-
// peerBundle contains the peers belonging to a given IP address.
type peerBundle struct {
// Location contains the geographical location based on the bundle's IP address.
@@ -233,6 +221,7 @@ type peerBundle struct {
// removeKnown removes the known peer belonging to the
// given IP address and node ID from the peer tree.
func (pc *peerContainer) removeKnown(ip, id string) (events []*peerEvent) {
+ // TODO (kurkomisi): Remove peers that don't have traffic samples anymore.
if bundle, ok := pc.Bundles[ip]; ok {
if _, ok := bundle.KnownPeers[id]; ok {
events = append(events, &peerEvent{
@@ -251,6 +240,8 @@ func (pc *peerContainer) removeKnown(ip, id string) (events []*peerEvent) {
})
delete(pc.Bundles, ip)
}
+ } else {
+ log.Warn("No bundle to remove", "ip", ip)
}
return events
}
@@ -321,6 +312,8 @@ type knownPeer struct {
Ingress ChartEntries `json:"ingress,omitempty"`
Egress ChartEntries `json:"egress,omitempty"`
+ Active bool `json:"active"` // Denotes if the peer is still connected.
+
listElement *list.Element // Pointer to the peer element in the list.
ip, id string // The IP and the ID by which the peer can be accessed in the tree.
prevIngress float64
@@ -338,11 +331,15 @@ type peerAttempt struct {
}
type RemovedPeerType string
+type ActivityType string
const (
RemoveKnown RemovedPeerType = "known"
RemoveAttempt RemovedPeerType = "attempt"
RemoveBundle RemovedPeerType = "bundle"
+
+ Active ActivityType = "active"
+ Inactive ActivityType = "inactive"
)
// peerEvent contains the attributes of a peer event.
@@ -355,6 +352,7 @@ type peerEvent struct {
Disconnected *time.Time `json:"disconnected,omitempty"` // Timestamp of the disonnection moment.
Ingress ChartEntries `json:"ingress,omitempty"` // Ingress samples.
Egress ChartEntries `json:"egress,omitempty"` // Egress samples.
+ Activity ActivityType `json:"activity,omitempty"` // Connection status change.
}
// trafficMap is a container for the periodically collected peer traffic.
@@ -475,30 +473,6 @@ func (db *Dashboard) collectPeerData() {
// Protect 'peers', because it is part of the history.
db.peerLock.Lock()
- // Usually the active peers don't produce events, and marking
- // them as active makes it sure that they won't be removed from
- // the tree. Only the active peers are registered into the peer
- // registry, so after the traffic collection the ingress and the
- // egress maps contain all the active peers.
- //
- // It is important to mark the active ones before the merge with
- // the diff, otherwise the active peers can be removed.
- //
- // After a metering period the active peers can become inactive,
- // so at the beginning it is necessary to transpose them to the
- // inactive list.
- peers.resetActive()
- for ip, bundle := range *ingress {
- for id := range bundle {
- // Only set the peers that are inserted both
- // into the ingress and the egress maps.
- if _, ok := (*egress)[ip][id]; ok {
- peers.setActive(ip, id)
- } else {
- log.Warn("Peer missing traffic sample", "IP", ip, "ID", id)
- }
- }
- }
var diff []*peerEvent
for i := 0; i < len(newPeerEvents); i++ {
if newPeerEvents[i].IP == "" {
@@ -558,9 +532,11 @@ func (db *Dashboard) collectPeerData() {
}
db.peerLock.Unlock()
- db.sendToAll(&Message{Network: &NetworkMessage{
- Diff: diff,
- }})
+ if len(diff) > 0 {
+ db.sendToAll(&Message{Network: &NetworkMessage{
+ Diff: diff,
+ }})
+ }
// Clear the traffic maps, and the event array,
// prepare them for the next metering.
*ingress, *egress = make(trafficMap), make(trafficMap)