Merge pull request #966 from nymtech/feature/nova-network-explorer

Fixes to Network Explorer for new testnet
This commit is contained in:
Mark Sinclair
2021-12-16 11:13:06 +00:00
committed by GitHub
25 changed files with 247 additions and 125 deletions
+2
View File
@@ -22,6 +22,8 @@ jobs:
node-version: '14'
- run: npm install
continue-on-error: true
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test
continue-on-error: true
- run: npm run build
Generated
+9 -6
View File
@@ -3910,10 +3910,11 @@ dependencies = [
[[package]]
name = "okapi"
version = "0.6.0-alpha-1"
version = "0.7.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4"
checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893"
dependencies = [
"log",
"schemars",
"serde",
"serde_json",
@@ -5150,10 +5151,12 @@ dependencies = [
[[package]]
name = "rocket_okapi"
version = "0.7.0-alpha-1"
version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d"
checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
dependencies = [
"either",
"log",
"okapi",
"rocket",
"rocket_okapi_codegen",
@@ -5164,9 +5167,9 @@ dependencies = [
[[package]]
name = "rocket_okapi_codegen"
version = "0.7.0-alpha-1"
version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41"
checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
dependencies = [
"darling 0.13.0",
"proc-macro2",
+2 -2
View File
@@ -16,8 +16,8 @@ serde_json = "1.0.66"
tokio = {version = "1.9.0", features = ["full"] }
chrono = { version = "0.4.19", features = ["serde"] }
schemars = { version = "0.8", features = ["preserve_order"] }
okapi = { version = "0.6.0-alpha-1", features = ["derive_json_schema"] }
rocket_okapi = "0.7.0-alpha-1"
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
log = "0.4.0"
pretty_env_logger = "0.4.0"
thiserror = "1.0.29"
@@ -15,6 +15,13 @@ impl GeoLocateTask {
}
pub(crate) fn start(mut self) {
if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() {
error!(
"Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started."
);
return;
}
info!("Spawning mix node locator task runner...");
tokio::spawn(async move {
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
+5 -2
View File
@@ -2,9 +2,12 @@ use crate::country_statistics::country_nodes_distribution::CountryNodesDistribut
use crate::state::ExplorerApiStateContext;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
pub fn country_statistics_make_default_routes() -> Vec<Route> {
routes_with_openapi![index]
pub fn country_statistics_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: index]
}
// We could either separate stuff by structure (like this, http is separate), or we could just
+38 -27
View File
@@ -1,12 +1,13 @@
use log::info;
use rocket::http::Method;
use rocket::Request;
use rocket::{Build, Request, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins};
use rocket_okapi::swagger_ui::make_swagger_ui;
use crate::country_statistics::http::country_statistics_make_default_routes;
use crate::http::swagger::get_docs;
use crate::mix_node::http::mix_node_make_default_routes;
use crate::mix_nodes::http::mix_nodes_make_default_routes;
use crate::ping::http::ping_make_default_routes;
use crate::state::ExplorerApiStateContext;
@@ -15,35 +16,45 @@ mod swagger;
pub(crate) fn start(state: ExplorerApiStateContext) {
tokio::spawn(async move {
info!("Starting up...");
let allowed_origins = AllowedOrigins::all();
// You can also deserialize this
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
allowed_headers: AllowedHeaders::some(&["*"]),
allow_credentials: true,
..Default::default()
}
.to_cors()
.unwrap();
let config = rocket::config::Config::release_default();
rocket::build()
.configure(config)
.mount("/countries", country_statistics_make_default_routes())
.mount("/ping", ping_make_default_routes())
.mount("/mix-node", mix_node_make_default_routes())
.mount("/swagger", make_swagger_ui(&get_docs()))
.register("/", catchers![not_found])
.manage(state)
.attach(cors)
.launch()
.await
configure_rocket(state).launch().await
});
}
fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
let allowed_origins = AllowedOrigins::all();
// You can also deserialize this
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
allowed_headers: AllowedHeaders::some(&["*"]),
allow_credentials: true,
..Default::default()
}
.to_cors()
.unwrap();
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let config = rocket::config::Config::release_default();
let mut building_rocket = rocket::build().configure(config);
mount_endpoints_and_merged_docs! {
building_rocket,
"/v1".to_owned(),
openapi_settings,
"/ping" => ping_make_default_routes(&openapi_settings),
"/countries" => country_statistics_make_default_routes(&openapi_settings),
"/mix-node" => mix_node_make_default_routes(&openapi_settings),
"/mix-nodes" => mix_nodes_make_default_routes(&openapi_settings),
};
building_rocket
.mount("/swagger", make_swagger_ui(&get_docs()))
.register("/", catchers![not_found])
.manage(state)
.attach(cors)
}
#[catch(404)]
pub(crate) fn not_found(req: &Request) -> String {
format!("I couldn't find '{}'. Try something else?", req.uri())
+2 -6
View File
@@ -1,12 +1,8 @@
use rocket_okapi::swagger_ui::{SwaggerUIConfig, UrlObject};
use rocket_okapi::swagger_ui::SwaggerUIConfig;
pub(crate) fn get_docs() -> SwaggerUIConfig {
SwaggerUIConfig {
urls: vec![
UrlObject::new("Country statistics", "/countries/openapi.json"),
UrlObject::new("Node ping", "/ping/openapi.json"),
UrlObject::new("Mix node", "/mix-node/openapi.json"),
],
url: "../v1/openapi.json".to_owned(),
..Default::default()
}
}
+5
View File
@@ -35,6 +35,11 @@ impl ExplorerApi {
async fn run(&mut self) {
info!("Explorer API starting up...");
info!(
"Using validator API - {}",
network_defaults::default_api_endpoints()[0].clone()
);
// spawn concurrent tasks
mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start();
country_statistics::distribution::CountryStatisticsDistributionTask::new(
+6 -12
View File
@@ -1,6 +1,9 @@
use reqwest::Error as ReqwestError;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use serde::Serialize;
use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode};
@@ -9,13 +12,12 @@ use crate::mix_node::models::{NodeDescription, NodeStats};
use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location};
use crate::state::ExplorerApiStateContext;
pub fn mix_node_make_default_routes() -> Vec<Route> {
routes_with_openapi![
get_delegations,
pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
settings: get_delegations,
get_all_delegations,
get_description,
get_stats,
list
]
}
@@ -29,14 +31,6 @@ pub(crate) struct PrettyMixNodeBondWithLocation {
pub mix_node: MixNode,
}
#[openapi(tag = "mix_node")]
#[get("/")]
pub(crate) async fn list(
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
}
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/delegations")]
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> {
+20
View File
@@ -0,0 +1,20 @@
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use crate::mix_node::http::PrettyMixNodeBondWithLocation;
use crate::state::ExplorerApiStateContext;
pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: list]
}
#[openapi(tag = "mix_nodes")]
#[get("/")]
pub(crate) async fn list(
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
}
+1
View File
@@ -1,3 +1,4 @@
pub(crate) mod http;
pub(crate) mod tasks;
mod utils;
+5 -2
View File
@@ -4,6 +4,9 @@ use std::time::Duration;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use mixnet_contract::MixNodeBond;
@@ -12,8 +15,8 @@ use crate::state::ExplorerApiStateContext;
const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10);
pub fn ping_make_default_routes() -> Vec<Route> {
routes_with_openapi![index]
pub fn ping_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: index]
}
#[openapi(tag = "ping")]
+5
View File
@@ -0,0 +1,5 @@
EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1
VALIDATOR_API_URL=https://sandbox-validator.nymtech.net
BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net
CURRENCY_DENOM=unymt
CURRENCY_STAKING_DENOM=unyxt
+60
View File
@@ -50,6 +50,7 @@
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "7.32.0",
"eslint-config-airbnb": "18.2.1",
"eslint-config-prettier": "8.3.0",
@@ -6198,6 +6199,39 @@
"tslib": "^2.0.3"
}
},
"node_modules/dotenv-defaults": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz",
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==",
"dev": true,
"dependencies": {
"dotenv": "^8.2.0"
}
},
"node_modules/dotenv-defaults/node_modules/dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/dotenv-webpack": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz",
"integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==",
"dev": true,
"dependencies": {
"dotenv-defaults": "^2.0.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"webpack": "^4 || ^5"
}
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -21882,6 +21916,32 @@
"tslib": "^2.0.3"
}
},
"dotenv-defaults": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz",
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==",
"dev": true,
"requires": {
"dotenv": "^8.2.0"
},
"dependencies": {
"dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true
}
}
},
"dotenv-webpack": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz",
"integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==",
"dev": true,
"requires": {
"dotenv-defaults": "^2.0.2"
}
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+1
View File
@@ -45,6 +45,7 @@
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "7.32.0",
"eslint-config-airbnb": "18.2.1",
"eslint-config-prettier": "8.3.0",
+11 -11
View File
@@ -1,17 +1,17 @@
// master APIs
export const MASTER_URL = 'https://testnet-milhon-explorer.nymtech.net';
export const MASTER_VALIDATOR_URL =
'https://testnet-milhon-validator1.nymtech.net';
export const BIG_DIPPER = 'https://testnet-milhon-blocks.nymtech.net';
export const API_BASE_URL = process.env.EXPLORER_API_URL;
export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL;
export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
// specific API routes
export const MIXNODE_PING = `${MASTER_URL}/api/ping`;
export const MIXNODES_API = `${MASTER_URL}/api/mix-node`;
export const GATEWAYS_API = `${MASTER_VALIDATOR_URL}/api/v1/gateways`;
export const VALIDATORS_API = `${MASTER_VALIDATOR_URL}/validators`;
export const BLOCK_API = `${MASTER_VALIDATOR_URL}/block`;
export const COUNTRY_DATA_API = `${MASTER_URL}/api/countries`;
export const UPTIME_STORY_API = `${MASTER_VALIDATOR_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
export const MIXNODE_PING = `${API_BASE_URL}/ping`;
export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`;
export const VALIDATORS_API = `${VALIDATOR_API_BASE_URL}/validators`;
export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`;
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
// errors
export const MIXNODE_API_ERROR =
+3 -2
View File
@@ -6,6 +6,7 @@ import {
COUNTRY_DATA_API,
MIXNODE_PING,
UPTIME_STORY_API,
MIXNODE_API,
} from './constants';
import {
@@ -87,10 +88,10 @@ export class Api {
static fetchDelegationsById = async (
id: string,
): Promise<DelegationsResponse> =>
(await fetch(`${MIXNODES_API}/${id}/delegations`)).json();
(await fetch(`${MIXNODE_API}/${id}/delegations`)).json();
static fetchStatsById = async (id: string): Promise<StatsResponse> =>
(await fetch(`${MIXNODES_API}/${id}/stats`)).json();
(await fetch(`${MIXNODE_API}/${id}/stats`)).json();
static fetchStatusById = async (id: string): Promise<StatusResponse> =>
(await fetch(`${MIXNODE_PING}/${id}`)).json();
+16 -17
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import { printableCoin } from '@nymproject/nym-validator-client';
import { Alert, CircularProgress, useMediaQuery, Box } from '@mui/material';
import { Alert, Box, CircularProgress, useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
@@ -11,6 +10,7 @@ import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import { useMainContext } from 'src/context/main';
import { ExpandMore } from '@mui/icons-material';
import { currencyToString } from '../utils/currency';
export const BondBreakdownTable: React.FC = () => {
const { mixnodeDetailInfo, delegations } = useMainContext();
@@ -34,24 +34,23 @@ export const BondBreakdownTable: React.FC = () => {
const thisMixnode = mixnodeDetailInfo?.data[0];
// delegations
const decimalisedDelegations = printableCoin({
amount: thisMixnode.total_delegation.amount.toString(),
denom: thisMixnode.total_delegation.denom,
});
const decimalisedDelegations = currencyToString(
thisMixnode.total_delegation.amount.toString(),
thisMixnode.total_delegation.denom,
);
// pledges
const decimalisedPledges = printableCoin({
amount: thisMixnode.bond_amount.amount.toString(),
denom: thisMixnode.bond_amount.denom,
});
const decimalisedPledges = currencyToString(
thisMixnode.pledge_amount.amount.toString(),
thisMixnode.pledge_amount.denom,
);
// bonds total (del + pledges)
const pledgesSum = Number(thisMixnode.bond_amount.amount);
const pledgesSum = Number(thisMixnode.pledge_amount.amount);
const delegationsSum = Number(thisMixnode.total_delegation.amount);
const bondsTotal = printableCoin({
amount: (delegationsSum + pledgesSum).toString(),
denom: 'upunk',
});
const bondsTotal = currencyToString(
(delegationsSum + pledgesSum).toString(),
);
setBonds({
delegations: decimalisedDelegations,
@@ -89,7 +88,7 @@ export const BondBreakdownTable: React.FC = () => {
mixnodeDetailInfo.data[0].total_delegation.amount,
);
const rawPledgeAmount = Number(
mixnodeDetailInfo.data[0].bond_amount.amount,
mixnodeDetailInfo.data[0].pledge_amount.amount,
);
const rawTotalBondsAmount = rawDelegationAmount + rawPledgeAmount;
return ((num * 100) / rawTotalBondsAmount).toFixed(1);
@@ -203,7 +202,7 @@ export const BondBreakdownTable: React.FC = () => {
{owner}
</TableCell>
<TableCell align="left">
{printableCoin({ amount: amount.toString(), denom })}
{currencyToString(amount.toString(), denom)}
</TableCell>
<TableCell align="left">
{calcBondPercentage(amount)}%
+2 -2
View File
@@ -8,9 +8,9 @@ import {
TableHead,
TableRow,
} from '@mui/material';
import { printableCoin } from '@nymproject/nym-validator-client';
import { cellStyles } from './Universal-DataGrid';
import { MixnodeRowType } from '../utils/index';
import { currencyToString } from '../utils/currency';
export type ColumnsType = {
field: string;
@@ -28,7 +28,7 @@ export interface UniversalTableProps {
function formatCellValues(val: string | number, field: string) {
if (field === 'bond') {
return printableCoin({ amount: val.toString(), denom: 'upunk' });
return currencyToString(val.toString());
}
return val;
}
+6 -12
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import { Button, Card, Grid, Typography } from '@mui/material';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { printableCoin } from '@nymproject/nym-validator-client';
import { SelectChangeEvent } from '@mui/material/Select';
import { useMainContext } from 'src/context/main';
import { gatewayToGridRow } from 'src/utils';
@@ -13,6 +12,7 @@ import {
cellStyles,
UniversalDataGrid,
} from 'src/components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency';
export const PageGateways: React.FC = () => {
const { gateways } = useMainContext();
@@ -79,17 +79,11 @@ export const PageGateways: React.FC = () => {
renderHeader: () => <CustomColumnHeading headingTitle="Pledge" />,
headerClassName: 'MuiDataGrid-header-override',
headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => {
const bondAsPunk = printableCoin({
amount: params.value as string,
denom: 'upunk',
});
return (
<Typography sx={cellStyles} data-testid="pledge-amount">
{bondAsPunk}
</Typography>
);
},
renderCell: (params: GridRenderCellParams) => (
<Typography sx={cellStyles} data-testid="pledge-amount">
{currencyToString(params.value)}
</Typography>
),
},
{
field: 'host',
+10 -16
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { printableCoin } from '@nymproject/nym-validator-client';
import { Button, Grid, Link as MuiLink, Card } from '@mui/material';
import { Link as RRDLink } from 'react-router-dom';
import { SelectChangeEvent } from '@mui/material/Select';
@@ -15,6 +14,7 @@ import {
UniversalDataGrid,
cellStyles,
} from 'src/components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency';
export const PageMixnodes: React.FC = () => {
const { mixnodes } = useMainContext();
@@ -92,21 +92,15 @@ export const PageMixnodes: React.FC = () => {
headerClassName: 'MuiDataGrid-header-override',
width: 150,
headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => {
const bondAsPunk = printableCoin({
amount: params.value as string,
denom: 'upunk',
});
return (
<MuiLink
sx={cellStyles}
component={RRDLink}
to={`/network-components/mixnodes/${params.row.identity_key}`}
>
{bondAsPunk}
</MuiLink>
);
},
renderCell: (params: GridRenderCellParams) => (
<MuiLink
sx={cellStyles}
component={RRDLink}
to={`/network-components/mixnodes/${params.row.identity_key}`}
>
{currencyToString(params.value)}
</MuiLink>
),
},
{
field: 'location',
+7 -6
View File
@@ -7,13 +7,14 @@ export interface ClientConfig {
export interface MixNode {
host: string;
location: string;
mix_port: number;
http_api_port: number;
verloc_port: number;
sphinx_key: string;
identity_key: string;
version: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
profit_margin_percent: number;
location: string;
}
export interface Gateway {
@@ -32,7 +33,7 @@ export interface Amount {
}
export interface MixNodeResponseItem {
bond_amount: Amount;
pledge_amount: Amount;
total_delegation: Amount;
owner: string;
layer: string;
@@ -74,7 +75,7 @@ export type MixNodeHistoryResponse = StatsResponse;
export interface GatewayResponseItem {
block_height: number;
bond_amount: Amount;
pledge_amount: Amount;
total_delegation: Amount;
owner: string;
gateway: Gateway;
+19
View File
@@ -0,0 +1,19 @@
import { printableCoin } from '@nymproject/nym-validator-client';
const DENOM = process.env.CURRENCY_DENOM || 'unym';
const DENOM_STAKING = process.env.CURRENCY_STAKING_DENOM || 'unyx';
export const currencyToString = (amount: string, denom: string = DENOM) =>
printableCoin({
amount,
denom,
});
export const stakingCurrencyToString = (
amount: string,
denom: string = DENOM_STAKING,
) =>
printableCoin({
amount,
denom,
});
+2 -2
View File
@@ -69,7 +69,7 @@ export function mixnodeToGridRow(arrayOfMixnodes: MixNodeResponse): any {
return !arrayOfMixnodes
? []
: arrayOfMixnodes.map((mn) => {
const pledge = Number(mn.bond_amount.amount) || 0;
const pledge = Number(mn.pledge_amount.amount) || 0;
const delegations = Number(mn.total_delegation.amount) || 0;
const totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
@@ -96,7 +96,7 @@ export function gatewayToGridRow(
owner: gw.owner,
identity_key: gw.gateway.identity_key || '',
location: gw?.gateway?.location || '',
bond: gw.bond_amount.amount || 0,
bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '',
}));
}
+3
View File
@@ -5,6 +5,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const WebpackFavicons = require('webpack-favicons');
const Dotenv = require('dotenv-webpack');
module.exports = {
entry: './src/index.tsx',
@@ -78,6 +79,8 @@ module.exports = {
new WebpackFavicons({
src: 'src/logo.svg',
}),
new Dotenv(),
],
output: {
path: path.resolve(__dirname, 'dist'),