fedimint_gateway_ui/
payment_summary.rs1use std::time::{Duration, UNIX_EPOCH};
2
3use fedimint_core::time::now;
4use fedimint_gateway_common::{PaymentStats, PaymentSummaryPayload, PaymentSummaryResponse};
5use maud::{Markup, html};
6
7use crate::DynGatewayApi;
8
9pub async fn render<E>(api: &DynGatewayApi<E>) -> Markup
10where
11 E: std::fmt::Display,
12{
13 let now = now();
14 let now_millis = now
15 .duration_since(UNIX_EPOCH)
16 .expect("Before unix epoch")
17 .as_millis() as u64;
18
19 let one_day_ago = now
20 .checked_sub(Duration::from_secs(60 * 60 * 24))
21 .expect("Before unix epoch");
22 let one_day_ago_millis = one_day_ago
23 .duration_since(UNIX_EPOCH)
24 .expect("Before unix epoch")
25 .as_millis() as u64;
26
27 let payment_summary = api
29 .handle_payment_summary_msg(PaymentSummaryPayload {
30 start_millis: one_day_ago_millis,
31 end_millis: now_millis,
32 })
33 .await;
34
35 match payment_summary {
36 Ok(summary) => render_summary(&summary),
37 Err(e) => html! {
38 div class="card h-100 border-danger" {
39 div class="card-header dashboard-header bg-danger text-white" {
40 "Payment Summary"
41 }
42 div class="card-body" {
43 div class="alert alert-danger mb-0" {
44 strong { "Failed to fetch payment summary: " }
45 (e.to_string())
46 }
47 }
48 }
49 },
50 }
51}
52
53fn render_summary(summary: &PaymentSummaryResponse) -> Markup {
54 html! {
55 div class="card h-100" {
56 div class="card-header dashboard-header" { "Payment Summary (Last 24h)" }
57 div class="card-body" {
58 div class="row" {
59 div class="col-md-6" {
60 (render_stats_table("Outgoing Payments", &summary.outgoing, "text-danger"))
61 }
62 div class="col-md-6" {
63 (render_stats_table("Incoming Payments", &summary.incoming, "text-success"))
64 }
65 }
66 }
67 }
68 }
69}
70
71fn render_stats_table(title: &str, stats: &PaymentStats, title_class: &str) -> Markup {
72 html! {
73 div {
74 h5 class=(format!("{} mb-3", title_class)) { (title) }
75
76 table class="table table-sm mb-0" {
77 tbody {
78 tr {
79 th { "✅ Total Success" }
80 td { (stats.total_success) }
81 }
82 tr {
83 th { "❌ Total Failure" }
84 td { (stats.total_failure) }
85 }
86 tr {
87 th { "💸 Total Fees" }
88 td { (format!("{} msats", stats.total_fees.msats)) }
89 }
90 tr {
91 th { "⚡ Average Latency" }
92 td {
93 (match stats.average_latency {
94 Some(d) => format_duration(d),
95 None => "—".into(),
96 })
97 }
98 }
99 tr {
100 th { "📈 Median Latency" }
101 td {
102 (match stats.median_latency {
103 Some(d) => format_duration(d),
104 None => "—".into(),
105 })
106 }
107 }
108 }
109 }
110 }
111 }
112}
113
114fn format_duration(d: Duration) -> String {
115 if d.as_secs() > 0 {
116 format!("{:.2}s", d.as_secs_f64())
117 } else {
118 format!("{} ms", d.as_millis())
119 }
120}