From 4692b1083cba611be61c94bab958c2800ead58b0 Mon Sep 17 00:00:00 2001 From: Dmitriy Sheleg Date: Wed, 14 Apr 2021 14:47:17 +0300 Subject: [PATCH 01/12] feat(fronted): Implementation of new design on Nodes page --- backend/src/index.js | 7 +- backend/src/near.js | 5 +- backend/src/wamp.js | 4 + frontend/package-lock.json | 17 + frontend/package.json | 2 + .../images/icon-arrow-pagination-left.svg | 1 + .../public/static/images/icon-maximize.svg | 1 + .../public/static/images/icon-minimize.svg | 1 + .../components/nodes/CumuativeStakeChart.tsx | 50 ++ frontend/src/components/nodes/NodeNav.tsx | 87 +++- frontend/src/components/nodes/NodeRow.tsx | 336 +++++++++---- frontend/src/components/nodes/Nodes.tsx | 52 +- frontend/src/components/nodes/NodesCard.tsx | 128 +++++ .../components/nodes/NodesContentHeader.tsx | 38 ++ frontend/src/components/nodes/NodesEpoch.tsx | 90 ++++ frontend/src/components/nodes/PoposalList.tsx | 54 ++ frontend/src/components/nodes/ProposalRow.tsx | 120 +++-- frontend/src/components/nodes/Proposals.tsx | 85 +++- .../src/components/nodes/ValidatingLabel.tsx | 55 ++ .../src/components/nodes/ValidatorRow.tsx | 468 ++++++++++++------ frontend/src/components/nodes/Validators.tsx | 89 +++- .../src/components/nodes/ValidatorsList.tsx | 61 +++ frontend/src/components/utils/Content.tsx | 92 ++-- frontend/src/components/utils/Pagination.tsx | 111 +++++ frontend/src/components/utils/ProgressBar.tsx | 57 +++ frontend/src/components/utils/Table.tsx | 142 ++++++ frontend/src/context/NodeStatsProvider.tsx | 10 +- frontend/src/pages/nodes/online-nodes.jsx | 36 +- frontend/src/pages/nodes/proposals.jsx | 36 +- frontend/src/pages/nodes/validators.jsx | 37 +- 30 files changed, 1831 insertions(+), 441 deletions(-) create mode 100644 frontend/public/static/images/icon-arrow-pagination-left.svg create mode 100644 frontend/public/static/images/icon-maximize.svg create mode 100644 frontend/public/static/images/icon-minimize.svg create mode 100644 frontend/src/components/nodes/CumuativeStakeChart.tsx create mode 100644 frontend/src/components/nodes/NodesCard.tsx create mode 100644 frontend/src/components/nodes/NodesContentHeader.tsx create mode 100644 frontend/src/components/nodes/NodesEpoch.tsx create mode 100644 frontend/src/components/nodes/PoposalList.tsx create mode 100644 frontend/src/components/nodes/ValidatingLabel.tsx create mode 100644 frontend/src/components/nodes/ValidatorsList.tsx create mode 100644 frontend/src/components/utils/Pagination.tsx create mode 100644 frontend/src/components/utils/ProgressBar.tsx create mode 100644 frontend/src/components/utils/Table.tsx diff --git a/backend/src/index.js b/backend/src/index.js index d6111cb87..88b43de71 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -255,7 +255,11 @@ async function main() { console.log("Starting regular node status check..."); try { if (wamp.session) { - let { currentValidators, proposals } = await queryNodeStats(); + let { + currentValidators, + proposals, + seatPrice, + } = await queryNodeStats(); let validators = await addNodeInfo(currentValidators); let onlineValidatingNodes = pickOnlineValidatingNode(validators); let onlineNodes = await queryOnlineNodes(); @@ -271,6 +275,7 @@ async function main() { "node-stats", { validatorAmount: validators.length, + seatPriceAmount: seatPrice, onlineNodeAmount: onlineNodes.length, proposalAmount: proposals.length, }, diff --git a/backend/src/near.js b/backend/src/near.js index 4da0108c8..56308f557 100644 --- a/backend/src/near.js +++ b/backend/src/near.js @@ -18,8 +18,11 @@ const queryFinalTimestamp = async () => { const queryNodeStats = async () => { let nodes = await nearRpc.sendJsonRpc("validators", [null]); let proposals = nodes.current_proposals; + let seatPrice = nearApi.validators + .findSeatPrice(nodes.current_validators, nodes.numSeats) + .toString(); let currentValidators = getCurrentNodes(nodes); - return { currentValidators, proposals }; + return { currentValidators, proposals, seatPrice }; }; const signNewValidators = (newValidators) => { diff --git a/backend/src/wamp.js b/backend/src/wamp.js index 012e09c35..05816d3bc 100644 --- a/backend/src/wamp.js +++ b/backend/src/wamp.js @@ -165,6 +165,10 @@ wampHandlers["get-account-details"] = async ([accountId]) => { .callViewMethod(lockupAccountId, "get_locked_amount", {}) .then((balance) => new BN(balance)) .catch(ignore_if_does_not_exist), + nearRpc + .callViewMethod(lockupAccountId, "get_reward_fee_fraction", {}) + .then((fee) => fee) + .catch(ignore_if_does_not_exist), nearRpc .callViewMethod(lockupAccountId, "get_staking_pool_account_id", {}) .catch(ignore_if_does_not_exist), diff --git a/frontend/package-lock.json b/frontend/package-lock.json index edcb93750..03d514871 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11522,6 +11522,15 @@ "strip-json-comments": "~2.0.1" } }, + "rc-progress": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.1.3.tgz", + "integrity": "sha512-Jl4fzbBExHYMoC6HBPzel0a9VmhcSXx24LVt/mdhDM90MuzoMCJjXZAlhA0V0CJi+SKjMhfBoIQ6Lla1nD4QNw==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6" + } + }, "react": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", @@ -11643,6 +11652,14 @@ } } }, + "react-paginate": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-7.1.2.tgz", + "integrity": "sha512-yAl7taPNIUegS5b6kdNvEMh7iu9T3spuzOCUw+BB0ScTLaC4dfSl686Kl+rVCvkOr67qcbi6T8+tpo4hEVCC1Q==", + "requires": { + "prop-types": "^15.6.1" + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4d62c8205..7b2a66b0f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,6 +68,7 @@ "moment": "^2.29.1", "near-api-js": "^0.30.0", "next": "^10.0.7", + "rc-progress": "^3.1.3", "react": "^16.14.0", "react-bootstrap": "^1.5.0", "react-countdown": "^2.3.1", @@ -76,6 +77,7 @@ "react-flip-move": "^3.0.3", "react-infinite-scroll-component": "^5.1.0", "react-motion": "^0.5.2", + "react-paginate": "^7.1.2", "react-text-collapse": "^0.5.2", "styled-components": "^4.4.0" }, diff --git a/frontend/public/static/images/icon-arrow-pagination-left.svg b/frontend/public/static/images/icon-arrow-pagination-left.svg new file mode 100644 index 000000000..5fdb0132e --- /dev/null +++ b/frontend/public/static/images/icon-arrow-pagination-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/static/images/icon-maximize.svg b/frontend/public/static/images/icon-maximize.svg new file mode 100644 index 000000000..f90d12e9b --- /dev/null +++ b/frontend/public/static/images/icon-maximize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/static/images/icon-minimize.svg b/frontend/public/static/images/icon-minimize.svg new file mode 100644 index 000000000..b168a90ba --- /dev/null +++ b/frontend/public/static/images/icon-minimize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/nodes/CumuativeStakeChart.tsx b/frontend/src/components/nodes/CumuativeStakeChart.tsx new file mode 100644 index 000000000..bd31969c9 --- /dev/null +++ b/frontend/src/components/nodes/CumuativeStakeChart.tsx @@ -0,0 +1,50 @@ +interface Props { + value: CummulativeStake; +} + +interface CummulativeStake { + total: number; + current: number; +} + +const CumulativeStakeChart = ({ value }: Props) => ( +
+
+
+
{value.current}%
+ +
+); + +export default CumulativeStakeChart; diff --git a/frontend/src/components/nodes/NodeNav.tsx b/frontend/src/components/nodes/NodeNav.tsx index c7b5fdde1..441393c1f 100644 --- a/frontend/src/components/nodes/NodeNav.tsx +++ b/frontend/src/components/nodes/NodeNav.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Row, Col } from "react-bootstrap"; +import { Badge, Col, Row } from "react-bootstrap"; import { NodeStatsConsumer } from "../../context/NodeStatsProvider"; @@ -16,82 +16,115 @@ class NodeNav extends React.PureComponent { {(context) => ( <> - + - {`${ - typeof context.validatorAmount !== "undefined" + Validating{" "} + + {typeof context.validatorAmount !== "undefined" ? context.validatorAmount - : "-" - } Validating & New Upcoming`} + : "--"} + - {`${ - typeof context.onlineNodeAmount !== "undefined" + Online{" "} + + {typeof context.onlineNodeAmount !== "undefined" ? context.onlineNodeAmount - : "-" - } Online-nodes`} + : "--"} + - {`${ - typeof context.proposalAmount !== "undefined" + Proposed{" "} + + {typeof context.proposalAmount !== "undefined" ? context.proposalAmount - : "-" - } Proposal-nodes`} + : "--"} + - + {/* Nodes Map - + */} - + )} ); diff --git a/frontend/src/components/nodes/Nodes.tsx b/frontend/src/components/nodes/Nodes.tsx index f663e495b..8bd5186fe 100644 --- a/frontend/src/components/nodes/Nodes.tsx +++ b/frontend/src/components/nodes/Nodes.tsx @@ -3,19 +3,65 @@ import React from "react"; import * as N from "../../libraries/explorer-wamp/nodes"; import { NodeConsumer } from "../../context/NodeProvider"; +import { onPageChange as P } from "../utils/Pagination"; +import { Table } from "../utils/Table"; + import NodeRow from "./NodeRow"; import PaginationSpinner from "../utils/PaginationSpinner"; +const itemsPerPage = 10; class Nodes extends React.Component { + state = { + activePage: 0, + startPage: 1, + endPage: itemsPerPage, + }; + + onPageChange = ({ selected }: P) => { + this.setState({ + activePage: selected, + startPage: selected * itemsPerPage + 1, + endPage: selected * itemsPerPage + itemsPerPage, + }); + }; render() { + const { activePage, startPage, endPage } = this.state; return ( {(context) => ( <> {context.onlineNodes ? ( - context.onlineNodes.map((node: N.NodeInfo) => ( - - )) + + + + + + + + + {context.onlineNodes + .slice(startPage - 1, endPage) + .map((node: N.NodeInfo, index: number) => ( + + ))} + +
+ #Validator
) : (
@@ -229,13 +231,15 @@ exports[` renders with account created at genesis (legacy) 1`] className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0 - Ⓝ + + Ⓝ @@ -519,13 +523,15 @@ exports[` renders with account created at genesis 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0.00065 - Ⓝ + + Ⓝ @@ -563,13 +569,15 @@ exports[` renders with account created at genesis 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0 - Ⓝ + + Ⓝ @@ -853,13 +861,15 @@ exports[` renders with account deletion at 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0.00065 - Ⓝ + + Ⓝ @@ -897,13 +907,15 @@ exports[` renders with account deletion at 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0 - Ⓝ + + Ⓝ @@ -1238,13 +1250,15 @@ exports[` renders with lockup account 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0.00065 - Ⓝ + + Ⓝ @@ -1282,13 +1296,15 @@ exports[` renders with lockup account 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0 - Ⓝ + + Ⓝ @@ -1572,13 +1588,15 @@ exports[` renders without lockup account 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > <0.00001 - Ⓝ + + Ⓝ @@ -1616,13 +1634,15 @@ exports[` renders without lockup account 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0 - Ⓝ + + Ⓝ diff --git a/frontend/src/components/nodes/CumuativeStakeChart.tsx b/frontend/src/components/nodes/CumulativeStakeChart.tsx similarity index 100% rename from frontend/src/components/nodes/CumuativeStakeChart.tsx rename to frontend/src/components/nodes/CumulativeStakeChart.tsx diff --git a/frontend/src/components/nodes/NodesEpoch.tsx b/frontend/src/components/nodes/NodesEpoch.tsx index c6b8a62a1..970899937 100644 --- a/frontend/src/components/nodes/NodesEpoch.tsx +++ b/frontend/src/components/nodes/NodesEpoch.tsx @@ -17,17 +17,27 @@ interface State { } class NodesEpoch extends React.PureComponent { - state = { - timeRemaining: undefined, - epochProseed: 0, - }; + constructor(props: any) { + super(props); + this.timer = null; + this.state = { + timeRemaining: undefined, + epochProseed: 0, + }; + } + + timer: ReturnType | null; componentDidMount() { this.timer = setInterval(() => this.epochDuration(), 1000); } componentWillUnmount() { - clearInterval(this.timer); + const timer = this.timer; + this.timer = null; + if (timer !== null) { + clearTimeout(timer); + } } epochDuration = () => { diff --git a/frontend/src/components/nodes/ValidatorRow.tsx b/frontend/src/components/nodes/ValidatorRow.tsx index 33ebac3e9..7b313443a 100644 --- a/frontend/src/components/nodes/ValidatorRow.tsx +++ b/frontend/src/components/nodes/ValidatorRow.tsx @@ -12,7 +12,7 @@ import Term from "../utils/Term"; import Timer from "../utils/Timer"; import TransactionLink from "../utils/TransactionLink"; import ValidatingLabel from "./ValidatingLabel"; -import CumulativeStakeChart from "./CumuativeStakeChart"; +import CumulativeStakeChart from "./CumulativeStakeChart"; interface Props { node: N.Validating; diff --git a/frontend/src/components/nodes/Validators.tsx b/frontend/src/components/nodes/Validators.tsx index be4ec5991..80dd81151 100644 --- a/frontend/src/components/nodes/Validators.tsx +++ b/frontend/src/components/nodes/Validators.tsx @@ -4,9 +4,9 @@ import { NodeConsumer } from "../../context/NodeProvider"; import { onPageChange as P } from "../utils/Pagination"; import { Table } from "../utils/Table"; +import PaginationSpinner from "../utils/PaginationSpinner"; import ValidatorsList from "./ValidatorsList"; -import PaginationSpinner from "../utils/PaginationSpinner"; interface Props { type: string; diff --git a/frontend/src/components/nodes/__test__/NodeRow.test.tsx b/frontend/src/components/nodes/__test__/NodeRow.test.tsx index b4e59a6c5..e4217bb08 100644 --- a/frontend/src/components/nodes/__test__/NodeRow.test.tsx +++ b/frontend/src/components/nodes/__test__/NodeRow.test.tsx @@ -21,6 +21,7 @@ describe("", () => { longitude: -90.7854, city: "", }} + index={1} /> ) ).toMatchSnapshot(); diff --git a/frontend/src/components/nodes/__test__/NodesEpoch.test.tsx b/frontend/src/components/nodes/__test__/NodesEpoch.test.tsx new file mode 100644 index 000000000..db21cf109 --- /dev/null +++ b/frontend/src/components/nodes/__test__/NodesEpoch.test.tsx @@ -0,0 +1,25 @@ +import renderer from "react-test-renderer"; + +import NodesEpoch from "../NodesEpoch"; + +describe("", () => { + it("renders", () => { + expect( + renderer.create( + + ) + ).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx b/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx index 63080d1e7..99a5e8045 100644 --- a/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx +++ b/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx @@ -3,10 +3,11 @@ import renderer from "react-test-renderer"; import ValidatorRow from "../ValidatorRow"; describe("", () => { - it("renders", () => { + it("renders row on Validators tab", () => { expect( renderer.create( ", () => { public_key: "ed25519:935JMz1vLcJxFApG3TY4MA4RHhvResvoGwCrQoJxHPn9", shards: [0], stake: "91037770393145811562101780866", + fee: { numerator: 10, denominator: 100 }, + delegators: 11, }} + index={1} + cellCount={7} + validatorType="validators" + /> + ) + ).toMatchSnapshot(); + }); + + it("renders row on Proposals tab", () => { + expect( + renderer.create( + ) ).toMatchSnapshot(); diff --git a/frontend/src/components/nodes/__test__/ValidatorsList.test.tsx b/frontend/src/components/nodes/__test__/ValidatorsList.test.tsx new file mode 100644 index 000000000..1914966ce --- /dev/null +++ b/frontend/src/components/nodes/__test__/ValidatorsList.test.tsx @@ -0,0 +1,124 @@ +import renderer from "react-test-renderer"; + +import ValidatorsList from "../ValidatorsList"; + +describe("", () => { + it("renders validators list", () => { + expect( + renderer.create( + + ) + ).toMatchSnapshot(); + }); + + it("renders proposals list", () => { + expect( + renderer.create( + + ) + ).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/components/nodes/__test__/__snapshots__/NodeRow.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/NodeRow.test.tsx.snap index 7d10949c1..013f3e5ed 100644 --- a/frontend/src/components/nodes/__test__/__snapshots__/NodeRow.test.tsx.snap +++ b/frontend/src/components/nodes/__test__/__snapshots__/NodeRow.test.tsx.snap @@ -1,117 +1,99 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` renders 1`] = ` -
-
-
-
+ + 1 + +
- @ - alice - - + Online
-
-
- - near-rs | ver.0.4.5 build 6a527109-modified + alice + ...
+
+
- - 1889 +
-
-
-
-
-
-
- fhgjrkfhurjt - ... -
-
-
-
- - Last seen - - Β Β  - - in 1y - + +
-
- + + `; diff --git a/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap new file mode 100644 index 000000000..15a396101 --- /dev/null +++ b/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap @@ -0,0 +1,180 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders 1`] = ` +
+
+
+
+
+
+ Current Epoch Start: + + + Block # + 36647454 + +
+
+
+
+ Current Epoch Start +
+
+ + Block # + 36647454 + +
+
+
+
+ + 0 + % complete + + + ( + 00:00:00 + + remaining) +
+
+
+ + 0 + % + + } + style={Object {}} + type="circle" + viewBox="0 0 100 100" + > + + + +
+ + 0 + % + +
+
+
+
+
+
+
+ + + + +
+
+
+`; diff --git a/frontend/src/components/nodes/__test__/__snapshots__/ProposalRow.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/ProposalRow.test.tsx.snap deleted file mode 100644 index 4c05d382b..000000000 --- a/frontend/src/components/nodes/__test__/__snapshots__/ProposalRow.test.tsx.snap +++ /dev/null @@ -1,78 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` renders 1`] = ` -
-
-

- -

-
-
-
-
-

- @ - dokiacapital.pool.6fb1358 - - - Staking - - 91,037.77039 - Ⓝ - - -

-
-
-
-
-
-
- 935JMz1vLcJx - ... -
-
-
-
-`; diff --git a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap index 255908473..f6b52c859 100644 --- a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap +++ b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap @@ -1,98 +1,200 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders 1`] = ` -
renders row on Proposals tab 1`] = ` + -
-
-
+ + 1 + +
- @ - dokiacapital.pool.6fb1358 - - Staking - - 91,037.77039 - Ⓝ - + Pending - +
+
- - +
+ magic.poolv1.near + ... +
+
+ + + 100% + + + 121 + + + + 10,831,273.77981 + + NEAR + + + +`; + +exports[` renders row on Validators tab 1`] = ` + + + + + + 1 + +
+ + Active + +
+
- -
-
- - 1768/1771 (99.831)% + dokiacapital.pool.6f + ...
-
-
+ + + 10% + + + 11 + + + + 91,037.77039 + + NEAR + + + +
+
+
+
+ 0 + % +
+
+ + `; diff --git a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap new file mode 100644 index 000000000..568645e1a --- /dev/null +++ b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap @@ -0,0 +1,540 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders proposals list 1`] = ` +Array [ + + + + + + 25 + + +
+
+ + Pending + +
+
+
+
+ astro-stakers.poolv1 + ... +
+
+ +
+
+ + + 1% + + + 1304 + + + + 19,378,033.39859 + + NEAR + + + , + + + + + + 26 + + +
+
+ + Pending + +
+
+
+
+ chorusone.poolv1.nea + ... +
+
+
+
+ + + 8% + + + 125 + + + + 6,174,390.41291 + + NEAR + + + , +] +`; + +exports[` renders validators list 1`] = ` +Array [ + + + + + + 25 + + +
+
+ + Active + +
+
+
+
+ astro-stakers.poolv1 + ... +
+
+ +
+
+ + + 1% + + + 1304 + + + + 19,407,279.888 + + NEAR + + + +
+
+
+
+ 71.5 + % +
+
+ + , + + + + + + 26 + + +
+
+ + Active + +
+
+
+
+ baziliknear.poolv1.n + ... +
+
+
+
+ + + 3% + + + 135 + + + + 4,035,443.45331 + + NEAR + + + +
+
+
+
+ 86.37 + % +
+
+ + , + + + + + + 27 + + +
+
+ + Active + +
+
+
+
+ audit_one.poolv1.nea + ... +
+
+
+
+ + + 7% + + + 27 + + + + 3,698,682.66061 + + NEAR + + + +
+
+
+
+ 100 + % +
+
+ + , +] +`; diff --git a/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap b/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap index 04c66da06..de03d447e 100644 --- a/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap +++ b/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap @@ -214,13 +214,15 @@ exports[` renders Stake 1`] = ` Array [ "Staked: ", <0.00001 - Ⓝ + + Ⓝ , " ", "with ed25519:BgXFiJS...", @@ -231,13 +233,15 @@ exports[` renders Transfer 1`] = ` Array [ "Transferred ", <0.00001 - Ⓝ + + Ⓝ , " to ", renders Failure receipt 1`] = ` className="receipt-row-receipt-hash col" > 0.00034 - Ⓝ + + Ⓝ
@@ -387,13 +389,15 @@ exports[` renders Failure receipt 1`] = ` className="receipt-row-receipt-hash col" > 0 - Ⓝ + + Ⓝ
@@ -440,13 +444,15 @@ exports[` renders Failure receipt 1`] = ` > Transferred 0.00353 - Ⓝ + + Ⓝ to renders receipt with many outcome receipts 1`] = ` className="receipt-row-receipt-hash col" > 0.00209 - Ⓝ + + Ⓝ
@@ -872,13 +880,15 @@ Memo: Swapping 9807229473212706028 🍌 to get 9807229473212706028 πŸ₯’ className="receipt-row-receipt-hash col" > 0.00041 - Ⓝ + + Ⓝ
@@ -1155,13 +1165,15 @@ Memo: Swapping 9807229473212706028 🍌 to get 9807229473212706028 πŸ₯’ className="receipt-row-receipt-hash col" > 0 - Ⓝ + + Ⓝ
@@ -1208,13 +1220,15 @@ Memo: Swapping 9807229473212706028 🍌 to get 9807229473212706028 πŸ₯’ > Transferred 0.00269 - Ⓝ + + Ⓝ to 0.00035 - Ⓝ + + Ⓝ @@ -1666,13 +1682,15 @@ Memo: Swapping 9807229473212706028 🍌 to get 9807229473212706028 πŸ₯’ className="receipt-row-receipt-hash col" > 0 - Ⓝ + + Ⓝ @@ -1719,13 +1737,15 @@ Memo: Swapping 9807229473212706028 🍌 to get 9807229473212706028 πŸ₯’ > Transferred 0.00068 - Ⓝ + + Ⓝ to 0 - Ⓝ + + Ⓝ @@ -1947,13 +1969,15 @@ Memo: Swapping 9807229473212706028 🍌 to get 9807229473212706028 πŸ₯’ > Transferred 0.00104 - Ⓝ + + Ⓝ to renders successful receipt 1`] = ` className="receipt-row-receipt-hash col" > 0.00061 - Ⓝ + + Ⓝ @@ -2405,13 +2431,15 @@ exports[` renders successful receipt 1`] = ` className="receipt-row-receipt-hash col" > 0 - Ⓝ + + Ⓝ @@ -2458,13 +2486,15 @@ exports[` renders successful receipt 1`] = ` > Transferred 0.01844 - Ⓝ + + Ⓝ to renders no deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > <0.00001 - Ⓝ + + Ⓝ @@ -246,13 +248,15 @@ exports[` renders no deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0 - Ⓝ + + Ⓝ @@ -668,13 +672,15 @@ exports[` renders with one small deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > <0.00001 - Ⓝ + + Ⓝ @@ -716,13 +722,15 @@ exports[` renders with one small deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > <0.00001 - Ⓝ + + Ⓝ @@ -1138,13 +1146,15 @@ exports[` renders with two big deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > <0.00001 - Ⓝ + + Ⓝ @@ -1186,13 +1196,15 @@ exports[` renders with two big deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > 0.00014 - Ⓝ + + Ⓝ diff --git a/frontend/src/context/NodeStatsProvider.tsx b/frontend/src/context/NodeStatsProvider.tsx index b7f73d30c..858b6568a 100644 --- a/frontend/src/context/NodeStatsProvider.tsx +++ b/frontend/src/context/NodeStatsProvider.tsx @@ -15,7 +15,7 @@ export interface NodeStatsContext { const NodeStatsContext = createContext({}); export interface Props { - children: React.Component; + children: React.Component | React.ReactNode; } const NodeStatsProvider = (props: Props) => { diff --git a/frontend/src/libraries/explorer-wamp/nodes.ts b/frontend/src/libraries/explorer-wamp/nodes.ts index a9d6e9bd2..95b69037e 100644 --- a/frontend/src/libraries/explorer-wamp/nodes.ts +++ b/frontend/src/libraries/explorer-wamp/nodes.ts @@ -24,7 +24,7 @@ export interface Validating { stake: string; new?: boolean; removed?: boolean; - shards: [number]; + shards?: [number]; nodeInfo?: NodeInfo; fee: { numerator: number; denominator: number }; delegators?: number; From d6fa33ddd4a1851ac64f13e0a472c269087cdd33 Mon Sep 17 00:00:00 2001 From: Dmitriy Sheleg Date: Thu, 6 May 2021 22:31:50 +0300 Subject: [PATCH 06/12] Fix tests --- frontend/cypress/integration/nodes-page.spec.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/cypress/integration/nodes-page.spec.js b/frontend/cypress/integration/nodes-page.spec.js index 8c19060ff..6ce8f490d 100644 --- a/frontend/cypress/integration/nodes-page.spec.js +++ b/frontend/cypress/integration/nodes-page.spec.js @@ -18,12 +18,8 @@ context("Nodes", () => { cy.url().should("include", "/nodes/validators"); cy.get(".node-selector").should("have.class", "node-selected"); cy.wait(3000) - .get(".node-row .node-row-title") + .get(".validator-nodes-row td") .within(($el) => cy.get($el).should("exist", $el.text())); - cy.get(".node-row .node-row-text .row div").within(($el) => - cy.get($el).should("exist", $el.text()) - ); - cy.get(".node-row .node-row-txid").should("exist"); }); // it("Check online nodes tab", () => { From 724b2662abebed3c7eec5d453ed822837b1cce62 Mon Sep 17 00:00:00 2001 From: Dmitriy Sheleg Date: Mon, 10 May 2021 18:01:03 +0300 Subject: [PATCH 07/12] Rewrite caching of fee and delegators --- backend/src/index.js | 82 +++++++++++-------- backend/src/near.js | 2 + .../components/nodes/CumulativeStakeChart.tsx | 6 +- frontend/src/components/nodes/NodesCard.tsx | 49 ++++++----- frontend/src/components/nodes/NodesEpoch.tsx | 70 ++++++++-------- .../src/components/nodes/ValidatorRow.tsx | 20 +++-- frontend/src/components/nodes/Validators.tsx | 34 ++------ .../src/components/nodes/ValidatorsList.tsx | 4 +- frontend/src/components/utils/Balance.jsx | 2 +- frontend/src/context/DatabaseProvider.tsx | 13 +-- frontend/src/context/NodeStatsProvider.tsx | 55 +++++++++++-- frontend/src/libraries/explorer-wamp/index.ts | 9 ++ 12 files changed, 203 insertions(+), 143 deletions(-) diff --git a/backend/src/index.js b/backend/src/index.js index c00390ea6..b8a5424de 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -53,9 +53,10 @@ const { aggregateLiveAccountsCountByDate, } = require("./stats"); +let validators = null; +let proposals = null; let validatorsExtraInfo = null; let proposalsExtraInfo = null; -let validatorsExtraInfoTimer = 5000; async function startLegacySync() { console.log("Starting NEAR Explorer legacy syncing service..."); @@ -266,30 +267,18 @@ async function main() { seatPrice, totalStake, epochStartHeight, + epochLength, } = await queryNodeStats(); - let validators = await addNodeInfo(currentValidators); - let proposals = await addNodeInfo(currentProposals); + validators = await addNodeInfo(currentValidators); + proposals = await addNodeInfo(currentProposals); let onlineValidatingNodes = pickOnlineValidatingNode(validators); let onlineNodes = await queryOnlineNodes(); - validatorsExtraInfoTimer = - validatorsExtraInfoTimer !== 0 - ? validatorsExtraInfoTimer - regularCheckNodeStatusInterval - : validatorsExtraInfoTimer + - regularCheckNodeValidatorsExtraInfo - - regularCheckNodeStatusInterval; - - if (validatorsExtraInfoTimer === 0) { - validatorsExtraInfo = await regularCheckValidatorsExtraInfo( - validators - ); - proposalsExtraInfo = await regularCheckValidatorsExtraInfo(proposals); - } - - if (validatorsExtraInfo?.length > 0) { + if (validatorsExtraInfo) { validators = validatorsExtraInfo; } - if (proposalsExtraInfo?.length > 0) { + + if (proposalsExtraInfo) { proposals = proposalsExtraInfo; } @@ -307,6 +296,7 @@ async function main() { proposalAmount: proposals.length, totalStakeAmount: totalStake, epochStartHeight, + epochLength, }, wamp ); @@ -320,22 +310,48 @@ async function main() { setTimeout(regularCheckNodeStatus, 0); // Periodic check of validator's fee and delegators - const regularCheckValidatorsExtraInfo = async (validators) => { - for (let i = 0; i < validators.length; i++) { - const { account_id } = validators[i]; - validators[i].fee = await nearRpc.callViewMethod( - account_id, - "get_reward_fee_fraction", - {} - ); - validators[i].delegators = await nearRpc.callViewMethod( - account_id, - "get_number_of_accounts", - {} - ); + const regularCheckValidatorsExtraInfo = async () => { + if (validators) { + validatorsExtraInfo = validatorsExtraInfo || validators; + + for (let i = 0; i < validatorsExtraInfo.length; i++) { + const { account_id } = validatorsExtraInfo[i]; + validatorsExtraInfo[i].fee = await nearRpc.callViewMethod( + account_id, + "get_reward_fee_fraction", + {} + ); + validatorsExtraInfo[i].delegators = await nearRpc.callViewMethod( + account_id, + "get_number_of_accounts", + {} + ); + } } - return validators; + + if (proposals) { + proposalsExtraInfo = proposalsExtraInfo || proposals; + + for (let i = 0; i < proposalsExtraInfo.length; i++) { + const { account_id } = proposalsExtraInfo[i]; + proposalsExtraInfo[i].fee = await nearRpc.callViewMethod( + account_id, + "get_reward_fee_fraction", + {} + ); + proposalsExtraInfo[i].delegators = await nearRpc.callViewMethod( + account_id, + "get_number_of_accounts", + {} + ); + } + } + setTimeout( + regularCheckValidatorsExtraInfo, + regularCheckNodeValidatorsExtraInfo + ); }; + setTimeout(regularCheckValidatorsExtraInfo, 0); if (isLegacySyncBackendEnabled) { await startLegacySync(); diff --git a/backend/src/near.js b/backend/src/near.js index 949123a02..02cdbd586 100644 --- a/backend/src/near.js +++ b/backend/src/near.js @@ -35,6 +35,7 @@ const queryNodeStats = async () => { let currentProposals = epochStatus.current_proposals; let currentValidators = getCurrentNodes(epochStatus); let { epoch_start_height: epochStartHeight } = epochStatus; + let { epoch_length: epochLength } = networkProtocolConfig; if (currentEpochStartHeight !== epochStartHeight) { // Update seat_price and total_stake each time when epoch starts @@ -54,6 +55,7 @@ const queryNodeStats = async () => { seatPrice, totalStake, epochStartHeight, + epochLength, }; }; diff --git a/frontend/src/components/nodes/CumulativeStakeChart.tsx b/frontend/src/components/nodes/CumulativeStakeChart.tsx index 775b29d49..387e6a4e6 100644 --- a/frontend/src/components/nodes/CumulativeStakeChart.tsx +++ b/frontend/src/components/nodes/CumulativeStakeChart.tsx @@ -10,7 +10,10 @@ interface CumulativeStake { const CumulativeStakeChart = ({ value }: Props) => (
-
+
{value.current}%
)} - + ); export default DashboardNode; diff --git a/frontend/src/components/nodes/NodeNav.tsx b/frontend/src/components/nodes/NodeNav.tsx index ed10c7b21..5151a77d4 100644 --- a/frontend/src/components/nodes/NodeNav.tsx +++ b/frontend/src/components/nodes/NodeNav.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Badge, Col, Row } from "react-bootstrap"; -import { NodeStatsConsumer } from "../../context/NodeStatsProvider"; +import { NetworkStatsConsumer } from "../../context/NetworkStatsProvider"; import Link from "../utils/Link"; @@ -13,7 +13,7 @@ class NodeNav extends React.PureComponent { render() { const { role } = this.props; return ( - + {(context) => ( <> @@ -27,8 +27,8 @@ class NodeNav extends React.PureComponent { Validating{" "} - {typeof context.validatorAmount !== "undefined" - ? context.validatorAmount + {context.networkStats + ? context.networkStats.currentValidatorsCount : "--"} @@ -61,8 +61,8 @@ class NodeNav extends React.PureComponent { Proposed{" "} - {typeof context.proposalAmount !== "undefined" - ? context.proposalAmount + {context.networkStats + ? context.networkStats.currentProposalsCount : "--"} @@ -133,7 +133,7 @@ class NodeNav extends React.PureComponent { `} )} - + ); } } diff --git a/frontend/src/components/nodes/NodesCard.tsx b/frontend/src/components/nodes/NodesCard.tsx index 25b24528b..e258200bb 100644 --- a/frontend/src/components/nodes/NodesCard.tsx +++ b/frontend/src/components/nodes/NodesCard.tsx @@ -1,3 +1,5 @@ +import BN from "bn.js"; + import React from "react"; import { Badge, @@ -8,8 +10,10 @@ import { Spinner, } from "react-bootstrap"; -import { NodeStatsConsumer } from "../../context/NodeStatsProvider"; -import { showInYocto } from "../utils/Balance"; +import { utils } from "near-api-js"; + +import { NetworkStatsConsumer } from "../../context/NetworkStatsProvider"; +import { showInYocto, formatWithCommas } from "../utils/Balance"; const NearBadge = () => ( @@ -27,15 +31,30 @@ const NearBadge = () => ( ); -const NodeBalance = ({ amount, type }: any) => { +const NodeBalance = ({ + amount, + type, +}: { + amount: BN; + type: "totalSupply" | "totalStakeAmount" | "seatPriceAmount"; +}) => { if (!amount) return null; let value; + let suffix; if (type === "totalSupply") { - value = (Number(amount) / 10 ** (24 + 6)).toFixed(1); + value = formatWithCommas( + (amount.div(utils.format.NEAR_NOMINATION).toNumber() / 10 ** 6).toFixed(1) + ); + suffix = "M"; } else if (type === "totalStakeAmount") { - value = (Number(amount) / 10 ** (24 + 3)).toFixed(1); + value = formatWithCommas( + (amount.div(utils.format.NEAR_NOMINATION).toNumber() / 10 ** 6).toFixed(1) + ); + suffix = "M"; } else if (type === "seatPriceAmount") { - value = (Number(amount) / 10 ** 24).toFixed(1); + value = formatWithCommas( + amount.div(utils.format.NEAR_NOMINATION).toNumber().toFixed(0) + ); } else { value = amount; } @@ -47,7 +66,9 @@ const NodeBalance = ({ amount, type }: any) => { overlay={{amountPrecise}} > - {value} + {value} + {suffix && {suffix}}{" "} + ); @@ -56,7 +77,7 @@ const NodeBalance = ({ amount, type }: any) => { class NodesCard extends React.PureComponent { render() { return ( - + {(context) => ( <> @@ -66,8 +87,8 @@ class NodesCard extends React.PureComponent { Nodes validating - {typeof context.validatorAmount !== "undefined" ? ( - context.validatorAmount + {context.networkStats ? ( + context.networkStats.currentValidatorsCount ) : ( )} @@ -81,8 +102,7 @@ class NodesCard extends React.PureComponent { Total Supply - {" "} - {context.epochStartBlock?.totalSupply ? ( + {context.epochStartBlock ? ( - {context.totalStakeAmount ? ( + {context.networkStats ? ( ) : ( @@ -118,9 +138,9 @@ class NodesCard extends React.PureComponent { Seat Price - {context.seatPriceAmount ? ( + {context.networkStats ? ( ) : ( @@ -161,6 +181,12 @@ class NodesCard extends React.PureComponent { align-items: center; } + .node-balance-suffix { + font-size: 25px; + line-height: 35px; + align-self: flex-end; + } + .nodes-card-badge { margin-left: 10px; } @@ -176,6 +202,10 @@ class NodesCard extends React.PureComponent { .nodes-card-text { font-size: 20px; } + .node-balance-suffix { + font-size: 14px; + line-height: 22px; + } } @media (max-width: 355px) { .nodes-card-text { @@ -185,7 +215,7 @@ class NodesCard extends React.PureComponent { `} )} - + ); } } diff --git a/frontend/src/components/nodes/NodesContentHeader.tsx b/frontend/src/components/nodes/NodesContentHeader.tsx index 553405f98..ac4918219 100644 --- a/frontend/src/components/nodes/NodesContentHeader.tsx +++ b/frontend/src/components/nodes/NodesContentHeader.tsx @@ -2,7 +2,6 @@ import React from "react"; import { Col, Row } from "react-bootstrap"; import NodeNav from "./NodeNav"; -import NodeStatsProvider from "../../context/NodeStatsProvider"; interface Props { navRole: string; @@ -26,9 +25,7 @@ class NodesContentHeader extends React.PureComponent {

Nodes

- - - + ); diff --git a/frontend/src/components/nodes/NodesEpoch.tsx b/frontend/src/components/nodes/NodesEpoch.tsx index d35365787..8c12634d7 100644 --- a/frontend/src/components/nodes/NodesEpoch.tsx +++ b/frontend/src/components/nodes/NodesEpoch.tsx @@ -1,80 +1,33 @@ -import BN from "bn.js"; import React from "react"; import moment from "moment"; import { Row, Col } from "react-bootstrap"; -import { LatestBlockInfo } from "../../context/NodeStatsProvider"; - import ProgressBar from "../utils/ProgressBar"; -import { BlockInfo } from "../../libraries/explorer-wamp/blocks"; interface Props { - epochStartHeight: number; - epochStartBlock?: BlockInfo; - latestBlock?: LatestBlockInfo; epochLength: number; + epochStartHeight: number; + latestBlockHeight: number; + epochStartTimestamp: number; + latestBlockTimestamp: number; } -interface State { - timeRemaining?: number; - epochProgress: number; -} - -class NodesEpoch extends React.PureComponent { - constructor(props: any) { - super(props); - this.timer = null; - this.state = { - timeRemaining: undefined, - epochProgress: 0, - }; - } - - timer: ReturnType | null; - - componentDidMount() { - this.timer = setInterval(() => this.epochDuration(), 1000); - } - - componentWillUnmount() { - const timer = this.timer; - this.timer = null; - if (timer !== null) { - clearTimeout(timer); - } - } - - epochDuration = () => { - if ( - this.props.epochStartBlock?.timestamp && - this.props.latestBlock?.height - ) { - const { epochStartBlock, latestBlock, epochLength } = this.props; - - const epochProgress = latestBlock?.height - ? ((latestBlock?.height.toNumber() - epochStartBlock.height) / - epochLength) * - 100 - : 0; - const timeRemaining = latestBlock?.timestamp - ? (latestBlock?.timestamp - .sub(new BN(epochStartBlock.timestamp).muln(10 ** 6)) - .divn(10 ** 6) - .toNumber() / - epochProgress) * - (100 - epochProgress) - : 0; - this.setState({ - timeRemaining, - epochProgress, - }); - } - return null; - }; - +class NodesEpoch extends React.PureComponent { render() { - const { epochProgress, timeRemaining } = this.state; + const { + epochStartHeight, + latestBlockHeight, + epochLength, + epochStartTimestamp, + latestBlockTimestamp, + } = this.props; + + const epochProgress = + ((latestBlockHeight - epochStartHeight) / epochLength) * 100; + const timeRemaining = + ((latestBlockTimestamp - epochStartTimestamp) / epochProgress) * + (100 - epochProgress); return ( @@ -84,18 +37,14 @@ class NodesEpoch extends React.PureComponent { Current Epoch Start:{" "} - - Block #{this.props.epochStartHeight ?? "00000000"} - + Block #{epochStartHeight} Current Epoch Start - - Block #{this.props.epochStartHeight ?? "00000000"} - + Block #{epochStartHeight} @@ -103,12 +52,8 @@ class NodesEpoch extends React.PureComponent { {epochProgress.toFixed(0)}% complete - {" "} - {`(${ - timeRemaining - ? moment(timeRemaining)?.format("HH:mm:ss") - : "00:00:00" - } remaining)`} + + {` (${moment.utc(timeRemaining).format("HH:mm:ss")} remaining)`} diff --git a/frontend/src/components/nodes/ValidatorRow.tsx b/frontend/src/components/nodes/ValidatorRow.tsx index 6438f489d..5495f40a1 100644 --- a/frontend/src/components/nodes/ValidatorRow.tsx +++ b/frontend/src/components/nodes/ValidatorRow.tsx @@ -15,7 +15,7 @@ import ValidatingLabel from "./ValidatingLabel"; import CumulativeStakeChart from "./CumulativeStakeChart"; interface Props { - node: N.Validating; + node: N.ValidationNodeInfo; index: number; cellCount: number; validatorType: string; @@ -39,7 +39,7 @@ class ValidatorRow extends React.PureComponent { let validatorFee = node.fee ? `${((node.fee.numerator / node.fee.denominator) * 100).toFixed(0)}%` : null; - let validatorDelegators = node.delegators ?? null; + let validatorDelegators = node.delegatorsCount ?? null; const nodeDetailsEnable = Boolean( (node.num_produced_blocks && node.num_expected_blocks) || node.nodeInfo ); diff --git a/frontend/src/components/nodes/Validators.tsx b/frontend/src/components/nodes/Validators.tsx index 20dc3f71b..0828de2a8 100644 --- a/frontend/src/components/nodes/Validators.tsx +++ b/frontend/src/components/nodes/Validators.tsx @@ -41,7 +41,9 @@ class Validators extends React.PureComponent { {(context) => { const validatorType = - type === "validators" ? context.validators : context.proposals; + type === "validators" + ? context.currentValidators + : context.currentProposals; return ( <> {validatorType ? ( diff --git a/frontend/src/components/nodes/ValidatorsList.tsx b/frontend/src/components/nodes/ValidatorsList.tsx index 28843c348..7410b4a96 100644 --- a/frontend/src/components/nodes/ValidatorsList.tsx +++ b/frontend/src/components/nodes/ValidatorsList.tsx @@ -41,10 +41,10 @@ class ValidatorsList extends React.PureComponent { ); const validatorsList = validators - .sort((a: N.Validating, b: N.Validating) => + .sort((a: N.ValidationNodeInfo, b: N.ValidationNodeInfo) => new BN(b.stake).sub(new BN(a.stake)) ) - .map((node: N.Validating, index: number) => { + .map((node: N.ValidationNodeInfo, index: number) => { if (validatorType === "validators") { return { ...node, @@ -59,7 +59,7 @@ class ValidatorsList extends React.PureComponent { <> {validatorsList .slice(startPage - 1, endPage) - .map((node: N.Validating, index: number) => ( + .map((node: N.ValidationNodeInfo, index: number) => ( ", () => { expect( renderer.create( ) ).toMatchSnapshot(); diff --git a/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx b/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx index 99a5e8045..b0ded4ec3 100644 --- a/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx +++ b/frontend/src/components/nodes/__test__/ValidatorRow.test.tsx @@ -1,3 +1,5 @@ +import BN from "bn.js"; + import renderer from "react-test-renderer"; import ValidatorRow from "../ValidatorRow"; @@ -16,8 +18,13 @@ describe("", () => { public_key: "ed25519:935JMz1vLcJxFApG3TY4MA4RHhvResvoGwCrQoJxHPn9", shards: [0], stake: "91037770393145811562101780866", + cumulativeStakeAmount: { + total: new BN("91037770393145811562101780866"), + networkHolderIndex: 0, + }, + totalStake: new BN("91037770393145811562101780866"), fee: { numerator: 10, denominator: 100 }, - delegators: 11, + delegatorsCount: 11, }} index={1} cellCount={7} @@ -36,8 +43,13 @@ describe("", () => { account_id: "magic.poolv1.near", public_key: "ed25519:5fwufMXx9CTyLRkz7x3htQa3rFGzFZuz6d43LhCTmKCS", stake: "10831273779812239906416722375682", + cumulativeStakeAmount: { + total: new BN("10831273779812239906416722375682"), + networkHolderIndex: 0, + }, + totalStake: new BN("10831273779812239906416722375682"), fee: { numerator: 100, denominator: 100 }, - delegators: 121, + delegatorsCount: 121, }} index={1} cellCount={6} diff --git a/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap index 15a396101..52b5d9e4d 100644 --- a/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap +++ b/frontend/src/components/nodes/__test__/__snapshots__/NodesEpoch.test.tsx.snap @@ -55,14 +55,10 @@ exports[` renders 1`] = ` - 0 + 50 % complete - - ( - 00:00:00 - - remaining) + (06:00:00 remaining)
renders 1`] = ` - 0 + 50 % } @@ -108,14 +104,14 @@ exports[` renders 1`] = ` a 48,48 0 1 1 0,96 a 48,48 0 1 1 0,-96" fillOpacity="0" - opacity={0} + opacity={1} stroke="" strokeLinecap="round" strokeWidth={4} style={ Object { "stroke": "#37dbf4", - "strokeDasharray": "0px 301.59289474462014px", + "strokeDasharray": "150.79644737231007px 301.59289474462014px", "strokeDashoffset": "-0px", "transition": "stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s", } @@ -128,7 +124,7 @@ exports[` renders 1`] = ` - 0 + 50 %
@@ -167,7 +163,7 @@ exports[` renders 1`] = ` strokeWidth={1} style={ Object { - "strokeDasharray": "0px, 100px", + "strokeDasharray": "49.5px, 100px", "strokeDashoffset": "-0px", "transition": "stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear", } diff --git a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap index f6b52c859..bb5f71547 100644 --- a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap +++ b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorRow.test.tsx.snap @@ -73,7 +73,6 @@ exports[` renders row on Proposals tab 1`] = ` className="jsx-2866537324 text-right validator-nodes-text stake-text" > renders row on Proposals tab 1`] = ` `; exports[` renders row on Validators tab 1`] = ` - - - + - - - 1 - - -
+ + + 1 + +
- - Active - -
-
+ + Active + +
- dokiacapital.pool.6f - ... +
+ dokiacapital.pool.6f + ... +
-
- - - 10% - - - 11 - - - + - 91,037.77039 - - NEAR - - - -
+ + 11 + + + + 91,037.77039 + + NEAR + + +
-
-
- 0 - % +
+
+
+ 100 + % +
-
- - + + , + + + Validators 1 - + + 1 + hold a cumulative stake above 33%. Delegating to the validators below improves the decentralization of the network. + + , +] `; diff --git a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap index 568645e1a..4f8e81c65 100644 --- a/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap +++ b/frontend/src/components/nodes/__test__/__snapshots__/ValidatorsList.test.tsx.snap @@ -85,13 +85,14 @@ Array [ - 1304 +
- 125 +
- 1304 +
71.5 % @@ -384,13 +392,14 @@ Array [ - 135 +
86.37 % @@ -493,13 +507,14 @@ Array [ - 27 +
100 % diff --git a/frontend/src/components/stats/StakingBar.tsx b/frontend/src/components/stats/StakingBar.tsx index e82dfdda2..1509b27b0 100644 --- a/frontend/src/components/stats/StakingBar.tsx +++ b/frontend/src/components/stats/StakingBar.tsx @@ -5,10 +5,10 @@ import ReactEcharts from "echarts-for-react"; import { utils } from "near-api-js"; -import { Validating } from "../../libraries/explorer-wamp/nodes"; +import { ValidationNodeInfo } from "../../libraries/explorer-wamp/nodes"; export interface Props { - validators: Validating[]; + validators: ValidationNodeInfo[]; } const StakingBar = ({ validators }: Props) => { diff --git a/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap b/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap index de03d447e..a9206dac6 100644 --- a/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap +++ b/frontend/src/components/transactions/__tests__/__snapshots__/ActionMessage.test.tsx.snap @@ -214,7 +214,6 @@ exports[` renders Stake 1`] = ` Array [ "Staked: ", renders Transfer 1`] = ` Array [ "Transferred ", renders Failure receipt 1`] = ` className="receipt-row-receipt-hash col" > renders Failure receipt 1`] = ` className="receipt-row-receipt-hash col" > renders Failure receipt 1`] = ` > Transferred renders receipt with many outcome receipts 1`] = ` className="receipt-row-receipt-hash col" > Transferred Transferred Transferred renders successful receipt 1`] = ` className="receipt-row-receipt-hash col" > renders successful receipt 1`] = ` className="receipt-row-receipt-hash col" > renders successful receipt 1`] = ` > Transferred renders no deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > renders no deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > renders with one small deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > renders with one small deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > renders with two big deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > renders with two big deposit 1`] = ` className="ml-auto card-cell-text align-self-center col-md-12 col-auto" > { return formatWithCommas(amountStr) + " yoctoⓃ"; }; -const formatWithCommas = (value) => { +export const formatWithCommas = (value) => { const pattern = /(-?\d+)(\d{3})/; while (pattern.test(value)) { value = value.toString().replace(pattern, "$1,$2"); diff --git a/frontend/src/context/NetworkStatsProvider.tsx b/frontend/src/context/NetworkStatsProvider.tsx new file mode 100644 index 000000000..06f568fcc --- /dev/null +++ b/frontend/src/context/NetworkStatsProvider.tsx @@ -0,0 +1,104 @@ +import BN from "bn.js"; +import React, { createContext, useEffect, useState } from "react"; +import { ExplorerApi } from "../libraries/explorer-wamp/index"; +import BlocksApi, { + DetailedBlockInfo, +} from "../libraries/explorer-wamp/blocks"; + +export interface NetworkStats { + currentValidatorsCount: number; + currentProposalsCount: number; + onlineNodesCount: number; + epochLength: number; + epochStartHeight: number; + totalStake: BN; + seatPrice: BN; +} + +export interface FinalityStatus { + finalBlockHeight: number; + finalBlockTimestampNanosecond: BN; +} + +export interface NetworkStatsContext { + networkStats?: NetworkStats; + finalityStatus?: FinalityStatus; + epochStartBlock?: DetailedBlockInfo; +} + +const NetworkStatsContext = createContext({}); + +export interface Props { + children: React.Component | React.ReactNode; +} + +const NetworkStatsProvider = (props: Props) => { + const [networkStats, dispatchNetworkStats] = useState(); + const [ + epochStartBlock, + dispatchEpochStartBlock, + ] = useState(); + const [finalityStatus, dispatchFinalityStatus] = useState(); + + const storeNetworkStats = (_positionalArgs: any, namedArgs: any) => { + dispatchNetworkStats({ + currentValidatorsCount: namedArgs.currentValidatorsCount, + currentProposalsCount: namedArgs.currentProposalsCount, + onlineNodesCount: namedArgs.onlineNodesCount, + epochLength: namedArgs.epochLength, + epochStartHeight: namedArgs.epochStartHeight, + seatPrice: new BN(namedArgs.seatPrice), + totalStake: new BN(namedArgs.totalStake), + }); + }; + + const storeFinalityStatus = (_positionalArgs: any, namedArgs: any) => { + dispatchFinalityStatus({ + finalBlockTimestampNanosecond: new BN( + namedArgs.finalBlockTimestampNanosecond + ), + finalBlockHeight: namedArgs.finalBlockHeight, + }); + }; + + useEffect(() => { + const explorerApi = new ExplorerApi(); + explorerApi.subscribe("network-stats", storeNetworkStats); + explorerApi.subscribe("finality-status", storeFinalityStatus); + + return () => { + explorerApi.unsubscribe("network-stats"); + explorerApi.unsubscribe("finality-status"); + }; + }, []); + + useEffect(() => { + if (networkStats) { + new BlocksApi() + .getBlockInfo(networkStats.epochStartHeight) + .then((blockInfo: any) => { + dispatchEpochStartBlock(blockInfo); + return; + }) + .catch((err: any) => console.error(err)); + } + }, [networkStats?.epochStartHeight]); + + return ( + + {props.children} + + ); +}; + +const NetworkStatsConsumer = NetworkStatsContext.Consumer; + +export { NetworkStatsConsumer, NetworkStatsContext }; + +export default NetworkStatsProvider; diff --git a/frontend/src/context/NodeProvider.tsx b/frontend/src/context/NodeProvider.tsx index 3faa71e0d..84f013b00 100644 --- a/frontend/src/context/NodeProvider.tsx +++ b/frontend/src/context/NodeProvider.tsx @@ -1,12 +1,12 @@ import React, { createContext, useEffect, useState } from "react"; import { ExplorerApi } from "../libraries/explorer-wamp"; -import { NodeInfo, Validating } from "../libraries/explorer-wamp/nodes"; +import { NodeInfo, ValidationNodeInfo } from "../libraries/explorer-wamp/nodes"; export interface INodeContext { - validators?: Validating[]; + currentValidators?: ValidationNodeInfo[]; onlineNodes?: NodeInfo[]; - proposals?: Validating[]; + currentProposals?: ValidationNodeInfo[]; onlineValidatingNodes?: NodeInfo[]; } @@ -17,15 +17,19 @@ export interface Props { } const NodeProvider = (props: Props) => { - const [validators, dispatchValidators] = useState(); + const [currentValidators, dispatchValidators] = useState< + ValidationNodeInfo[] + >(); + const [currentProposals, dispatchCurrentProposals] = useState< + ValidationNodeInfo[] + >(); const [onlineNodes, dispatchOnlineNodes] = useState(); - const [proposals, dispatchProposals] = useState(); const [onlineValidatingNodes, dispatchNodes] = useState(); const fetchNodeInfo = (_positionalArgs: any, namedArgs: INodeContext) => { - dispatchValidators(namedArgs.validators); + dispatchValidators(namedArgs.currentValidators); + dispatchCurrentProposals(namedArgs.currentProposals); dispatchOnlineNodes(namedArgs.onlineNodes); - dispatchProposals(namedArgs.proposals); dispatchNodes(namedArgs.onlineValidatingNodes); }; @@ -40,7 +44,12 @@ const NodeProvider = (props: Props) => { return ( {props.children} diff --git a/frontend/src/context/NodeStatsProvider.tsx b/frontend/src/context/NodeStatsProvider.tsx deleted file mode 100644 index 94bc7a5fa..000000000 --- a/frontend/src/context/NodeStatsProvider.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import BN from "bn.js"; -import React, { createContext, useEffect, useState } from "react"; -import { - ExplorerApi, - instrumentTopicNameWithDataSource, -} from "../libraries/explorer-wamp/index"; -import BlocksApi, { BlockInfo } from "../libraries/explorer-wamp/blocks"; - -export interface NodeStatsContext { - validatorAmount?: number; - seatPriceAmount?: number; - onlineNodeAmount?: number; - proposalAmount?: number; - totalStakeAmount?: number; - epochStartHeight?: number; - epochStartBlock?: BlockInfo; - latestBlock?: LatestBlockInfo; - epochLength?: number; -} - -export interface LatestBlockInfo { - height?: BN; - timestamp?: BN; -} - -const NodeStatsContext = createContext({}); - -export interface Props { - children: React.Component | React.ReactNode; -} - -const NodeStatsProvider = (props: Props) => { - const [validatorAmount, dispatchValidatorAmount] = useState(); - const [onlineNodeAmount, dispatchOnlineNodeAmount] = useState(); - const [proposalAmount, dispatchProposalAmount] = useState(); - const [seatPriceAmount, dispatchSeatPriceAmount] = useState(); - const [totalStakeAmount, dispatchTotalStakeAmount] = useState(); - const [epochStartHeight, dispatchEpochStartHeight] = useState(); - const [epochStartBlock, dispatchEpochStartBlock] = useState(); - const [latestBlockHeight, dispatchLatestBlockHeight] = useState(); - const [finalTimestamp, dispatchFinalTimestamp] = useState(); - const [epochLength, dispatchEpochLenght] = useState(); - - const storeNodeInfo = (_positionalArgs: any, namedArgs: NodeStatsContext) => { - dispatchValidatorAmount(namedArgs.validatorAmount); - dispatchSeatPriceAmount(namedArgs.seatPriceAmount); - dispatchOnlineNodeAmount(namedArgs.onlineNodeAmount); - dispatchProposalAmount(namedArgs.proposalAmount); - dispatchTotalStakeAmount(namedArgs.totalStakeAmount); - dispatchEpochStartHeight(namedArgs.epochStartHeight); - dispatchEpochLenght(namedArgs.epochLength); - }; - - const storeLatestBlockStats = (_positionalArgs: any, namedArgs: any) => { - dispatchLatestBlockHeight(new BN(namedArgs.latestBlockHeight)); - }; - - const storeFinalTimestamp = ( - _positionalArgs: any, - namedArgs: { - finalTimestamp: string; - } - ) => { - dispatchFinalTimestamp(new BN(namedArgs.finalTimestamp)); - }; - - useEffect(() => { - const explorerApi = new ExplorerApi(); - explorerApi.subscribe("node-stats", storeNodeInfo); - - explorerApi.subscribe( - instrumentTopicNameWithDataSource("chain-blocks-stats"), - storeLatestBlockStats - ); - - explorerApi.subscribe("final-timestamp", storeFinalTimestamp); - - return () => { - explorerApi.unsubscribe( - instrumentTopicNameWithDataSource("chain-blocks-stats") - ); - explorerApi.unsubscribe("node-stats"); - }; - }, []); - - useEffect(() => { - if (epochStartHeight) { - new BlocksApi() - .getBlockInfo(epochStartHeight) - .then((blockInfo: any) => { - dispatchEpochStartBlock(blockInfo); - return; - }) - .catch((err: any) => console.error(err)); - } - }, [epochStartHeight]); - - return ( - - {props.children} - - ); -}; - -const NodeStatsConsumer = NodeStatsContext.Consumer; - -export { NodeStatsConsumer, NodeStatsContext }; - -export default NodeStatsProvider; diff --git a/frontend/src/libraries/explorer-wamp/blocks.ts b/frontend/src/libraries/explorer-wamp/blocks.ts index 62b9b0800..62cf340fd 100644 --- a/frontend/src/libraries/explorer-wamp/blocks.ts +++ b/frontend/src/libraries/explorer-wamp/blocks.ts @@ -8,13 +8,16 @@ export interface BlockInfo { hash: string; height: number; timestamp: number; - totalSupply?: string; prevHash: string; transactionsCount: number; - gasPrice?: string; - gasUsed?: string; } +export type DetailedBlockInfo = BlockInfo & { + totalSupply: BN; + gasPrice: BN; + gasUsed: BN; +}; + export default class BlocksApi extends ExplorerApi { async getBlocks( limit = 15, @@ -80,7 +83,7 @@ export default class BlocksApi extends ExplorerApi { blocks = blocks.map((block: any) => { return { hash: block.hash, - height: block.height, + height: parseInt(block.height), timestamp: parseInt(block.timestamp), prevHash: block.prev_hash, transactionsCount: block.transactions_count, @@ -94,7 +97,7 @@ export default class BlocksApi extends ExplorerApi { } } - async getBlockInfo(blockId: string | number): Promise { + async getBlockInfo(blockId: string | number): Promise { try { let block; if (this.dataSource === DATA_SOURCE_TYPE.LEGACY_SYNC_BACKEND) { @@ -152,19 +155,20 @@ export default class BlocksApi extends ExplorerApi { throw Error(`unsupported data source ${this.dataSource}`); } + let gasUsedInBlock; if (block === undefined) { throw new Error("block not found"); } else { - let gasUsedResult; + let gasUsedInChunks; if (this.dataSource === DATA_SOURCE_TYPE.LEGACY_SYNC_BACKEND) { - gasUsedResult = await this.call("select", [ + gasUsedInChunks = await this.call("select", [ `SELECT gas_used FROM chunks WHERE block_hash = :block_hash`, { block_hash: block.hash, }, ]); } else if (this.dataSource === DATA_SOURCE_TYPE.INDEXER_BACKEND) { - gasUsedResult = await this.call("select:INDEXER_BACKEND", [ + gasUsedInChunks = await this.call("select:INDEXER_BACKEND", [ `SELECT gas_used FROM chunks WHERE included_in_block_hash = :block_hash`, { block_hash: block.hash, @@ -173,26 +177,25 @@ export default class BlocksApi extends ExplorerApi { } else { throw Error(`unsupported data source ${this.dataSource}`); } - let gasUsedArray = gasUsedResult.map( - (gas: any) => new BN(gas.gas_used) - ); - let gasUsed = gasUsedArray.reduce( - (gas: BN, currentGas: BN) => gas.add(currentGas), + gasUsedInBlock = gasUsedInChunks.reduce( + (currentGas: BN, chunk: { gas_used: string }) => { + currentGas.iadd(new BN(chunk.gas_used)); + return currentGas; + }, new BN(0) ); - block.gasUsed = gasUsed.toString(); } return { - gasUsed: block.gasUsed, - gasPrice: block.gas_price, hash: block.hash, - height: block.height, prevHash: block.prev_hash, + height: parseInt(block.height), timestamp: parseInt(block.timestamp), transactionsCount: block.transactions_count, - totalSupply: block.total_supply, - } as BlockInfo; + totalSupply: new BN(block.total_supply), + gasUsed: gasUsedInBlock, + gasPrice: new BN(block.gas_price), + } as DetailedBlockInfo; } catch (error) { console.error("Blocks.getBlockInfo failed to fetch data due to:"); console.error(error); diff --git a/frontend/src/libraries/explorer-wamp/nodes.ts b/frontend/src/libraries/explorer-wamp/nodes.ts index 95b69037e..2b4c6ca00 100644 --- a/frontend/src/libraries/explorer-wamp/nodes.ts +++ b/frontend/src/libraries/explorer-wamp/nodes.ts @@ -15,7 +15,7 @@ export interface NodeInfo { city?: string; } -export interface Validating { +export interface BaseValidationNodeInfo { account_id: string; is_slashed?: boolean; num_produced_blocks?: number; @@ -26,12 +26,22 @@ export interface Validating { removed?: boolean; shards?: [number]; nodeInfo?: NodeInfo; +} + +export interface StakingPoolInfo { fee: { numerator: number; denominator: number }; - delegators?: number; - cumulativeStakeAmount?: CumulativeStake; - totalStake?: BN; + delegatorsCount: number; } +export interface StakeInfo { + cumulativeStakeAmount: CumulativeStake; + totalStake: BN; +} + +export type ValidationNodeInfo = BaseValidationNodeInfo & + StakingPoolInfo & + StakeInfo; + interface CumulativeStake { total: BN; networkHolderIndex: number; diff --git a/frontend/src/pages/blocks/[hash].jsx b/frontend/src/pages/blocks/[hash].jsx index d4a1b0fb0..2a3afcf38 100644 --- a/frontend/src/pages/blocks/[hash].jsx +++ b/frontend/src/pages/blocks/[hash].jsx @@ -1,3 +1,5 @@ +import BN from "bn.js"; + import Head from "next/head"; import React from "react"; @@ -15,7 +17,14 @@ import Content from "../../components/utils/Content"; class BlockDetail extends React.Component { static async getInitialProps({ req, query: { hash } }) { try { - return await new BlocksApi(req).getBlockInfo(hash); + const block = await new BlocksApi(req).getBlockInfo(hash); + return { + ...block, + // the return value should be a serializable object per Next.js documentation, so we map BN to strings + totalSupply: block.gasPrice.toString(), + gasPrice: block.gasPrice.toString(), + gasUsed: block.gasUsed.toString(), + }; } catch (err) { return { hash, err }; } @@ -28,6 +37,18 @@ class BlockDetail extends React.Component { } render() { + const block = { + hash: this.props.hash, + height: this.props.height, + timestamp: this.props.timestamp, + prevHash: this.props.prevHash, + transactionsCount: this.props.transactionsCount, + // `props` should be a serializable object per Next.js documentation, so we map back from strings to BN + totalSupply: new BN(this.props.totalSupply), + gasPrice: new BN(this.props.gasPrice), + gasUsed: new BN(this.props.gasUsed), + }; + return ( <> @@ -36,9 +57,9 @@ class BlockDetail extends React.Component { {`Block ${ - this.props.height - ? `#${this.props.height}` - : `${this.props.hash.substring(0, 7)}...` + block.height + ? `#${block.height}` + : `${block.hash.substring(0, 7)}...` }`} } border={false} @@ -46,7 +67,7 @@ class BlockDetail extends React.Component { {this.props.err ? ( `Information is not available at the moment. Please, check if the block hash is correct or try later.` ) : ( - + )} {!this.props.err ? ( @@ -55,7 +76,7 @@ class BlockDetail extends React.Component { icon={} title={

Transactions

} > - + ) : null} diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx index 27a1bf0a1..2b92febd3 100644 --- a/frontend/src/pages/index.jsx +++ b/frontend/src/pages/index.jsx @@ -3,7 +3,7 @@ import Head from "next/head"; import { Container, Row, Col } from "react-bootstrap"; import Mixpanel from "../libraries/mixpanel"; -import NodeStatsProvider from "../context/NodeStatsProvider"; +import NetworkStatsProvider from "../context/NetworkStatsProvider"; import Search from "../components/utils/Search"; import DashboardNode from "../components/dashboard/DashboardNode"; @@ -35,9 +35,9 @@ class Dashboard extends React.Component { - + - + diff --git a/frontend/src/pages/nodes/online-nodes.jsx b/frontend/src/pages/nodes/online-nodes.jsx index 4ffea58fa..8e50583c9 100644 --- a/frontend/src/pages/nodes/online-nodes.jsx +++ b/frontend/src/pages/nodes/online-nodes.jsx @@ -12,9 +12,9 @@ import Content from "../../components/utils/Content"; import NodeProvider from "../../context/NodeProvider"; import NodesContentHeader from "../../components/nodes/NodesContentHeader"; -import NodeStatsProvider, { - NodeStatsConsumer, -} from "../../context/NodeStatsProvider"; +import NetworkStatsProvider, { + NetworkStatsConsumer, +} from "../../context/NetworkStatsProvider"; class OnlineNodes extends React.Component { componentDidMount() { @@ -28,27 +28,42 @@ class OnlineNodes extends React.Component { NEAR Explorer | Nodes - - - - {(context) => } - - - + + + + {({ networkStats, epochStartBlock, finalityStatus }) => { + if (!networkStats || !epochStartBlock || !finalityStatus) { + return null; + } + return ( + + ); + }} + + - } - > - - - - - - + } + > + + + + + + + diff --git a/frontend/src/pages/nodes/proposals.jsx b/frontend/src/pages/nodes/proposals.jsx index 093cde1ea..d70e6560c 100644 --- a/frontend/src/pages/nodes/proposals.jsx +++ b/frontend/src/pages/nodes/proposals.jsx @@ -70,10 +70,25 @@ class ProposalsPage extends React.Component { background-color: #ffffff; } + @media (max-width: 576px) { + .proposals-page > .container-fluid, + .proposals-page > .container-fluid > .container { + padding-left: 0; + padding-right: 0; + } + } + @media (min-width: 576px) { + .content-header { + padding-left: 32px; + padding-right: 32px; + } + } + .content-header { background: #fafafa; margin-left: -15px; margin-right: -15px; + padding-bottom: 0; } `} diff --git a/frontend/src/pages/nodes/validators.jsx b/frontend/src/pages/nodes/validators.jsx index 784d4080f..675c032bf 100644 --- a/frontend/src/pages/nodes/validators.jsx +++ b/frontend/src/pages/nodes/validators.jsx @@ -72,10 +72,25 @@ class ValidatorsPage extends React.Component { .nodes-page { background-color: #ffffff; } + + @media (max-width: 576px) { + .nodes-page > .container-fluid, + .nodes-page > .container-fluid > .container { + padding-left: 0; + padding-right: 0; + } + } + @media (min-width: 576px) { + .content-header { + padding-left: 32px; + padding-right: 32px; + } + } .content-header { background: #fafafa; margin-left: -15px; margin-right: -15px; + padding-bottom: 0; } `} From 3fb9cf9e2f9296eaae82fd6bf84a8e3018c606d7 Mon Sep 17 00:00:00 2001 From: Vlad Frolov Date: Tue, 18 May 2021 17:47:26 +0300 Subject: [PATCH 11/12] added explanations about various terms on the Validation Nodes page --- .../src/components/nodes/ValidatorRow.tsx | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/nodes/ValidatorRow.tsx b/frontend/src/components/nodes/ValidatorRow.tsx index 5495f40a1..29826b7b6 100644 --- a/frontend/src/components/nodes/ValidatorRow.tsx +++ b/frontend/src/components/nodes/ValidatorRow.tsx @@ -195,7 +195,10 @@ class ValidatorRow extends React.PureComponent { @@ -226,7 +229,9 @@ class ValidatorRow extends React.PureComponent { @@ -260,7 +265,9 @@ class ValidatorRow extends React.PureComponent { @@ -281,7 +288,17 @@ class ValidatorRow extends React.PureComponent { + { + "NEAR Protocol could have multiple implementations, so agent is the name of that implementation, where 'near-rs' is " + } + + {"the official implementation"} + + {"."} + + } /> @@ -305,10 +322,7 @@ class ValidatorRow extends React.PureComponent { - + {"Node Agent Version / Build"} From 95995f2b3a643929309af67a4a50f144ca424e71 Mon Sep 17 00:00:00 2001 From: Vlad Frolov Date: Tue, 18 May 2021 17:48:50 +0300 Subject: [PATCH 12/12] fix(backend): added graceful handling of errors to staking pool information fetching --- backend/src/index.js | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/backend/src/index.js b/backend/src/index.js index 0216904a4..ad2a1eae5 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -336,23 +336,30 @@ async function main() { // Periodic check of validators' staking pool fee and delegators count const regularFetchStakingPoolsInfo = async () => { - const stakingPoolsAccountId = new Set([ - ...currentValidators.map(({ account_id }) => account_id), - ...currentProposals.map(({ account_id }) => account_id), - ]); + try { + const stakingPoolsAccountId = new Set([ + ...currentValidators.map(({ account_id }) => account_id), + ...currentProposals.map(({ account_id }) => account_id), + ]); - for (const stakingPoolAccountId of stakingPoolsAccountId) { - const fee = await nearRpc.callViewMethod( - stakingPoolAccountId, - "get_reward_fee_fraction", - {} - ); - const delegatorsCount = await nearRpc.callViewMethod( - stakingPoolAccountId, - "get_number_of_accounts", - {} + for (const stakingPoolAccountId of stakingPoolsAccountId) { + const fee = await nearRpc.callViewMethod( + stakingPoolAccountId, + "get_reward_fee_fraction", + {} + ); + const delegatorsCount = await nearRpc.callViewMethod( + stakingPoolAccountId, + "get_number_of_accounts", + {} + ); + stakingPoolsInfo.set(stakingPoolAccountId, { fee, delegatorsCount }); + } + } catch (error) { + console.warn( + "Regular regular network info publishing crashed due to:", + error ); - stakingPoolsInfo.set(stakingPoolAccountId, { fee, delegatorsCount }); } setTimeout( regularFetchStakingPoolsInfo,