mirror of
https://github.com/transatoshi-mw/grin-explorer.git
synced 2025-10-21 13:33:41 +00:00
adding node stats
This commit is contained in:
700
Cargo.lock
generated
700
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grin-explorer"
|
name = "grin-explorer"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
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
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Node address.
|
# Node address.
|
||||||
ip = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
|
|
||||||
# Node port.
|
# Node port.
|
||||||
port = "3413"
|
port = "3413"
|
||||||
@@ -25,9 +25,12 @@ coingecko_api = "enabled"
|
|||||||
# Enable or disable node POST API public access.
|
# Enable or disable node POST API public access.
|
||||||
public_api = "enabled"
|
public_api = "enabled"
|
||||||
|
|
||||||
|
# List of external node endpoints, used for peer stats.
|
||||||
|
# Comment out if you wish, only local peer stats will be used then.
|
||||||
|
# external_nodes = ["https://grinnode.live:3413", "https://grincoin.org"]
|
||||||
|
|
||||||
# Grinnode config
|
# Grinnode config
|
||||||
# ip = "grinnode.live"
|
# host = "grinnode.live"
|
||||||
# port = "3413"
|
# port = "3413"
|
||||||
# proto = "https"
|
# proto = "https"
|
||||||
# coingecko_api = "enabled"
|
# coingecko_api = "enabled"
|
||||||
@@ -35,14 +38,14 @@ public_api = "enabled"
|
|||||||
|
|
||||||
|
|
||||||
# Grincoin config
|
# Grincoin config
|
||||||
# ip = "grincoin.org"
|
# host = "grincoin.org"
|
||||||
# proto = "https"
|
# proto = "https"
|
||||||
# coingecko_api = "enabled"
|
# coingecko_api = "enabled"
|
||||||
# public_api = "enabled"
|
# public_api = "enabled"
|
||||||
|
|
||||||
|
|
||||||
# Testnet config
|
# Testnet config
|
||||||
# ip = "127.0.0.1"
|
# host = "127.0.0.1"
|
||||||
# port = "13413"
|
# port = "13413"
|
||||||
# proto = "http"
|
# proto = "http"
|
||||||
# user = "grin"
|
# user = "grin"
|
||||||
|
26
src/data.rs
26
src/data.rs
@@ -155,7 +155,7 @@ impl Transactions {
|
|||||||
// Explorer configuration
|
// Explorer configuration
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExplorerConfig {
|
pub struct ExplorerConfig {
|
||||||
pub ip: String,
|
pub host: String,
|
||||||
pub port: String,
|
pub port: String,
|
||||||
pub proto: String,
|
pub proto: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
@@ -166,12 +166,13 @@ 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExplorerConfig {
|
impl ExplorerConfig {
|
||||||
pub fn new() -> ExplorerConfig {
|
pub fn new() -> ExplorerConfig {
|
||||||
ExplorerConfig {
|
ExplorerConfig {
|
||||||
ip: String::new(),
|
host: String::new(),
|
||||||
port: String::new(),
|
port: String::new(),
|
||||||
proto: String::new(),
|
proto: String::new(),
|
||||||
user: String::new(),
|
user: String::new(),
|
||||||
@@ -182,10 +183,12 @@ 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Output data
|
// Output data
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
@@ -208,3 +211,22 @@ impl Output {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Statistics data
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Statistics {
|
||||||
|
pub user_agent: Vec<String>,
|
||||||
|
pub count: Vec<String>,
|
||||||
|
pub total: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Statistics {
|
||||||
|
pub fn new() -> Statistics {
|
||||||
|
Statistics {
|
||||||
|
user_agent: Vec::new(),
|
||||||
|
count: Vec::new(),
|
||||||
|
total: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
23
src/main.rs
23
src/main.rs
@@ -17,6 +17,7 @@ use crate::data::Block;
|
|||||||
use crate::data::Dashboard;
|
use crate::data::Dashboard;
|
||||||
use crate::data::Kernel;
|
use crate::data::Kernel;
|
||||||
use crate::data::Output;
|
use crate::data::Output;
|
||||||
|
use crate::data::Statistics;
|
||||||
use crate::data::Transactions;
|
use crate::data::Transactions;
|
||||||
use crate::requests::CONFIG;
|
use crate::requests::CONFIG;
|
||||||
|
|
||||||
@@ -225,6 +226,21 @@ pub async fn search(input: Option<&str>) -> Either<Template, Redirect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Rendering Statistics page.
|
||||||
|
#[get("/stats")]
|
||||||
|
fn stats(statistics: &State<Arc<Mutex<Statistics>>>) -> Template {
|
||||||
|
let data = statistics.lock().unwrap();
|
||||||
|
|
||||||
|
Template::render("stats", context! {
|
||||||
|
route: "stats",
|
||||||
|
user_agent: data.user_agent.clone(),
|
||||||
|
count: data.count.clone(),
|
||||||
|
total: data.total,
|
||||||
|
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>")]
|
||||||
@@ -649,13 +665,15 @@ async fn main() {
|
|||||||
let blocks_clone = blocks.clone();
|
let blocks_clone = blocks.clone();
|
||||||
let txns = Arc::new(Mutex::new(Transactions::new()));
|
let txns = Arc::new(Mutex::new(Transactions::new()));
|
||||||
let txns_clone = txns.clone();
|
let txns_clone = txns.clone();
|
||||||
|
let stats = Arc::new(Mutex::new(Statistics::new()));
|
||||||
|
let stats_clone = stats.clone();
|
||||||
let mut ready = false;
|
let mut ready = false;
|
||||||
|
|
||||||
// Starting the Worker
|
// Starting the Worker
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let result = worker::run(dash_clone.clone(), blocks_clone.clone(),
|
let result = worker::run(dash_clone.clone(), blocks_clone.clone(),
|
||||||
txns_clone.clone()).await;
|
txns_clone.clone(), stats_clone.clone()).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_v) => {
|
Ok(_v) => {
|
||||||
@@ -679,6 +697,7 @@ async fn main() {
|
|||||||
.manage(dash)
|
.manage(dash)
|
||||||
.manage(blocks)
|
.manage(blocks)
|
||||||
.manage(txns)
|
.manage(txns)
|
||||||
|
.manage(stats)
|
||||||
.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,
|
||||||
@@ -688,7 +707,7 @@ async fn main() {
|
|||||||
block_weight, block_details_by_height, block_header_by_hash,
|
block_weight, block_details_by_height, block_header_by_hash,
|
||||||
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])
|
output, api_owner, api_foreign, stats])
|
||||||
.mount("/static", FileServer::from("static"))
|
.mount("/static", FileServer::from("static"))
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
.launch()
|
.launch()
|
||||||
|
156
src/requests.rs
156
src/requests.rs
@@ -11,36 +11,60 @@ use std::collections::HashMap;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::data::Dashboard;
|
|
||||||
use crate::data::Block;
|
use crate::data::Block;
|
||||||
use crate::data::Transactions;
|
use crate::data::Dashboard;
|
||||||
use crate::data::ExplorerConfig;
|
use crate::data::ExplorerConfig;
|
||||||
use crate::data::Kernel;
|
use crate::data::Kernel;
|
||||||
use crate::data::Output;
|
use crate::data::Output;
|
||||||
|
use crate::data::Statistics;
|
||||||
|
use crate::data::Transactions;
|
||||||
|
|
||||||
|
|
||||||
// Static explorer config structure
|
// Static explorer config structure
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CONFIG: ExplorerConfig = {
|
pub static ref CONFIG: ExplorerConfig = {
|
||||||
let mut cfg = ExplorerConfig::new();
|
let mut cfg = ExplorerConfig::new();
|
||||||
let settings = Config::builder().add_source(config::File::with_name("Explorer"))
|
let toml = Config::builder().add_source(config::File::with_name("Explorer")).build().unwrap();
|
||||||
.build().unwrap();
|
|
||||||
|
|
||||||
let settings: HashMap<String, String> = settings.try_deserialize().unwrap();
|
// Mandatory settings
|
||||||
|
cfg.host = toml.get_string("host").unwrap();
|
||||||
for (name, value) in settings {
|
cfg.proto = toml.get_string("proto").unwrap();
|
||||||
match name.as_str() {
|
cfg.coingecko_api = toml.get_string("coingecko_api").unwrap();
|
||||||
"ip" => cfg.ip = value,
|
cfg.public_api = toml.get_string("public_api").unwrap();
|
||||||
"port" => cfg.port = value,
|
|
||||||
"proto" => cfg.proto = value,
|
// Optional settings
|
||||||
"user" => cfg.user = value,
|
match toml.get_string("port") {
|
||||||
"api_secret_path" => cfg.api_secret_path = value,
|
Ok(v) => cfg.port = v,
|
||||||
"foreign_api_secret_path" => cfg.foreign_api_secret_path = value,
|
Err(_e) => {},
|
||||||
"grin_dir" => cfg.grin_dir = value,
|
}
|
||||||
"coingecko_api" => cfg.coingecko_api = value,
|
|
||||||
"public_api" => cfg.public_api = value,
|
match toml.get_string("user") {
|
||||||
_ => error!("unknown config setting '{}'.", name),
|
Ok(v) => cfg.user = v,
|
||||||
}
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
match toml.get_string("api_secret_path") {
|
||||||
|
Ok(v) => cfg.api_secret_path = v,
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
match toml.get_string("foreign_api_secret_path") {
|
||||||
|
Ok(v) => cfg.foreign_api_secret_path = v,
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
match toml.get_string("grin_dir") {
|
||||||
|
Ok(v) => cfg.grin_dir = v,
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
match toml.get_array("external_nodes") {
|
||||||
|
Ok(nodes) => {
|
||||||
|
for endpoint in nodes.clone() {
|
||||||
|
cfg.external_nodes.push(endpoint.into_string().unwrap());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_e) => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.api_secret_path.is_empty() == false {
|
if cfg.api_secret_path.is_empty() == false {
|
||||||
@@ -66,9 +90,9 @@ pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Resul
|
|||||||
let secret;
|
let secret;
|
||||||
|
|
||||||
if CONFIG.port.is_empty() == false {
|
if CONFIG.port.is_empty() == false {
|
||||||
rpc_url = format!("{}://{}:{}/v2/{}", CONFIG.proto, CONFIG.ip, CONFIG.port, rpc_type);
|
rpc_url = format!("{}://{}:{}/v2/{}", CONFIG.proto, CONFIG.host, CONFIG.port, rpc_type);
|
||||||
} else {
|
} else {
|
||||||
rpc_url = format!("{}://{}/v2/{}", CONFIG.proto, CONFIG.ip, rpc_type);
|
rpc_url = format!("{}://{}/v2/{}", CONFIG.proto, CONFIG.host, rpc_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if rpc_type == "owner" {
|
if rpc_type == "owner" {
|
||||||
@@ -96,6 +120,39 @@ pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RPC requests to grin node.
|
||||||
|
// The same call as above but with the option to add ip, proto and port.
|
||||||
|
pub async fn call_external(method: &str, params: &str, id: &str, rpc_type: &str, endpoint: String) -> Result<Value, anyhow::Error> {
|
||||||
|
let rpc_url;
|
||||||
|
let secret;
|
||||||
|
|
||||||
|
rpc_url = format!("{}/v2/{}", endpoint, rpc_type);
|
||||||
|
|
||||||
|
if rpc_type == "owner" {
|
||||||
|
secret = CONFIG.api_secret.clone();
|
||||||
|
} else {
|
||||||
|
secret = CONFIG.foreign_api_secret.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = client.post(rpc_url)
|
||||||
|
.body(format!("{{\"method\": \"{}\", \"params\": {}, \"id\": {}, \"jsonrpc\": \"2.0\"}}", method, params, id))
|
||||||
|
.basic_auth(CONFIG.user.clone(), Some(secret))
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match result.error_for_status_ref() {
|
||||||
|
Ok(_res) => (),
|
||||||
|
Err(err) => { error!("rpc failed, status code: {:?}", err.status().unwrap()); },
|
||||||
|
}
|
||||||
|
|
||||||
|
let val: Value = serde_json::from_str(&result.text().await?)?;
|
||||||
|
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Collecting: height, sync, node_ver, proto_ver.
|
// Collecting: height, sync, node_ver, proto_ver.
|
||||||
pub async fn get_status(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), anyhow::Error> {
|
pub async fn get_status(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), anyhow::Error> {
|
||||||
let resp = call("get_status", "[]", "1", "owner").await?;
|
let resp = call("get_status", "[]", "1", "owner").await?;
|
||||||
@@ -129,16 +186,15 @@ pub async fn get_mempool(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), anyhow:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Collecting: inbound, outbound.
|
// Collecting: inbound, outbound, user_agent.
|
||||||
pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>)
|
pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>, statistics: Arc<Mutex<Statistics>>) -> Result<(), anyhow::Error> {
|
||||||
-> Result<(), anyhow::Error> {
|
let mut peers = HashMap::new();
|
||||||
let resp = call("get_connected_peers", "[]", "1", "owner").await?;
|
let mut inbound = 0;
|
||||||
|
let mut outbound = 0;
|
||||||
|
|
||||||
let mut data = dashboard.lock().unwrap();
|
let resp = call("get_connected_peers", "[]", "1", "owner").await?;
|
||||||
|
|
||||||
if resp != Value::Null {
|
if resp != Value::Null {
|
||||||
let mut inbound = 0;
|
|
||||||
let mut outbound = 0;
|
|
||||||
|
|
||||||
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" {
|
||||||
@@ -147,11 +203,47 @@ pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>)
|
|||||||
if peer["direction"] == "Outbound" {
|
if peer["direction"] == "Outbound" {
|
||||||
outbound += 1;
|
outbound += 1;
|
||||||
}
|
}
|
||||||
|
// Collecting user_agent nodes stats
|
||||||
|
*peers.entry(peer["user_agent"].to_string()).or_insert(0) += 1;
|
||||||
}
|
}
|
||||||
data.inbound = inbound;
|
|
||||||
data.outbound = outbound;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collecting peers stats from external endpoints
|
||||||
|
for endpoint in CONFIG.external_nodes.clone() {
|
||||||
|
match call_external("get_connected_peers", "[]", "1", "owner", endpoint).await {
|
||||||
|
Ok(resp) => {
|
||||||
|
if resp != Value::Null {
|
||||||
|
for peer in resp["result"]["Ok"].as_array().unwrap() {
|
||||||
|
// Collecting user_agent nodes stats
|
||||||
|
*peers.entry(peer["user_agent"].to_string()).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => warn!("{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort HashMap into Vec
|
||||||
|
let mut peers_vec: Vec<(&String, &u32)> = peers.iter().collect();
|
||||||
|
peers_vec.sort_by(|a, b| b.1.cmp(a.1));
|
||||||
|
|
||||||
|
let mut dash = dashboard.lock().unwrap();
|
||||||
|
let mut stats = statistics.lock().unwrap();
|
||||||
|
|
||||||
|
stats.user_agent.clear();
|
||||||
|
stats.count.clear();
|
||||||
|
stats.total = 0;
|
||||||
|
|
||||||
|
for v in peers_vec {
|
||||||
|
stats.total = stats.total + v.1;
|
||||||
|
stats.user_agent.push(v.0.to_string());
|
||||||
|
stats.count.push(v.1.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
dash.inbound = inbound;
|
||||||
|
dash.outbound = outbound;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +312,7 @@ pub fn get_disk_usage(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
|
|||||||
match get_size(chain_dir.clone()) {
|
match get_size(chain_dir.clone()) {
|
||||||
Ok(chain_size) => data.disk_usage = format!("{:.2}", (chain_size as f64) / 1000.0 / 1000.0 / 1000.0),
|
Ok(chain_size) => data.disk_usage = format!("{:.2}", (chain_size as f64) / 1000.0 / 1000.0 / 1000.0),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if CONFIG.ip == "127.0.0.1" || CONFIG.ip == "0.0.0.0" {
|
if CONFIG.host == "127.0.0.1" || CONFIG.host == "0.0.0.0" {
|
||||||
error!("{}: \"{}\"", e, chain_dir);
|
error!("{}: \"{}\"", e, chain_dir);
|
||||||
} else {
|
} else {
|
||||||
// Ignore error for external node connection
|
// Ignore error for external node connection
|
||||||
|
@@ -1,18 +1,20 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::data::Dashboard;
|
|
||||||
use crate::data::Block;
|
use crate::data::Block;
|
||||||
|
use crate::data::Dashboard;
|
||||||
|
use crate::data::Statistics;
|
||||||
use crate::data::Transactions;
|
use crate::data::Transactions;
|
||||||
|
|
||||||
use crate::requests;
|
use crate::requests;
|
||||||
|
|
||||||
|
|
||||||
// Tokio Runtime Worker.
|
// Tokio Runtime Worker.
|
||||||
// Collecting all the data.
|
// Collecting all the data.
|
||||||
pub async fn run(dash: Arc<Mutex<Dashboard>>, blocks: Arc<Mutex<Vec<Block>>>,
|
pub async fn run(dash: Arc<Mutex<Dashboard>>, blocks: Arc<Mutex<Vec<Block>>>,
|
||||||
txns: Arc<Mutex<Transactions>>) -> Result<(), anyhow::Error> {
|
txns: Arc<Mutex<Transactions>>, stats: Arc<Mutex<Statistics>>) -> 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()).await?;
|
let _ = requests::get_connected_peers(dash.clone(), stats.clone()).await?;
|
||||||
let _ = requests::get_market(dash.clone()).await?;
|
let _ = requests::get_market(dash.clone()).await?;
|
||||||
requests::get_disk_usage(dash.clone())?;
|
requests::get_disk_usage(dash.clone())?;
|
||||||
let _ = requests::get_mining_stats(dash.clone()).await?;
|
let _ = requests::get_mining_stats(dash.clone()).await?;
|
||||||
|
20
static/scripts/chart.js
Normal file
20
static/scripts/chart.js
Normal file
File diff suppressed because one or more lines are too long
@@ -44,6 +44,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/block_list"><i class="bi bi-box"></i> Blocks</a>
|
<a class="nav-link" href="/block_list"><i class="bi bi-box"></i> Blocks</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/stats"><i class="bi bi-calculator"></i> Statistics</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>
|
||||||
@@ -51,6 +54,19 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/block_list"><div class="darkorange-text"><i class="bi bi-box"></i> Blocks</div></a>
|
<a class="nav-link" href="/block_list"><div class="darkorange-text"><i class="bi bi-box"></i> Blocks</div></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/stats"><i class="bi bi-calculator"></i> Statistics</a>
|
||||||
|
</li>
|
||||||
|
{% elif route == "stats" %}
|
||||||
|
<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"><div class="darkorange-text"><i class="bi bi-calculator"></i> Statistics</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>
|
||||||
@@ -58,6 +74,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/block_list"><i class="bi bi-box"></i> Blocks</a>
|
<a class="nav-link" href="/block_list"><i class="bi bi-box"></i> Blocks</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/stats"><i class="bi bi-calculator"></i> Statistics</a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -220,7 +239,7 @@
|
|||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col d-flex justify-content-center">
|
<div class="col d-flex justify-content-center">
|
||||||
<a class="text-decoration-none me-2" href="https://github.com/aglkm/grin-explorer">
|
<a class="text-decoration-none me-2" href="https://github.com/aglkm/grin-explorer">
|
||||||
<span style="color:grey"><i class="bi bi-github me-1"></i>v.0.1.5</span>
|
<span style="color:grey"><i class="bi bi-github me-1"></i>v.0.1.6</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="text-decoration-none" href="/search">
|
<a class="text-decoration-none" href="/search">
|
||||||
<span style="color:grey"><i class="bi bi-search me-1"></i>Search</span>
|
<span style="color:grey"><i class="bi bi-search me-1"></i>Search</span>
|
||||||
|
132
templates/stats.html.tera
Normal file
132
templates/stats.html.tera
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<script src="/static/scripts/chart.js"></script>
|
||||||
|
|
||||||
|
<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"><i class="bi bi-pc-display-horizontal"></i> NODE VERSION STATS ({{ total }})</div>
|
||||||
|
<div style="position: relative; height:60vh; width:90vw"><canvas id="1"></canvas></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none d-sm-block"> <!-- Show on >= sm screens -->
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="card border-start-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
#
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
VERSION
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-end-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
COUNT
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for v in user_agent %}
|
||||||
|
<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">
|
||||||
|
{{ loop.index }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">
|
||||||
|
{{ v }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card border-top-0 border-end-0 rounded-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="value-text">
|
||||||
|
{{ count[loop.index0] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-sm-none"> <!-- Show on < sm screens-->
|
||||||
|
{% for v in user_agent %}
|
||||||
|
{% if loop.index0 == 0 %}
|
||||||
|
<div class="card border-start-0 rounded-0">
|
||||||
|
{% else %}
|
||||||
|
<div class="card border-top-0 border-start-0 rounded-0">
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-start">
|
||||||
|
<div class="darkorange-text">
|
||||||
|
#{{ loop.index }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="value-text">
|
||||||
|
{{ v }}
|
||||||
|
</div>
|
||||||
|
<div class="value-text">
|
||||||
|
{{ count[loop.index0] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var ctx = document.getElementById('1').getContext('2d');
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: 'gray',
|
||||||
|
},
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
labels: {{ user_agent }},
|
||||||
|
datasets: [{
|
||||||
|
label: " Count",
|
||||||
|
borderWidth: 1,
|
||||||
|
data: {{ count }}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(document.getElementById("1"), {
|
||||||
|
type: 'pie',
|
||||||
|
data: data,
|
||||||
|
options: options
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
Reference in New Issue
Block a user