Compare commits

...

12 Commits

Author SHA1 Message Date
Tommy Verrall 4621978a54 add models 2024-05-02 14:04:28 +02:00
Tommy Verrall 93a1857c6b make models public 2024-05-02 13:41:07 +02:00
Tommy Verrall c6942769fc more more more 2024-05-02 13:24:40 +02:00
Tommy Verrall 33118ad648 a lot more overhead 2024-05-02 13:09:29 +02:00
Tommy Verrall 1cd489034f pr comments 2024-05-02 11:57:47 +02:00
Tommy Verrall 6c731a2f06 round and round with error logging 2024-05-02 11:56:24 +02:00
Tommy Verrall 5dda372437 use anyhow instead to return error messages 2024-05-02 11:26:01 +02:00
Tommy Verrall edf9c0f7b5 simplify the error logging 2024-05-02 11:19:26 +02:00
Tommy Verrall c371e6c4bc cargo fmt 2024-05-02 11:12:12 +02:00
Tommy Verrall 4198cd81f3 including proper reqwest error message 2024-05-02 11:09:50 +02:00
Tommy Verrall bbf57482fc change stats model to cater for both endpoints after nym-node intro 2024-05-02 11:01:50 +02:00
Tommy Verrall e59a444074 include nym-node endpoint 2024-05-02 10:25:20 +02:00
5 changed files with 141 additions and 26 deletions
+1
View File
@@ -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 }
+60 -8
View File
@@ -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")]
+61 -6
View File
@@ -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>
);
};
};
+8 -4
View File
@@ -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;