fedimint_server_ui/dashboard/
mod.rs

1pub mod audit;
2pub mod bitcoin;
3pub(crate) mod consensus_explorer;
4pub mod general;
5pub mod invite;
6pub mod latency;
7pub mod modules;
8
9use axum::Router;
10use axum::extract::{Form, State};
11use axum::response::{Html, IntoResponse};
12use axum::routing::{get, post};
13use axum_extra::extract::cookie::CookieJar;
14use consensus_explorer::consensus_explorer_view;
15use fedimint_server_core::dashboard_ui::{DashboardApiModuleExt, DynDashboardApi};
16use maud::{DOCTYPE, Markup, html};
17use {fedimint_lnv2_server, fedimint_meta_server, fedimint_wallet_server};
18
19use crate::assets::WithStaticRoutesExt as _;
20use crate::auth::UserAuth;
21use crate::dashboard::modules::{lnv2, meta, wallet};
22use crate::{
23    EXPLORER_IDX_ROUTE, EXPLORER_ROUTE, LOGIN_ROUTE, LoginInput, ROOT_ROUTE, UiState, common_head,
24    login_form_response, login_submit_response,
25};
26
27pub fn dashboard_layout(content: Markup) -> Markup {
28    html! {
29        (DOCTYPE)
30        html {
31            head {
32                (common_head("Dashboard"))
33            }
34            body {
35                div class="container" {
36                    header class="text-center" {
37                        h1 class="header-title" { "Fedimint Guardian UI" }
38                    }
39
40                    (content)
41                }
42                script src="/assets/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous" {}
43            }
44        }
45    }
46}
47
48// Dashboard login form handler
49async fn login_form(State(_state): State<UiState<DynDashboardApi>>) -> impl IntoResponse {
50    login_form_response()
51}
52
53// Dashboard login submit handler
54async fn login_submit(
55    State(state): State<UiState<DynDashboardApi>>,
56    jar: CookieJar,
57    Form(input): Form<LoginInput>,
58) -> impl IntoResponse {
59    login_submit_response(
60        state.api.auth().await,
61        state.auth_cookie_name,
62        state.auth_cookie_value,
63        jar,
64        input,
65    )
66    .into_response()
67}
68
69// Main dashboard view
70async fn dashboard_view(
71    State(state): State<UiState<DynDashboardApi>>,
72    _auth: UserAuth,
73) -> impl IntoResponse {
74    let guardian_names = state.api.guardian_names().await;
75    let federation_name = state.api.federation_name().await;
76    let session_count = state.api.session_count().await;
77    let consensus_ord_latency = state.api.consensus_ord_latency().await;
78    let p2p_connection_status = state.api.p2p_connection_status().await;
79    let invite_code = state.api.federation_invite_code().await;
80    let audit_summary = state.api.federation_audit().await;
81    let bitcoin_rpc_url = state.api.bitcoin_rpc_url().await;
82    let bitcoin_rpc_status = state.api.bitcoin_rpc_status().await;
83
84    let content = html! {
85        div class="row gy-4" {
86            div class="col-md-6" {
87                (general::render(&federation_name, session_count, &guardian_names))
88            }
89
90            div class="col-md-6" {
91                (invite::render(&invite_code))
92            }
93        }
94
95        div class="row gy-4 mt-2" {
96            div class="col-lg-6" {
97                (audit::render(&audit_summary))
98            }
99
100            div class="col-lg-6" {
101                (latency::render(consensus_ord_latency, &p2p_connection_status))
102            }
103        }
104
105        div class="row gy-4 mt-2" {
106            div class="col-12" {
107                (bitcoin::render(bitcoin_rpc_url, &bitcoin_rpc_status))
108            }
109        }
110
111        // Conditionally add Lightning V2 UI if the module is available
112        @if let Some(lightning) = state.api.get_module::<fedimint_lnv2_server::Lightning>() {
113            div class="row gy-4 mt-2" {
114                div class="col-12" {
115                    (lnv2::render(lightning).await)
116                }
117            }
118        }
119
120        // Conditionally add Wallet UI if the module is available
121        @if let Some(wallet_module) = state.api.get_module::<fedimint_wallet_server::Wallet>() {
122            div class="row gy-4 mt-2" {
123                div class="col-12" {
124                    (wallet::render(wallet_module).await)
125                }
126            }
127        }
128
129        // Conditionally add Meta UI if the module is available
130        @if let Some(meta_module) = state.api.get_module::<fedimint_meta_server::Meta>() {
131            div class="row gy-4 mt-2" {
132                div class="col-12" {
133                    (meta::render(meta_module).await)
134                }
135            }
136        }
137    };
138
139    Html(dashboard_layout(content).into_string()).into_response()
140}
141
142pub fn router(api: DynDashboardApi) -> Router {
143    let mut app = Router::new()
144        .route(ROOT_ROUTE, get(dashboard_view))
145        .route(LOGIN_ROUTE, get(login_form).post(login_submit))
146        .route(EXPLORER_ROUTE, get(consensus_explorer_view))
147        .route(EXPLORER_IDX_ROUTE, get(consensus_explorer_view))
148        .with_static_routes();
149
150    // routeradd LNv2 gateway routes if the module exists
151    if api
152        .get_module::<fedimint_lnv2_server::Lightning>()
153        .is_some()
154    {
155        app = app
156            .route(lnv2::LNV2_ADD_ROUTE, post(lnv2::post_add))
157            .route(lnv2::LNV2_REMOVE_ROUTE, post(lnv2::post_remove));
158    }
159
160    // Only add Meta module routes if the module exists
161    if api.get_module::<fedimint_meta_server::Meta>().is_some() {
162        app = app
163            .route(meta::META_SUBMIT_ROUTE, post(meta::post_submit))
164            .route(meta::META_SET_ROUTE, post(meta::post_set))
165            .route(meta::META_RESET_ROUTE, post(meta::post_reset))
166            .route(meta::META_DELETE_ROUTE, post(meta::post_delete));
167    }
168
169    // Finalize the router with state
170    app.with_state(UiState::new(api))
171}