diff --git a/src/main.rs b/src/main.rs index 5b7b17f..81ae0ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,20 @@ extern crate rocket; use readable::byte::Byte; use readable::up::UptimeFull; -use rocket::serde::{Serialize, Deserialize}; -use rocket::{get, launch, routes, State, fairing::AdHoc}; -use rocket_dyn_templates::{context, Template}; -use std::path::Path; +use readable::date::Date; +use readable::time::{secs_to_clock, unix_clock}; use reqwest; +use rocket::serde::{Deserialize, Serialize}; +use rocket::{fairing::AdHoc, get, launch, routes, State}; +use rocket_dyn_templates::{context, Template}; use std::collections::HashMap; -use sysinfo::{Components, Disks, Networks, System}; -use vnstat_parse::{Error, Vnstat}; +use std::path::Path; +use sysinfo::{Components, Disks, System}; use tokio::task; +use vnstat_parse::Vnstat; mod monero_rpc; -use monero_rpc::{NodeInfo, NodeInfoInner}; +use monero_rpc::{NodeInfoOuter, NodeInfo, BlockHeaderInfosOuter, BlockHeaderInfo}; #[derive(Serialize, Debug)] struct ComponentInfo { @@ -53,6 +55,7 @@ struct SysInfo { disks: Vec, components: Vec, network_ifs: Vec, + network_ifs_errored: Vec<(String, String)>, hostname: Option, operating_system: Option, uptime: String, @@ -96,16 +99,19 @@ fn get_system_info(network_if_names: &Vec) -> SysInfo { } let mut network_ifs = Vec::new(); + let mut network_ifs_errored = Vec::new(); for network_if_name in network_if_names { - // TODO: Fail more gracefully - let vnstat_data = Vnstat::get(&network_if_name).unwrap(); - println!("{:?}", vnstat_data); - network_ifs.push(NetworkInfo{ - name: network_if_name.to_string(), - total_sent_today: format!("{} {}", vnstat_data.day_tx, vnstat_data.day_tx_unit), - total_received_today: format!("{} {}", vnstat_data.day_rx, vnstat_data.day_rx_unit), - average_rate_today: format!("{} {}", vnstat_data.day_avg_rate, vnstat_data.day_avg_rate_unit), - }) + match Vnstat::get(&network_if_name) { + Ok(vns) => { + network_ifs.push(NetworkInfo { + name: network_if_name.to_string(), + total_sent_today: format!("{} {}", vns.day_tx, vns.day_tx_unit), + total_received_today: format!("{} {}", vns.day_rx, vns.day_rx_unit), + average_rate_today: format!("{} {}", vns.day_avg_rate, vns.day_avg_rate_unit), + }); + } + Err(e) => network_ifs_errored.push((network_if_name.to_string(), e.to_string())), + } } SysInfo { @@ -118,6 +124,7 @@ fn get_system_info(network_if_names: &Vec) -> SysInfo { used_swap: Byte::from(sys.used_swap()).to_string(), }, network_ifs: network_ifs, + network_ifs_errored: network_ifs_errored, hostname: System::host_name(), operating_system: System::long_os_version(), uptime: UptimeFull::from(System::uptime()).to_string(), @@ -126,8 +133,7 @@ fn get_system_info(network_if_names: &Vec) -> SysInfo { } } - -fn get_node_info() -> NodeInfoInner { +fn get_node_info(json_rpc_url: &str) -> NodeInfo { let mut map = HashMap::new(); map.insert("jsonrpc", "2.0"); map.insert("id", "0"); @@ -137,9 +143,14 @@ fn get_node_info() -> NodeInfoInner { let client = reqwest::blocking::Client::new(); // TODO: Properly handle this unwrap - half the point of this is to capture // when the node is down - don't want it crashing when - let resp = client.post("http://127.0.0.1:18081/json_rpc").json(&map).send().unwrap(); - resp.json::() - }).unwrap(); + let resp = client + .post(json_rpc_url) + .json(&map) + .send() + .unwrap(); + resp.json::() + }) + .unwrap(); let mut node_info = node_info.result; // TODO: Put this into a Serialize method let approx_uptime = node_info.adjusted_time - node_info.start_time; @@ -149,19 +160,78 @@ fn get_node_info() -> NodeInfoInner { node_info } +#[derive(Serialize)] +struct RpcOuter { + jsonrpc: String, + id: String, + method: String, + params: HeightRange, +} + +#[derive(Serialize)] +struct HeightRange { + start_height: u64, + end_height: u64, +} + +fn get_latest_twenty_blocks(json_rpc_url: &str, current_height: u64) -> Vec { + let rpc_call = RpcOuter { + jsonrpc: "2.0".to_string(), + id: "0".to_string(), + method: "get_block_headers_range".to_string(), + params: HeightRange { + start_height: current_height - 20, + end_height: current_height - 1, + }, + }; + + let blocks = task::block_in_place(move || { + let client = reqwest::blocking::Client::new(); + // TODO: Properly handle this unwrap - half the point of this is to capture + // when the node is down - don't want it crashing when + let resp = client + .post(json_rpc_url) + .json(&rpc_call) + .send() + .unwrap(); + resp.json::() + }).unwrap(); + let mut latest_twenty_blocks = blocks.result.headers; + for i in 0..latest_twenty_blocks.len() { + let clock_time = secs_to_clock(unix_clock(latest_twenty_blocks[i].timestamp)); + let date = Date::from_unix(latest_twenty_blocks[i].timestamp).unwrap(); + latest_twenty_blocks[i].timestamp_pretty = format!("{} {:02}-{:02}-{:02}", date, clock_time.0, clock_time.1, clock_time.2); + } + latest_twenty_blocks +} + #[derive(Deserialize)] #[serde(crate = "rocket::serde")] struct Config { network_interfaces: Vec, + json_rpc_url: String, +} + +// TODO Figure out why this isn't being loaded with the AdHoc thing +// I suspect I need to do the full fairing thingy. +impl Default for Config { + fn default() -> Self { + Self { + network_interfaces: Vec::new(), + json_rpc_url: "http://127.0.0.1:18081/json_rpc".to_string(), + } + } } #[get("/")] fn index(config: &State) -> Template { - let node_info = get_node_info(); + let node_info = get_node_info(&config.json_rpc_url); + let latest_twenty_blocks = get_latest_twenty_blocks(&config.json_rpc_url, node_info.height); let system_context = get_system_info(&config.network_interfaces); let context = context! { system: system_context, node_info: node_info, + latest_twenty_blocks: latest_twenty_blocks, }; Template::render("index", &context) } diff --git a/src/monero_rpc.rs b/src/monero_rpc.rs index c8c25be..d2758e1 100644 --- a/src/monero_rpc.rs +++ b/src/monero_rpc.rs @@ -1,14 +1,14 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] -pub struct NodeInfo { +pub struct NodeInfoOuter { pub id: String, pub jsonrpc: String, - pub result: NodeInfoInner + pub result: NodeInfo, } #[derive(Serialize, Deserialize, Debug)] -pub struct NodeInfoInner { +pub struct NodeInfo { pub adjusted_time: u64, alt_blocks_count: u64, block_size_limit: u64, @@ -25,7 +25,7 @@ pub struct NodeInfoInner { difficulty_top64: u64, pub free_space: u64, grey_peerlist_size: u64, - height: u64, + pub height: u64, height_without_bootstrap: u64, incoming_connections_count: u64, mainnet: bool, @@ -60,6 +60,51 @@ pub struct NodeInfoInner { pub db_size_pretty: String, } +#[derive(Serialize, Deserialize, Debug)] +pub struct BlockHeaderInfosOuter { + pub id: String, + pub jsonrpc: String, + pub result: BlockHeaderInfos, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlockHeaderInfos { + credits: u64, + pub headers: Vec, + status: String, + top_hash: String, + untrusted: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlockHeaderInfo { + block_size: u64, + block_weight: u64, + cumulative_difficulty: u64, + cumulative_difficulty_top64: u64, + depth: u64, + difficulty: u64, + difficulty_top64: u64, + hash: String, + height: u64, + long_term_weight: u64, + major_version: u64, + miner_tx_hash: String, + minor_version: u64, + nonce: u64, + num_txes: u64, + orphan_status: bool, + pow_hash: String, + prev_hash: String, + reward: u64, + pub timestamp: u64, + wide_cumulative_difficulty: String, + wide_difficulty: String, + #[serde(skip_deserializing, default = "none")] + pub timestamp_pretty: String +} + + fn none() -> String { "".to_string() } diff --git a/templates/index.html.tera b/templates/index.html.tera index 9ea76b3..ae0e4b7 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -18,16 +18,29 @@ font-weight: bold; color: red; } - span.success { + .success { font-weight: bold; color: green; } .warning { color: yellow; } + .error { + color: red; + } .node_data { font-family: monospace, monospace; } + table { + border: 2px solid; + } + thead { + + } + th, td { + border: 1px solid; + padding: 7px; + } @@ -77,7 +90,10 @@

Total Received Today: {{ network_if.total_received_today }}

Average Rate: {{ network_if.average_rate_today }}

{% endfor %} -

+ {% for network_if in system.network_ifs_errored %} +

interface {{ network_if[0] }} has an error

+

Error: {{ network_if[1] }}

+ {% endfor %}

Monerod Status

@@ -114,10 +130,33 @@ -

Node Peers

-
- +

Latest 20 Blocks

+
+ + + + + + + + + + + + + {% for block in latest_twenty_blocks | reverse %} + + + + + + + + {% endfor %} + +
HeightSizeTransactionsTimestamp (UTC)Hash
{{ block.height }}{{ block.block_size }}{{ block.num_txes }}{{ block.timestamp_pretty }}{{ block.hash }}
+
{{__tera_context}}