Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4621978a54 | |||
| 93a1857c6b | |||
| c6942769fc | |||
| 33118ad648 | |||
| 1cd489034f | |||
| 6c731a2f06 | |||
| 5dda372437 | |||
| edf9c0f7b5 | |||
| c371e6c4bc | |||
| 4198cd81f3 | |||
| bbf57482fc | |||
| e59a444074 |
@@ -7,6 +7,7 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.82"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||
dotenvy = { workspace = true }
|
||||
|
||||
@@ -9,9 +9,12 @@ use crate::mix_node::models::{
|
||||
EconomicDynamicsStats, NodeDescription, NodeStats, SummedDelegations,
|
||||
};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use crate::mix_node::models::{NewModelDescription, OldModelDescription};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use nym_explorer_api_requests::PrettyDetailedMixNodeBond;
|
||||
|
||||
use nym_mixnet_contract_common::{Delegation, MixId};
|
||||
use reqwest::Error as ReqwestError;
|
||||
use rocket::response::status::NotFound;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
@@ -30,18 +33,67 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>,
|
||||
]
|
||||
}
|
||||
|
||||
async fn get_mix_node_description(host: &str, port: u16) -> Result<NodeDescription, ReqwestError> {
|
||||
reqwest::get(format!("http://{host}:{port}/description"))
|
||||
.await?
|
||||
.json::<NodeDescription>()
|
||||
async fn get_mix_node_description(host: &str, port: u16) -> Result<NodeDescription> {
|
||||
let first_url = format!("http://{host}:{port}/description");
|
||||
let first_response = reqwest::get(&first_url).await.context(format!(
|
||||
"Failed to fetch description from nym-mixnode /description url: {}",
|
||||
first_url
|
||||
))?;
|
||||
|
||||
match first_response
|
||||
.error_for_status()
|
||||
.context("Nym-mixnodes /description url returned error status")?
|
||||
.json::<OldModelDescription>()
|
||||
.await
|
||||
{
|
||||
Ok(description) => return Ok(description.into()),
|
||||
Err(e) => log::warn!("Failed to parse old model description: {}", e),
|
||||
}
|
||||
|
||||
let second_url = format!("http://{host}:{port}/api/v1/description");
|
||||
let second_response = reqwest::get(&second_url).await.context(format!(
|
||||
"Failed to fetch description from nym-node /api/v1/description url: {}",
|
||||
second_url
|
||||
))?;
|
||||
|
||||
let description = second_response
|
||||
.error_for_status()
|
||||
.context("Nym-node /api/v1/description url returned error status")?
|
||||
.json::<NewModelDescription>()
|
||||
.await
|
||||
.context("Failed to parse JSON from nym-node /api/v1/description url")?;
|
||||
|
||||
Ok(description.into())
|
||||
}
|
||||
|
||||
async fn get_mix_node_stats(host: &str, port: u16) -> Result<NodeStats, ReqwestError> {
|
||||
reqwest::get(format!("http://{host}:{port}/stats"))
|
||||
.await?
|
||||
pub async fn get_mix_node_stats(host: &str, port: u16) -> Result<NodeStats> {
|
||||
let primary_url = format!("http://{host}:{port}/stats");
|
||||
let secondary_url = format!("http://{host}:{port}/api/v1/metrics/mixing");
|
||||
|
||||
let primary_response = reqwest::get(&primary_url)
|
||||
.await
|
||||
.context("Failed to fetch from primary nym-mixnode /stats url")?;
|
||||
let primary_stats = primary_response
|
||||
.error_for_status()
|
||||
.context("Nym-mixnode url returned error status")?
|
||||
.json::<NodeStats>()
|
||||
.await
|
||||
.context("Failed to parse JSON from primary nym-mixnode /stats url");
|
||||
|
||||
if let Ok(stats) = primary_stats {
|
||||
return Ok(stats);
|
||||
}
|
||||
|
||||
let secondary_response = reqwest::get(&secondary_url)
|
||||
.await
|
||||
.context("Failed to fetch from nym-node /api/v1/metrics/mixing url")?;
|
||||
let stats = secondary_response
|
||||
.error_for_status()
|
||||
.context("Nym-node /api/v1/metrics/mixing returned error status")?
|
||||
.json::<NodeStats>()
|
||||
.await
|
||||
.context("Failed to parse JSON from nym-node /api/v1/metrics/mixing")?;
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_nodes")]
|
||||
|
||||
@@ -92,31 +92,86 @@ impl ThreadsafeMixNodeCache {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub(crate) struct NodeDescription {
|
||||
pub(crate) name: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) link: String,
|
||||
pub(crate) location: String,
|
||||
pub(crate) name: Option<String>,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) link: Option<String>,
|
||||
pub(crate) location: Option<String>,
|
||||
pub(crate) moniker: Option<String>,
|
||||
pub(crate) website: Option<String>,
|
||||
pub(crate) security_contact: Option<String>,
|
||||
pub(crate) details: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Deserialize, JsonSchema)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct OldModelDescription {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub link: String,
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NewModelDescription {
|
||||
pub moniker: String,
|
||||
pub website: String,
|
||||
pub security_contact: String,
|
||||
pub details: String,
|
||||
}
|
||||
|
||||
impl From<OldModelDescription> for NodeDescription {
|
||||
fn from(old: OldModelDescription) -> Self {
|
||||
NodeDescription {
|
||||
name: Some(old.name),
|
||||
description: Some(old.description),
|
||||
link: Some(old.link),
|
||||
location: Some(old.location),
|
||||
moniker: None,
|
||||
website: None,
|
||||
security_contact: None,
|
||||
details: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NewModelDescription> for NodeDescription {
|
||||
fn from(new: NewModelDescription) -> Self {
|
||||
NodeDescription {
|
||||
name: None,
|
||||
description: Some(new.details),
|
||||
link: Some(new.website),
|
||||
location: None,
|
||||
moniker: Some(new.moniker),
|
||||
website: None,
|
||||
security_contact: Some(new.security_contact),
|
||||
details: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Deserialize, JsonSchema, Debug)]
|
||||
pub(crate) struct NodeStats {
|
||||
#[serde(
|
||||
serialize_with = "humantime_serde::serialize",
|
||||
deserialize_with = "humantime_serde::deserialize"
|
||||
)]
|
||||
update_time: SystemTime,
|
||||
|
||||
#[serde(
|
||||
serialize_with = "humantime_serde::serialize",
|
||||
deserialize_with = "humantime_serde::deserialize"
|
||||
)]
|
||||
previous_update_time: SystemTime,
|
||||
|
||||
#[serde(alias = "received_since_startup")]
|
||||
packets_received_since_startup: u64,
|
||||
#[serde(alias = "sent_since_startup")]
|
||||
packets_sent_since_startup: u64,
|
||||
#[serde(alias = "dropped_since_startup")]
|
||||
packets_explicitly_dropped_since_startup: u64,
|
||||
#[serde(alias = "received_since_last_update")]
|
||||
packets_received_since_last_update: u64,
|
||||
#[serde(alias = "sent_since_last_update")]
|
||||
packets_sent_since_last_update: u64,
|
||||
#[serde(alias = "dropped_since_last_update")]
|
||||
packets_explicitly_dropped_since_last_update: u64,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,15 @@ interface MixNodeDetailProps {
|
||||
mixnodeDescription: MixNodeDescriptionResponse;
|
||||
}
|
||||
|
||||
export const MixNodeDetailSection: FCWithChildren<MixNodeDetailProps> = ({ mixNodeRow, mixnodeDescription }) => {
|
||||
export const MixNodeDetailSection: React.FC<MixNodeDetailProps> = ({ mixNodeRow, mixnodeDescription }) => {
|
||||
const theme = useTheme();
|
||||
const palette = [theme.palette.text.primary];
|
||||
const isMobile = useIsMobile();
|
||||
const statusText = React.useMemo(() => getMixNodeStatusText(mixNodeRow.status), [mixNodeRow.status]);
|
||||
|
||||
const title = mixnodeDescription.name || mixnodeDescription.moniker || "Unknown Node";
|
||||
const description = mixnodeDescription.description || mixnodeDescription.details || "No description available.";
|
||||
const link = mixnodeDescription.link || mixnodeDescription.website || '#';
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
@@ -35,11 +38,11 @@ export const MixNodeDetailSection: FCWithChildren<MixNodeDetailProps> = ({ mixNo
|
||||
placeItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Identicon size={43} string={mixNodeRow.identity_key} palette={palette} />
|
||||
<Identicon size={43} string={mixNodeRow.identity_key} />
|
||||
</Box>
|
||||
<Box ml={isMobile ? 0 : 2} mt={isMobile ? 2 : 0}>
|
||||
<Typography fontSize={21}>{mixnodeDescription.name}</Typography>
|
||||
<Typography>{(mixnodeDescription.description || '').slice(0, 1000)}</Typography>
|
||||
<Typography fontSize={21}>{title}</Typography>
|
||||
<Typography>{description.slice(0, 1000)}</Typography>
|
||||
<Button
|
||||
component="a"
|
||||
variant="text"
|
||||
@@ -49,7 +52,7 @@ export const MixNodeDetailSection: FCWithChildren<MixNodeDetailProps> = ({ mixNo
|
||||
fontWeight: 600,
|
||||
padding: 0,
|
||||
}}
|
||||
href={mixnodeDescription.link}
|
||||
href={link}
|
||||
target="_blank"
|
||||
>
|
||||
<Typography
|
||||
@@ -59,7 +62,7 @@ export const MixNodeDetailSection: FCWithChildren<MixNodeDetailProps> = ({ mixNo
|
||||
overflow="hidden"
|
||||
maxWidth="250px"
|
||||
>
|
||||
{mixnodeDescription.link}
|
||||
Visit Node
|
||||
</Typography>
|
||||
</Button>
|
||||
</Box>
|
||||
@@ -92,4 +95,4 @@ export const MixNodeDetailSection: FCWithChildren<MixNodeDetailProps> = ({ mixNo
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -169,10 +169,14 @@ export interface GatewayReportResponse {
|
||||
export type GatewayHistoryResponse = StatsResponse;
|
||||
|
||||
export interface MixNodeDescriptionResponse {
|
||||
name: string;
|
||||
description: string;
|
||||
link: string;
|
||||
location: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
link?: string;
|
||||
location?: string;
|
||||
moniker?: string;
|
||||
website?: string;
|
||||
security_contact?: string;
|
||||
details?: string;
|
||||
}
|
||||
|
||||
export type MixNodeStatsResponse = StatsResponse;
|
||||
|
||||
Reference in New Issue
Block a user