mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-14 06:01:04 +00:00
Categorize nodes by chains
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
|
||||
.App-header {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
background: #eee;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Types } from '@dotstats/common';
|
||||
import { Node, Icon, Tile, Ago } from './components';
|
||||
import { Chains, Node, Icon, Tile, Ago } from './components';
|
||||
import { Connection } from './message';
|
||||
import { State } from './state';
|
||||
import { formatNumber } from './utils';
|
||||
@@ -20,22 +20,33 @@ export default class App extends React.Component<{}, 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>;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
|
||||
this.connect();
|
||||
this.connection = Connection.create((changes) => {
|
||||
if (changes) {
|
||||
this.setState(changes);
|
||||
}
|
||||
|
||||
return this.state;
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { best, blockTimestamp, timeDiff } = this.state;
|
||||
const { best, blockTimestamp, timeDiff, chains, subscribed } = this.state;
|
||||
|
||||
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>
|
||||
@@ -63,16 +74,6 @@ export default class App extends React.Component<{}, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private async connect() {
|
||||
Connection.create((changes) => {
|
||||
if (changes) {
|
||||
this.setState(changes);
|
||||
}
|
||||
|
||||
return this.state;
|
||||
});
|
||||
}
|
||||
|
||||
private nodes(): Node.Props[] {
|
||||
return Array.from(this.state.nodes.values()).sort((a, b) => b.blockDetails[0] - a.blockDetails[0]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
.Chains {
|
||||
background: #3c3c3b;
|
||||
color: #fff;
|
||||
padding: 0.25em 1em;
|
||||
}
|
||||
|
||||
.Chains .Icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.Chains-chain {
|
||||
padding: 0.25em 0.75em;
|
||||
margin-left: 0.5em;
|
||||
background: #222;
|
||||
color: #999;
|
||||
border-radius: 0.3em;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-family: monospace, sans-serif;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Chains-chain-selected {
|
||||
background: #eee;
|
||||
color: #000;
|
||||
border-radius: 0.3em 0.3em 0 0;
|
||||
padding-bottom: 1em;
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as React from 'react';
|
||||
import { Connection } from '../message';
|
||||
import { Icon } from './Icon';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
|
||||
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 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>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
private get chains(): Types.ChainLabel[] {
|
||||
return Array.from(this.props.chains);
|
||||
}
|
||||
|
||||
private async subscribe(chain: Types.ChainLabel) {
|
||||
const connection = await this.props.connection;
|
||||
|
||||
connection.subscribe(chain);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './Chains';
|
||||
export * from './Icon';
|
||||
export * from './Node';
|
||||
export * from './Tile';
|
||||
|
||||
@@ -63,6 +63,10 @@ export class Connection {
|
||||
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);
|
||||
@@ -79,7 +83,8 @@ export class Connection {
|
||||
private handleMessages = (event: MessageEvent) => {
|
||||
const data = event.data as FeedMessage.Data;
|
||||
const nodes = this.state.nodes;
|
||||
const changes = { nodes };
|
||||
const chains = this.state.chains;
|
||||
const changes = { nodes, chains };
|
||||
|
||||
messages: for (const message of FeedMessage.deserialize(data)) {
|
||||
switch (message.action) {
|
||||
@@ -140,6 +145,44 @@ export class Connection {
|
||||
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;
|
||||
}
|
||||
@@ -149,6 +192,16 @@ export class Connection {
|
||||
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();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Types } from '@dotstats/common';
|
||||
import { Node } from './components/Node';
|
||||
import { Types, Maybe } from '@dotstats/common';
|
||||
|
||||
export interface State {
|
||||
best: Types.BlockNumber,
|
||||
blockTimestamp: Types.Timestamp,
|
||||
timeDiff: Types.Milliseconds,
|
||||
nodes: Map<Types.NodeId, Node.Props>
|
||||
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>;
|
||||
|
||||
Reference in New Issue
Block a user