Skip to content

Commit

Permalink
feat: show latency and relay info in peers table
Browse files Browse the repository at this point in the history
Improve the peers table layout to include latency info where available along with notes on connection type, and if the connection is via a relay, or to a bootrap node
  • Loading branch information
fsdiogo authored and olizilla committed Jul 12, 2019
1 parent 493434a commit 75ff98a
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 17 deletions.
3 changes: 3 additions & 0 deletions public/locales/en/peers.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"address": "Address",
"location": "Location",
"unknownLocation": "Unknown",
"latency": "latency",
"bootstrapNode": "bootstrap node",
"viaRelay": "via <0>{node}</0>",
"addConnection": "Add Connection",
"insertPeerAddress": "Insert the peer address you want to connect to.",
"add": "Add",
Expand Down
5 changes: 5 additions & 0 deletions src/bundles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ bundle.selectGatewayUrl = createSelector(
(config) => getURLFromAddress('Gateway', config) || 'https://ipfs.io'
)

bundle.selectBootstrapPeers = createSelector(
`selectConfigObject`,
(config) => config && config.Bootstrap
)

// TODO: this is a work-around for IPFS companion blocking the config API
// see: https://github.com/ipfs-shipyard/ipfs-companion/issues/454
bundle.selectIsConfigBlocked = createSelector(
Expand Down
47 changes: 43 additions & 4 deletions src/bundles/peer-locations.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,23 +146,28 @@ export default function (opts) {
selectPeerLocationsForSwarm: createSelector(
'selectPeers',
'selectPeerLocations',
(peers, locations) => peers && peers.map((peer, idx) => {
'selectBootstrapPeers',
(peers, locations, bootstrapPeers) => peers && peers.map(peer => {
const peerId = peer.peer.toB58String()
const address = peer.addr.toString()
const locationObj = locations[peerId]
const location = toLocationString(locationObj)
const flagCode = locationObj && locationObj.country_code
const coordinates = locationObj && [
locationObj.longitude,
locationObj.latitude
]
const connection = parseConnection(peer.addr)
const latency = parseLatency(peer.latency)
const notes = parseNotes(peer, bootstrapPeers)

return {
peerId,
address,
location,
flagCode,
coordinates
coordinates,
connection,
latency,
notes
}
})
),
Expand Down Expand Up @@ -276,3 +281,37 @@ const toLocationString = loc => {
const { country_name: country, city } = loc
return city && country ? `${city}, ${country}` : country
}

const parseConnection = (multiaddr) => {
const opts = multiaddr.toOptions()

return `${opts.family}${opts.transport}`
}

const parseLatency = (latency) => {
if (latency === 'n/a') return

let value = parseInt(latency)
const unit = /(s|ms)/.exec(latency)[0]

value = unit === 's' ? value * 1000 : value

return `${value}ms`
}

const parseNotes = (peer, bootstrapPeers) => {
const peerId = peer.peer.toB58String()
const addr = peer.addr
const ipfsAddr = addr.encapsulate(`/ipfs/${peerId}`).toString()
const p2pAddr = addr.encapsulate(`/p2p/${peerId}`).toString()

if (bootstrapPeers.includes(ipfsAddr) || bootstrapPeers.includes(p2pAddr)) {
return { type: 'BOOTSTRAP_NODE' }
}

const opts = addr.toOptions()

if (opts.transport === 'p2p-circuit') {
return { type: 'RELAY_NODE', node: opts.host }
}
}
16 changes: 13 additions & 3 deletions src/bundles/peer-locations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ function createMockConnectedBundle () {
}
}

function createMockConfigBundle () {
return {
name: 'config',
selectBootstrapPeers: () => []
}
}

const mockPeersBundle = {
name: 'peers',
reducer (state = { data: [] }, action) {
Expand Down Expand Up @@ -115,7 +122,8 @@ it('should get locations for peers', async () => {
createPeerLocationsBundle({
// Ensure added peers are all processed concurrently
concurrency: 5
})
}),
createMockConfigBundle()
)()

const peers = store.selectPeers()
Expand Down Expand Up @@ -151,7 +159,8 @@ it('should fail on non IPv4 address', async () => {
createPeerLocationsBundle({
// Ensure added peers are all processed concurrently
concurrency: 5
})
}),
createMockConfigBundle()
)()

const peers = store.selectPeers()
Expand Down Expand Up @@ -190,7 +199,8 @@ it('should resolve alternative address for failed address lookup', async () => {
createMockConnectedBundle(),
createMockIpfsBundle(createMockIpfs({ maxLatency: 1 })),
mockPeersBundle,
createPeerLocationsBundle()
createPeerLocationsBundle(),
createMockConfigBundle()
)()

const peers = store.selectPeers()
Expand Down
2 changes: 1 addition & 1 deletion src/bundles/peers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ms from 'milliseconds'
const bundle = createAsyncResourceBundle({
name: 'peers',
actionBaseType: 'PEERS',
getPromise: ({ getIpfs }) => getIpfs().swarm.peers()
getPromise: ({ getIpfs }) => getIpfs().swarm.peers({ verbose: true })
.then((peers) => peers.sort((a, b) => {
const aAddr = a.addr.toString()
const bAddr = b.addr.toString()
Expand Down
42 changes: 33 additions & 9 deletions src/peers/PeersTable/PeersTable.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'redux-bundler-react'
import { translate } from 'react-i18next'
import { translate, Trans } from 'react-i18next'
import { Table, Column, AutoSizer } from 'react-virtualized'
import CountryFlag from 'react-country-flag'
import Address from '../../components/address/Address'
import Cid from '../../components/cid/Cid'

export class PeersTable extends React.Component {
static propTypes = {
Expand All @@ -17,8 +17,8 @@ export class PeersTable extends React.Component {
// Windows doesn't render the flags as emojis ¯\_(ツ)_/¯
const isWindows = window.navigator.appVersion.indexOf('Win') !== -1
return (
<span className='pr2 f4'>
{flagCode ? <CountryFlag code={flagCode} svg={isWindows} /> : '🏳️‍🌈'}
<span className='f4 pr2'>
{flagCode ? <CountryFlag code={flagCode} svg={isWindows} /> : '🌐'}
</span>
)
}
Expand All @@ -32,10 +32,32 @@ export class PeersTable extends React.Component {
</span>
)

addressCellRenderer = ({ cellData }) => (
<Address value={cellData} />
latencyCellRenderer = ({ cellData }) => {
const style = { width: '60px' }

return cellData
? <span class='dib tr' style={style}>{cellData}</span>
: <span className='dib tr o-40' style={style}>-</span>
}

peerIdCellRenderer = ({ cellData }) => (
<Cid value={cellData} />
)

notesCellRenderer = ({ cellData }) => {
if (!cellData) return

if (cellData.type === 'BOOTSTRAP_NODE') {
return this.props.t('bootstrapNode')
} else if (cellData.type === 'RELAY_NODE') {
return <Trans
i18nKey='viaRelay'
defaults='via <0>{node}</0>'
values={{ node: cellData.node }}
components={[<Cid value={cellData.node} />]} />
}
}

rowClassRenderer = ({ index }) => {
return index === -1 ? 'bb b--near-white bg-near-white' : 'bb b--near-white'
}
Expand All @@ -58,9 +80,11 @@ export class PeersTable extends React.Component {
rowHeight={36}
rowCount={peerLocationsForSwarm.length}
rowGetter={({ index }) => peerLocationsForSwarm[index]}>
<Column label={t('peerId')} dataKey='peerId' width={380} className='charcoal monospace truncate f7 pl2' />
<Column label={t('address')} cellRenderer={this.addressCellRenderer} dataKey='address' width={300} flexGrow={1} className='f6 pl2' />
<Column label={t('location')} cellRenderer={this.locationCellRenderer} dataKey='location' width={380} flexGrow={1} className='f5 navy-muted fw5 truncate pl2' />
<Column label={t('location')} cellRenderer={this.locationCellRenderer} dataKey='locationCode' width={450} className='f6 navy-muted truncate pl2' />
<Column label={t('latency')} cellRenderer={this.latencyCellRenderer} dataKey='latency' width={250} className='f6 navy-muted monospace pl2' />
<Column label={t('peerId')} cellRenderer={this.peerIdCellRenderer} dataKey='peerId' width={250} className='charcoal monospace truncate f7 pl2' />
<Column label={t('connection')} dataKey='connection' width={400} className='f6 navy-muted truncate pl2' />
<Column label={t('notes')} cellRenderer={this.notesCellRenderer} dataKey='notes' width={400} className='charcoal monospace truncate f7 pl2' />
</Table>
)}
</AutoSizer> }
Expand Down

0 comments on commit 75ff98a

Please sign in to comment.