Skip to content

Commit 1883b2d

Browse files
committed
chore: usability polish
1 parent b6f1f9c commit 1883b2d

8 files changed

Lines changed: 110 additions & 33 deletions

File tree

crates/bfte/src/logging.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ use std::io;
33
use bfte_util_error::{Whatever, WhateverResult};
44
use snafu::FromString as _;
55
use tracing_subscriber::EnvFilter;
6-
use tracing_subscriber::filter::LevelFilter;
76

87
pub fn init_logging() -> WhateverResult<()> {
98
tracing_subscriber::fmt()
109
.with_writer(io::stderr)
1110
.with_env_filter(
1211
EnvFilter::builder()
13-
.with_default_directive(LevelFilter::INFO.into())
12+
.with_default_directive("bfte=info".parse().expect("Can't fail"))
1413
.from_env_lossy(),
1514
)
1615
.try_init()

crates/node-ui-axum/src/error.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ use bfte_util_error::Whatever;
77
use maud::html;
88
use serde::Serialize;
99
use snafu::Snafu;
10+
use tracing::debug;
1011

11-
use crate::ROUTE_LOGIN;
1212
use crate::misc::{AppJson, Maud};
13+
use crate::{LOG_TARGET, ROUTE_LOGIN};
1314

1415
#[derive(Debug, Snafu)]
1516
#[snafu(visibility(pub(crate)))]
@@ -18,6 +19,14 @@ pub enum UserRequestError {
1819
InvalidData,
1920
#[snafu(display("Wrong Password"))]
2021
WrongPassword,
22+
#[snafu(display("Failed to create consensus: {source}"))]
23+
ConsensusCreate {
24+
source: Whatever,
25+
},
26+
#[snafu(display("Failed to join consensus: {source}"))]
27+
ConsensusJoin {
28+
source: Whatever,
29+
},
2130
Other {
2231
source: Whatever,
2332
},
@@ -29,10 +38,14 @@ impl IntoResponse for &UserRequestError {
2938
p id="error-response" { (self.to_string()) }
3039
});
3140

41+
debug!(target: LOG_TARGET, err = self.to_string(), "Error handling request");
42+
3243
let (status_code, html) = match self {
3344
UserRequestError::SomethingNotFound => (StatusCode::NOT_FOUND, html),
3445
UserRequestError::InvalidData => (StatusCode::BAD_REQUEST, html),
3546
UserRequestError::WrongPassword => (StatusCode::BAD_REQUEST, html),
47+
UserRequestError::ConsensusCreate { .. } => (StatusCode::BAD_REQUEST, html),
48+
UserRequestError::ConsensusJoin { .. } => (StatusCode::BAD_REQUEST, html),
3649
UserRequestError::Other { .. } => (StatusCode::BAD_REQUEST, html),
3750
};
3851
(status_code, html).into_response()

crates/node-ui-axum/src/fragments.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,23 @@ pub(crate) fn labeled_input(
2020
data-bind=[bind];
2121
}
2222
}
23+
24+
#[bon::builder]
25+
pub(crate) fn labeled_textarea(
26+
name: &str,
27+
label: &str,
28+
id: Option<&str>,
29+
placeholder: Option<&str>,
30+
bind: Option<&str>,
31+
required: Option<bool>,
32+
) -> Markup {
33+
let id = id.unwrap_or(name);
34+
let required = required.unwrap_or_default();
35+
36+
html! {
37+
label for=(name) { (label) }
38+
textarea
39+
id=(id) name=(name) placeholder=[placeholder] required[required]
40+
data-bind=[bind] {}
41+
}
42+
}

crates/node-ui-axum/src/page.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ impl UiState {
8888
a ."secondary" data-discover="true" href=(ROUTE_INVITE) { "Invite Code" }
8989
}
9090
li {
91-
a ."secondary"
92-
data-discover="true"
91+
a ."secondary"
92+
data-discover="true"
9393
href=(ROUTE_EXPLORER)
9494
aria-current=[active_nabvar.is_explorer().then_some("page")]
9595
{ "Explorer" }

crates/node-ui-axum/src/routes/init.rs

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
use std::time::Duration;
2+
13
use axum::Form;
24
use axum::extract::State;
35
use axum::response::{IntoResponse, Redirect, Response};
46
use bfte_invite::InviteString;
57
use maud::{Markup, html};
68
use serde::Deserialize;
79
use snafu::ResultExt as _;
10+
use tokio::time::timeout;
811

9-
use crate::error::{OtherSnafu, RequestResult};
10-
use crate::fragments::labeled_input;
12+
use crate::error::{ConsensusCreateSnafu, ConsensusJoinSnafu, OtherSnafu, RequestResult};
13+
use crate::fragments::labeled_textarea;
1114
use crate::misc::Maud;
1215
use crate::{ArcUiState, ROUTE_INIT_CONSENSUS, ROUTE_UI, UiState};
1316

@@ -23,23 +26,29 @@ pub struct Input {
2326

2427
pub async fn post(state: State<ArcUiState>, Form(form): Form<Input>) -> RequestResult<Response> {
2528
if let Some(invite) = form.invite {
26-
state
27-
.node_api
28-
.consensus_join(&invite.into())
29-
.await
30-
.context(OtherSnafu)?;
29+
timeout(
30+
Duration::from_secs(30),
31+
state.node_api.consensus_join(&invite.into()),
32+
)
33+
.await
34+
.whatever_context("Timeout")
35+
.context(ConsensusJoinSnafu)?
36+
.context(ConsensusJoinSnafu)?;
3137
} else {
3238
state
3339
.node_api
3440
.consensus_init(vec![/* TODO */])
3541
.await
36-
.context(OtherSnafu)?;
42+
.context(ConsensusCreateSnafu)?;
3743
}
3844
Ok(Redirect::to(ROUTE_UI).into_response())
3945
}
4046

4147
impl UiState {
4248
pub(crate) async fn render_consensus_init_page(&self) -> RequestResult<Markup> {
49+
let has_secret = self.node_api.has_root_secret().context(OtherSnafu)?;
50+
let is_db_ephemeral = self.node_api.is_database_ephemeral().context(OtherSnafu)?;
51+
let can_create_consensus = has_secret && !is_db_ephemeral;
4352
let content = html! {
4453
section ."init-consensus-form" {
4554
div class="grid" {
@@ -49,9 +58,23 @@ impl UiState {
4958
header {
5059
h2 { "Create new consensus" }
5160
}
52-
form method="post" action=(ROUTE_INIT_CONSENSUS) {
61+
div role="status" {
62+
p id="error-response-form-create";
63+
}
64+
form
65+
method="post"
66+
action=(ROUTE_INIT_CONSENSUS)
67+
x-target="_top"
68+
"x-target.error"="error-response-form-create:error-response"
69+
{
5370
p { "Start a new consensus network as the first node." }
54-
button type="submit" { "Create" }
71+
button type="submit" disabled[(!can_create_consensus)] { "Create" }
72+
@if !has_secret {
73+
p { "Must have a root secret set with" code { "--secret-path" } "." }
74+
}
75+
@if is_db_ephemeral {
76+
p { "Must set location for a persistent database set with" code { "--data-dir" } "."}
77+
}
5578
}
5679
}
5780
}
@@ -62,12 +85,21 @@ impl UiState {
6285
header {
6386
h2 { "Join existing consensus" }
6487
}
65-
form method="post" action=(ROUTE_INIT_CONSENSUS) {
88+
div role="status" {
89+
p id="error-response-form-join";
90+
}
91+
form
92+
method="post"
93+
action=(ROUTE_INIT_CONSENSUS)
94+
x-target="_top"
95+
"x-target.error"="error-response-form-join:error-response"
96+
{
6697
(
67-
labeled_input()
98+
labeled_textarea()
6899
.name("invite")
69-
.label("Invite code")
70-
.r#type("text")
100+
.label(
101+
"Join an existing consensus as a non-voting node with an invite code"
102+
)
71103
.required(true)
72104
.call()
73105
)

crates/node-ui-axum/src/routes/module/consensus_ctrl.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl UiState {
5858
method="post"
5959
x-target="_none"
6060
"x-target.error"="error-response-form-add:error-response"
61-
"x-target.back"="_top"
61+
"x-target.away"="_top"
6262
action=(format!("/ui/module/{}/add_peer_vote", module_id))
6363
{
6464
fieldset role="group" {
@@ -85,7 +85,7 @@ impl UiState {
8585
method="post"
8686
x-target="_none"
8787
"x-target.error"="error-response-form-remove:error-response"
88-
"x-target.back"="_top"
88+
"x-target.away"="_top"
8989
action=(format!("/ui/module/{}/remove_peer_vote", module_id))
9090
{
9191
fieldset role="group" {

crates/node-ui/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub trait INodeUiApi {
4141
async fn change_ui_password(&self, pass: &str) -> WhateverResult<()>;
4242
fn is_ui_password_temporary(&self) -> WhateverResult<bool>;
4343

44+
fn has_root_secret(&self) -> WhateverResult<bool>;
4445
fn is_consensus_initialized(&self) -> WhateverResult<bool>;
4546
async fn consensus_init(&self, extra_peers: Vec<PeerPubkey>) -> WhateverResult<()>;
4647
async fn consensus_join(&self, invite: &Invite) -> WhateverResult<()>;
@@ -54,5 +55,8 @@ pub trait INodeUiApi {
5455
fn is_database_ephemeral(&self) -> WhateverResult<bool>;
5556
async fn generate_invite_code(&self) -> WhateverResult<Invite>;
5657

57-
async fn get_consensus_history(&self, limit: usize) -> WhateverResult<Vec<ConsensusHistoryEntry>>;
58+
async fn get_consensus_history(
59+
&self,
60+
limit: usize,
61+
) -> WhateverResult<Vec<ConsensusHistoryEntry>>;
5862
}

crates/node/src/ui_api.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ impl INodeUiApi for NodeUiApi {
4343
Ok(())
4444
}
4545

46+
fn has_root_secret(&self) -> WhateverResult<bool> {
47+
Ok(self.node_ref()?.root_secret().is_some())
48+
}
49+
4650
fn is_ui_password_temporary(&self) -> WhateverResult<bool> {
4751
Ok(self
4852
.node_ref()?
@@ -59,7 +63,7 @@ impl INodeUiApi for NodeUiApi {
5963
.node_ref()?
6064
.consensus_init(extra_peers)
6165
.await
62-
.whatever_context("Failed to join consensus")?)
66+
.whatever_context("Failed to init consensus")?)
6367
}
6468
async fn consensus_join(&self, invite: &Invite) -> WhateverResult<()> {
6569
Ok(self
@@ -112,25 +116,30 @@ impl INodeUiApi for NodeUiApi {
112116
self.node_ref()?.generate_invite_code().await
113117
}
114118

115-
async fn get_consensus_history(&self, limit: usize) -> WhateverResult<Vec<ConsensusHistoryEntry>> {
119+
async fn get_consensus_history(
120+
&self,
121+
limit: usize,
122+
) -> WhateverResult<Vec<ConsensusHistoryEntry>> {
116123
let node_ref = self.node_ref()?;
117124
let consensus_option = node_ref.consensus();
118125
let consensus = consensus_option
119126
.as_ref()
120127
.whatever_context("Consensus not initialized")?;
121-
128+
122129
let history_data = consensus.get_consensus_history(limit).await;
123-
130+
124131
let history = history_data
125132
.into_iter()
126-
.map(|(round, block_header, signatory_peers)| ConsensusHistoryEntry {
127-
round,
128-
is_dummy: block_header.is_none(),
129-
block_header,
130-
signatory_peers,
131-
})
133+
.map(
134+
|(round, block_header, signatory_peers)| ConsensusHistoryEntry {
135+
round,
136+
is_dummy: block_header.is_none(),
137+
block_header,
138+
signatory_peers,
139+
},
140+
)
132141
.collect();
133-
142+
134143
Ok(history)
135144
}
136145
}

0 commit comments

Comments
 (0)