diff --git a/packages/backend/src/Chain.ts b/packages/backend/src/Chain.ts index 00a8c13..e86a5cf 100644 --- a/packages/backend/src/Chain.ts +++ b/packages/backend/src/Chain.ts @@ -77,10 +77,13 @@ export default class Chain { if (node.height > this.height) { this.height = node.height; this.blockTimestamp = node.blockTimestamp; + node.propagationTime = 0 as Types.PropagationTime; this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp)); console.log(`[${this.label}] New block ${this.height}`); + } else if (node.height === this.height) { + node.propagationTime = (node.blockTimestamp - this.blockTimestamp) as Types.PropagationTime; } this.feeds.broadcast(Feed.imported(node)); diff --git a/packages/backend/src/Node.ts b/packages/backend/src/Node.ts index b86a604..1a92b2b 100644 --- a/packages/backend/src/Node.ts +++ b/packages/backend/src/Node.ts @@ -25,6 +25,7 @@ export default class Node { public latency = 0 as Types.Milliseconds; public blockTime = 0 as Types.Milliseconds; public blockTimestamp = 0 as Types.Timestamp; + public propagationTime: Maybe = null; private peers = 0 as Types.PeerCount; private txcount = 0 as Types.TransactionCount; @@ -132,7 +133,7 @@ export default class Node { } public blockDetails(): Types.BlockDetails { - return [this.height, this.best, this.blockTime, this.blockTimestamp]; + return [this.height, this.best, this.blockTime, this.blockTimestamp, this.propagationTime]; } public get average(): number { diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 61f3cbe..1879379 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,4 +1,4 @@ -import { Opaque } from './helpers'; +import { Opaque, Maybe } from './helpers'; import { Id } from './id'; export type ChainLabel = Opaque; @@ -11,9 +11,10 @@ export type BlockNumber = Opaque; export type BlockHash = Opaque; export type Milliseconds = Opaque; export type Timestamp = Opaque; +export type PropagationTime = Opaque; export type PeerCount = Opaque; export type TransactionCount = Opaque; -export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp]; +export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp, Maybe]; export type NodeDetails = [NodeName, NodeImplementation, NodeVersion]; export type NodeStats = [PeerCount, TransactionCount]; diff --git a/packages/frontend/src/App.css b/packages/frontend/src/App.css index aedd3ec..2708c60 100644 --- a/packages/frontend/src/App.css +++ b/packages/frontend/src/App.css @@ -3,27 +3,12 @@ font-family: Roboto, Helvetica, Arial, sans-serif; } -.App-header { - width: 100%; - background: #eee; - color: #000; -} - -.App-list { - width: 100%; - border-collapse: collapse; -} - -.App-list thead { - background: #3c3c3b; - color: #fff; -} - -.App-list tbody { - font-family: monospace, sans-serif; -} - -.App-list th, .App-list td { - text-align: left; - padding: 0.8em 1em; +.App-no-telemetry { + width: 100vw; + height: 100vh; + line-height: 80vh; + font-size: 3.5em; + font-weight: 100; + text-align: center; + color: #888; } diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index 3264a90..0195d02 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -1,19 +1,10 @@ import * as React from 'react'; import { Types } from '@dotstats/common'; -import { Chains, Node, Icon, Tile, Ago } from './components'; +import { Chains, Chain, Ago } from './components'; import { Connection } from './message'; import { State } from './state'; -import { formatNumber } from './utils'; import './App.css'; -import nodeIcon from './icons/server.svg'; -import nodeTypeIcon from './icons/terminal.svg'; -import peersIcon from './icons/broadcast.svg'; -import transactionsIcon from './icons/inbox.svg'; -import blockIcon from './icons/package.svg'; -import blockHashIcon from './icons/file-binary.svg'; -import blockTimeIcon from './icons/history.svg'; -import lastTimeIcon from './icons/watch.svg'; export default class App extends React.Component<{}, State> { public state: State = { @@ -40,41 +31,19 @@ export default class App extends React.Component<{}, State> { } public render() { - const { best, blockTimestamp, timeDiff, chains, subscribed } = this.state; + const { chains, timeDiff, subscribed } = this.state; Ago.timeDiff = timeDiff; + if (chains.size === 0) { + return
Waiting for telemetry data...
; + } + return (
-
- #{formatNumber(best)} - -
- - - - - - - - - - - - - - - { - this.nodes().map((props) => ) - } - -
+
); } - - private nodes(): Node.Props[] { - return Array.from(this.state.nodes.values()).sort((a, b) => b.blockDetails[0] - a.blockDetails[0]); - } } diff --git a/packages/frontend/src/components/Chain.css b/packages/frontend/src/components/Chain.css new file mode 100644 index 0000000..39a6c68 --- /dev/null +++ b/packages/frontend/src/components/Chain.css @@ -0,0 +1,33 @@ +.Chain-header { + width: 100%; + background: #eee; + color: #000; +} + +.Chain-content { + position: absolute; + left: 80px; + right: 0; + min-height: 50vh; + background: #222; + color: #fff; + box-shadow: rgba(0,0,0,0.5) 0 3px 30px; +} + +.Chain-node-list { + width: 100%; + border-collapse: collapse; +} + +.Chain-node-list thead { + background: #3c3c3b; +} + +.Chain-node-list tbody { + font-family: monospace, sans-serif; +} + +.Chain-node-list th, .Chain-node-list td { + text-align: left; + padding: 0.8em 1em; +} diff --git a/packages/frontend/src/components/Chain.tsx b/packages/frontend/src/components/Chain.tsx new file mode 100644 index 0000000..cfd68d6 --- /dev/null +++ b/packages/frontend/src/components/Chain.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { State } from '../state'; +import { formatNumber } from '../utils'; +import { Tile, Icon, Node, Ago } from './'; + +import nodeIcon from '../icons/server.svg'; +import nodeTypeIcon from '../icons/terminal.svg'; +import peersIcon from '../icons/broadcast.svg'; +import transactionsIcon from '../icons/inbox.svg'; +import blockIcon from '../icons/package.svg'; +import blockHashIcon from '../icons/file-binary.svg'; +import blockTimeIcon from '../icons/history.svg'; +import propagationTimeIcon from '../icons/dashboard.svg'; +import lastTimeIcon from '../icons/watch.svg'; + +import './Chain.css'; + +export namespace Chain { + export interface Props { + state: Readonly + } +} + +function sortNodes(a: Node.Props, b: Node.Props): number { + const aPropagation = a.blockDetails[4] == null ? Infinity : a.blockDetails[4] as number; + const bPropagation = b.blockDetails[4] == null ? Infinity : b.blockDetails[4] as number; + + return aPropagation - bPropagation; +} + +export function Chain(props: Chain.Props) { + const { best, blockTimestamp } = props.state; + + const nodes = Array.from(props.state.nodes.values()).sort(sortNodes); + + return ( +
+
+ #{formatNumber(best)} + +
+
+ + + + + + + + + + + + + + + + { + nodes.map((node) => ) + } + +
+
+
+ ); +} diff --git a/packages/frontend/src/components/Node.tsx b/packages/frontend/src/components/Node.tsx index 513d010..fb22b6e 100644 --- a/packages/frontend/src/components/Node.tsx +++ b/packages/frontend/src/components/Node.tsx @@ -14,7 +14,7 @@ export namespace Node { export function Node(props: Node.Props) { const [name, implementation, version] = props.nodeDetails; - const [height, hash, blockTime, blockTimestamp] = props.blockDetails; + const [height, hash, blockTime, blockTimestamp, propagationTime] = props.blockDetails; const [peers, txcount] = props.nodeStats; return ( @@ -26,6 +26,7 @@ export function Node(props: Node.Props) { #{formatNumber(height)} {trimHash(hash, 16)} {(blockTime / 1000).toFixed(3)}s + {propagationTime === null ? '∞' : `${propagationTime}ms`} ); diff --git a/packages/frontend/src/components/Tile.css b/packages/frontend/src/components/Tile.css index 1e40f89..2e5c9eb 100644 --- a/packages/frontend/src/components/Tile.css +++ b/packages/frontend/src/components/Tile.css @@ -9,17 +9,16 @@ .Tile-label { position: absolute; - top: 20px; + top: 25px; left: 100px; right: 0; font-size: 0.4em; text-transform: uppercase; - font-weight: bold; } .Tile-content { position: absolute; - bottom: 20px; + bottom: 25px; left: 100px; right: 0; font-weight: 300; diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 31279c8..5e219e3 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -1,4 +1,5 @@ export * from './Chains'; +export * from './Chain'; export * from './Icon'; export * from './Node'; export * from './Tile';