feat: add boilerplate templates, utxo page
This commit is contained in:
parent
951758902a
commit
4509b1f9a6
137
Cargo.lock
generated
137
Cargo.lock
generated
@ -451,6 +451,20 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boilerplate"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1906889b1f805a715eac02b2dea416e25c5cfa00f099530fa9d137a3cff93113"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"mime",
|
||||||
|
"new_mime_guess",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.16.0"
|
||||||
@ -569,7 +583,7 @@ dependencies = [
|
|||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim 0.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -838,6 +852,41 @@ dependencies = [
|
|||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.20.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim 0.10.0",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@ -1250,6 +1299,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "html-escaper"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "459a0ca33ee92551e0a3bb1774f2d3bdd1c09fb6341845736662dd25e1fcb52a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@ -1306,6 +1361,12 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range-header"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@ -1418,6 +1479,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@ -1629,6 +1696,16 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -1673,6 +1750,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "neptune-core"
|
name = "neptune-core"
|
||||||
version = "0.0.5"
|
version = "0.0.5"
|
||||||
|
source = "git+https://github.com/Neptune-Crypto/neptune-core.git?rev=9a7973259d63f148f8bf353866f260ba939d4649#9a7973259d63f148f8bf353866f260ba939d4649"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aead",
|
"aead",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
@ -1730,17 +1808,30 @@ name = "neptune-explorer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.5",
|
"axum 0.7.5",
|
||||||
|
"boilerplate",
|
||||||
"clap",
|
"clap",
|
||||||
|
"html-escaper",
|
||||||
"neptune-core",
|
"neptune-core",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tarpc",
|
"tarpc",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "new_mime_guess"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2d684d1b59e0dc07b37e2203ef576987473288f530082512aff850585c61b1f"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@ -2207,7 +2298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48"
|
checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.12.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.58",
|
"syn 2.0.58",
|
||||||
@ -2655,6 +2746,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -2790,7 +2887,7 @@ dependencies = [
|
|||||||
"const_format",
|
"const_format",
|
||||||
"derive_tasm_object",
|
"derive_tasm_object",
|
||||||
"hex",
|
"hex",
|
||||||
"itertools 0.10.5",
|
"itertools 0.12.1",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"num",
|
"num",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@ -3048,6 +3145,31 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -3232,6 +3354,15 @@ version = "0.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
|||||||
@ -12,7 +12,7 @@ serde_json = "1.0.115"
|
|||||||
tokio = { version = "1.37.0", features = ["full", "tracing"] }
|
tokio = { version = "1.37.0", features = ["full", "tracing"] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
neptune-core = {path = "../neptune-core"}
|
neptune-core = {git = "https://github.com/Neptune-Crypto/neptune-core.git", rev = "9a7973259d63f148f8bf353866f260ba939d4649"}
|
||||||
tarpc = { version = "^0.34", features = [
|
tarpc = { version = "^0.34", features = [
|
||||||
"tokio1",
|
"tokio1",
|
||||||
"serde-transport",
|
"serde-transport",
|
||||||
@ -21,6 +21,10 @@ tarpc = { version = "^0.34", features = [
|
|||||||
] }
|
] }
|
||||||
clap = "4.5.4"
|
clap = "4.5.4"
|
||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
|
#boilerplate = { version = "1.0.0", features = ["axum"] }
|
||||||
|
boilerplate = { version = "1.0.0" }
|
||||||
|
html-escaper = "0.2.0"
|
||||||
|
tower-http = { version = "0.5.2", features = ["fs"] }
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|||||||
208
src/main.rs
208
src/main.rs
@ -5,10 +5,15 @@ use axum::{
|
|||||||
routing::get,
|
routing::get,
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
use tower_http::{
|
||||||
|
services::ServeFile,
|
||||||
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::ops::Deref;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use html_escaper::{Escape, Trusted};
|
||||||
|
|
||||||
use neptune_core::config_models::network::Network;
|
use neptune_core::config_models::network::Network;
|
||||||
use neptune_core::models::blockchain::block::block_height::BlockHeight;
|
use neptune_core::models::blockchain::block::block_height::BlockHeight;
|
||||||
@ -32,7 +37,7 @@ pub struct Config {
|
|||||||
port: u16,
|
port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppState {
|
pub struct AppState {
|
||||||
network: Network,
|
network: Network,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
config: Config,
|
config: Config,
|
||||||
@ -205,76 +210,18 @@ async fn root(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Html<String> {
|
) -> Html<String> {
|
||||||
|
|
||||||
let network = state.network;
|
#[derive(boilerplate::Boilerplate)]
|
||||||
let genesis_block_hex = state.genesis_digest.to_hex();
|
#[boilerplate(filename = "web/html/page/root.html")]
|
||||||
|
pub struct RootHtmlPage(Arc<AppState>);
|
||||||
|
impl Deref for RootHtmlPage {
|
||||||
|
type Target = AppState;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let html = format!(r#"
|
let root_page = RootHtmlPage(state);
|
||||||
<html>
|
Html(root_page.to_string())
|
||||||
<head>
|
|
||||||
<title>Neptune Block Explorer: (network: {network})</title>
|
|
||||||
<style>
|
|
||||||
div.indent {{position: relative; left: 20px;}}
|
|
||||||
body {{font-family: arial, helvetica;}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Neptune Block Explorer (network: {network})</h1>
|
|
||||||
<script>
|
|
||||||
function handle_submit(form){{
|
|
||||||
let value = form.height_or_digest.value;
|
|
||||||
var is_digest = value.length == 80;
|
|
||||||
var type = is_digest ? "digest" : "height";
|
|
||||||
var uri = form.action + "/" + type + "/" + value;
|
|
||||||
window.location.href = uri;
|
|
||||||
return false;
|
|
||||||
}}
|
|
||||||
</script>
|
|
||||||
<form action="/block" method="get" onsubmit="return handle_submit(this)">
|
|
||||||
Block height or digest:
|
|
||||||
<input type="text" size="80" name="height_or_digest"/>
|
|
||||||
<input type="submit" name="height" value="Lookup Block"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
Quick Lookup:
|
|
||||||
<a href="/block/genesis">Genesis Block</a> |
|
|
||||||
<a href="/block/tip">Tip</a><br/>
|
|
||||||
|
|
||||||
<h2>REST RPCs</h2>
|
|
||||||
|
|
||||||
<h3>/block_info</h3>
|
|
||||||
<div class="indent">
|
|
||||||
<h4>Examples</h4>
|
|
||||||
|
|
||||||
<a href="/rpc/block_info/genesis">/rpc/block_info/genesis</a><br/>
|
|
||||||
<a href="/rpc/block_info/tip">/rpc/block_info/tip</a><br/>
|
|
||||||
<a href="/rpc/block_info/height/2">/rpc/block_info/height/2</a><br/>
|
|
||||||
<a href="/rpc/block_info/digest/{genesis_block_hex}">/rpc/block_info/digest/{genesis_block_hex}</a><br/>
|
|
||||||
<a href="/rpc/block_info/height_or_digest/1">/rpc/block_info/height_or_digest/1</a><br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>/block_digest</h3>
|
|
||||||
<div class="indent">
|
|
||||||
<h4>Examples</h4>
|
|
||||||
|
|
||||||
<a href="/rpc/block_digest/genesis">/rpc/block_digest/genesis</a><br/>
|
|
||||||
<a href="/rpc/block_digest/tip">/rpc/block_digest/tip</a><br/>
|
|
||||||
<a href="/rpc/block_digest/height/2">/rpc/block_digest/height/2</a><br/>
|
|
||||||
<a href="/rpc/block_digest/digest/{genesis_block_hex}">/rpc/block_digest/digest/{genesis_block_hex}</a><br/>
|
|
||||||
<a href="/rpc/block_digest/height_or_digest/{genesis_block_hex}">/rpc/block_digest/height_or_digest/{genesis_block_hex}</a><br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>/utxo_digest</h3>
|
|
||||||
<div class="indent">
|
|
||||||
<h4>Examples</h4>
|
|
||||||
|
|
||||||
<a href="/rpc/utxo_digest/2">/rpc/utxo_digest/2</a><br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"#);
|
|
||||||
|
|
||||||
Html(html)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_page(
|
async fn block_page(
|
||||||
@ -285,77 +232,63 @@ async fn block_page(
|
|||||||
block_page_with_value(value_path, state).await
|
block_page_with_value(value_path, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(boilerplate::Boilerplate)]
|
||||||
|
#[boilerplate(filename = "web/html/components/header.html")]
|
||||||
|
pub struct HeaderHtml{
|
||||||
|
site_name: String,
|
||||||
|
state: Arc<AppState>,
|
||||||
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
async fn block_page_with_value(
|
async fn block_page_with_value(
|
||||||
Path((path_block_selector, value)): Path<(PathBlockSelector, String)>,
|
Path((path_block_selector, value)): Path<(PathBlockSelector, String)>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Html<String>, Response> {
|
) -> Result<Html<String>, Response> {
|
||||||
|
|
||||||
let block_info = block_info_with_value_worker(state, path_block_selector, &value).await?;
|
#[derive(boilerplate::Boilerplate)]
|
||||||
let BlockInfo {height, digest, timestamp, num_inputs, num_outputs, num_uncle_blocks, difficulty, mining_reward, fee, is_genesis, is_tip, ..} = block_info;
|
#[boilerplate(filename = "web/html/page/block_info.html")]
|
||||||
|
pub struct BlockInfoHtmlPage{
|
||||||
let digest_hex = digest.to_hex();
|
header: HeaderHtml,
|
||||||
|
block_info: BlockInfo
|
||||||
let prev_link = match is_genesis {
|
|
||||||
true => "".to_string(),
|
|
||||||
false => format!("<a href='/block/height/{}'>Previous Block</a>", height.previous())
|
|
||||||
};
|
|
||||||
|
|
||||||
let next_link = match is_tip {
|
|
||||||
true => "".to_string(),
|
|
||||||
false => format!("<a href='/block/height/{}'>Next Block</a>", height.next())
|
|
||||||
};
|
|
||||||
|
|
||||||
let special_block_notice = match (is_genesis, is_tip) {
|
|
||||||
(true, false) => "<p>This is the Genesis Block</p>",
|
|
||||||
(false, true) => "<p>This is the Latest Block (tip)</p>",
|
|
||||||
_ => "",
|
|
||||||
};
|
|
||||||
|
|
||||||
let timestamp_display = timestamp.standard_format();
|
|
||||||
|
|
||||||
let html = format!( r#"
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Neptune Block Explorer: Block Height {height}</title>
|
|
||||||
<style>
|
|
||||||
div.indent {{position: relative; left: 20px;}}
|
|
||||||
body {{font-family: arial, helvetica;}}
|
|
||||||
table.alt {{margin-top: 10px; margin-bottom: 10px; padding: 10px; border-collapse: collapse; }}
|
|
||||||
table.alt td, th {{ padding: 5px;}}
|
|
||||||
table.alt tr:nth-child(odd) td{{background:#eee;}}
|
|
||||||
table.alt tr:nth-child(even) td{{background:#fff;}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Block height: {height}</h1>
|
|
||||||
<b>Digest:</b> {digest_hex}
|
|
||||||
|
|
||||||
{special_block_notice}
|
|
||||||
|
|
||||||
<table class="alt">
|
|
||||||
<tr><td>Created</td><td>{timestamp_display}</td></tr>
|
|
||||||
<tr><td>Inputs</td><td>{num_inputs}<br/></td></tr>
|
|
||||||
<tr><td>Outputs</td><td>{num_outputs}</td></tr>
|
|
||||||
<tr><td>Uncle blocks</td><td>{num_uncle_blocks}</td></tr>
|
|
||||||
<tr><td>Difficulty</td><td>{difficulty}</td></tr>
|
|
||||||
<tr><td>Mining Reward</td><td>{mining_reward}</td></tr>
|
|
||||||
<tr><td>Fee</td><td>{fee}</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="/">Home</a>
|
|
||||||
{prev_link}
|
|
||||||
{next_link}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"# );
|
|
||||||
|
|
||||||
Ok(Html(html))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let header = HeaderHtml{site_name: "Neptune Explorer".to_string(), state: state.clone()};
|
||||||
|
|
||||||
|
let block_info = block_info_with_value_worker(state, path_block_selector, &value).await?;
|
||||||
|
let block_info_page = BlockInfoHtmlPage{header, block_info};
|
||||||
|
Ok(Html(block_info_page.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
async fn utxo_page(
|
||||||
|
Path(index): Path<u64>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Result<Html<String>, Response> {
|
||||||
|
|
||||||
|
#[derive(boilerplate::Boilerplate)]
|
||||||
|
#[boilerplate(filename = "web/html/page/utxo.html")]
|
||||||
|
pub struct UtxoHtmlPage{
|
||||||
|
header: HeaderHtml,
|
||||||
|
index: u64,
|
||||||
|
digest: Digest,
|
||||||
|
}
|
||||||
|
|
||||||
|
let digest = match state
|
||||||
|
.rpc_client
|
||||||
|
.utxo_digest(context::current(), index)
|
||||||
|
.await
|
||||||
|
.map_err(rpc_err)?
|
||||||
|
{
|
||||||
|
Some(digest) => digest,
|
||||||
|
None => return Err(not_found_err()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = HeaderHtml{site_name: "Neptune Explorer".to_string(), state: state.clone()};
|
||||||
|
|
||||||
|
let utxo_page = UtxoHtmlPage{index, header, digest};
|
||||||
|
Ok(Html(utxo_page.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), RpcError> {
|
async fn main() -> Result<(), RpcError> {
|
||||||
@ -371,6 +304,7 @@ async fn main() -> Result<(), RpcError> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
// -- RPC calls --
|
||||||
.route("/rpc/block_info/:selector", get(block_info))
|
.route("/rpc/block_info/:selector", get(block_info))
|
||||||
.route("/rpc/block_info/:selector/:value", get(block_info_with_value))
|
.route("/rpc/block_info/:selector/:value", get(block_info_with_value))
|
||||||
.route(
|
.route(
|
||||||
@ -379,9 +313,17 @@ async fn main() -> Result<(), RpcError> {
|
|||||||
)
|
)
|
||||||
.route("/rpc/block_digest/:selector", get(block_digest))
|
.route("/rpc/block_digest/:selector", get(block_digest))
|
||||||
.route("/rpc/utxo_digest/:index", get(utxo_digest))
|
.route("/rpc/utxo_digest/:index", get(utxo_digest))
|
||||||
|
|
||||||
|
// -- Dynamic HTML pages --
|
||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
.route("/block/:selector", get(block_page))
|
.route("/block/:selector", get(block_page))
|
||||||
.route("/block/:selector/:value", get(block_page_with_value))
|
.route("/block/:selector/:value", get(block_page_with_value))
|
||||||
|
.route("/utxo/:value", get(utxo_page))
|
||||||
|
|
||||||
|
// -- Static files --
|
||||||
|
.route_service("/css/styles.css", ServeFile::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/web/css/styles.css")))
|
||||||
|
|
||||||
|
// add state
|
||||||
.with_state(shared_state);
|
.with_state(shared_state);
|
||||||
|
|
||||||
println!("Running on http://localhost:3000");
|
println!("Running on http://localhost:3000");
|
||||||
|
|||||||
42
src/web/css/styles.css
Normal file
42
src/web/css/styles.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
div.box h1,
|
||||||
|
div.box h2,
|
||||||
|
div.box h3 {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.indent {
|
||||||
|
position: relative;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.box {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: solid 1px #ccc;
|
||||||
|
border-top-left-radius: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: arial, helvetica;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.alt {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.alt td,
|
||||||
|
th {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.alt tr:nth-child(odd) td {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.alt tr:nth-child(even) td {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
1
src/web/html/components/header.html
Normal file
1
src/web/html/components/header.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1>{{self.site_name}} : {{self.state.network}}</h1>
|
||||||
74
src/web/html/page/block_info.html
Normal file
74
src/web/html/page/block_info.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Neptune Block Explorer: Block Height {{self.block_info.height}}</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/styles.css" media="screen" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{Trusted(self.header.to_string())}}
|
||||||
|
|
||||||
|
<h2>Block height: {{self.block_info.height}}</h2>
|
||||||
|
|
||||||
|
<!-- special_block_notice -->
|
||||||
|
%% if self.block_info.is_genesis {
|
||||||
|
<p>This is the Genesis Block</p>
|
||||||
|
%% }
|
||||||
|
%% if self.block_info.is_tip {
|
||||||
|
<p>This is the Latest Block (tip)</p>
|
||||||
|
%% }
|
||||||
|
|
||||||
|
<table class="alt">
|
||||||
|
<tr>
|
||||||
|
<td>Digest</td>
|
||||||
|
<td>{{self.block_info.digest.to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Created</td>
|
||||||
|
<td>{{self.block_info.timestamp.standard_format()}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Inputs</td>
|
||||||
|
<td>{{self.block_info.num_inputs}}<br /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Outputs</td>
|
||||||
|
<td>{{self.block_info.num_outputs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Uncle blocks</td>
|
||||||
|
<td>{{self.block_info.num_uncle_blocks}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Difficulty</td>
|
||||||
|
<td>{{self.block_info.difficulty}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Mining Reward</td>
|
||||||
|
<td>{{self.block_info.mining_reward}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Fee</td>
|
||||||
|
<td>{{self.block_info.fee}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
| <a href='/block/genesis'>Genesis</a>
|
||||||
|
| <a href='/block/tip'>Tip</a>
|
||||||
|
%% if self.block_info.is_genesis {
|
||||||
|
| Previous Block
|
||||||
|
%% } else {
|
||||||
|
| <a href='/block/height/{{self.block_info.height.previous()}}'>Previous Block</a>
|
||||||
|
%% }
|
||||||
|
|
||||||
|
%% if self.block_info.is_tip {
|
||||||
|
| Next Block
|
||||||
|
%% } else {
|
||||||
|
| <a href='/block/height/{{self.block_info.height.next()}}'>Next Block</a>
|
||||||
|
%% }
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
80
src/web/html/page/root.html
Normal file
80
src/web/html/page/root.html
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Neptune Block Explorer: (network: {{self.network}})</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/styles.css" media="screen" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Neptune Block Explorer (network: {{self.network}})</h1>
|
||||||
|
<script>
|
||||||
|
function handle_submit(form){
|
||||||
|
let value = form.height_or_digest.value;
|
||||||
|
var is_digest = value.length == 80;
|
||||||
|
var type = is_digest ? "digest" : "height";
|
||||||
|
var uri = form.action + "/" + type + "/" + value;
|
||||||
|
window.location.href = uri;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function handle_utxo_submit(form) {
|
||||||
|
let value = form.utxo.value;
|
||||||
|
var uri = form.action + "/" + value;
|
||||||
|
window.location.href = uri;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>Block Lookup</h3>
|
||||||
|
<form action="/block" method="get" onsubmit="return handle_submit(this)">
|
||||||
|
Block height or digest:
|
||||||
|
<input type="text" size="80" name="height_or_digest"/>
|
||||||
|
<input type="submit" name="height" value="Lookup Block"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
Quick Lookup:
|
||||||
|
<a href="/block/genesis">Genesis Block</a> |
|
||||||
|
<a href="/block/tip">Tip</a><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>Utxo Lookup</h3>
|
||||||
|
<form action="/utxo" method="get" onsubmit="return handle_utxo_submit(this)">
|
||||||
|
Utxo index:
|
||||||
|
<input type="text" size="10" name="utxo" />
|
||||||
|
<input type="submit" name="height" value="Lookup Utxo" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>REST RPCs</h2>
|
||||||
|
|
||||||
|
<h3>/block_info</h3>
|
||||||
|
<div class="indent">
|
||||||
|
<h4>Examples</h4>
|
||||||
|
|
||||||
|
<a href="/rpc/block_info/genesis">/rpc/block_info/genesis</a><br/>
|
||||||
|
<a href="/rpc/block_info/tip">/rpc/block_info/tip</a><br/>
|
||||||
|
<a href="/rpc/block_info/height/2">/rpc/block_info/height/2</a><br/>
|
||||||
|
<a href="/rpc/block_info/digest/{{self.genesis_digest.to_hex()}}">/rpc/block_info/digest/{{self.genesis_digest.to_hex()}}</a><br/>
|
||||||
|
<a href="/rpc/block_info/height_or_digest/1">/rpc/block_info/height_or_digest/1</a><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>/block_digest</h3>
|
||||||
|
<div class="indent">
|
||||||
|
<h4>Examples</h4>
|
||||||
|
|
||||||
|
<a href="/rpc/block_digest/genesis">/rpc/block_digest/genesis</a><br/>
|
||||||
|
<a href="/rpc/block_digest/tip">/rpc/block_digest/tip</a><br/>
|
||||||
|
<a href="/rpc/block_digest/height/2">/rpc/block_digest/height/2</a><br/>
|
||||||
|
<a href="/rpc/block_digest/digest/{genesis_block_hex}">/rpc/block_digest/digest/{genesis_block_hex}</a><br/>
|
||||||
|
<a href="/rpc/block_digest/height_or_digest/{genesis_block_hex}">/rpc/block_digest/height_or_digest/{genesis_block_hex}</a><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>/utxo_digest</h3>
|
||||||
|
<div class="indent">
|
||||||
|
<h4>Examples</h4>
|
||||||
|
|
||||||
|
<a href="/rpc/utxo_digest/2">/rpc/utxo_digest/2</a><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
src/web/html/page/utxo.html
Normal file
30
src/web/html/page/utxo.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Neptune Block Explorer: Utxo {{self.index}}</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/styles.css" media="screen" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{Trusted(self.header.to_string())}}
|
||||||
|
|
||||||
|
<h3>Utxo Information</h3>
|
||||||
|
<table class="alt">
|
||||||
|
<tr>
|
||||||
|
<td>Index</td>
|
||||||
|
<td>{{self.index}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Digest</td>
|
||||||
|
<td>{{self.digest.to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
| <a href='/block/genesis'>Genesis</a>
|
||||||
|
| <a href='/block/tip'>Tip</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
templates/web
Symbolic link
1
templates/web
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../src/web
|
||||||
Loading…
x
Reference in New Issue
Block a user