feat: integrate team feedback

Changes:
 + present not-found page for unhandled uri paths, eg /stuff
 + display the tip-height on front page
 + add favicon and neptune logo to front page
 + use ServerDir instead of ServeFile for /image and /css
 + add help tooltips for fields on front page
 + use monospace font for digests
This commit is contained in:
danda 2024-05-14 11:54:42 -07:00
parent d7d1778fea
commit faf303bbdd
10 changed files with 134 additions and 41 deletions

View File

@ -1,7 +1,9 @@
use crate::html::component::header::HeaderHtml; use crate::html::component::header::HeaderHtml;
use crate::http_util::not_found_html_err; use crate::http_util::not_found_html_err;
use crate::http_util::not_found_html_handler;
use crate::model::app_state::AppState; use crate::model::app_state::AppState;
use axum::extract::State; use axum::extract::State;
use axum::http::StatusCode;
use axum::response::Html; use axum::response::Html;
use axum::response::Response; use axum::response::Response;
use html_escaper::Escape; use html_escaper::Escape;
@ -39,3 +41,7 @@ pub fn not_found_html_response(
) -> Response { ) -> Response {
not_found_html_err(not_found_page(State(state), error_msg)) not_found_html_err(not_found_page(State(state), error_msg))
} }
pub fn not_found_html_fallback(state: Arc<AppState>) -> (StatusCode, Html<String>) {
not_found_html_handler(not_found_page(State(state), None))
}

View File

@ -1,22 +1,28 @@
use crate::html::page::not_found::not_found_html_response;
use crate::model::app_state::AppState; use crate::model::app_state::AppState;
use axum::extract::State; use axum::extract::State;
use axum::response::Html; use axum::response::Html;
use axum::response::Response;
use html_escaper::Escape; use html_escaper::Escape;
use std::ops::Deref; use neptune_core::models::blockchain::block::block_height::BlockHeight;
use std::sync::Arc; use std::sync::Arc;
use tarpc::context;
#[axum::debug_handler] #[axum::debug_handler]
pub async fn root(State(state): State<Arc<AppState>>) -> Html<String> { pub async fn root(State(state): State<Arc<AppState>>) -> Result<Html<String>, Response> {
#[derive(boilerplate::Boilerplate)] #[derive(boilerplate::Boilerplate)]
#[boilerplate(filename = "web/html/page/root.html")] #[boilerplate(filename = "web/html/page/root.html")]
pub struct RootHtmlPage(Arc<AppState>); pub struct RootHtmlPage {
impl Deref for RootHtmlPage { tip_height: BlockHeight,
type Target = AppState; state: Arc<AppState>,
fn deref(&self) -> &Self::Target {
&self.0
}
} }
let root_page = RootHtmlPage(state); let tip_height = state
Html(root_page.to_string()) .rpc_client
.block_height(context::current())
.await
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?;
let root_page = RootHtmlPage { tip_height, state };
Ok(Html(root_page.to_string()))
} }

View File

@ -15,6 +15,10 @@ pub fn not_found_html_err(html: Html<String>) -> Response {
(StatusCode::NOT_FOUND, html).into_response() (StatusCode::NOT_FOUND, html).into_response()
} }
pub fn not_found_html_handler(html: Html<String>) -> (StatusCode, Html<String>) {
(StatusCode::NOT_FOUND, html)
}
pub fn rpc_err(e: RpcError) -> Response { pub fn rpc_err(e: RpcError) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response() (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
} }

View File

@ -5,6 +5,7 @@ use neptune_core::models::blockchain::block::block_selector::BlockSelector;
use neptune_core::rpc_server::RPCClient; use neptune_core::rpc_server::RPCClient;
use neptune_explorer::html::page::block::block_page; use neptune_explorer::html::page::block::block_page;
use neptune_explorer::html::page::block::block_page_with_value; use neptune_explorer::html::page::block::block_page_with_value;
use neptune_explorer::html::page::not_found::not_found_html_fallback;
use neptune_explorer::html::page::root::root; use neptune_explorer::html::page::root::root;
use neptune_explorer::html::page::utxo::utxo_page; use neptune_explorer::html::page::utxo::utxo_page;
use neptune_explorer::model::app_state::AppState; use neptune_explorer::model::app_state::AppState;
@ -21,7 +22,7 @@ use tarpc::client;
use tarpc::client::RpcError; use tarpc::client::RpcError;
use tarpc::context; use tarpc::context;
use tarpc::tokio_serde::formats::Json as RpcJson; use tarpc::tokio_serde::formats::Json as RpcJson;
use tower_http::services::ServeFile; use tower_http::services::ServeDir;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), RpcError> { async fn main() -> Result<(), RpcError> {
@ -58,20 +59,16 @@ async fn main() -> Result<(), RpcError> {
.route("/block/:selector/:value", get(block_page_with_value)) .route("/block/:selector/:value", get(block_page_with_value))
.route("/utxo/:value", get(utxo_page)) .route("/utxo/:value", get(utxo_page))
// -- Static files -- // -- Static files --
.route_service( .nest_service(
"/css/pico.min.css", "/css",
ServeFile::new(concat!( ServeDir::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/web/css")),
env!("CARGO_MANIFEST_DIR"),
"/templates/web/css/pico.min.css"
)),
) )
.route_service( .nest_service(
"/css/styles.css", "/image",
ServeFile::new(concat!( ServeDir::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/web/image")),
env!("CARGO_MANIFEST_DIR"),
"/templates/web/css/styles.css"
)),
) )
// handle route not-found
.fallback(not_found_html_fallback(shared_state.clone()))
// add state // add state
.with_state(shared_state); .with_state(shared_state);

View File

@ -3,6 +3,59 @@ div.indent {
left: 20px; left: 20px;
} }
.mono {
font-family: monospace, monospace;
}
.center-text { .center-text {
text-align: center; text-align: center;
} }
/* Tooltip container */
.tooltip {
position: relative;
display: inline-block;
bottom: 0.1em;
transition: 0s;
}
/* Tooltip text */
.tooltip .tooltiptext {
transition-delay: 0.5s;
top: -5px;
left: 115%;
visibility: hidden;
width: 300px;
background-color: white;
color: rgb(0, 118, 118);
text-align: center;
padding: 5px;
border-radius: 6px;
border: solid 1px rgb(1, 220, 220);
/* Position the tooltip text - see examples below! */
position: absolute;
z-index: 1;
}
/* adds a speech-bubble thingy at top-left of tooltip */
.tooltip .tooltiptext::after {
content: " ";
position: absolute;
top: 20px;
right: 100%;
/* To the left of the tooltip */
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent rgb(1, 220, 220) transparent transparent;
}
/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
visibility: visible;
}
.tooltip:hover {
cursor: help;
}

View File

@ -27,7 +27,7 @@
<table class="striped"> <table class="striped">
<tr> <tr>
<td>Digest</td> <td>Digest</td>
<td>{{self.block_info.digest.to_hex()}}</td> <td class="mono">{{self.block_info.digest.to_hex()}}</td>
</tr> </tr>
<tr> <tr>
<td>Created</td> <td>Created</td>

View File

@ -1,8 +1,9 @@
<html> <html>
<head> <head>
<title>Neptune Block Explorer: (network: {{self.network}})</title> <title>Neptune Block Explorer: (network: {{self.state.network}})</title>
<link rel="stylesheet" type="text/css" href="/css/styles.css" media="screen" /> <link rel="stylesheet" type="text/css" href="/css/styles.css" media="screen" />
<link rel="stylesheet" type="text/css" href="/css/pico.min.css" media="screen" /> <link rel="stylesheet" type="text/css" href="/css/pico.min.css" media="screen" />
<link rel="icon" type="image/png" sizes="48x48" href="/image/neptune-favicon.png">
<script> <script>
function handle_submit(form) { function handle_submit(form) {
@ -24,17 +25,29 @@
</head> </head>
<body> <body>
<header class="container"> <header class="container">
<h1>Neptune Block Explorer (network: {{self.network}})</h1> <h1>
<img src="/image/neptune-logo.png" align="right"/>
Neptune Block Explorer (network: {{self.state.network}})
</h1>
The blockchain tip is at height: {{self.tip_height}}
</header> </header>
<main class="container"> <main class="container">
<article> <article>
<details open> <details open>
<summary>Block Lookup</summary> <summary>
Block Lookup
</summary>
<form action="/block" method="get" onsubmit="return handle_submit(this)"> <form action="/block" method="get" onsubmit="return handle_submit(this)">
<span class="tooltip">🛈
<span class="tooltiptext">
Provide a numeric block height or hexadecimal digest identifier to lookup any block in the Neptune blockchain.
</span>
</span>
Block height or digest: Block height or digest:
<input type="text" size="80" name="height_or_digest"/> <input type="text" size="80" name="height_or_digest" class="mono"/>
<input type="submit" name="height" value="Lookup Block"/> <input type="submit" name="height" value="Lookup Block"/>
</form> </form>
@ -45,19 +58,28 @@ Quick Lookup:
</article> </article>
<article> <article>
<details> <details open>
<summary>Utxo Lookup</summary> <summary>UTXO Lookup</summary>
<form action="/utxo" method="get" onsubmit="return handle_utxo_submit(this)"> <form action="/utxo" method="get" onsubmit="return handle_utxo_submit(this)">
Utxo index: <span class="tooltip">🛈
<span class="tooltiptext">
An Unspent Transaction Output (UTXO) index can be found in the output of <i>neptune-cli wallet-status</i>. Look for the field: <b>aocl_leaf_index</b>
</span>
</span>
UTXO index:
<input type="text" size="10" name="utxo" /> <input type="text" size="10" name="utxo" />
<input type="submit" name="height" value="Lookup Utxo" /> <input type="submit" name="height" value="Lookup Utxo" />
</form> </form>
</details> </details>
</article> </article>
<h2>REST RPCs</h2>
<article> <article>
<details>
<summary>REST RPCs</summary>
<section>
RPC endpoints are available for automating block explorer queries:
</section>
<details> <details>
<summary>/block_info</summary> <summary>/block_info</summary>
<div class="indent"> <div class="indent">
@ -67,14 +89,12 @@ Quick Lookup:
<li><a href="/rpc/block_info/genesis">/rpc/block_info/genesis</a></li> <li><a href="/rpc/block_info/genesis">/rpc/block_info/genesis</a></li>
<li><a href="/rpc/block_info/tip">/rpc/block_info/tip</a></li> <li><a href="/rpc/block_info/tip">/rpc/block_info/tip</a></li>
<li><a href="/rpc/block_info/height/2">/rpc/block_info/height/2</a></li> <li><a href="/rpc/block_info/height/2">/rpc/block_info/height/2</a></li>
<li><a href="/rpc/block_info/digest/{{self.genesis_digest.to_hex()}}">/rpc/block_info/digest/{{self.genesis_digest.to_hex()}}</a></li> <li><a href="/rpc/block_info/digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_info/digest/{{self.state.genesis_digest.to_hex()}}</a></li>
<li><a href="/rpc/block_info/height_or_digest/1">/rpc/block_info/height_or_digest/1</a></li> <li><a href="/rpc/block_info/height_or_digest/1">/rpc/block_info/height_or_digest/1</a></li>
</ul> </ul>
</div> </div>
</details> </details>
</article>
<article>
<details> <details>
<summary>/block_digest</summary> <summary>/block_digest</summary>
<div class="indent"> <div class="indent">
@ -84,14 +104,12 @@ Quick Lookup:
<li><a href="/rpc/block_digest/genesis">/rpc/block_digest/genesis</a></li> <li><a href="/rpc/block_digest/genesis">/rpc/block_digest/genesis</a></li>
<li><a href="/rpc/block_digest/tip">/rpc/block_digest/tip</a></li> <li><a href="/rpc/block_digest/tip">/rpc/block_digest/tip</a></li>
<li><a href="/rpc/block_digest/height/2">/rpc/block_digest/height/2</a></li> <li><a href="/rpc/block_digest/height/2">/rpc/block_digest/height/2</a></li>
<li><a href="/rpc/block_digest/digest/{{self.genesis_digest.to_hex()}}">/rpc/block_digest/digest/{{self.genesis_digest.to_hex()}}</a></li> <li><a href="/rpc/block_digest/digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_digest/digest/{{self.state.genesis_digest.to_hex()}}</a></li>
<li><a href="/rpc/block_digest/height_or_digest/{{self.genesis_digest.to_hex()}}">/rpc/block_digest/height_or_digest/{{self.genesis_digest.to_hex()}}</a></li> <li><a href="/rpc/block_digest/height_or_digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_digest/height_or_digest/{{self.state.genesis_digest.to_hex()}}</a></li>
</ul> </ul>
</div> </div>
</details> </details>
</article>
<article>
<details> <details>
<summary>/utxo_digest</summary> <summary>/utxo_digest</summary>
<div class="indent"> <div class="indent">
@ -101,6 +119,8 @@ Quick Lookup:
<li><a href="/rpc/utxo_digest/2">/rpc/utxo_digest/2</a><br/></li> <li><a href="/rpc/utxo_digest/2">/rpc/utxo_digest/2</a><br/></li>
</ul> </ul>
</div> </div>
</details>
</details> </details>
</article> </article>

View File

@ -13,7 +13,14 @@
<main class="container"> <main class="container">
<article> <article>
<summary>Utxo Information</summary> <summary>
<span class="tooltip">🛈
<span class="tooltiptext">
UTXO = Unspent Transaction Output. It represents an output of transaction A which can also be an input to transaction B.
</span>
</span>
UTXO Information
</summary>
<table class="striped"> <table class="striped">
<tr> <tr>
<td>Index</td> <td>Index</td>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB