Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update peers table #1062

Merged
merged 12 commits into from
Jul 12, 2019
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