diff --git a/public/locales/en/peers.json b/public/locales/en/peers.json index 4bcaf5c4a..b04301fe5 100644 --- a/public/locales/en/peers.json +++ b/public/locales/en/peers.json @@ -5,6 +5,9 @@ "address": "Address", "location": "Location", "unknownLocation": "Unknown", + "latency": "latency", + "bootstrapNode": "bootstrap node", + "viaRelay": "via <0>{node}", "addConnection": "Add Connection", "insertPeerAddress": "Insert the peer address you want to connect to.", "add": "Add", diff --git a/src/bundles/config.js b/src/bundles/config.js index 044b348cb..43547220a 100644 --- a/src/bundles/config.js +++ b/src/bundles/config.js @@ -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( diff --git a/src/bundles/peer-locations.js b/src/bundles/peer-locations.js index f45475648..030c3432c 100644 --- a/src/bundles/peer-locations.js +++ b/src/bundles/peer-locations.js @@ -146,9 +146,9 @@ 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 @@ -156,13 +156,18 @@ export default function (opts) { 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 } }) ), @@ -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 } + } +} diff --git a/src/bundles/peer-locations.test.js b/src/bundles/peer-locations.test.js index 61739b054..643d76f8d 100644 --- a/src/bundles/peer-locations.test.js +++ b/src/bundles/peer-locations.test.js @@ -45,6 +45,13 @@ function createMockConnectedBundle () { } } +function createMockConfigBundle () { + return { + name: 'config', + selectBootstrapPeers: () => [] + } +} + const mockPeersBundle = { name: 'peers', reducer (state = { data: [] }, action) { @@ -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() @@ -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() @@ -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() diff --git a/src/bundles/peers.js b/src/bundles/peers.js index 93457a900..0c466c92a 100644 --- a/src/bundles/peers.js +++ b/src/bundles/peers.js @@ -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() diff --git a/src/peers/PeersTable/PeersTable.js b/src/peers/PeersTable/PeersTable.js index 5db0b3221..8c749d6ea 100644 --- a/src/peers/PeersTable/PeersTable.js +++ b/src/peers/PeersTable/PeersTable.js @@ -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 = { @@ -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 ( - - {flagCode ? : '🏳️‍🌈'} + + {flagCode ? : '🌐'} ) } @@ -32,10 +32,32 @@ export class PeersTable extends React.Component { ) - addressCellRenderer = ({ cellData }) => ( -
+ latencyCellRenderer = ({ cellData }) => { + const style = { width: '60px' } + + return cellData + ? {cellData} + : - + } + + peerIdCellRenderer = ({ cellData }) => ( + ) + notesCellRenderer = ({ cellData }) => { + if (!cellData) return + + if (cellData.type === 'BOOTSTRAP_NODE') { + return this.props.t('bootstrapNode') + } else if (cellData.type === 'RELAY_NODE') { + return ]} /> + } + } + rowClassRenderer = ({ index }) => { return index === -1 ? 'bb b--near-white bg-near-white' : 'bb b--near-white' } @@ -58,9 +80,11 @@ export class PeersTable extends React.Component { rowHeight={36} rowCount={peerLocationsForSwarm.length} rowGetter={({ index }) => peerLocationsForSwarm[index]}> - - - + + + + + )} }