mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-18 05:01:02 +00:00
Reformatting
This commit is contained in:
@@ -16,65 +16,65 @@ import blockTimeIcon from './icons/history.svg';
|
||||
import lastTimeIcon from './icons/watch.svg';
|
||||
|
||||
export default class App extends React.Component<{}, State> {
|
||||
public state: State = {
|
||||
best: 0 as Types.BlockNumber,
|
||||
blockTimestamp: 0 as Types.Timestamp,
|
||||
timeDiff: 0 as Types.Milliseconds,
|
||||
subscribed: null,
|
||||
chains: new Set(),
|
||||
nodes: new Map()
|
||||
};
|
||||
public state: State = {
|
||||
best: 0 as Types.BlockNumber,
|
||||
blockTimestamp: 0 as Types.Timestamp,
|
||||
timeDiff: 0 as Types.Milliseconds,
|
||||
subscribed: null,
|
||||
chains: new Set(),
|
||||
nodes: new Map()
|
||||
};
|
||||
|
||||
private connection: Promise<Connection>;
|
||||
private connection: Promise<Connection>;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
|
||||
this.connection = Connection.create((changes) => {
|
||||
if (changes) {
|
||||
this.setState(changes);
|
||||
}
|
||||
this.connection = Connection.create((changes) => {
|
||||
if (changes) {
|
||||
this.setState(changes);
|
||||
}
|
||||
|
||||
return this.state;
|
||||
});
|
||||
}
|
||||
return this.state;
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { best, blockTimestamp, timeDiff, chains, subscribed } = this.state;
|
||||
public render() {
|
||||
const { best, blockTimestamp, timeDiff, chains, subscribed } = this.state;
|
||||
|
||||
Ago.timeDiff = timeDiff;
|
||||
Ago.timeDiff = timeDiff;
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<Chains chains={chains} subscribed={subscribed} connection={this.connection} />
|
||||
<div className="App-header">
|
||||
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
||||
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
||||
</div>
|
||||
<table className="App-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><Icon src={nodeIcon} alt="Node" /></th>
|
||||
<th><Icon src={nodeTypeIcon} alt="Implementation" /></th>
|
||||
<th><Icon src={peersIcon} alt="Peer Count" /></th>
|
||||
<th><Icon src={transactionsIcon} alt="Transactions in Queue" /></th>
|
||||
<th><Icon src={blockIcon} alt="Block" /></th>
|
||||
<th><Icon src={blockHashIcon} alt="Block Hash" /></th>
|
||||
<th><Icon src={blockTimeIcon} alt="Block Time" /></th>
|
||||
<th><Icon src={lastTimeIcon} alt="Last Block Time" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.nodes().map((props) => <Node key={props.id} {...props} />)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="App">
|
||||
<Chains chains={chains} subscribed={subscribed} connection={this.connection} />
|
||||
<div className="App-header">
|
||||
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
||||
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
||||
</div>
|
||||
<table className="App-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><Icon src={nodeIcon} alt="Node" /></th>
|
||||
<th><Icon src={nodeTypeIcon} alt="Implementation" /></th>
|
||||
<th><Icon src={peersIcon} alt="Peer Count" /></th>
|
||||
<th><Icon src={transactionsIcon} alt="Transactions in Queue" /></th>
|
||||
<th><Icon src={blockIcon} alt="Block" /></th>
|
||||
<th><Icon src={blockHashIcon} alt="Block Hash" /></th>
|
||||
<th><Icon src={blockTimeIcon} alt="Block Time" /></th>
|
||||
<th><Icon src={lastTimeIcon} alt="Last Block Time" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.nodes().map((props) => <Node key={props.id} {...props} />)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private nodes(): Node.Props[] {
|
||||
return Array.from(this.state.nodes.values()).sort((a, b) => b.blockDetails[0] - a.blockDetails[0]);
|
||||
}
|
||||
private nodes(): Node.Props[] {
|
||||
return Array.from(this.state.nodes.values()).sort((a, b) => b.blockDetails[0] - a.blockDetails[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,77 +3,77 @@ import './Tile.css';
|
||||
import { timestamp, Types } from '@dotstats/common';
|
||||
|
||||
export namespace Ago {
|
||||
export interface Props {
|
||||
when: Types.Timestamp,
|
||||
}
|
||||
export interface Props {
|
||||
when: Types.Timestamp,
|
||||
}
|
||||
|
||||
export interface State {
|
||||
now: Types.Timestamp,
|
||||
}
|
||||
export interface State {
|
||||
now: Types.Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
const tickers = new Map<Ago, (ts: Types.Timestamp) => void>();
|
||||
|
||||
function tick() {
|
||||
const now = timestamp();
|
||||
const now = timestamp();
|
||||
|
||||
for (const ticker of tickers.values()) {
|
||||
ticker(now);
|
||||
}
|
||||
for (const ticker of tickers.values()) {
|
||||
ticker(now);
|
||||
}
|
||||
|
||||
setTimeout(tick, 100);
|
||||
setTimeout(tick, 100);
|
||||
}
|
||||
|
||||
tick();
|
||||
|
||||
export namespace Ago {
|
||||
export interface State {
|
||||
now: Types.Timestamp
|
||||
}
|
||||
export interface State {
|
||||
now: Types.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
export class Ago extends React.Component<Ago.Props, Ago.State> {
|
||||
public static timeDiff = 0 as Types.Milliseconds;
|
||||
public static timeDiff = 0 as Types.Milliseconds;
|
||||
|
||||
public state: Ago.State;
|
||||
public state: Ago.State;
|
||||
|
||||
constructor(props: Ago.Props) {
|
||||
super(props);
|
||||
constructor(props: Ago.Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
now: (timestamp() + Ago.timeDiff) as Types.Timestamp
|
||||
};
|
||||
this.state = {
|
||||
now: (timestamp() + Ago.timeDiff) as Types.Timestamp
|
||||
};
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
tickers.set(this, (now) => {
|
||||
this.setState({
|
||||
now: (now + Ago.timeDiff) as Types.Timestamp
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
tickers.delete(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.when === 0) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
tickers.set(this, (now) => {
|
||||
this.setState({
|
||||
now: (now + Ago.timeDiff) as Types.Timestamp
|
||||
});
|
||||
})
|
||||
const ago = Math.max(this.state.now - this.props.when, 0) / 1000;
|
||||
|
||||
let agoStr: string;
|
||||
|
||||
if (ago < 10) {
|
||||
agoStr = `${ago.toFixed(1)}s`;
|
||||
} else if (ago < 60) {
|
||||
agoStr = `${ago | 0}s`;
|
||||
} else {
|
||||
agoStr = `${ ago / 60 | 0}m`;
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
tickers.delete(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.when === 0) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
const ago = Math.max(this.state.now - this.props.when, 0) / 1000;
|
||||
|
||||
let agoStr: string;
|
||||
|
||||
if (ago < 10) {
|
||||
agoStr = `${ago.toFixed(1)}s`;
|
||||
} else if (ago < 60) {
|
||||
agoStr = `${ago | 0}s`;
|
||||
} else {
|
||||
agoStr = `${ ago / 60 | 0}m`;
|
||||
}
|
||||
|
||||
return <span title={new Date(this.props.when).toUTCString()}>{agoStr} ago</span>
|
||||
}
|
||||
return <span title={new Date(this.props.when).toUTCString()}>{agoStr} ago</span>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,44 +7,44 @@ import chainIcon from '../icons/link.svg';
|
||||
import './Chains.css';
|
||||
|
||||
export namespace Chains {
|
||||
export interface Props {
|
||||
chains: Set<Types.ChainLabel>,
|
||||
subscribed: Maybe<Types.ChainLabel>,
|
||||
connection: Promise<Connection>
|
||||
}
|
||||
export interface Props {
|
||||
chains: Set<Types.ChainLabel>,
|
||||
subscribed: Maybe<Types.ChainLabel>,
|
||||
connection: Promise<Connection>
|
||||
}
|
||||
}
|
||||
|
||||
export class Chains extends React.Component<Chains.Props, {}> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="Chains">
|
||||
<Icon src={chainIcon} alt="Observed chain" />
|
||||
{
|
||||
this.chains.map((chain) => this.renderChain(chain))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<div className="Chains">
|
||||
<Icon src={chainIcon} alt="Observed chain" />
|
||||
{
|
||||
this.chains.map((chain) => this.renderChain(chain))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderChain(chain: Types.ChainLabel): React.ReactNode {
|
||||
const className = chain === this.props.subscribed
|
||||
? 'Chains-chain Chains-chain-selected'
|
||||
: 'Chains-chain';
|
||||
private renderChain(chain: Types.ChainLabel): React.ReactNode {
|
||||
const className = chain === this.props.subscribed
|
||||
? 'Chains-chain Chains-chain-selected'
|
||||
: 'Chains-chain';
|
||||
|
||||
return (
|
||||
<a key={chain} className={className} onClick={this.subscribe.bind(this, chain)}>
|
||||
{chain}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<a key={chain} className={className} onClick={this.subscribe.bind(this, chain)}>
|
||||
{chain}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
private get chains(): Types.ChainLabel[] {
|
||||
return Array.from(this.props.chains);
|
||||
}
|
||||
private get chains(): Types.ChainLabel[] {
|
||||
return Array.from(this.props.chains);
|
||||
}
|
||||
|
||||
private async subscribe(chain: Types.ChainLabel) {
|
||||
const connection = await this.props.connection;
|
||||
private async subscribe(chain: Types.ChainLabel) {
|
||||
const connection = await this.props.connection;
|
||||
|
||||
connection.subscribe(chain);
|
||||
}
|
||||
connection.subscribe(chain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,21 @@ import ReactSVG from 'react-svg';
|
||||
import './Icon.css';
|
||||
|
||||
export interface Props {
|
||||
src: string,
|
||||
alt: string,
|
||||
className?: string,
|
||||
src: string,
|
||||
alt: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
export class Icon extends React.Component<{}, Props> {
|
||||
public props: Props;
|
||||
public props: Props;
|
||||
|
||||
public shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
public shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { alt, className, src } = this.props;
|
||||
public render() {
|
||||
const { alt, className, src } = this.props;
|
||||
|
||||
return <ReactSVG title={alt} className={`Icon ${ className || '' }`} path={src} />;
|
||||
}
|
||||
return <ReactSVG title={alt} className={`Icon ${ className || '' }`} path={src} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,29 @@ import { Ago } from './Ago';
|
||||
import { Types } from '@dotstats/common';
|
||||
|
||||
export namespace Node {
|
||||
export interface Props {
|
||||
id: Types.NodeId,
|
||||
nodeDetails: Types.NodeDetails,
|
||||
nodeStats: Types.NodeStats,
|
||||
blockDetails: Types.BlockDetails,
|
||||
}
|
||||
export interface Props {
|
||||
id: Types.NodeId,
|
||||
nodeDetails: Types.NodeDetails,
|
||||
nodeStats: Types.NodeStats,
|
||||
blockDetails: Types.BlockDetails,
|
||||
}
|
||||
}
|
||||
|
||||
export function Node(props: Node.Props) {
|
||||
const [name, implementation, version] = props.nodeDetails;
|
||||
const [height, hash, blockTime, blockTimestamp] = props.blockDetails;
|
||||
const [peers, txcount] = props.nodeStats;
|
||||
const [name, implementation, version] = props.nodeDetails;
|
||||
const [height, hash, blockTime, blockTimestamp] = props.blockDetails;
|
||||
const [peers, txcount] = props.nodeStats;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{name}</td>
|
||||
<td>{implementation} v{version}</td>
|
||||
<td>{peers}</td>
|
||||
<td>{txcount}</td>
|
||||
<td>#{formatNumber(height)}</td>
|
||||
<td><span title={hash}>{trimHash(hash, 16)}</span></td>
|
||||
<td>{(blockTime / 1000).toFixed(3)}s</td>
|
||||
<td><Ago when={blockTimestamp} /></td>
|
||||
</tr>
|
||||
);
|
||||
return (
|
||||
<tr>
|
||||
<td>{name}</td>
|
||||
<td>{implementation} v{version}</td>
|
||||
<td>{peers}</td>
|
||||
<td>{txcount}</td>
|
||||
<td>#{formatNumber(height)}</td>
|
||||
<td><span title={hash}>{trimHash(hash, 16)}</span></td>
|
||||
<td>{(blockTime / 1000).toFixed(3)}s</td>
|
||||
<td><Ago when={blockTimestamp} /></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,19 +3,19 @@ import './Tile.css';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
export namespace Tile {
|
||||
export interface Props {
|
||||
title: string,
|
||||
icon: string,
|
||||
children?: React.ReactNode,
|
||||
}
|
||||
export interface Props {
|
||||
title: string,
|
||||
icon: string,
|
||||
children?: React.ReactNode,
|
||||
}
|
||||
}
|
||||
|
||||
export function Tile(props: Tile.Props) {
|
||||
return (
|
||||
<div className="Tile">
|
||||
<Icon src={props.icon} alt={props.title} />
|
||||
<span className="Tile-label">{props.title}</span>
|
||||
<span className="Tile-content">{props.children}</span>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="Tile">
|
||||
<Icon src={props.icon} alt={props.title} />
|
||||
<span className="Tile-label">{props.title}</span>
|
||||
<span className="Tile-content">{props.children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+185
-185
@@ -7,205 +7,205 @@ const TIMEOUT_BASE = (1000 * 5) as Types.Milliseconds; // 5 seconds
|
||||
const TIMEOUT_MAX = (1000 * 60 * 5) as Types.Milliseconds; // 5 minutes
|
||||
|
||||
export class Connection {
|
||||
public static async create(update: Update): Promise<Connection> {
|
||||
return new Connection(await Connection.socket(), update);
|
||||
public static async create(update: Update): Promise<Connection> {
|
||||
return new Connection(await Connection.socket(), update);
|
||||
}
|
||||
|
||||
private static readonly address = `ws://${window.location.hostname}:8080`;
|
||||
|
||||
private static async socket(): Promise<WebSocket> {
|
||||
let socket = await Connection.trySocket();
|
||||
let timeout = TIMEOUT_BASE;
|
||||
|
||||
while (!socket) {
|
||||
await sleep(timeout);
|
||||
|
||||
timeout = Math.max(timeout * 2, TIMEOUT_MAX) as Types.Milliseconds;
|
||||
socket = await Connection.trySocket();
|
||||
}
|
||||
|
||||
private static readonly address = `ws://${window.location.hostname}:8080`;
|
||||
return socket;
|
||||
}
|
||||
|
||||
private static async socket(): Promise<WebSocket> {
|
||||
let socket = await Connection.trySocket();
|
||||
let timeout = TIMEOUT_BASE;
|
||||
private static async trySocket(): Promise<Maybe<WebSocket>> {
|
||||
return new Promise<Maybe<WebSocket>>((resolve, _) => {
|
||||
function clean() {
|
||||
socket.removeEventListener('open', onSuccess);
|
||||
socket.removeEventListener('close', onFailure);
|
||||
socket.removeEventListener('error', onFailure);
|
||||
}
|
||||
|
||||
while (!socket) {
|
||||
await sleep(timeout);
|
||||
function onSuccess() {
|
||||
clean();
|
||||
resolve(socket);
|
||||
}
|
||||
|
||||
timeout = Math.max(timeout * 2, TIMEOUT_MAX) as Types.Milliseconds;
|
||||
socket = await Connection.trySocket();
|
||||
function onFailure() {
|
||||
clean();
|
||||
resolve(null);
|
||||
}
|
||||
|
||||
const socket = new WebSocket(Connection.address);
|
||||
|
||||
socket.addEventListener('open', onSuccess);
|
||||
socket.addEventListener('error', onFailure);
|
||||
socket.addEventListener('close', onFailure);
|
||||
});
|
||||
}
|
||||
|
||||
private socket: WebSocket;
|
||||
private state: Readonly<State>;
|
||||
private readonly update: Update;
|
||||
|
||||
constructor(socket: WebSocket, update: Update) {
|
||||
this.socket = socket;
|
||||
this.update = update;
|
||||
this.bindSocket();
|
||||
}
|
||||
|
||||
public subscribe(chain: Types.ChainLabel) {
|
||||
this.socket.send(`subscribe:${chain}`);
|
||||
}
|
||||
|
||||
private bindSocket() {
|
||||
this.state = this.update({ nodes: new Map() });
|
||||
this.socket.addEventListener('message', this.handleMessages);
|
||||
this.socket.addEventListener('close', this.handleDisconnect);
|
||||
this.socket.addEventListener('error', this.handleDisconnect);
|
||||
}
|
||||
|
||||
private clean() {
|
||||
this.socket.removeEventListener('message', this.handleMessages);
|
||||
this.socket.removeEventListener('close', this.handleDisconnect);
|
||||
this.socket.removeEventListener('error', this.handleDisconnect);
|
||||
}
|
||||
|
||||
private handleMessages = (event: MessageEvent) => {
|
||||
const data = event.data as FeedMessage.Data;
|
||||
const nodes = this.state.nodes;
|
||||
const chains = this.state.chains;
|
||||
const changes = { nodes, chains };
|
||||
|
||||
messages: for (const message of FeedMessage.deserialize(data)) {
|
||||
switch (message.action) {
|
||||
case Actions.BestBlock: {
|
||||
const [best, blockTimestamp] = message.payload;
|
||||
|
||||
this.state = this.update({ best, blockTimestamp });
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
case Actions.AddedNode: {
|
||||
const [id, nodeDetails, nodeStats, blockDetails] = message.payload;
|
||||
const node = { id, nodeDetails, nodeStats, blockDetails };
|
||||
|
||||
private static async trySocket(): Promise<Maybe<WebSocket>> {
|
||||
return new Promise<Maybe<WebSocket>>((resolve, _) => {
|
||||
function clean() {
|
||||
socket.removeEventListener('open', onSuccess);
|
||||
socket.removeEventListener('close', onFailure);
|
||||
socket.removeEventListener('error', onFailure);
|
||||
}
|
||||
nodes.set(id, node);
|
||||
|
||||
function onSuccess() {
|
||||
clean();
|
||||
resolve(socket);
|
||||
}
|
||||
|
||||
function onFailure() {
|
||||
clean();
|
||||
resolve(null);
|
||||
}
|
||||
|
||||
const socket = new WebSocket(Connection.address);
|
||||
|
||||
socket.addEventListener('open', onSuccess);
|
||||
socket.addEventListener('error', onFailure);
|
||||
socket.addEventListener('close', onFailure);
|
||||
});
|
||||
}
|
||||
|
||||
private socket: WebSocket;
|
||||
private state: Readonly<State>;
|
||||
private readonly update: Update;
|
||||
|
||||
constructor(socket: WebSocket, update: Update) {
|
||||
this.socket = socket;
|
||||
this.update = update;
|
||||
this.bindSocket();
|
||||
}
|
||||
|
||||
public subscribe(chain: Types.ChainLabel) {
|
||||
this.socket.send(`subscribe:${chain}`);
|
||||
}
|
||||
|
||||
private bindSocket() {
|
||||
this.state = this.update({ nodes: new Map() });
|
||||
this.socket.addEventListener('message', this.handleMessages);
|
||||
this.socket.addEventListener('close', this.handleDisconnect);
|
||||
this.socket.addEventListener('error', this.handleDisconnect);
|
||||
}
|
||||
|
||||
private clean() {
|
||||
this.socket.removeEventListener('message', this.handleMessages);
|
||||
this.socket.removeEventListener('close', this.handleDisconnect);
|
||||
this.socket.removeEventListener('error', this.handleDisconnect);
|
||||
}
|
||||
|
||||
private handleMessages = (event: MessageEvent) => {
|
||||
const data = event.data as FeedMessage.Data;
|
||||
const nodes = this.state.nodes;
|
||||
const chains = this.state.chains;
|
||||
const changes = { nodes, chains };
|
||||
|
||||
messages: for (const message of FeedMessage.deserialize(data)) {
|
||||
switch (message.action) {
|
||||
case Actions.BestBlock: {
|
||||
const [best, blockTimestamp] = message.payload;
|
||||
|
||||
this.state = this.update({ best, blockTimestamp });
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
case Actions.AddedNode: {
|
||||
const [id, nodeDetails, nodeStats, blockDetails] = message.payload;
|
||||
const node = { id, nodeDetails, nodeStats, blockDetails };
|
||||
|
||||
nodes.set(id, node);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.RemovedNode: {
|
||||
nodes.delete(message.payload);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.ImportedBlock: {
|
||||
const [id, blockDetails] = message.payload;
|
||||
const node = nodes.get(id);
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.blockDetails = blockDetails;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.NodeStats: {
|
||||
const [id, nodeStats] = message.payload;
|
||||
const node = nodes.get(id);
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.nodeStats = nodeStats;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.TimeSync: {
|
||||
this.state = this.update({
|
||||
timeDiff: (timestamp() - message.payload) as Types.Milliseconds
|
||||
});
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
case Actions.AddedChain: {
|
||||
chains.add(message.payload);
|
||||
|
||||
this.autoSubscribe();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.RemovedChain: {
|
||||
chains.delete(message.payload);
|
||||
|
||||
if (this.state.subscribed === message.payload) {
|
||||
nodes.clear();
|
||||
|
||||
this.state = this.update({ subscribed: null, nodes, chains });
|
||||
this.autoSubscribe();
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.SubscribedTo: {
|
||||
this.state = this.update({ subscribed: message.payload });
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
case Actions.UnsubscribedFrom: {
|
||||
if (this.state.subscribed === message.payload) {
|
||||
nodes.clear();
|
||||
this.state = this.update({ subscribed: null, nodes });
|
||||
}
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
default: {
|
||||
continue messages;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.state = this.update(changes);
|
||||
}
|
||||
case Actions.RemovedNode: {
|
||||
nodes.delete(message.payload);
|
||||
|
||||
private autoSubscribe() {
|
||||
const { subscribed, chains } = this.state;
|
||||
|
||||
if (subscribed == null && chains.size) {
|
||||
const first = chains.values().next().value;
|
||||
|
||||
this.subscribe(first);
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.ImportedBlock: {
|
||||
const [id, blockDetails] = message.payload;
|
||||
const node = nodes.get(id);
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.blockDetails = blockDetails;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.NodeStats: {
|
||||
const [id, nodeStats] = message.payload;
|
||||
const node = nodes.get(id);
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.nodeStats = nodeStats;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.TimeSync: {
|
||||
this.state = this.update({
|
||||
timeDiff: (timestamp() - message.payload) as Types.Milliseconds
|
||||
});
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
case Actions.AddedChain: {
|
||||
chains.add(message.payload);
|
||||
|
||||
this.autoSubscribe();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.RemovedChain: {
|
||||
chains.delete(message.payload);
|
||||
|
||||
if (this.state.subscribed === message.payload) {
|
||||
nodes.clear();
|
||||
|
||||
this.state = this.update({ subscribed: null, nodes, chains });
|
||||
this.autoSubscribe();
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.SubscribedTo: {
|
||||
this.state = this.update({ subscribed: message.payload });
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
case Actions.UnsubscribedFrom: {
|
||||
if (this.state.subscribed === message.payload) {
|
||||
nodes.clear();
|
||||
this.state = this.update({ subscribed: null, nodes });
|
||||
}
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
default: {
|
||||
continue messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleDisconnect = async () => {
|
||||
this.clean();
|
||||
this.socket.close();
|
||||
this.socket = await Connection.socket();
|
||||
this.bindSocket();
|
||||
this.state = this.update(changes);
|
||||
}
|
||||
|
||||
private autoSubscribe() {
|
||||
const { subscribed, chains } = this.state;
|
||||
|
||||
if (subscribed == null && chains.size) {
|
||||
const first = chains.values().next().value;
|
||||
|
||||
this.subscribe(first);
|
||||
}
|
||||
}
|
||||
|
||||
private handleDisconnect = async () => {
|
||||
this.clean();
|
||||
this.socket.close();
|
||||
this.socket = await Connection.socket();
|
||||
this.bindSocket();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Node } from './components/Node';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
|
||||
export interface State {
|
||||
best: Types.BlockNumber,
|
||||
blockTimestamp: Types.Timestamp,
|
||||
timeDiff: Types.Milliseconds,
|
||||
subscribed: Maybe<Types.ChainLabel>,
|
||||
chains: Set<Types.ChainLabel>,
|
||||
nodes: Map<Types.NodeId, Node.Props>,
|
||||
best: Types.BlockNumber,
|
||||
blockTimestamp: Types.Timestamp,
|
||||
timeDiff: Types.Milliseconds,
|
||||
subscribed: Maybe<Types.ChainLabel>,
|
||||
chains: Set<Types.ChainLabel>,
|
||||
nodes: Map<Types.NodeId, Node.Props>,
|
||||
}
|
||||
|
||||
export type Update = <K extends keyof State>(changes: Pick<State, K> | null) => Readonly<State>;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
export function formatNumber(num: number): string {
|
||||
const input = num.toString();
|
||||
const input = num.toString();
|
||||
|
||||
let output = '';
|
||||
let length = input.length;
|
||||
let output = '';
|
||||
let length = input.length;
|
||||
|
||||
while (length > 3) {
|
||||
output = ',' + input.substr(length - 3, 3) + output;
|
||||
length -= 3;
|
||||
}
|
||||
while (length > 3) {
|
||||
output = ',' + input.substr(length - 3, 3) + output;
|
||||
length -= 3;
|
||||
}
|
||||
|
||||
output = input.substr(0, length) + output;
|
||||
output = input.substr(0, length) + output;
|
||||
|
||||
return output;
|
||||
return output;
|
||||
}
|
||||
|
||||
export function trimHash(hash: string, length: number): string {
|
||||
if (hash.length < length) {
|
||||
return hash;
|
||||
}
|
||||
if (hash.length < length) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
const side = ((length - 2) / 2) | 0;
|
||||
const side = ((length - 2) / 2) | 0;
|
||||
|
||||
return hash.substr(0, side) + '..' + hash.substr(-side, side);
|
||||
return hash.substr(0, side) + '..' + hash.substr(-side, side);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user