added network page
This commit is contained in:
Generated
+19
-1
@@ -386,6 +386,17 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "country-emoji"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88f41dcf7008e5669247a47e0e704390241718e347fecfd2e6865ede17a3e798"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"unidecode",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -837,11 +848,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grin-explorer"
|
name = "grin-explorer"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
|
"country-emoji",
|
||||||
"either",
|
"either",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
@@ -2719,6 +2731,12 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unidecode"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.2"
|
version = "2.5.2"
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grin-explorer"
|
name = "grin-explorer"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@@ -9,6 +9,7 @@ edition = "2021"
|
|||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
chrono = "0.4.37"
|
chrono = "0.4.37"
|
||||||
config = "0.14.0"
|
config = "0.14.0"
|
||||||
|
country-emoji = "0.3.3"
|
||||||
either = "1.11.0"
|
either = "1.11.0"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
fs_extra = "1.3.0"
|
fs_extra = "1.3.0"
|
||||||
|
|||||||
+66
-2
@@ -192,7 +192,8 @@ pub struct ExplorerConfig {
|
|||||||
pub foreign_api_secret: String,
|
pub foreign_api_secret: String,
|
||||||
pub coingecko_api: String,
|
pub coingecko_api: String,
|
||||||
pub public_api: String,
|
pub public_api: String,
|
||||||
pub external_nodes: Vec<String>,
|
pub stats_source: Vec<String>,
|
||||||
|
pub public_nodes: Vec<String>,
|
||||||
pub database: String,
|
pub database: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +211,8 @@ impl ExplorerConfig {
|
|||||||
foreign_api_secret: String::new(),
|
foreign_api_secret: String::new(),
|
||||||
coingecko_api: String::new(),
|
coingecko_api: String::new(),
|
||||||
public_api: String::new(),
|
public_api: String::new(),
|
||||||
external_nodes: Vec::new(),
|
stats_source: Vec::new(),
|
||||||
|
public_nodes: Vec::new(),
|
||||||
database: String::new(),
|
database: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,3 +277,65 @@ impl Statistics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public node data
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PublicNode {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub height: String,
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicNode {
|
||||||
|
pub fn new() -> PublicNode {
|
||||||
|
PublicNode {
|
||||||
|
name: String::new(),
|
||||||
|
version: String::new(),
|
||||||
|
height: String::new(),
|
||||||
|
hash: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Connected node data
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ConnectedNode {
|
||||||
|
pub address: String,
|
||||||
|
pub user_agent: String,
|
||||||
|
pub location: String,
|
||||||
|
pub flag: String,
|
||||||
|
pub isp: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectedNode {
|
||||||
|
pub fn new() -> ConnectedNode {
|
||||||
|
ConnectedNode {
|
||||||
|
address: String::new(),
|
||||||
|
user_agent: String::new(),
|
||||||
|
location: String::new(),
|
||||||
|
flag: String::new(),
|
||||||
|
isp: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Network data
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct NetStats {
|
||||||
|
pub pub_nodes: Vec<PublicNode>,
|
||||||
|
pub reach_nodes: Vec<ConnectedNode>,
|
||||||
|
pub conn_nodes: Vec<ConnectedNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetStats {
|
||||||
|
pub fn new() -> NetStats {
|
||||||
|
NetStats {
|
||||||
|
pub_nodes: Vec::new(),
|
||||||
|
reach_nodes: Vec::new(),
|
||||||
|
conn_nodes: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -43,10 +43,19 @@ lazy_static! {
|
|||||||
Err(_e) => {},
|
Err(_e) => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
match toml.get_array("external_nodes") {
|
match toml.get_array("stats_source") {
|
||||||
Ok(nodes) => {
|
Ok(nodes) => {
|
||||||
for endpoint in nodes.clone() {
|
for endpoint in nodes.clone() {
|
||||||
cfg.external_nodes.push(endpoint.into_string().unwrap());
|
cfg.stats_source.push(endpoint.into_string().unwrap());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
match toml.get_array("public_nodes") {
|
||||||
|
Ok(nodes) => {
|
||||||
|
for endpoint in nodes.clone() {
|
||||||
|
cfg.public_nodes.push(endpoint.into_string().unwrap());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(_e) => {},
|
Err(_e) => {},
|
||||||
|
|||||||
+24
-4
@@ -12,7 +12,7 @@ use std::time::Duration;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tera_thousands::separate_with_commas;
|
use tera_thousands::separate_with_commas;
|
||||||
|
|
||||||
use crate::data::{Block, Dashboard, Kernel, Output, Statistics, Transactions, OUTPUT_SIZE, KERNEL_SIZE};
|
use crate::data::{Block, Dashboard, Kernel, NetStats, Output, Statistics, Transactions, OUTPUT_SIZE, KERNEL_SIZE};
|
||||||
use crate::exconfig::CONFIG;
|
use crate::exconfig::CONFIG;
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
@@ -387,6 +387,7 @@ fn donate() -> Template {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Rendering API Overview page.
|
// Rendering API Overview page.
|
||||||
#[get("/api_overview")]
|
#[get("/api_overview")]
|
||||||
fn api_overview() -> Template {
|
fn api_overview() -> Template {
|
||||||
@@ -398,6 +399,21 @@ fn api_overview() -> Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Rendering Network page.
|
||||||
|
#[get("/network")]
|
||||||
|
fn network(netstats: &State<Arc<Mutex<NetStats>>>) -> Template {
|
||||||
|
let data = netstats.lock().unwrap();
|
||||||
|
|
||||||
|
Template::render("network", context! {
|
||||||
|
route: "network",
|
||||||
|
pub_nodes: &data.pub_nodes,
|
||||||
|
reach_nodes: &data.reach_nodes,
|
||||||
|
reach_len: &data.reach_nodes.len(),
|
||||||
|
cg_api: CONFIG.coingecko_api.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Owner API.
|
// Owner API.
|
||||||
// Whitelisted methods: get_connected_peers, get_peers, get_status.
|
// Whitelisted methods: get_connected_peers, get_peers, get_status.
|
||||||
#[post("/v2/owner", data="<data>")]
|
#[post("/v2/owner", data="<data>")]
|
||||||
@@ -887,6 +903,8 @@ async fn main() {
|
|||||||
let txns_clone = txns.clone();
|
let txns_clone = txns.clone();
|
||||||
let stats = Arc::new(Mutex::new(Statistics::new()));
|
let stats = Arc::new(Mutex::new(Statistics::new()));
|
||||||
let stats_clone = stats.clone();
|
let stats_clone = stats.clone();
|
||||||
|
let netstats = Arc::new(Mutex::new(NetStats::new()));
|
||||||
|
let netstats_clone = netstats.clone();
|
||||||
|
|
||||||
let mut ready_data = false;
|
let mut ready_data = false;
|
||||||
let mut ready_stats = false;
|
let mut ready_stats = false;
|
||||||
@@ -925,7 +943,8 @@ async fn main() {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let result = worker::data(dash_clone.clone(), blocks_clone.clone(),
|
let result = worker::data(dash_clone.clone(), blocks_clone.clone(),
|
||||||
txns_clone.clone(), stats_clone.clone()).await;
|
txns_clone.clone(), stats_clone.clone(),
|
||||||
|
netstats_clone.clone()).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_v) => {
|
Ok(_v) => {
|
||||||
@@ -945,7 +964,7 @@ async fn main() {
|
|||||||
if date != date_now {
|
if date != date_now {
|
||||||
date = date_now;
|
date = date_now;
|
||||||
let result = worker::stats(dash_clone.clone(), txns_clone.clone(),
|
let result = worker::stats(dash_clone.clone(), txns_clone.clone(),
|
||||||
stats_clone.clone()).await;
|
stats_clone.clone(), netstats_clone.clone()).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_v) => {
|
Ok(_v) => {
|
||||||
@@ -976,6 +995,7 @@ async fn main() {
|
|||||||
.manage(blocks)
|
.manage(blocks)
|
||||||
.manage(txns)
|
.manage(txns)
|
||||||
.manage(stats)
|
.manage(stats)
|
||||||
|
.manage(netstats)
|
||||||
.mount("/", routes![index, peers_inbound, peers_outbound, sync_status, market_supply,
|
.mount("/", routes![index, peers_inbound, peers_outbound, sync_status, market_supply,
|
||||||
inflation_rate, volume_usd, volume_btc, price_usd, price_btc,
|
inflation_rate, volume_usd, volume_btc, price_usd, price_btc,
|
||||||
mcap_usd, mcap_btc,latest_height, disk_usage, network_hashrate,
|
mcap_usd, mcap_btc,latest_height, disk_usage, network_hashrate,
|
||||||
@@ -986,7 +1006,7 @@ async fn main() {
|
|||||||
soft_supply, production_cost, reward_ratio, breakeven_cost,
|
soft_supply, production_cost, reward_ratio, breakeven_cost,
|
||||||
last_block_age, block_list_by_height, block_list_index, search, kernel,
|
last_block_age, block_list_by_height, block_list_index, search, kernel,
|
||||||
output, api_owner, api_foreign, stats, unspent_outputs, kernels,
|
output, api_owner, api_foreign, stats, unspent_outputs, kernels,
|
||||||
emission, api_overview, donate, supply_raw])
|
emission, api_overview, donate, supply_raw, network])
|
||||||
.mount("/static", FileServer::from("static"))
|
.mount("/static", FileServer::from("static"))
|
||||||
.attach(Template::custom(|engines| {engines.tera.register_filter("separate_with_commas", separate_with_commas)}))
|
.attach(Template::custom(|engines| {engines.tera.register_filter("separate_with_commas", separate_with_commas)}))
|
||||||
.launch()
|
.launch()
|
||||||
|
|||||||
+124
-4
@@ -1,15 +1,17 @@
|
|||||||
use chrono::{Utc, DateTime};
|
use chrono::{Utc, DateTime};
|
||||||
|
use country_emoji::code_to_flag;
|
||||||
use fs_extra::dir::get_size;
|
use fs_extra::dir::get_size;
|
||||||
use humantime::format_duration;
|
use humantime::format_duration;
|
||||||
use num_format::{Locale, ToFormattedString};
|
use num_format::{Locale, ToFormattedString};
|
||||||
use reqwest::Error;
|
use reqwest::Error;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::net::{TcpStream, SocketAddr};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::data::{Block, Dashboard, Kernel, Output, Statistics, Transactions};
|
use crate::data::{Block, ConnectedNode, Dashboard, Kernel, NetStats, Output, PublicNode, Statistics, Transactions};
|
||||||
use crate::data::{KERNEL_WEIGHT, INPUT_WEIGHT, OUTPUT_WEIGHT, KERNEL_SIZE, INPUT_SIZE, OUTPUT_SIZE};
|
use crate::data::{KERNEL_WEIGHT, INPUT_WEIGHT, OUTPUT_WEIGHT, KERNEL_SIZE, INPUT_SIZE, OUTPUT_SIZE};
|
||||||
use crate::exconfig::CONFIG;
|
use crate::exconfig::CONFIG;
|
||||||
|
|
||||||
@@ -128,16 +130,20 @@ pub async fn get_mempool(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), anyhow:
|
|||||||
|
|
||||||
|
|
||||||
// Collecting: inbound, outbound, user_agent.
|
// Collecting: inbound, outbound, user_agent.
|
||||||
pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>, statistics: Arc<Mutex<Statistics>>) -> Result<(), anyhow::Error> {
|
pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>, statistics: Arc<Mutex<Statistics>>,
|
||||||
|
netstats: Arc<Mutex<NetStats>>) -> Result<(), anyhow::Error> {
|
||||||
let mut peers = HashMap::new();
|
let mut peers = HashMap::new();
|
||||||
let mut addrs = Vec::new();
|
let mut addrs = Vec::new();
|
||||||
|
let mut connected_nodes = Vec::<ConnectedNode>::new();
|
||||||
let mut inbound = 0;
|
let mut inbound = 0;
|
||||||
let mut outbound = 0;
|
let mut outbound = 0;
|
||||||
|
|
||||||
let resp = call("get_connected_peers", "[]", "1", "owner").await?;
|
let resp = call("get_connected_peers", "[]", "1", "owner").await?;
|
||||||
|
|
||||||
if resp != Value::Null {
|
if resp != Value::Null {
|
||||||
|
let mut node = ConnectedNode::new();
|
||||||
|
|
||||||
|
// Collecting peers from local node
|
||||||
for peer in resp["result"]["Ok"].as_array().unwrap() {
|
for peer in resp["result"]["Ok"].as_array().unwrap() {
|
||||||
if peer["direction"] == "Inbound" {
|
if peer["direction"] == "Inbound" {
|
||||||
inbound += 1;
|
inbound += 1;
|
||||||
@@ -149,22 +155,29 @@ pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>, statistics: A
|
|||||||
if !addrs.contains(&peer["addr"].to_string()) {
|
if !addrs.contains(&peer["addr"].to_string()) {
|
||||||
*peers.entry(peer["user_agent"].to_string()).or_insert(0) += 1;
|
*peers.entry(peer["user_agent"].to_string()).or_insert(0) += 1;
|
||||||
addrs.push(peer["addr"].to_string());
|
addrs.push(peer["addr"].to_string());
|
||||||
|
node.address = peer["addr"].as_str().unwrap().to_string();
|
||||||
|
node.user_agent = peer["user_agent"].as_str().unwrap().to_string();
|
||||||
|
connected_nodes.push(node.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collecting peers stats from external endpoints
|
// Collecting peers from external endpoints
|
||||||
for endpoint in CONFIG.external_nodes.clone() {
|
for endpoint in CONFIG.stats_source.clone() {
|
||||||
match call_external("get_connected_peers", "[]", "1", "owner", endpoint).await {
|
match call_external("get_connected_peers", "[]", "1", "owner", endpoint).await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if resp != Value::Null {
|
if resp != Value::Null {
|
||||||
|
let mut node = ConnectedNode::new();
|
||||||
if resp["result"]["Ok"].is_null() == false {
|
if resp["result"]["Ok"].is_null() == false {
|
||||||
for peer in resp["result"]["Ok"].as_array().unwrap() {
|
for peer in resp["result"]["Ok"].as_array().unwrap() {
|
||||||
// Collecting user_agent nodes stats
|
// Collecting user_agent nodes stats
|
||||||
if !addrs.contains(&peer["addr"].to_string()) {
|
if !addrs.contains(&peer["addr"].to_string()) {
|
||||||
*peers.entry(peer["user_agent"].to_string()).or_insert(0) += 1;
|
*peers.entry(peer["user_agent"].to_string()).or_insert(0) += 1;
|
||||||
addrs.push(peer["addr"].to_string());
|
addrs.push(peer["addr"].to_string());
|
||||||
|
node.address = peer["addr"].as_str().unwrap().to_string();
|
||||||
|
node.user_agent = peer["user_agent"].as_str().unwrap().to_string();
|
||||||
|
connected_nodes.push(node.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,6 +207,10 @@ pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>, statistics: A
|
|||||||
dash.inbound = inbound;
|
dash.inbound = inbound;
|
||||||
dash.outbound = outbound;
|
dash.outbound = outbound;
|
||||||
|
|
||||||
|
let mut nstats = netstats.lock().unwrap();
|
||||||
|
|
||||||
|
nstats.conn_nodes = connected_nodes.clone();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,3 +797,106 @@ pub async fn get_unspent_outputs(dashboard: Arc<Mutex<Dashboard>>) -> Result<(),
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get public nodes data
|
||||||
|
pub async fn get_pubnodes_stats(netstats: Arc<Mutex<NetStats>>) -> Result<(), anyhow::Error> {
|
||||||
|
let mut nodes = Vec::<PublicNode>::new();
|
||||||
|
|
||||||
|
for endpoint in CONFIG.public_nodes.clone() {
|
||||||
|
let mut node = PublicNode::new();
|
||||||
|
|
||||||
|
node.name = endpoint
|
||||||
|
.strip_prefix("https://")
|
||||||
|
.or_else(|| endpoint.strip_prefix("http://"))
|
||||||
|
.unwrap_or(&endpoint)
|
||||||
|
.split('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
match call_external("get_version", "[]", "1", "foreign", endpoint.clone()).await {
|
||||||
|
Ok(resp) => {
|
||||||
|
if resp != Value::Null {
|
||||||
|
node.version = resp["result"]["Ok"]["node_version"].as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{}", e);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
match call_external("get_tip", "[]", "1", "foreign", endpoint).await {
|
||||||
|
Ok(resp) => {
|
||||||
|
if resp != Value::Null {
|
||||||
|
node.height = resp["result"]["Ok"]["height"].to_string();
|
||||||
|
node.hash = resp["result"]["Ok"]["last_block_pushed"].as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{}", e);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut network = netstats.lock().unwrap();
|
||||||
|
|
||||||
|
network.pub_nodes = nodes.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn get_reachable_nodes(netstats: Arc<Mutex<NetStats>>) -> Result<(), anyhow::Error> {
|
||||||
|
let conn_nodes = get_conn_nodes(netstats.clone());
|
||||||
|
let mut reach_nodes = Vec::<ConnectedNode>::new();
|
||||||
|
|
||||||
|
for mut node in conn_nodes.clone() {
|
||||||
|
let socket_addr: SocketAddr = match node.address.parse() {
|
||||||
|
Ok(addr) => addr,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attempt to connect with a timeout
|
||||||
|
match TcpStream::connect_timeout(&socket_addr, Duration::from_millis(3000)) {
|
||||||
|
Ok(_) => {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
if let Some((ip, _port)) = node.address.split_once(':') {
|
||||||
|
//let url = format!("https://api.country.is/{}", ip);
|
||||||
|
let url = format!("http://ip-api.com/json/{}", ip);
|
||||||
|
|
||||||
|
let resp: Value = client.get(&url).send().await?.json().await?;
|
||||||
|
if resp != Value::Null && resp["status"] == "success" {
|
||||||
|
if let Some(code) = resp["countryCode"].as_str() {
|
||||||
|
node.location = resp["country"].as_str().unwrap().to_string();
|
||||||
|
node.isp = resp["isp"].as_str().unwrap().to_string();
|
||||||
|
node.flag = code_to_flag(code).unwrap().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reach_nodes.contains(&node) {
|
||||||
|
reach_nodes.push(node.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
reach_nodes.retain(|value| value.address != node.address);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nstats = netstats.lock().unwrap();
|
||||||
|
|
||||||
|
nstats.reach_nodes = reach_nodes.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_conn_nodes(netstats: Arc<Mutex<NetStats>>) -> Vec<ConnectedNode> {
|
||||||
|
let nstats = netstats.lock().unwrap();
|
||||||
|
|
||||||
|
nstats.conn_nodes.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use crate::data::Block;
|
use crate::data::Block;
|
||||||
use crate::data::Dashboard;
|
use crate::data::Dashboard;
|
||||||
|
use crate::data::NetStats;
|
||||||
use crate::data::Statistics;
|
use crate::data::Statistics;
|
||||||
use crate::data::Transactions;
|
use crate::data::Transactions;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
@@ -12,23 +13,26 @@ use crate::requests;
|
|||||||
|
|
||||||
// Collecting main data.
|
// Collecting main data.
|
||||||
pub async fn data(dash: Arc<Mutex<Dashboard>>, blocks: Arc<Mutex<Vec<Block>>>,
|
pub async fn data(dash: Arc<Mutex<Dashboard>>, blocks: Arc<Mutex<Vec<Block>>>,
|
||||||
txns: Arc<Mutex<Transactions>>, stats: Arc<Mutex<Statistics>>) -> Result<(), anyhow::Error> {
|
txns: Arc<Mutex<Transactions>>, stats: Arc<Mutex<Statistics>>,
|
||||||
|
netstats: Arc<Mutex<NetStats>>) -> Result<(), anyhow::Error> {
|
||||||
let _ = requests::get_status(dash.clone()).await?;
|
let _ = requests::get_status(dash.clone()).await?;
|
||||||
let _ = requests::get_mempool(dash.clone()).await?;
|
let _ = requests::get_mempool(dash.clone()).await?;
|
||||||
let _ = requests::get_connected_peers(dash.clone(), stats.clone()).await?;
|
let _ = requests::get_connected_peers(dash.clone(), stats.clone(), netstats.clone()).await?;
|
||||||
let _ = requests::get_market(dash.clone()).await?;
|
let _ = requests::get_market(dash.clone()).await?;
|
||||||
let _ = requests::get_disk_usage(dash.clone())?;
|
let _ = requests::get_disk_usage(dash.clone())?;
|
||||||
let _ = requests::get_mining_stats(dash.clone()).await?;
|
let _ = requests::get_mining_stats(dash.clone()).await?;
|
||||||
let _ = requests::get_recent_blocks(dash.clone(), blocks.clone()).await?;
|
let _ = requests::get_recent_blocks(dash.clone(), blocks.clone()).await?;
|
||||||
let _ = requests::get_txn_stats(dash.clone(), txns.clone()).await?;
|
let _ = requests::get_txn_stats(dash.clone(), txns.clone()).await?;
|
||||||
|
let _ = requests::get_pubnodes_stats(netstats.clone()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collecting statistics.
|
// Collecting statistics.
|
||||||
pub async fn stats(dash: Arc<Mutex<Dashboard>>, txns: Arc<Mutex<Transactions>>, stats: Arc<Mutex<Statistics>>) -> Result<(), anyhow::Error> {
|
pub async fn stats(dash: Arc<Mutex<Dashboard>>, txns: Arc<Mutex<Transactions>>, stats: Arc<Mutex<Statistics>>, netstats: Arc<Mutex<NetStats>>) -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
let _ = requests::get_unspent_outputs(dash.clone()).await?;
|
let _ = requests::get_unspent_outputs(dash.clone()).await?;
|
||||||
|
let _ = requests::get_reachable_nodes(netstats.clone()).await?;
|
||||||
|
|
||||||
let mut stats = stats.lock().unwrap();
|
let mut stats = stats.lock().unwrap();
|
||||||
let dash = dash.lock().unwrap();
|
let dash = dash.lock().unwrap();
|
||||||
|
|||||||
@@ -50,6 +50,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/network"><i class="bi bi-hdd-network"></i> Network</a>
|
||||||
|
</li>
|
||||||
{% elif route == "block_list" or route == "block_list_by_height" %}
|
{% elif route == "block_list" or route == "block_list_by_height" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
||||||
@@ -63,6 +66,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/network"><i class="bi bi-hdd-network"></i> Network</a>
|
||||||
|
</li>
|
||||||
{% elif route == "stats" %}
|
{% elif route == "stats" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
||||||
@@ -76,6 +82,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/network"><i class="bi bi-hdd-network"></i> Network</a>
|
||||||
|
</li>
|
||||||
{% elif route == "emission" %}
|
{% elif route == "emission" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
||||||
@@ -89,6 +98,25 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/emission"><div class="darkorange-text"><i class="bi bi-graph-down-arrow"></i> Emission</div></a>
|
<a class="nav-link" href="/emission"><div class="darkorange-text"><i class="bi bi-graph-down-arrow"></i> Emission</div></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/network"><i class="bi bi-hdd-network"></i> Network</a>
|
||||||
|
</li>
|
||||||
|
{% elif route == "network" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/block_list"><i class="bi bi-box"></i> Blocks</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/stats"><i class="bi bi-calculator"></i> Statistics</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/network"><div class="darkorange-text"><i class="bi bi-hdd-network"></i> Network</div></a>
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="bi bi-speedometer"></i> Dashboard</a>
|
||||||
@@ -102,6 +130,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
<a class="nav-link" href="/emission"><i class="bi bi-graph-down-arrow"></i> Emission</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/network"><i class="bi bi-hdd-network"></i> Network</a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<code>
|
||||||
|
|
||||||
|
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body" align="center">
|
||||||
|
<div class="value-text">
|
||||||
|
<div class="darkorange-text">PUBLIC NODES</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none d-md-block"> <!-- Show on >= md screens -->
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="card border-start-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
NAME
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
VERSION
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
LATEST BLOCK
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-end-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
BLOCK HASH
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% for node in pub_nodes %}
|
||||||
|
<div class="card-group rounded-0">
|
||||||
|
<div class="card border-top-0 border-start-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.version }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.height }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">
|
||||||
|
<a class="text-decoration-none" href="/hash/{{ node.hash }}">
|
||||||
|
{{ node.hash | truncate(length=15) }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="d-md-none"> <!-- Show on < md screens-->
|
||||||
|
{% for node in pub_nodes %}
|
||||||
|
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body" align="left">
|
||||||
|
<div class="d-flex justify-content-start">
|
||||||
|
<i class="bi bi-pc-display-horizontal darkorange-text"></i>
|
||||||
|
<div class="darkorange-text">{{ node.name }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">Version</div>
|
||||||
|
<div class="value-text">{{ node.version }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">Latest Block</div>
|
||||||
|
<div class="value-text">{{ node.height }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">Block Hash</div>
|
||||||
|
<div class="value-text">
|
||||||
|
<a class="text-decoration-none" href="/hash/{{ node.hash }}">
|
||||||
|
{{ node.hash | truncate(length=15) }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body" align="center">
|
||||||
|
<div class="value-text">
|
||||||
|
<div class="darkorange-text">REACHABLE NODES ({{ reach_len }})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none d-md-block"> <!-- Show on >= md screens -->
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="card border-start-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
ADDRESS
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
USER AGENT
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
ISP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-end-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
LOCATION
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% for node in reach_nodes %}
|
||||||
|
<div class="card-group rounded-0">
|
||||||
|
<div class="card border-top-0 border-start-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.address }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.user_agent }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.isp }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">{{ node.location }} {{ node.flag }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="d-md-none"> <!-- Show on < md screens-->
|
||||||
|
{% for node in reach_nodes %}
|
||||||
|
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body" align="left">
|
||||||
|
<div class="d-flex justify-content-start">
|
||||||
|
<i class="bi bi-pc-display-horizontal darkorange-text"></i>
|
||||||
|
<div class="darkorange-text">{{ node.address }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">User Agent</div>
|
||||||
|
<div class="value-text">{{ node.user_agent }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">ISP</div>
|
||||||
|
<div class="value-text">{{ node.isp }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">Location</div>
|
||||||
|
<div class="value-text">{{ node.location }} {{ node.flag }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</code>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user