diff --git a/packages/backend/src/Node.ts b/packages/backend/src/Node.ts index accba94..9c9d4e6 100644 --- a/packages/backend/src/Node.ts +++ b/packages/backend/src/Node.ts @@ -20,7 +20,8 @@ export default class Node { public readonly chain: Types.ChainLabel; public readonly implementation: Types.NodeImplementation; public readonly version: Types.NodeVersion; - public readonly pubkey: Maybe; + public readonly address: Maybe; + public readonly authority: boolean; public readonly events = new EventEmitter() as EventEmitter & NodeEvents; @@ -52,17 +53,19 @@ export default class Node { config: string, implentation: Types.NodeImplementation, version: Types.NodeVersion, - pubkey: Maybe, + address: Maybe, + authority: boolean, messages: Array, ) { this.ip = ip; - this.id = getId(pubkey); + this.id = getId(address); this.name = name; this.chain = chain; this.config = config; this.implementation = implentation; this.version = version; - this.pubkey = pubkey; + this.address = address; + this.authority = authority; this.lastMessage = timestamp(); this.socket = socket; @@ -93,10 +96,12 @@ export default class Node { this.pingStart = 0 as Types.Timestamp; }); - // Handle cached messages - for (const message of messages) { - this.onMessage(message); - } + process.nextTick(() => { + // Handle cached messages + for (const message of messages) { + this.onMessage(message); + } + }); locate(ip).then((location) => { if (!location) { @@ -128,9 +133,9 @@ export default class Node { if (message.msg === "system.connected") { cleanup(); - const { name, chain, config, implementation, version, pubkey } = message; + const { name, chain, config, implementation, version, pubkey, authority } = message; - resolve(new Node(ip, socket, name, chain, config, implementation, version, pubkey, messages)); + resolve(new Node(ip, socket, name, chain, config, implementation, version, pubkey, authority === true, messages)); } else { if (messages.length === 10) { messages.shift(); @@ -162,7 +167,9 @@ export default class Node { } public nodeDetails(): Types.NodeDetails { - return [this.name, this.implementation, this.version]; + const authority = this.authority ? this.address : null; + + return [this.name, this.implementation, this.version, authority]; } public nodeStats(): Types.NodeStats { @@ -196,7 +203,7 @@ export default class Node { this.socket.close(); this.socket.terminate(); - refreshId(this.pubkey, this.id); + refreshId(this.address, this.id); this.events.emit('disconnect'); } diff --git a/packages/backend/src/message.ts b/packages/backend/src/message.ts index c41708d..906cbb8 100644 --- a/packages/backend/src/message.ts +++ b/packages/backend/src/message.ts @@ -34,42 +34,43 @@ interface MessageBase { } export interface BestBlock { - best: Types.BlockHash, - height: Types.BlockNumber, - ts: Date, + best: Types.BlockHash; + height: Types.BlockNumber; + ts: Date; } -interface SystemConnected { - msg: 'system.connected', - name: Types.NodeName, - chain: Types.ChainLabel, - config: string, - implementation: Types.NodeImplementation, - version: Types.NodeVersion, - pubkey: Maybe, +export interface SystemConnected { + msg: 'system.connected'; + name: Types.NodeName; + chain: Types.ChainLabel; + config: string; + implementation: Types.NodeImplementation; + version: Types.NodeVersion; + pubkey: Maybe; + authority: Maybe; } export interface SystemInterval extends BestBlock { - msg: 'system.interval', - txcount: Types.TransactionCount, - peers: Types.PeerCount, - status: 'Idle' | string, // TODO: 'Idle' | ...? + msg: 'system.interval'; + txcount: Types.TransactionCount; + peers: Types.PeerCount; + status: 'Idle' | string; // TODO: 'Idle' | ...? } -interface NodeStart extends BestBlock { - msg: 'node.start', +export interface NodeStart extends BestBlock { + msg: 'node.start'; } -interface BlockImport extends BestBlock { - msg: 'block.import', +export interface BlockImport extends BestBlock { + msg: 'block.import'; } // Union type export type Message = MessageBase & ( - SystemConnected | - SystemInterval | - NodeStart | - BlockImport + | SystemConnected + | SystemInterval + | NodeStart + | BlockImport ); diff --git a/packages/backend/src/nodeId.ts b/packages/backend/src/nodeId.ts index cf3dd3c..dca5788 100644 --- a/packages/backend/src/nodeId.ts +++ b/packages/backend/src/nodeId.ts @@ -9,7 +9,7 @@ interface NodeIdCache { } const nextId = idGenerator(); -const idCache = new Map(); +const idCache = new Map(); function clearCache() { const now = timestamp(); @@ -25,7 +25,7 @@ function clearCache() { clearCache(); -export function getId(pubkey: Maybe): Types.NodeId { +export function getId(pubkey: Maybe): Types.NodeId { if (!pubkey) { return nextId(); } @@ -44,7 +44,7 @@ export function getId(pubkey: Maybe): Types.NodeId { return id; } -export function refreshId(pubkey: Maybe, id: Types.NodeId) { +export function refreshId(pubkey: Maybe, id: Types.NodeId) { if (!pubkey) { return; } diff --git a/packages/common/src/feed.ts b/packages/common/src/feed.ts index 352f4fb..d6b6bf1 100644 --- a/packages/common/src/feed.ts +++ b/packages/common/src/feed.ts @@ -1,6 +1,7 @@ import { Opaque, Maybe } from './helpers'; import { FeedVersion, + Address, Latitude, Longitude, City, diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 6e7feb4..bf10430 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -8,4 +8,4 @@ import * as FeedMessage from './feed'; export { Types, FeedMessage }; // Increment this if breaking changes were made to types in `feed.ts` -export const VERSION: Types.FeedVersion = 9 as Types.FeedVersion; +export const VERSION: Types.FeedVersion = 10 as Types.FeedVersion; diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 692ee46..d330b0d 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -8,9 +8,9 @@ export type NodeId = Id<'Node'>; export type NodeName = Opaque; export type NodeImplementation = Opaque; export type NodeVersion = Opaque; -export type NodePubKey = Opaque; export type BlockNumber = Opaque; export type BlockHash = Opaque; +export type Address = Opaque; export type Milliseconds = Opaque; export type Timestamp = Opaque; export type PropagationTime = Opaque; @@ -22,6 +22,6 @@ export type Longitude = Opaque; export type City = Opaque; export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp, Maybe]; -export type NodeDetails = [NodeName, NodeImplementation, NodeVersion]; +export type NodeDetails = [NodeName, NodeImplementation, NodeVersion, Maybe
]; export type NodeStats = [PeerCount, TransactionCount]; export type NodeLocation = [Latitude, Longitude, City]; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 31456f9..76c2a63 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -5,6 +5,7 @@ "license": "GPL-3.0", "description": "Polkadot Telemetry frontend", "dependencies": { + "polkadot-identicon": "^1.1.0", "react": "16.4.0", "react-dom": "16.4.0", "react-scripts-ts": "2.17.0", diff --git a/packages/frontend/src/components/Chain.tsx b/packages/frontend/src/components/Chain.tsx index 7fe26b3..fc9c898 100644 --- a/packages/frontend/src/components/Chain.tsx +++ b/packages/frontend/src/components/Chain.tsx @@ -141,7 +141,10 @@ export class Chain extends React.Component { { - this.nodes().sort(sortNodes).map((node) => ) + this + .nodes() + .sort(sortNodes) + .map((node) => ) } @@ -160,17 +163,17 @@ export class Chain extends React.Component { const left = Math.round(((180 + lon) / 360) * map.width + map.left); const top = Math.round(((90 - lat) / 180) * map.height + map.top) * MAP_HEIGHT_ADJUST; - let quarter = 0; + let quarter: Node.Location.Quarter = 0; if (lon > 0) { - quarter |= 1; + quarter = (quarter | 1) as Node.Location.Quarter; } if (lat < 0) { - quarter |= 2; + quarter = (quarter | 2) as Node.Location.Quarter; } - return { left, top, quarter: quarter as 0 | 1 | 2 | 3 }; + return { left, top, quarter }; } private calculateMapDimensions: () => void = () => { diff --git a/packages/frontend/src/components/Node/Location.css b/packages/frontend/src/components/Node/Location.css index 74322ae..184d230 100644 --- a/packages/frontend/src/components/Node/Location.css +++ b/packages/frontend/src/components/Node/Location.css @@ -45,8 +45,6 @@ .Node-Location-details { min-width: 335px; position: absolute; - /*left: 16px; - top: -4px;*/ font-family: monospace, sans-serif; background: #222; color: #fff; @@ -108,3 +106,12 @@ border-color: rgba(255,255,255,0); } } + +.Node-Location-validator { + display: inline-block; + width: 16px; + height: 16px; + transform: scale(1.5); + transform-origin: right 50%; + margin-left: 16px; +} diff --git a/packages/frontend/src/components/Node/Location.tsx b/packages/frontend/src/components/Node/Location.tsx index 7fdab97..0f72ce3 100644 --- a/packages/frontend/src/components/Node/Location.tsx +++ b/packages/frontend/src/components/Node/Location.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; +import Identicon from 'polkadot-identicon'; import { Types } from '@dotstats/common'; import { formatNumber, trimHash, milliOrSecond, secondsWithPrecision } from '../../utils'; import { Ago, Icon } from '../'; import { State as AppState } from '../../state'; import nodeIcon from '../../icons/server.svg'; +import nodeValidatorIcon from '../../icons/shield.svg'; import nodeTypeIcon from '../../icons/terminal.svg'; import nodeLocationIcon from '../../icons/location.svg'; import blockIcon from '../../icons/package.svg'; @@ -16,10 +18,12 @@ import lastTimeIcon from '../../icons/watch.svg'; import './Location.css'; namespace Location { + export type Quarter = 0 | 1 | 2 | 3; + export interface Position { left: number; top: number; - quarter: 0 | 1 | 2 | 3; + quarter: Quarter; } export interface State { @@ -58,15 +62,30 @@ class Location extends React.Component + + + {trimHash(validator, 30)} + + + + ); + } + return ( + {validatorRow} diff --git a/packages/frontend/src/components/Node/Row.css b/packages/frontend/src/components/Node/Row.css index b483779..fb2e148 100644 --- a/packages/frontend/src/components/Node/Row.css +++ b/packages/frontend/src/components/Node/Row.css @@ -5,3 +5,14 @@ .Node-Row-synced { color: #fff; } + +.Node-Row-validator { + display: block; + width: 16px; + height: 16px; + cursor: pointer; +} + +.Node-Row-validator:hover { + transform: scale(2); +} diff --git a/packages/frontend/src/components/Node/Row.tsx b/packages/frontend/src/components/Node/Row.tsx index 73fe9c8..b702a98 100644 --- a/packages/frontend/src/components/Node/Row.tsx +++ b/packages/frontend/src/components/Node/Row.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; +import Identicon from 'polkadot-identicon'; import { formatNumber, trimHash, milliOrSecond, secondsWithPrecision } from '../../utils'; import { State as AppState } from '../../state'; import { SEMVER_PATTERN } from './'; import { Ago, Icon } from '../'; import nodeIcon from '../../icons/server.svg'; +import nodeValidatorIcon from '../../icons/shield.svg'; import nodeTypeIcon from '../../icons/terminal.svg'; import peersIcon from '../../icons/broadcast.svg'; import transactionsIcon from '../../icons/inbox.svg'; @@ -22,6 +24,7 @@ export default class Row extends React.Component { + @@ -38,7 +41,7 @@ export default class Row extends React.Component { public render() { const { nodeDetails, blockDetails, nodeStats } = this.props; - const [name, implementation, version] = nodeDetails; + const [name, implementation, version, validator] = nodeDetails; const [height, hash, blockTime, blockTimestamp, propagationTime] = blockDetails; const [peers, txcount] = nodeStats; const [semver] = version.match(SEMVER_PATTERN) || [version]; @@ -52,6 +55,7 @@ export default class Row extends React.Component { return ( + diff --git a/yarn.lock b/yarn.lock index 8fb494d..1351a0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1052,6 +1052,12 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base-x@^3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77" + dependencies: + safe-buffer "^5.0.1" + base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -1086,6 +1092,23 @@ binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" +bindings@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" + +blake2js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/blake2js/-/blake2js-1.0.0.tgz#b6890c394ddb9405d06d9a7ad3cf656711dcad89" + dependencies: + bindings "^1.2.1" + debug "^2.2.0" + nan "^2.4.0" + readable-stream "^2.1.4" + +blakejs@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5" + bluebird@^3.4.7, bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -1246,6 +1269,12 @@ browserslist@^2.1.2, browserslist@^2.5.1: caniuse-lite "^1.0.30000792" electron-to-chromium "^1.3.30" +bs58@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + dependencies: + base-x "^3.0.2" + bser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -1782,6 +1811,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-class@^15.6.0: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn@5.1.0, cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2642,7 +2679,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.16: +fbjs@^0.8.16, fbjs@^0.8.9: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" dependencies: @@ -4637,6 +4674,10 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" +nan@^2.4.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" + nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" @@ -4906,6 +4947,17 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +oo7-react@^0.4.9: + version "0.4.9" + resolved "https://registry.yarnpkg.com/oo7-react/-/oo7-react-0.4.9.tgz#67160c1978add5063c7d0629a08494ae03667456" + dependencies: + oo7 "^0.5.9" + react "^15.4.2" + +oo7@^0.5.9: + version "0.5.9" + resolved "https://registry.yarnpkg.com/oo7/-/oo7-0.5.9.tgz#a4e948cab6ab9e1bf032c9ad588f4cbf31478b51" + opn@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" @@ -5167,6 +5219,15 @@ pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" +polkadot-identicon@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/polkadot-identicon/-/polkadot-identicon-1.1.0.tgz#33c0dee86fe176b98db4334a36cef0988f3a3b27" + dependencies: + blake2js "^1.0.0" + oo7-react "^0.4.9" + react "^16.4.2" + ss58 "^1.0.0" + portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -5520,7 +5581,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.6.0: +prop-types@^15.5.10, prop-types@^15.6.0: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: @@ -5699,9 +5760,9 @@ react-error-overlay@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" -react-scripts-ts@2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.16.0.tgz#45f831a12139c3b59d6bb729c1b6ef51e0f22908" +react-scripts-ts@2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.17.0.tgz#398bae19a30c9b39b3dfe0720ebb40c60c2f6574" dependencies: autoprefixer "7.1.6" babel-jest "^22.1.0" @@ -5758,6 +5819,25 @@ react@16.4.0: object-assign "^4.1.1" prop-types "^15.6.0" +react@^15.4.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react@^16.4.2: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -5788,7 +5868,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -6451,6 +6531,13 @@ sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" +ss58@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ss58/-/ss58-1.0.2.tgz#e59f3482098c4b3d975ca6d359d1807441e07dfd" + dependencies: + blakejs "^1.1.0" + bs58 "^4.0.1" + sshpk@^1.7.0: version "1.14.2" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
{name}
{implementation} v{version}
{name}{validator ? : null} {implementation} v{semver} {peers} {txcount}