Merge pull request #4 from aglkm/v015

V015
This commit is contained in:
aglkm
2024-06-20 15:18:11 +03:00
committed by GitHub
16 changed files with 549 additions and 321 deletions

93
Cargo.lock generated
View File

@@ -41,6 +41,55 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.86" version = "1.0.86"
@@ -234,6 +283,12 @@ dependencies = [
"phf_codegen", "phf_codegen",
] ]
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.1.0" version = "2.1.0"
@@ -482,6 +537,29 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "env_filter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@@ -736,13 +814,14 @@ dependencies = [
[[package]] [[package]]
name = "grin-explorer" name = "grin-explorer"
version = "0.1.4" version = "0.1.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"colored", "colored",
"config", "config",
"either", "either",
"env_logger",
"fs_extra", "fs_extra",
"futures", "futures",
"humantime", "humantime",
@@ -982,6 +1061,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.11"
@@ -2540,6 +2625,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "grin-explorer" name = "grin-explorer"
version = "0.1.4" version = "0.1.5"
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
@@ -20,6 +20,7 @@ lazy_static = "1.4.0"
shellexpand = "3.1.0" shellexpand = "3.1.0"
either = "1.11.0" either = "1.11.0"
anyhow = "1.0.86" anyhow = "1.0.86"
env_logger = "0.11.3"
[dependencies.reqwest] [dependencies.reqwest]
version = "0.11.23" version = "0.11.23"

View File

@@ -7,20 +7,38 @@ port = "3413"
# Node protocol. Either HTTP or HTTPS. # Node protocol. Either HTTP or HTTPS.
proto = "http" proto = "http"
# API username. # API username. Comment out if user is not required.
user = "grin" user = "grin"
# API secret path. # API secret path. Comment out if secret is not required.
api_secret_path = "~/.grin/main/.api_secret" api_secret_path = "~/.grin/main/.api_secret"
# Foreign API secret path. # Foreign API secret path. Comment out if secret is not required.
foreign_api_secret_path = "~/.grin/main/.foreign_api_secret" foreign_api_secret_path = "~/.grin/main/.foreign_api_secret"
# Path to Grin directory. # Path to Grin directory.
grin_dir = "~/.grin" grin_dir = "~/.grin"
# CoinGecko API on/off switch. # Enable or disable CoinGecko API.
coingecko_api = "on" coingecko_api = "enabled"
# Enable or disable node POST API public access.
public_api = "enabled"
# Grinnode config
# ip = "grinnode.live"
# port = "3413"
# proto = "https"
# coingecko_api = "enabled"
# public_api = "enabled"
# Grincoin config
# ip = "grincoin.org"
# proto = "https"
# coingecko_api = "enabled"
# public_api = "enabled"
# Testnet config # Testnet config
@@ -31,5 +49,6 @@ coingecko_api = "on"
# api_secret_path = "~/.grin/test/.api_secret" # api_secret_path = "~/.grin/test/.api_secret"
# foreign_api_secret_path = "~/.grin/test/.foreign_api_secret" # foreign_api_secret_path = "~/.grin/test/.foreign_api_secret"
# grin_dir = "~/.grin" # grin_dir = "~/.grin"
# coingecko_api = "off" # coingecko_api = "disabled"
# public_api = "enabled"

View File

@@ -22,15 +22,14 @@ Grin is the very first, simple and fair MimbleWimble blockchain implementation.
cd grin-explorer cd grin-explorer
cargo build --release cargo build --release
``` ```
4. Run executable: `./target/release/grin-explorer` 4. Run executable: `RUST_LOG=rocket=warn,grin_explorer ./target/release/grin-explorer`
You will see the following output: You will see the following output:
``` ```
[ INFO ] Starting up Explorer. [2024-06-19T13:12:34Z INFO grin_explorer] starting up.
[ INFO ] Starting up Rocket engine. [2024-06-19T13:12:34Z WARN rocket::launch] 🚀 Rocket has launched from http://127.0.0.1:8000
🚀 Rocket has launched from http://127.0.0.1:8000 [2024-06-19T13:12:34Z INFO grin_explorer] ready.
[ OK ] Explorer Ready.
``` ```
5. Open explorer in your browser: http://127.0.0.1:8000 5. Open explorer in your browser: http://127.0.0.1:8000

View File

@@ -1,6 +1,5 @@
[default] [default]
address = "127.0.0.1" address = "127.0.0.1"
log_level = "critical"
# Uncomment and change default port number (8000) if another instance of the explorer is needed to run. # Uncomment and change default port number (8000) if another instance of the explorer is needed to run.
# E.g. Mainnet (8000) and Testnet (8001) instances. # E.g. Mainnet (8000) and Testnet (8001) instances.

View File

@@ -35,8 +35,6 @@ pub struct Dashboard {
// mempool // mempool
pub txns: String, pub txns: String,
pub stem: String, pub stem: String,
// coingecko api
pub cg_api: String,
} }
impl Dashboard { impl Dashboard {
@@ -66,7 +64,6 @@ impl Dashboard {
breakeven_cost: String::new(), breakeven_cost: String::new(),
txns: String::new(), txns: String::new(),
stem: String::new(), stem: String::new(),
cg_api: String::new(),
} }
} }
} }
@@ -170,6 +167,7 @@ pub struct ExplorerConfig {
pub api_secret: String, pub api_secret: String,
pub foreign_api_secret: String, pub foreign_api_secret: String,
pub coingecko_api: String, pub coingecko_api: String,
pub public_api: String,
} }
impl ExplorerConfig { impl ExplorerConfig {
@@ -185,6 +183,29 @@ impl ExplorerConfig {
api_secret: String::new(), api_secret: String::new(),
foreign_api_secret: String::new(), foreign_api_secret: String::new(),
coingecko_api: String::new(), coingecko_api: String::new(),
public_api: String::new(),
}
}
}
// Output data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Output {
pub height: String,
pub commit: String,
pub out_type: String,
pub status: String,
pub raw_data: String,
}
impl Output {
pub fn new() -> Output {
Output {
height: String::new(),
commit: String::new(),
out_type: String::new(),
status: String::new(),
raw_data: String::new(),
} }
} }
} }

View File

@@ -5,7 +5,6 @@ use rocket::fs::FileServer;
use rocket::State; use rocket::State;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use colored::Colorize;
use rocket::tokio; use rocket::tokio;
use rocket::response::Redirect; use rocket::response::Redirect;
use either::Either; use either::Either;
@@ -19,6 +18,8 @@ use crate::data::Dashboard;
use crate::data::Block; use crate::data::Block;
use crate::data::Transactions; use crate::data::Transactions;
use crate::data::Kernel; use crate::data::Kernel;
use crate::data::Output;
use crate::requests::CONFIG;
// Rendering main (Dashboard) page. // Rendering main (Dashboard) page.
@@ -30,7 +31,7 @@ fn index(dashboard: &State<Arc<Mutex<Dashboard>>>) -> Template {
route: "index", route: "index",
node_ver: &data.node_ver, node_ver: &data.node_ver,
proto_ver: &data.proto_ver, proto_ver: &data.proto_ver,
cg_api: &data.cg_api, cg_api: CONFIG.coingecko_api.clone(),
}) })
} }
@@ -39,7 +40,8 @@ fn index(dashboard: &State<Arc<Mutex<Dashboard>>>) -> Template {
#[get("/block_list")] #[get("/block_list")]
fn block_list() -> Template { fn block_list() -> Template {
Template::render("block_list", context! { Template::render("block_list", context! {
route: "block_list", route: "block_list",
cg_api: CONFIG.coingecko_api.clone(),
}) })
} }
@@ -60,11 +62,13 @@ async fn block_list_by_height(input_height: &str) -> Template {
if index >= height { if index >= height {
Template::render("block_list", context! { Template::render("block_list", context! {
route: "block_list", route: "block_list",
cg_api: CONFIG.coingecko_api.clone(),
}) })
} else { } else {
Template::render("block_list", context! { Template::render("block_list", context! {
route: "block_list_by_height", route: "block_list_by_height",
cg_api: CONFIG.coingecko_api.clone(),
index, index,
blocks, blocks,
height, height,
@@ -72,7 +76,8 @@ async fn block_list_by_height(input_height: &str) -> Template {
} }
} else { } else {
Template::render("block_list", context! { Template::render("block_list", context! {
route: "block_list", route: "block_list",
cg_api: CONFIG.coingecko_api.clone(),
}) })
} }
} }
@@ -88,14 +93,16 @@ async fn block_details_by_height(height: &str) -> Template {
if block.height.is_empty() == false { if block.height.is_empty() == false {
return Template::render("block_details", context! { return Template::render("block_details", context! {
route: "block_details", route: "block_details",
cg_api: CONFIG.coingecko_api.clone(),
block, block,
}); });
} }
} }
Template::render("error", context! { Template::render("error", context! {
route: "error", route: "error",
cg_api: CONFIG.coingecko_api.clone(),
}) })
} }
@@ -114,7 +121,8 @@ async fn block_header_by_hash(hash: &str) -> Either<Template, Redirect> {
} }
return Either::Left(Template::render("error", context! { return Either::Left(Template::render("error", context! {
route: "error", route: "error",
cg_api: CONFIG.coingecko_api.clone(),
})) }))
} }
@@ -128,13 +136,37 @@ async fn kernel(excess: &str) -> Template {
if kernel.excess.is_empty() == false { if kernel.excess.is_empty() == false {
return Template::render("kernel", context! { return Template::render("kernel", context! {
route: "kernel", route: "kernel",
cg_api: CONFIG.coingecko_api.clone(),
kernel, kernel,
}) })
} }
return Template::render("error", context! { return Template::render("error", context! {
route: "error", route: "error",
cg_api: CONFIG.coingecko_api.clone(),
})
}
// Rendering page for a specified output.
#[get("/output/<commit>")]
async fn output(commit: &str) -> Template {
let mut output = Output::new();
let _ = requests::get_output(&commit, &mut output).await;
if output.commit.is_empty() == false {
return Template::render("output", context! {
route: "output",
cg_api: CONFIG.coingecko_api.clone(),
output,
})
}
return Template::render("error", context! {
route: "error",
cg_api: CONFIG.coingecko_api.clone(),
}) })
} }
@@ -143,11 +175,14 @@ async fn kernel(excess: &str) -> Template {
// Using Option<&str> to match '/search' query without input params. // Using Option<&str> to match '/search' query without input params.
// https://github.com/rwf2/Rocket/issues/608 // https://github.com/rwf2/Rocket/issues/608
#[get("/search?<input>")] #[get("/search?<input>")]
fn search(input: Option<&str>) -> Either<Template, Redirect> { pub async fn search(input: Option<&str>) -> Either<Template, Redirect> {
// Unwrap Option and forward to Search page if no parameters // Unwrap Option and forward to Search page if no parameters
let input = match input { let input = match input {
Some(value) => value, Some(value) => value,
None => return Either::Left(Template::render("search", context! { route: "search", })), None => return Either::Left(Template::render("search", context! {
route: "search",
cg_api: CONFIG.coingecko_api.clone(),
})),
}; };
// Check for valid chars // Check for valid chars
@@ -161,46 +196,68 @@ fn search(input: Option<&str>) -> Either<Template, Redirect> {
} else if input.len() == 64 { } else if input.len() == 64 {
return Either::Right(Redirect::to(uri!(block_header_by_hash(input)))); return Either::Right(Redirect::to(uri!(block_header_by_hash(input))));
// Kernel // Kernel or Unspent Output
} else if input.len() == 66 { } else if input.len() == 66 {
return Either::Right(Redirect::to(uri!(kernel(input)))); // First search for Kernel.
// If found, redirect to Kernel page, otherwise search for Unspent Output.
// As we can't distinguish between Kernel and Output, this will produce a redundant
// get_kernel call, but will allow for better UI (no need to ask user to input the type
// of the search request).
let mut kernel = Kernel::new();
let _ = requests::get_kernel(&input, &mut kernel).await;
if kernel.excess.is_empty() == false {
// Here we are redirecting to kernel page and call get_kernel again there.
// Kernel page is a separate route and we want it to be accessed directly and
// via search functionality.
return Either::Right(Redirect::to(uri!(kernel(input))));
} else {
// If Kernel not found, then search for Unspent Output
return Either::Right(Redirect::to(uri!(output(input))));
}
} }
} }
Either::Left(Template::render("error", context! { Either::Left(Template::render("error", context! {
route: "error", route: "error",
cg_api: CONFIG.coingecko_api.clone(),
})) }))
} }
// Owner API. // Owner API.
// Whitelisted methods: get_connected_peers, get_peers, get_status.
#[post("/v2/owner", data="<data>")] #[post("/v2/owner", data="<data>")]
async fn api_owner(data: &str) -> String { async fn api_owner(data: &str) -> String {
let result = serde_json::from_str(data); if CONFIG.public_api == "enabled" {
let result = serde_json::from_str(data);
let v: Value = match result { let v: Value = match result {
Ok(value) => value,
Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(),
};
let method = match v["method"].as_str() {
Some(value) => value,
_ => return "{\"error\":\"bad syntax\"}".to_string(),
};
// Whitelisted methods: get_connected_peer, get_peers, get_status.
if method == "get_connected_peers" || method == "get_peers" || method == "get_status" {
let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "owner").await;
let result = match resp {
Ok(value) => value, Ok(value) => value,
Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(), Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(),
}; };
return result.to_string(); let method = match v["method"].as_str() {
} Some(value) => value,
_ => return "{\"error\":\"bad syntax\"}".to_string(),
};
"{\"error\":\"not allowed\"}".to_string() if method == "get_connected_peers" || method == "get_peers" || method == "get_status" {
let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "owner").await;
let result = match resp {
Ok(value) => value,
Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(),
};
return result.to_string();
}
"{\"error\":\"not allowed\"}".to_string()
} else {
"{\"error\":\"not allowed\"}".to_string()
}
} }
@@ -208,26 +265,30 @@ async fn api_owner(data: &str) -> String {
// All methods are whitelisted. // All methods are whitelisted.
#[post("/v2/foreign", data="<data>")] #[post("/v2/foreign", data="<data>")]
async fn api_foreign(data: &str) -> String { async fn api_foreign(data: &str) -> String {
let result = serde_json::from_str(data); if CONFIG.public_api == "enabled" {
let result = serde_json::from_str(data);
let v: Value = match result { let v: Value = match result {
Ok(value) => value, Ok(value) => value,
Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(), Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(),
}; };
let method = match v["method"].as_str() { let method = match v["method"].as_str() {
Some(value) => value, Some(value) => value,
_ => return "{\"error\":\"bad syntax\"}".to_string(), _ => return "{\"error\":\"bad syntax\"}".to_string(),
}; };
let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "foreign").await; let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "foreign").await;
let result = match resp { let result = match resp {
Ok(value) => value, Ok(value) => value,
Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(), Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(),
}; };
result.to_string() return result.to_string();
} else {
"{\"error\":\"not allowed\"}".to_string()
}
} }
@@ -369,7 +430,11 @@ fn last_block_age(blocks: &State<Arc<Mutex<Vec<Block>>>>) -> String {
fn disk_usage(dashboard: &State<Arc<Mutex<Dashboard>>>) -> String { fn disk_usage(dashboard: &State<Arc<Mutex<Dashboard>>>) -> String {
let data = dashboard.lock().unwrap(); let data = dashboard.lock().unwrap();
format!("{} GB", data.disk_usage) if data.disk_usage.is_empty() == false {
return format!("{} GB", data.disk_usage);
} else {
return format!("<i class=\"bi bi-x-lg\"></i>");
}
} }
@@ -575,7 +640,9 @@ fn block_list_index(dashboard: &State<Arc<Mutex<Dashboard>>>) -> String {
// Main // Main
#[rocket::main] #[rocket::main]
async fn main() { async fn main() {
println!("{} Starting up Explorer.", "[ INFO ]".cyan()); env_logger::init();
info!("starting up.");
let dash = Arc::new(Mutex::new(Dashboard::new())); let dash = Arc::new(Mutex::new(Dashboard::new()));
let dash_clone = dash.clone(); let dash_clone = dash.clone();
@@ -595,12 +662,12 @@ async fn main() {
Ok(_v) => { Ok(_v) => {
if ready == false { if ready == false {
ready = true; ready = true;
println!("{} Explorer Ready.", "[ OK ]".green()); info!("ready.");
} }
}, },
Err(e) => { Err(e) => {
ready = false; ready = false;
println!("{} {}.", "[ ERROR ]".red(), e); error!("{}", e);
}, },
} }
@@ -608,8 +675,6 @@ async fn main() {
} }
}); });
println!("{} Starting up Rocket engine.", "[ INFO ]".cyan());
// Starting Rocket engine. // Starting Rocket engine.
let _ = rocket::build() let _ = rocket::build()
.manage(dash) .manage(dash)
@@ -624,7 +689,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,
api_owner, api_foreign]) output, api_owner, api_foreign])
.mount("/static", FileServer::from("static")) .mount("/static", FileServer::from("static"))
.attach(Template::fairing()) .attach(Template::fairing())
.launch() .launch()

View File

@@ -16,12 +16,13 @@ use crate::data::Dashboard;
use crate::data::Block; use crate::data::Block;
use crate::data::Transactions; use crate::data::Transactions;
use crate::data::ExplorerConfig; use crate::data::ExplorerConfig;
use crate::Kernel; use crate::data::Kernel;
use crate::data::Output;
// Static explorer config structure // Static explorer config structure
lazy_static! { lazy_static! {
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 settings = Config::builder().add_source(config::File::with_name("Explorer"))
.build().unwrap(); .build().unwrap();
@@ -38,15 +39,22 @@ lazy_static! {
"foreign_api_secret_path" => cfg.foreign_api_secret_path = value, "foreign_api_secret_path" => cfg.foreign_api_secret_path = value,
"grin_dir" => cfg.grin_dir = value, "grin_dir" => cfg.grin_dir = value,
"coingecko_api" => cfg.coingecko_api = value, "coingecko_api" => cfg.coingecko_api = value,
"public_api" => cfg.public_api = value,
_ => println!("{} Unknown config setting '{}'.", "[ ERROR ]".red(), name), _ => println!("{} Unknown config setting '{}'.", "[ ERROR ]".red(), name),
} }
} }
cfg.api_secret = fs::read_to_string(format!("{}", if cfg.api_secret_path.is_empty() == false {
shellexpand::tilde(&cfg.api_secret_path))).unwrap(); cfg.api_secret = fs::read_to_string(format!("{}", shellexpand::tilde(&cfg.api_secret_path))).unwrap();
cfg.foreign_api_secret = fs::read_to_string(format!("{}", }
shellexpand::tilde(&cfg.foreign_api_secret_path))).unwrap();
cfg.grin_dir = format!("{}", shellexpand::tilde(&cfg.grin_dir)); if cfg.foreign_api_secret_path.is_empty() == false {
cfg.foreign_api_secret = fs::read_to_string(format!("{}", shellexpand::tilde(&cfg.foreign_api_secret_path))).unwrap();
}
if cfg.grin_dir.is_empty() == false {
cfg.grin_dir = format!("{}", shellexpand::tilde(&cfg.grin_dir));
}
cfg cfg
}; };
@@ -58,13 +66,16 @@ pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Resul
let rpc_url; let rpc_url;
let secret; let secret;
if rpc_type == "owner" { if CONFIG.port.is_empty() == false {
rpc_url = format!("{}://{}:{}/v2/owner", CONFIG.proto, CONFIG.ip, CONFIG.port); rpc_url = format!("{}://{}:{}/v2/{}", CONFIG.proto, CONFIG.ip, CONFIG.port, rpc_type);
secret = CONFIG.api_secret.clone(); } else {
rpc_url = format!("{}://{}/v2/{}", CONFIG.proto, CONFIG.ip, rpc_type);
} }
else {
rpc_url = format!("{}://{}:{}/v2/foreign", CONFIG.proto, CONFIG.ip, CONFIG.port); if rpc_type == "owner" {
secret = CONFIG.foreign_api_secret.clone(); secret = CONFIG.api_secret.clone();
} else {
secret = CONFIG.foreign_api_secret.clone();
} }
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -75,6 +86,11 @@ pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Resul
.send() .send()
.await?; .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.unwrap())?; let val: Value = serde_json::from_str(&result.text().await.unwrap())?;
Ok(val) Ok(val)
@@ -94,9 +110,6 @@ pub async fn get_status(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), anyhow::
data.proto_ver = resp["result"]["Ok"]["protocol_version"].to_string(); data.proto_ver = resp["result"]["Ok"]["protocol_version"].to_string();
} }
// Also set cg_api value
data.cg_api = CONFIG.coingecko_api.clone();
Ok(()) Ok(())
} }
@@ -150,7 +163,7 @@ pub async fn get_market(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
let result; let result;
let mut val = Value::Null; let mut val = Value::Null;
if CONFIG.coingecko_api == "on" { if CONFIG.coingecko_api == "enabled" {
client = reqwest::Client::new(); client = reqwest::Client::new();
result = client.get("https://api.coingecko.com/api/v3/simple/price?ids=grin&vs_currencies=usd%2Cbtc&include_24hr_vol=true").send().await?; result = client.get("https://api.coingecko.com/api/v3/simple/price?ids=grin&vs_currencies=usd%2Cbtc&include_24hr_vol=true").send().await?;
val = serde_json::from_str(&result.text().await.unwrap()).unwrap(); val = serde_json::from_str(&result.text().await.unwrap()).unwrap();
@@ -170,23 +183,18 @@ pub async fn get_market(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
data.supply = supply.to_formatted_string(&Locale::en); data.supply = supply.to_formatted_string(&Locale::en);
// https://john-tromp.medium.com/a-case-for-using-soft-total-supply-1169a188d153 // https://john-tromp.medium.com/a-case-for-using-soft-total-supply-1169a188d153
data.soft_supply = format!("{:.2}", data.soft_supply = format!("{:.2}", supply.to_string().parse::<f64>().unwrap() / 3150000000.0 * 100.0);
supply.to_string().parse::<f64>().unwrap() / 3150000000.0 * 100.0);
if CONFIG.coingecko_api == "on" && val != Value::Null { if CONFIG.coingecko_api == "enabled" && val != Value::Null {
// Check if CoingGecko API returned error // Check if CoingGecko API returned error
if let Some(status) = val.get("status") { if let Some(status) = val.get("status") {
println!("{} {}.", "[ WARNING ]".yellow(), warn!("{}", status["error_message"].as_str().unwrap().to_string());
status["error_message"].as_str().unwrap().to_string());
} else { } else {
data.price_usd = format!("{:.3}", val["grin"]["usd"].to_string().parse::<f64>() data.price_usd = format!("{:.3}", val["grin"]["usd"].to_string().parse::<f64>().unwrap());
.unwrap()); data.price_btc = format!("{:.8}", val["grin"]["btc"].to_string().parse::<f64>().unwrap());
data.price_btc = format!("{:.8}", val["grin"]["btc"].to_string().parse::<f64>()
.unwrap());
data.volume_usd = (val["grin"]["usd_24h_vol"].to_string().parse::<f64>().unwrap() as u64) data.volume_usd = (val["grin"]["usd_24h_vol"].to_string().parse::<f64>().unwrap() as u64)
.to_formatted_string(&Locale::en); .to_formatted_string(&Locale::en);
data.volume_btc = format!("{:.2}", val["grin"]["btc_24h_vol"].to_string().parse::<f64>() data.volume_btc = format!("{:.2}", val["grin"]["btc_24h_vol"].to_string().parse::<f64>().unwrap());
.unwrap());
data.cap_usd = (((supply as f64) * data.price_usd.parse::<f64>().unwrap()) as u64) data.cap_usd = (((supply as f64) * data.price_usd.parse::<f64>().unwrap()) as u64)
.to_formatted_string(&Locale::en); .to_formatted_string(&Locale::en);
data.cap_btc = (((supply as f64) * data.price_btc.parse::<f64>().unwrap()) as u64) data.cap_btc = (((supply as f64) * data.price_btc.parse::<f64>().unwrap()) as u64)
@@ -202,16 +210,24 @@ pub async fn get_market(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
// Collecting: disk_usage. // Collecting: disk_usage.
pub fn get_disk_usage(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> { pub fn get_disk_usage(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
let mut data = dashboard.lock().unwrap(); let mut data = dashboard.lock().unwrap();
let chain_data; let chain_dir;
if CONFIG.coingecko_api == "on" { if CONFIG.coingecko_api == "enabled" {
chain_data = format!("{}/main/chain_data", CONFIG.grin_dir); chain_dir = format!("{}/main/chain_data", CONFIG.grin_dir);
} else { } else {
chain_data = format!("{}/test/chain_data", CONFIG.grin_dir); chain_dir = format!("{}/test/chain_data", CONFIG.grin_dir);
} }
data.disk_usage = format!("{:.2}", (get_size(chain_data).unwrap() as f64) match get_size(chain_dir.clone()) {
/ 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) => {
if CONFIG.ip == "127.0.0.1" || CONFIG.ip == "0.0.0.0" {
error!("{}: \"{}\"", e, chain_dir);
} else {
// Ignore error for external node connection
}
},
}
Ok(()) Ok(())
} }
@@ -224,8 +240,7 @@ pub async fn get_mining_stats(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), an
if height.is_empty() == false && height.parse::<u64>().unwrap() > 1440 { if height.is_empty() == false && height.parse::<u64>().unwrap() > 1440 {
let params1 = &format!("[{}, null, null]", height)[..]; let params1 = &format!("[{}, null, null]", height)[..];
let params2 = &format!("[{}, null, null]", height.parse::<u64>().unwrap() let params2 = &format!("[{}, null, null]", height.parse::<u64>().unwrap() - difficulty_window)[..];
- difficulty_window)[..];
let resp1 = call("get_block", params1, "1", "foreign").await?; let resp1 = call("get_block", params1, "1", "foreign").await?;
let resp2 = call("get_block", params2, "1", "foreign").await?; let resp2 = call("get_block", params2, "1", "foreign").await?;
@@ -243,9 +258,9 @@ pub async fn get_mining_stats(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), an
// https://forum.grin.mw/t/difference-c31-and-c32-c33/7018/7 // https://forum.grin.mw/t/difference-c31-and-c32-c33/7018/7
let hashrate = (net_diff as f64) * 42.0 / 60.0 / 16384.0; let hashrate = (net_diff as f64) * 42.0 / 60.0 / 16384.0;
// KG/s // kG/s
if hashrate > 1000.0 { if hashrate > 1000.0 {
data.hashrate = format!("{:.2} KG/s", hashrate / 1000.0); data.hashrate = format!("{:.2} kG/s", hashrate / 1000.0);
// G/s // G/s
} else { } else {
data.hashrate = format!("{:.2} G/s", hashrate); data.hashrate = format!("{:.2} G/s", hashrate);
@@ -253,7 +268,7 @@ pub async fn get_mining_stats(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), an
data.difficulty = net_diff.to_string(); data.difficulty = net_diff.to_string();
if CONFIG.coingecko_api == "on" { if CONFIG.coingecko_api == "enabled" {
// Calculating G1-mini production per hour // Calculating G1-mini production per hour
let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0; let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0;
@@ -261,10 +276,12 @@ pub async fn get_mining_stats(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), an
// Assuming $0.07 per kW/h // Assuming $0.07 per kW/h
data.production_cost = format!("{:.3}", 120.0 / 1000.0 * 0.07 * (1.0 / coins_per_hour)); data.production_cost = format!("{:.3}", 120.0 / 1000.0 * 0.07 * (1.0 / coins_per_hour));
data.reward_ratio = format!("{:.2}", data.price_usd.parse::<f64>().unwrap() if data.price_usd.is_empty() == false {
data.reward_ratio = format!("{:.2}", data.price_usd.parse::<f64>().unwrap()
/ data.production_cost.parse::<f64>().unwrap()); / data.production_cost.parse::<f64>().unwrap());
data.breakeven_cost = format!("{:.2}", data.price_usd.parse::<f64>().unwrap() data.breakeven_cost = format!("{:.2}", data.price_usd.parse::<f64>().unwrap()
/ (120.0 / 1000.0 * (1.0 / coins_per_hour))); / (120.0 / 1000.0 * (1.0 / coins_per_hour)));
}
} }
} }
} }
@@ -403,6 +420,41 @@ pub async fn get_block_header(hash: &str, height: &mut String)
} }
// Get output.
pub async fn get_output(commit: &str, output: &mut Output) -> Result<(), anyhow::Error> {
// First check whether output is broadcasted but not confirmed yet (in mempool)
let mut resp = call("get_unconfirmed_transactions", "[]", "1", "foreign").await?;
if resp["result"]["Ok"].is_null() == false {
for tx in resp["result"]["Ok"].as_array().unwrap() {
for out in tx["tx"]["body"]["outputs"].as_array().unwrap() {
if out["commit"].as_str().unwrap() == commit {
// Only Plain outputs in the mempool
output.out_type = "Plain".to_string();
output.commit = out["commit"].as_str().unwrap().to_string();
output.status = "Unconfirmed".to_string();
// Found it, no need to continue
return Ok(());
}
}
}
}
let params = &format!("[[\"{}\"], null, null, true, true]", commit)[..];
resp = call("get_outputs", params, "1", "foreign").await?;
if resp["result"]["Ok"][0].is_null() == false {
output.height = resp["result"]["Ok"][0]["block_height"].to_string();
output.commit = resp["result"]["Ok"][0]["commit"].as_str().unwrap().to_string();
output.out_type = resp["result"]["Ok"][0]["output_type"].as_str().unwrap().to_string();
output.raw_data = serde_json::to_string_pretty(&resp).unwrap()
}
Ok(())
}
// Get kernel. // Get kernel.
pub async fn get_kernel(excess: &str, kernel: &mut Kernel) -> Result<(), anyhow::Error> { pub async fn get_kernel(excess: &str, kernel: &mut Kernel) -> Result<(), anyhow::Error> {
// First check whether kernel is broadcasted but not confirmed yet (in mempool) // First check whether kernel is broadcasted but not confirmed yet (in mempool)

View File

@@ -73,15 +73,21 @@
</div> </div>
</nav> </nav>
<div class="container-fluid">
<br>
{% block content %}{% endblock content %} {% block content %}{% endblock content %}
</div>
<footer class="shadow mt-auto"> <footer class="shadow mt-auto">
<code> <code>
<br> <br>
<div class="container-fluid"> <div class="container-fluid">
<div class="d-flex justify-content-center mb-2">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 191 191" height="35" width="35" style="enable-background:new 0 0 191 191;" xml:space="preserve"><style type="text/css">.st0{fill:#f2a900;}</style><circle cx="95.5" cy="95.5" r="95.5"/><circle class="st0" cx="95.3" cy="95.5" r="85.3"/><path d="M135.7,63.5c-2-4.6-3.9-13.6-9.1-15.6c-6.7-2.6-10.9,14.3-11.9,18.6h-1c-1.7-7.3-4-17.7-12-20c-3.7,15.6,3.9,32.5,11,46
c7.6-3.6,11.3-15,12-23h1l8,24c6.4-1.8,8.7-8.2,11-14c5-12.4,9.2-27.7,6-41C141.1,41.2,138.2,55,135.7,63.5 M39.7,95.5
c8.6-4.1,12.7-17.1,14-26h1c1,4.3,3.7,15.4,9.1,16.1c6.8,0.9,10.4-14.3,10.9-19.1h1c2.2,7.5,4.5,17.3,12,21
c2.8-11.7-0.5-24.1-4.8-35c-1.1-2.8-3.3-10.4-7.2-10.4c-6.3,0-9.9,16.7-11,21.4h-1l-8-24C41.6,46.2,34,82,39.7,95.5 M24.7,107.5
c10.4,45.6,58.6,71.4,102,52.1c15.8-7,28.3-19.5,35.5-35.1c1.9-4,5.5-11.4,3.4-16.7c-2.1-5.3-22.8,3.4-27.9,5.6
c-0.7,0.3-1.2,1-1.4,1.8c-0.3,2.2,1.2,4.3,3.4,4.6c0.1,0,0.2,0,0.3,0l8.7,0.7c-15.7,30.4-58.1,43.8-88,19.5
C54,134.6,48,128.2,44,120.5c-1.9-3.7-3.7-8.4-7-11C33.8,107.1,29,106.3,24.7,107.5L24.7,107.5z"/></svg>
</div>
<div class="card-group"> <div class="card-group">
<div class="card border-0"> <div class="card border-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
@@ -212,20 +218,22 @@
<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.4</span> <span style="color:grey"><i class="bi bi-github me-1"></i>v.0.1.5</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>
</a> </a>
</div> </div>
</div> </div>
{% if cg_api == "enabled" %}
<div class="row"> <div class="row">
<div class="col d-flex justify-content-center" style="color:grey"> <div class="col d-flex justify-content-center" style="color:grey">
Powered by CoinGecko Powered by CoinGecko
</div> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
<br> <br>
</code> </code>
</footer> </footer>

View File

@@ -4,7 +4,7 @@
<code> <code>
<div class="card"> <div class="card border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-box"></i> BLOCK {{ block.height }}</div> <div class="darkorange-text"><i class="bi bi-box"></i> BLOCK {{ block.height }}</div>
<br> <br>
@@ -35,9 +35,7 @@
</div> </div>
</div> </div>
<br> <div class="card border-top-0 border-start-0 border-end-0 rounded-0">
<div class="card">
<div class="card-body mb-2" align="left"> <div class="card-body mb-2" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> TRANSACTIONS</div> <div class="darkorange-text"><i class="bi bi-receipt"></i> TRANSACTIONS</div>
<br> <br>
@@ -110,9 +108,7 @@
</div> </div>
</div> </div>
<br> <div class="card border-top-0 border-start-0 border-end-0 rounded-0">
<div class="card">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-layout-text-sidebar-reverse"></i> RAW DATA</div> <div class="darkorange-text"><i class="bi bi-layout-text-sidebar-reverse"></i> RAW DATA</div>
<br> <br>
@@ -120,8 +116,6 @@
</div> </div>
</div> </div>
<br>
</code> </code>
{% endblock %} {% endblock %}

View File

@@ -4,51 +4,51 @@
<code> <code>
<div class="d-none d-md-block mb-4"> <!-- Show on >= md screens --> <div class="d-none d-md-block"> <!-- Show on >= md screens -->
<div class="card-group"> <div class="card-group">
<div class="card rounded-0"> <div class="card border-bottom-0 border-start-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
HEIGHT HEIGHT
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-0"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
AGE AGE
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-0"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
KERNELS KERNELS
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-0"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
INPUTS INPUTS
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-0"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
OUTPUTS OUTPUTS
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-0"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
FEES FEES
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-0"> <div class="card border-bottom-0 border-end-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="darkorange-text"> <div class="darkorange-text">
WEIGHT WEIGHT
@@ -60,7 +60,7 @@
{% for i in range(end=10) %} {% for i in range(end=10) %}
<div class="card-group rounded-0"> <div class="card-group rounded-0">
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 border-start-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text"> <div class="value-text">
@@ -73,7 +73,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text">{{ blocks[i].time }}</div> <div class="value-text">{{ blocks[i].time }}</div>
@@ -82,7 +82,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text">{{ blocks[i].ker_len }}</div> <div class="value-text">{{ blocks[i].ker_len }}</div>
@@ -91,7 +91,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text">{{ blocks[i].in_len }}</div> <div class="value-text">{{ blocks[i].in_len }}</div>
@@ -100,7 +100,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text">{{ blocks[i].out_len }}</div> <div class="value-text">{{ blocks[i].out_len }}</div>
@@ -109,7 +109,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text">ツ {{ blocks[i].fees / 1000000000.0 }}</div> <div class="value-text">ツ {{ blocks[i].fees / 1000000000.0 }}</div>
@@ -118,7 +118,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="card rounded-0 mt-1"> <div class="card border-bottom-0 border-end-0 rounded-0">
<div class="card-body"> <div class="card-body">
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text">{{ blocks[i].weight }} %</div> <div class="value-text">{{ blocks[i].weight }} %</div>
@@ -133,12 +133,12 @@
</div> </div>
<div class="d-md-none mb-4"> <!-- Show on < md screens--> <div class="d-md-none"> <!-- Show on < md screens-->
{% for i in range(end=10) %} {% for i in range(end=10) %}
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-start">
<div class="value-text">Block</div> <i class="bi bi-box darkorange-text"></i>&nbsp;
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="value-text"> <div class="value-text">
<a class="text-decoration-none darkorange-text" href="/block/{{ blocks[i].height }}"> <a class="text-decoration-none darkorange-text" href="/block/{{ blocks[i].height }}">
@@ -211,49 +211,53 @@
{% if route == "block_list_by_height" %} {% if route == "block_list_by_height" %}
<div class="d-flex justify-content-center sticky-bottom mb-3"> <div class="card rounded-0 border-start-0 border-end-0 sticky-bottom">
<div class="me-5"> <div class="mt-2 d-flex justify-content-center">
{% if height >= (index + 10) %} <div class="me-5">
<a class="text-decoration-none" href="/block_list/{{ index + 10 }}"> {% if height >= (index + 10) %}
{% elif height >= (index) and height < (index + 10) %} <a class="text-decoration-none" href="/block_list/{{ index + 10 }}">
<a class="text-decoration-none" href="/block_list/{{ height }}"> {% elif height >= (index) and height < (index + 10) %}
{% endif %} <a class="text-decoration-none" href="/block_list/{{ height }}">
<h2><i class="bi bi-arrow-left-square"></i></h2> {% endif %}
</a> <h2><i class="bi bi-arrow-left-square"></i></h2>
</div> </a>
<div class="me-5"> </div>
<a class="text-decoration-none" href="/block_list"> <div class="me-5">
<h2><i class="bi bi-house"></i></h2> <a class="text-decoration-none" href="/block_list">
</a> <h2><i class="bi bi-house"></i></h2>
</div> </a>
<div> </div>
{% if index >= 20 %} <div>
<a class="text-decoration-none" href="/block_list/{{ index - 10 }}"> {% if index >= 20 %}
{% elif index >= 10 and index < 20 %} <a class="text-decoration-none" href="/block_list/{{ index - 10 }}">
<a class="text-decoration-none" href="/block_list/{{ 9 }}"> {% elif index >= 10 and index < 20 %}
{% else %} <a class="text-decoration-none" href="/block_list/{{ 9 }}">
<a class="text-decoration-none disabled" href=""> {% else %}
{% endif %} <a class="text-decoration-none disabled" href="">
<h2><i class="bi bi-arrow-right-square"></i></h2> {% endif %}
</a> <h2><i class="bi bi-arrow-right-square"></i></h2>
</a>
</div>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="d-flex justify-content-center sticky-bottom mb-3"> <div class="card rounded-0 border-start-0 border-end-0 sticky-bottom">
<div class="me-5"> <div class="mt-2 d-flex justify-content-center">
<a class="text-decoration-none disabled" href=""> <div class="me-5">
<h2><i class="bi bi-arrow-left-square"></i></h2> <a class="text-decoration-none disabled" href="">
</a> <h2><i class="bi bi-arrow-left-square"></i></h2>
</div> </a>
<div class="me-5"> </div>
<a class="text-decoration-none" href="/block_list"> <div class="me-5">
<h2><i class="bi bi-house"></i></h2> <a class="text-decoration-none" href="/block_list">
</a> <h2><i class="bi bi-house"></i></h2>
</div> </a>
<div> </div>
<div hx-get="/rpc/block_list/index" hx-trigger="load, every 10s"></div> <div>
<div hx-get="/rpc/block_list/index" hx-trigger="load, every 10s"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -4,19 +4,18 @@
<code> <code>
<div class="card"> <div class="card border-start-0 border-end-0 rounded-0">
<div class="card-body"> <div class="card-body">
<h4>No results found.</h4><br> <h4>No results found.</h4><br>
<div class="value-text mb-2">Supported search inputs:</div> <div class="value-text mb-2">Supported search inputs:</div>
<div class="value-text"><i class="bi bi-dot"></i> block number</div> <div class="value-text"><i class="bi bi-dot"></i> Block Number</div>
<div class="value-text"><i class="bi bi-dot"></i> block hash</div> <div class="value-text"><i class="bi bi-dot"></i> Block Hash</div>
<div class="value-text"><i class="bi bi-dot"></i> kernel</div> <div class="value-text"><i class="bi bi-dot"></i> Kernel</div>
<div class="value-text"><i class="bi bi-dot"></i> Unspent Output</div>
</div> </div>
</div> </div>
</code> </code>
<br>
{% endblock %} {% endblock %}

View File

@@ -6,12 +6,12 @@
{# We have different UI to display if CoinGecko API is disabled by user #} {# We have different UI to display if CoinGecko API is disabled by user #}
{% if cg_api == "on" %} {% if cg_api == "enabled" %}
{# CoinGecko API is enabled #} {# CoinGecko API is enabled #}
<div class="d-none d-md-block"> <!-- Show on >= md screens --> <div class="d-none d-md-block"> <!-- Show on >= md screens -->
<div class="card-group mb-2"> <div class="card-group">
<div class="card me-2"> <div class="card border-bottom-0 border-start-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-cash-coin"></i> PRICE</div> <div class="darkorange-text"><i class="bi bi-cash-coin"></i> PRICE</div>
<br> <br>
@@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card border-bottom-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div> <div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div>
<br> <br>
@@ -65,10 +65,9 @@
</div> </div>
</div> </div>
<div class="card-group">
<div class="card-group mb-2"> <div class="card card-background border-0">
<div class="card card-background border-0 me-2"> <div class="card border-bottom-0 border-start-0 rounded-0 mx-0 mt-0">
<div class="card rounded-end-0 mx-0 mt-0 mb-2">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div> <div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div>
<br> <br>
@@ -85,7 +84,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card rounded-end-0 mx-0 my-0"> <div class="card border-bottom-0 border-start-0 rounded-0 mx-0 my-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div> <div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div>
<br> <br>
@@ -100,7 +99,7 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card border-bottom-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div> <div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div>
<br> <br>
@@ -146,9 +145,8 @@
</div> </div>
</div> </div>
<div class="card-group">
<div class="card-group mb-4"> <div class="card rounded-0 border-start-0">
<div class="card me-2">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div> <div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div>
<br> <br>
@@ -161,7 +159,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card me-2"> <div class="card rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div> <div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div>
<br> <br>
@@ -174,7 +172,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card"> <div class="card rounded-0 border-end-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div> <div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div>
<br> <br>
@@ -196,7 +194,7 @@
<div class="d-md-none"> <!-- Show on < md screens--> <div class="d-md-none"> <!-- Show on < md screens-->
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-cash-coin"></i> PRICE</div> <div class="darkorange-text"><i class="bi bi-cash-coin"></i> PRICE</div>
<br> <br>
@@ -218,7 +216,7 @@
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div> <div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div>
<br> <br>
@@ -249,8 +247,7 @@
</div> </div>
</div> </div>
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card mb-3">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div> <div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div>
@@ -269,7 +266,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div> <div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div>
<br> <br>
@@ -313,7 +310,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div> <div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div>
<br> <br>
@@ -327,9 +324,7 @@
</div> </div>
</div> </div>
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card mb-3">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div> <div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div>
<br> <br>
@@ -342,7 +337,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div> <div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div>
<br> <br>
@@ -355,7 +350,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-4"> <div class="card rounded-0 border-start-0 border-end-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div> <div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div>
<br> <br>
@@ -465,8 +460,8 @@
{# CoinGecko API is disabled #} {# CoinGecko API is disabled #}
<div class="d-none d-md-block"> <!-- Show on >= md screens --> <div class="d-none d-md-block"> <!-- Show on >= md screens -->
<div class="card-group mb-2"> <div class="card-group">
<div class="card me-2"> <div class="card border-bottom-0 border-start-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div> <div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div>
<br> <br>
@@ -489,7 +484,7 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card border-bottom-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div> <div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div>
<br> <br>
@@ -508,9 +503,8 @@
</div> </div>
</div> </div>
<div class="card-group">
<div class="card-group mb-2"> <div class="card border-bottom-0 border-start-0 rounded-0">
<div class="card me-2">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div> <div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div>
<br> <br>
@@ -527,7 +521,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card"> <div class="card border-bottom-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div> <div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div>
<br> <br>
@@ -542,9 +536,8 @@
</div> </div>
</div> </div>
<div class="card-group">
<div class="card-group mb-4"> <div class="card border-start-0 rounded-0">
<div class="card me-2">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div> <div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div>
<br> <br>
@@ -557,7 +550,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card me-2"> <div class="card rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div> <div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div>
<br> <br>
@@ -570,7 +563,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card"> <div class="card border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div> <div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div>
<br> <br>
@@ -593,7 +586,7 @@
<div class="d-md-none"> <!-- Show on < md screens--> <div class="d-md-none"> <!-- Show on < md screens-->
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div> <div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div>
<br> <br>
@@ -616,8 +609,7 @@
</div> </div>
</div> </div>
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card mb-3">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div> <div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div>
@@ -636,7 +628,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div> <div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div>
<br> <br>
@@ -653,7 +645,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div> <div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div>
<br> <br>
@@ -667,9 +659,7 @@
</div> </div>
</div> </div>
<div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card mb-3">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div> <div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div>
<br> <br>
@@ -682,7 +672,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card border-bottom-0 border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div> <div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div>
<br> <br>
@@ -695,7 +685,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-4"> <div class="card border-start-0 border-end-0 rounded-0">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div> <div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div>
<br> <br>
@@ -738,70 +728,8 @@
</div> </div>
</div> </div>
<div class="modal fade" id="mining_cost" tabindex="-1" aria-labelledby="mining_cost_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="mining_cost_label">Estimated Mining Cost</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Mining cost to produce 1 grin coin.<br>
Assuming that:<br>
Miner is G1-mini ASIC.<br>
Electricity cost is $0.07 per kW/h.<br>
<br>
<a class="text-decoration-none" href="https://ipollo.com/products/ipollo-g1-mini">https://ipollo.com/products/ipollo-g1-mini</a>
</div>
</div>
</div>
</div> </div>
<div class="modal fade" id="ratio" tabindex="-1" aria-labelledby="ratio_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="ratio_label">Reward/Cost Ratio</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Shows the result of the following formula:<br>
Price of 1 Grin (USD) / Mining Cost of 1 Grin (USD).<br>
<br>
<i class="bi bi-hand-thumbs-down"></i> - <= 1<br>
<i class="bi bi-hand-thumbs-up"></i> - from 1 to 2<br>
<i class="bi bi-emoji-sunglasses"></i> - from 2 to 3<br>
<i class='bi bi-rocket-takeoff'></i> - >= 3
</div>
</div>
</div>
</div>
<div class="modal fade" id="breakeven" tabindex="-1" aria-labelledby="breakeven_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="breakeven_label">Breakeven Electricity Cost</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Electricity threshold cost below which mining is profitable.<br>
Assuming G1-mini ASIC as a miner device.
</div>
</div>
</div>
</div>
</div>
{% endif %} {% endif %}
</code> </code>

View File

@@ -4,7 +4,7 @@
<code> <code>
<div class="card"> <div class="card border-start-0 border-end-0 rounded-0">
<div class="card-body"> <div class="card-body">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="darkorange-text"><i class="bi bi-card-text"></i> KERNEL</div> <div class="darkorange-text"><i class="bi bi-card-text"></i> KERNEL</div>
@@ -44,8 +44,7 @@
</div> </div>
{% if kernel.status != "Unconfirmed" %} {% if kernel.status != "Unconfirmed" %}
<br> <div class="card border-top-0 border-start-0 border-end-0 rounded-0">
<div class="card">
<div class="card-body" align="left"> <div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-layout-text-sidebar-reverse"></i> RAW DATA</div> <div class="darkorange-text"><i class="bi bi-layout-text-sidebar-reverse"></i> RAW DATA</div>
<br> <br>
@@ -54,8 +53,6 @@
</div> </div>
{% endif %} {% endif %}
<br>
</code> </code>
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,52 @@
{% extends "base" %}
{% block content %}
<code>
<div class="card rounded-0">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="darkorange-text"><i class="bi bi-card-text"></i> OUTPUT</div>
{% if output.status == "Unconfirmed" %}
<span class="badge text-bg-warning px-2 py-2">UNCONFIRMED</span>
{% endif %}
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Commitment&nbsp;</div>
<div class="value-text text-break text-end">{{ output.commit }}</div>
</div>
{% if output.status != "Unconfirmed" %}
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Block Height&nbsp;</div>
<div class="value-text text-end">
<a class="text-decoration-none" href="/block/{{ output.height }}">
{{ output.height }} <i class="bi bi-box-arrow-up-right"></i>
</a>
</div>
</div>
{% endif %}
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Type&nbsp;</div>
<div class="value-text text-end">{{ output.out_type }}</div>
</div>
</div>
</div>
{% if output.status != "Unconfirmed" %}
<div class="card border-top-0 rounded-0">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-layout-text-sidebar-reverse"></i> RAW DATA</div>
<br>
<div class="value-text">{{ output.raw_data }}</div>
</div>
</div>
{% endif %}
</code>
{% endblock %}

View File

@@ -4,11 +4,11 @@
<code> <code>
<div class="card"> <div class="card border-start-0 border-end-0 rounded-0">
<div class="card-body mx-2 mt-2 mb-3"> <div class="card-body mx-2 mt-2 mb-3">
<div class="d-flex justify-content-start mb-1"> <div class="d-flex justify-content-start mb-1">
<i class="bi bi-box me-2"></i> <i class="bi bi-box darkorange-text"></i>&nbsp;
<div class="value-text text-end" hx-get="/rpc/block/latest" hx-trigger="load, every 10s"></div> <div class="darkorange-text text-end" hx-get="/rpc/block/latest" hx-trigger="load, every 10s"></div>
</div> </div>
<form class="input-group" role="search" action="/search" method="GET" autocomplete="off"> <form class="input-group" role="search" action="/search" method="GET" autocomplete="off">
<input class="form-control text-center ms-0 me-2" type="search" placeholder="Explore Grin Network" aria-label="Search" name="input" required> <input class="form-control text-center ms-0 me-2" type="search" placeholder="Explore Grin Network" aria-label="Search" name="input" required>
@@ -18,14 +18,13 @@
</form> </form>
<br><br> <br><br>
<div class="value-text mb-2">Supported search inputs:</div> <div class="value-text mb-2">Supported search inputs:</div>
<div class="value-text"><i class="bi bi-dot"></i> block number</div> <div class="value-text"><i class="bi bi-dot"></i> Block Number</div>
<div class="value-text"><i class="bi bi-dot"></i> block hash</div> <div class="value-text"><i class="bi bi-dot"></i> Block Hash</div>
<div class="value-text"><i class="bi bi-dot"></i> kernel</div> <div class="value-text"><i class="bi bi-dot"></i> Kernel</div>
<div class="value-text"><i class="bi bi-dot"></i> Unspent Output</div>
</div> </div>
</div> </div>
<br>
</code> </code>
{% endblock %} {% endblock %}