diff --git a/src/api/rpc.ts b/src/api/rpc.ts index b883eea..8a7cc26 100644 --- a/src/api/rpc.ts +++ b/src/api/rpc.ts @@ -145,6 +145,24 @@ export const hmyv2_getTransactionsCount = (address: string, txType: RequestTxTyp }); }; +export const hmyv2_getNodeMetadata = (shard: string) => { + return rpcAdapter>(getApiUrl(shard), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "hmyv2_getNodeMetadata", + id: 1, + params: [], + }), + }).then(data => { + if (data.error) { + throw new Error(data.error.message) + } + return data.result + }); +}; + export const hmyv2_getStakingTransactionsHistory = (params: IGetTxsHistoryParams[]) => { return rpcAdapter>(API_URL, { method: "POST", @@ -280,3 +298,16 @@ export const getAllApprovalsForTokens = async (address: string, return { txnHistory, dataObj }; } + +const getApiUrl = (shard: string) => { + switch (shard) { + case "1": + return process.env.REACT_APP_RPC_URL_SHARD1 || 'https://a.api.s1.t.hmny.io/' + case "2": + return process.env.REACT_APP_RPC_URL_SHARD2 || 'https://a.api.s2.t.hmny.io/' + case "3": + return process.env.REACT_APP_RPC_URL_SHARD3 || 'https://a.api.s3.t.hmny.io/' + default: + return 'https://api.s0.t.hmny.io/' || process.env.REACT_APP_RPC_URL_SHARD0 + } +} \ No newline at end of file diff --git a/src/components/appHeader/ResourcesButton.tsx b/src/components/appHeader/ResourcesButton.tsx index b6680dc..29bb5a7 100644 --- a/src/components/appHeader/ResourcesButton.tsx +++ b/src/components/appHeader/ResourcesButton.tsx @@ -43,7 +43,6 @@ export function ResourcesButton() { > Charts & Stats - (new) Top Statistics - (new) diff --git a/src/pages/ChartsPage/StatPage.tsx b/src/pages/ChartsPage/StatPage.tsx new file mode 100644 index 0000000..ae8e9c1 --- /dev/null +++ b/src/pages/ChartsPage/StatPage.tsx @@ -0,0 +1,129 @@ +import {Box, Heading, Spinner, Text} from "grommet"; +import {BaseContainer, BasePage} from "../../components/ui"; +import styled from "styled-components"; +import {Link} from "react-router-dom"; +import {useMediaQuery} from "react-responsive"; +import { Alert, Info } from 'grommet-icons'; + +const TextLink = styled(Text)` + cursor: pointer; + text-decoration: underline; +` + +const ChartModalContainer = styled(Box)` +` + +const LoadingErrorModal = () => { + return + + Error on loading data + Please try again later + +} + +const parseKeyValue = (key : string) => { + return key.replace(/([A-Z])/g, ' $1').trim().split(' ') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export interface StatProps { + title: string + description?: string + items: any[] + keys: string[] + infoLeft: string + infoRight?: string + isLoading: boolean + loadingError?: string +} + +export const StatPage = (props: StatProps) => { + const { isLoading, loadingError } = props + + const rows = [] + for (let i = 0; i < props.items.length; i++) { + rows.push( + + + Shard {i} + + + {props.keys.map((key, _) => { + return {parseKeyValue(key)}: {props.items[i][key]} + })} + + + ) + } + + const isMobile = useMediaQuery({ query: '(max-width: 868px)' }) + + return + + + + + Charts + + + / + + + {props.title} + + + {!isMobile && props.description && + + {props.description} + + } + + + + + + + {props.infoLeft} + + {props.infoRight && + + {props.infoRight} + } + + + + + {isLoading && + + Loading Data + } + {!isLoading && loadingError && } + {!isLoading && !loadingError && rows} + + + +} diff --git a/src/pages/ChartsPage/ViewChangeStats.tsx b/src/pages/ChartsPage/ViewChangeStats.tsx new file mode 100644 index 0000000..eca9411 --- /dev/null +++ b/src/pages/ChartsPage/ViewChangeStats.tsx @@ -0,0 +1,55 @@ +import {useEffect, useState} from 'react' +import { StatPage } from './StatPage'; +import { hmyv2_getNodeMetadata } from 'src/api/rpc'; +import { config } from 'src/config'; + +const CONSENSUS = "consensus" +const BLOCK_NUM = "blocknum" +const VIEW_ID = "viewId" + +export const ViewChangeStats = () => { + const { availableShards } = config + const [items, setItems] = useState([]) + const [isLoading, setIsLoading] = useState(false); + const [loadingError, setLoadingError] = useState('') + const [totalViewChange, setTotalViewChange] = useState(0) + + useEffect(() => { + const loadData = async () => { + try { + setIsLoading(true) + for (let i in availableShards) { + const nodeMetadata = await hmyv2_getNodeMetadata(i) + const blockHeight = nodeMetadata[CONSENSUS][BLOCK_NUM] + const viewId = nodeMetadata[CONSENSUS][VIEW_ID] + const viewChange = viewId - blockHeight + setItems(items => [...items, { + viewChange: viewChange, + viewId: viewId, + blockHeight: blockHeight, + }]) + setTotalViewChange(totalViewChange => totalViewChange + viewChange) + } + } catch (e) { + console.error('Error on loading metrics:', e) + setLoadingError('Loading error') + } finally { + setIsLoading(false) + } + } + loadData() + }, []) + + const viewChangeProps = { + title: 'Harmony View Change Statistics', + description: '', + items: items, + keys: ['viewChange', 'viewId', 'blockHeight'], + isLoading, + loadingError, + infoLeft: `Total of ${totalViewChange} view changes across ${availableShards.length} shards`, + infoRight: 'Data is obtained from the explorer node, which may result in a slight delay' + } + + return +} diff --git a/src/pages/ChartsPage/index.tsx b/src/pages/ChartsPage/index.tsx index 5ed61bc..2201cfe 100644 --- a/src/pages/ChartsPage/index.tsx +++ b/src/pages/ChartsPage/index.tsx @@ -8,12 +8,14 @@ import {AverageFee} from "./AverageFee"; import {AverageBlockSize} from "./AverageBlockSize"; import styled from "styled-components"; import {useThemeMode} from "../../hooks/themeSwitcherHook"; +import { ViewChangeStats } from "./ViewChangeStats"; enum ChartType { tx = 'tx', addresses = 'addresses', fee = 'fee', - blockSize = 'blocksize' + blockSize = 'blocksize', + viewChange = 'viewchange' } const PreviewContainer = styled(Box)` @@ -46,6 +48,7 @@ const PreviewCard = (props: { type: ChartType, title: string }) => { return { {title} - - {type === ChartType.tx && } - {type === ChartType.addresses && } - {type === ChartType.fee && } - {type === ChartType.blockSize && } + + {type === ChartType.tx && } + {type === ChartType.addresses && } + {type === ChartType.fee && } + {type === ChartType.blockSize && } + {type === ChartType.viewChange && } } @@ -74,6 +78,8 @@ export function ChartsPage() { return } else if(route === ChartType.blockSize) { return + } else if(route === ChartType.viewChange) { + return } return ( @@ -89,13 +95,14 @@ export function ChartsPage() { wrap direction={'row'} pad={"small"} - justify={'center'} + justify={'start'} align={'center'} > + diff --git a/src/pages/ChartsPage/thumbnails/view_change.png b/src/pages/ChartsPage/thumbnails/view_change.png new file mode 100644 index 0000000..509fcc8 Binary files /dev/null and b/src/pages/ChartsPage/thumbnails/view_change.png differ diff --git a/src/pages/ChartsPage/thumbnails/view_change_dark.png b/src/pages/ChartsPage/thumbnails/view_change_dark.png new file mode 100644 index 0000000..82da7ff Binary files /dev/null and b/src/pages/ChartsPage/thumbnails/view_change_dark.png differ