ghost-telemetry-frontend/src/App.tsx
Uncle Fatso e63dad2106
initial commit for public repo
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2024-12-23 15:01:54 +03:00

237 lines
6.2 KiB
TypeScript

import * as React from 'react';
import { Types, SortedCollection, Maybe, Compare } from './common';
import { Chain, Ago } from './components';
import { Row, Column } from './components/List';
import { Connection } from './Connection';
import { Persistent, PersistentObject, PersistentSet } from './persist';
import {
bindState,
State,
Update,
Node,
ChainData,
comparePinnedChains,
StateSettings,
} from './state';
import { getHashData } from './utils';
import innerCircleIcon from './assets/inner-circle.svg';
import outerCircleIcon from './assets/outer-circle.svg';
import './App.css';
export default class App extends React.Component {
private chainsCache: ChainData[] = [];
// Custom state for finer control over updates
private readonly appState: Readonly<State>;
private readonly appUpdate: Update;
private readonly settings: PersistentObject<StateSettings>;
private readonly pins: PersistentSet<Types.NodeName>;
private readonly sortBy: Persistent<Maybe<number>>;
private readonly connection: Promise<Connection>;
constructor(props: Record<string, unknown>) {
super(props);
//todo! Check what's important to set to true, false
this.settings = new PersistentObject(
'settings',
{
validator: true,
location: true,
implementation: true,
networkId: true,
peers: true,
txs: true,
upload: false,
download: false,
stateCacheSize: false,
dbCacheSize: false,
diskRead: false,
diskWrite: false,
blocknumber: true,
blockhash: true,
finalized: false,
finalizedhash: false,
blocktime: true,
blockpropagation: true,
blocklasttime: false,
uptime: false,
version: false,
target_os: false,
target_arch: false,
cpu: false,
cpu_hashrate_score: true,
cpu_vendor: true,
core_count: false,
mem: true,
memory: false,
linux_distro: false,
linux_kernel: false,
memory_memcpy_score: true,
disk_sequential_write_score: true,
disk_random_write_score: true,
is_virtual_machine: false,
},
(settings) => {
const selectedColumns = this.selectedColumns(settings);
this.sortBy.set(null);
this.appUpdate({ settings, selectedColumns, sortBy: null });
}
);
this.pins = new PersistentSet<Types.NodeName>('pinned_names', (pins) => {
const { nodes } = this.appState;
nodes.mutEachAndSort((node) => node.setPinned(pins.has(node.name)));
this.appUpdate({ nodes, pins });
});
this.sortBy = new Persistent<Maybe<number>>('sortBy', null, (sortBy) => {
const compare = this.getComparator(sortBy);
this.appState.nodes.setComparator(compare);
this.appUpdate({ sortBy });
});
const { tab = '' } = getHashData();
this.appUpdate = bindState(this, {
status: 'offline',
best: 0 as Types.BlockNumber,
finalized: 0 as Types.BlockNumber,
blockTimestamp: 0 as Types.Timestamp,
blockAverage: null,
timeDiff: 0 as Types.Milliseconds,
subscribed: null,
chains: new Map(),
nodes: new SortedCollection(Node.compare),
settings: this.settings.raw(),
pins: this.pins.get(),
sortBy: this.sortBy.get(),
selectedColumns: this.selectedColumns(this.settings.raw()),
tab,
chainStats: null,
});
this.appState = this.appUpdate({});
const comparator = this.getComparator(this.sortBy.get());
this.appState.nodes.setComparator(comparator);
this.connection = Connection.create(
this.pins,
this.appState,
this.appUpdate
);
setInterval(() => (this.chainsCache = []), 10000);
}
public render() {
const { timeDiff, subscribed, status, tab } = this.appState;
const chains = this.chains();
const subscribedData = subscribed
? this.appState.chains.get(subscribed)
: null;
Ago.timeDiff = timeDiff;
if (chains.length === 0) {
return (
<div className="App App-no-telemetry">
<div className="App-no-telemetry-spinner">
<img src={innerCircleIcon} />
<img src={outerCircleIcon} />
</div>
<div className="App-no-telemetry-text">
Waiting for Ghosties&hellip;
</div>
</div>
);
}
return (
<div className="App">
<Chain
appState={this.appState}
appUpdate={this.appUpdate}
connection={this.connection}
settings={this.settings}
pins={this.pins}
sortBy={this.sortBy}
/>
</div>
);
}
public componentDidMount() {
window.addEventListener('hashchange', this.onHashChange);
}
public componentWillUnmount() {
window.removeEventListener('hashchange', this.onHashChange);
}
private onHashChange = () => {
const { tab = '' } = getHashData();
this.appUpdate({ tab });
};
private chains(): ChainData[] {
if (this.chainsCache.length === this.appState.chains.size) {
return this.chainsCache;
}
this.chainsCache = Array.from(this.appState.chains.values()).sort(
(a, b) => {
const pinned = comparePinnedChains(a.genesisHash, b.genesisHash);
if (pinned !== 0) {
return pinned;
}
return b.nodeCount - a.nodeCount;
}
);
return this.chainsCache;
}
private selectedColumns(settings: StateSettings): Column[] {
return Row.columns.filter(
({ setting }) => setting == null || settings[setting]
);
}
private getComparator(sortBy: Maybe<number>): Compare<Node> {
const columns = this.appState.selectedColumns;
if (sortBy != null) {
const [index, rev] = sortBy < 0 ? [~sortBy, -1] : [sortBy, 1];
const column = columns[index];
if (column != null && column.sortBy) {
const key = column.sortBy;
return (a, b) => {
const aKey = key(a);
const bKey = key(b);
if (aKey < bKey) {
return -1 * rev;
} else if (aKey > bKey) {
return 1 * rev;
} else {
return Node.compare(a, b);
}
};
}
}
return Node.compare;
}
}