mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-12 07:41:09 +00:00
Added ago timers
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
font-family: monospace, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Types } from '@dotstats/common';
|
||||
import { Node } from './Node';
|
||||
import { Icon } from './Icon';
|
||||
import { Node, Icon, Tile, Ago } from './components';
|
||||
import { Connection } from './message';
|
||||
import { State } from './state';
|
||||
import { formatNumber } from './utils';
|
||||
@@ -14,10 +13,13 @@ 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/dashboard.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,
|
||||
nodes: new Map()
|
||||
};
|
||||
|
||||
@@ -28,11 +30,14 @@ export default class App extends React.Component<{}, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { best, blockTimestamp, timeDiff } = this.state;
|
||||
|
||||
Ago.timeDiff = timeDiff;
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="App-header">
|
||||
<Icon src={blockIcon} alt="Best Block" /> #{formatNumber(this.state.best)}
|
||||
</div>
|
||||
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
||||
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
||||
<table className="App-list">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -43,6 +48,7 @@ export default class App extends React.Component<{}, State> {
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as React from 'react';
|
||||
import './Tile.css';
|
||||
import { timestamp, Types } from '@dotstats/common';
|
||||
|
||||
export namespace Ago {
|
||||
export interface Props {
|
||||
when: Types.Timestamp,
|
||||
}
|
||||
|
||||
export interface State {
|
||||
now: Types.Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
const tickers = new Map<Ago, (ts: Types.Timestamp) => void>();
|
||||
|
||||
function tick() {
|
||||
const now = timestamp();
|
||||
|
||||
for (const ticker of tickers.values()) {
|
||||
ticker(now);
|
||||
}
|
||||
|
||||
setTimeout(tick, 100);
|
||||
}
|
||||
|
||||
tick();
|
||||
|
||||
export namespace Ago {
|
||||
export interface State {
|
||||
now: Types.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
export class Ago extends React.Component<Ago.Props, Ago.State> {
|
||||
public static timeDiff = 0 as Types.Milliseconds;
|
||||
|
||||
public state: Ago.State;
|
||||
|
||||
constructor(props: Ago.Props) {
|
||||
super(props);
|
||||
|
||||
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() {
|
||||
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>
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { formatNumber, trimHash } from './utils';
|
||||
import { formatNumber, trimHash } from '../utils';
|
||||
import { Ago } from './Ago';
|
||||
import { Types } from '@dotstats/common';
|
||||
|
||||
export namespace Node {
|
||||
@@ -13,7 +14,7 @@ export namespace Node {
|
||||
|
||||
export function Node(props: Node.Props) {
|
||||
const [name, implementation, version] = props.nodeDetails;
|
||||
const [height, hash, blockTime] = props.blockDetails;
|
||||
const [height, hash, blockTime, blockTimestamp] = props.blockDetails;
|
||||
const [peers, txcount] = props.nodeStats;
|
||||
|
||||
return (
|
||||
@@ -24,7 +25,8 @@ export function Node(props: Node.Props) {
|
||||
<td>{txcount}</td>
|
||||
<td>#{formatNumber(height)}</td>
|
||||
<td><span title={hash}>{trimHash(hash, 16)}</span></td>
|
||||
<td>{blockTime / 1000}s</td>
|
||||
<td>{(blockTime / 1000).toFixed(3)}s</td>
|
||||
<td><Ago when={blockTimestamp} /></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.Tile {
|
||||
font-size: 2.5em;
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
width: 7em;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import './Tile.css';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
export namespace Tile {
|
||||
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} /> {props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './Icon';
|
||||
export * from './Node';
|
||||
export * from './Tile';
|
||||
export * from './Ago';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FeedMessage, Types, Maybe, sleep } from '@dotstats/common';
|
||||
import { timestamp, FeedMessage, Types, Maybe, sleep } from '@dotstats/common';
|
||||
import { State, Update } from './state';
|
||||
|
||||
const { Actions } = FeedMessage;
|
||||
@@ -13,7 +13,6 @@ export class Connection {
|
||||
|
||||
private static readonly address = `ws://${window.location.hostname}:8080`;
|
||||
|
||||
|
||||
private static async socket(): Promise<WebSocket> {
|
||||
let socket = await Connection.trySocket();
|
||||
let timeout = TIMEOUT_BASE;
|
||||
@@ -61,11 +60,11 @@ export class Connection {
|
||||
constructor(socket: WebSocket, update: Update) {
|
||||
this.socket = socket;
|
||||
this.update = update;
|
||||
this.state = update(null);
|
||||
this.bindSocket();
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -85,10 +84,13 @@ export class Connection {
|
||||
messages: for (const message of FeedMessage.deserialize(data)) {
|
||||
switch (message.action) {
|
||||
case Actions.BestBlock: {
|
||||
this.state = this.update({ best: message.payload });
|
||||
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 };
|
||||
@@ -97,14 +99,15 @@ export class Connection {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.RemovedNode: {
|
||||
nodes.delete(message.payload);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.ImportedBlock: {
|
||||
const [id, blockDetails] = message.payload;
|
||||
|
||||
const node = nodes.get(id);
|
||||
|
||||
if (!node) {
|
||||
@@ -115,9 +118,9 @@ export class Connection {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.NodeStats: {
|
||||
const [id, nodeStats] = message.payload;
|
||||
|
||||
const node = nodes.get(id);
|
||||
|
||||
if (!node) {
|
||||
@@ -128,8 +131,17 @@ export class Connection {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Actions.TimeSync: {
|
||||
this.state = this.update({
|
||||
timeDiff: (timestamp() - message.payload) as Types.Milliseconds
|
||||
});
|
||||
|
||||
continue messages;
|
||||
}
|
||||
|
||||
default: {
|
||||
return;
|
||||
continue messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Types } from '@dotstats/common';
|
||||
import { Node } from './Node';
|
||||
import { Node } from './components/Node';
|
||||
|
||||
export interface State {
|
||||
best: Types.BlockNumber,
|
||||
blockTimestamp: Types.Timestamp,
|
||||
timeDiff: Types.Milliseconds,
|
||||
nodes: Map<Types.NodeId, Node.Props>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user