1+ use std:: time:: Duration ;
2+
13use axum:: Form ;
24use axum:: extract:: State ;
35use axum:: response:: { IntoResponse , Redirect , Response } ;
46use bfte_invite:: InviteString ;
57use maud:: { Markup , html} ;
68use serde:: Deserialize ;
79use 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 ;
1114use crate :: misc:: Maud ;
1215use crate :: { ArcUiState , ROUTE_INIT_CONSENSUS , ROUTE_UI , UiState } ;
1316
@@ -23,23 +26,29 @@ pub struct Input {
2326
2427pub 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
4147impl 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 )
0 commit comments