consortium_cli/
display.rs1use std::io::{self, Write};
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::Arc;
8use std::time::Duration;
9
10use console::style;
11use indicatif::{ProgressBar, ProgressStyle};
12
13use consortium::worker::EventHandler;
14
15#[derive(Clone)]
19pub struct ProgressState {
20 completed: Arc<AtomicUsize>,
21 failed: Arc<AtomicUsize>,
22 total: usize,
23}
24
25impl ProgressState {
26 pub fn new(total: usize) -> Self {
27 Self {
28 completed: Arc::new(AtomicUsize::new(0)),
29 failed: Arc::new(AtomicUsize::new(0)),
30 total,
31 }
32 }
33
34 pub fn completed(&self) -> usize {
35 self.completed.load(Ordering::Relaxed)
36 }
37
38 pub fn failed(&self) -> usize {
39 self.failed.load(Ordering::Relaxed)
40 }
41
42 pub fn total(&self) -> usize {
43 self.total
44 }
45}
46
47pub struct ProgressHandler {
55 state: ProgressState,
56 bar: ProgressBar,
57}
58
59impl ProgressHandler {
60 pub fn new(state: ProgressState, bar: ProgressBar) -> Self {
61 Self { state, bar }
62 }
63}
64
65impl EventHandler for ProgressHandler {
66 fn on_close(&mut self, _node: &str, rc: i32) {
67 if rc != 0 {
68 self.state.failed.fetch_add(1, Ordering::Relaxed);
69 }
70 let done = self.state.completed.fetch_add(1, Ordering::Relaxed) + 1;
71 self.bar.set_position(done as u64);
72
73 let failed = self.state.failed();
74 if failed > 0 {
75 self.bar
76 .set_message(format!("({} failed)", style(failed).red()));
77 }
78 }
79
80 fn on_timeout(&mut self, _node: &str) {
81 self.state.failed.fetch_add(1, Ordering::Relaxed);
82 let done = self.state.completed.fetch_add(1, Ordering::Relaxed) + 1;
83 self.bar.set_position(done as u64);
84 self.bar
85 .set_message(format!("({} failed)", style(self.state.failed()).red()));
86 }
87}
88
89pub fn create_progress(total: usize) -> (ProgressBar, ProgressState, ProgressHandler) {
94 let state = ProgressState::new(total);
95 let bar = ProgressBar::new(total as u64);
96
97 bar.set_style(
98 ProgressStyle::with_template(
99 "{spinner:.green} [{bar:30.cyan/dim}] {pos}/{len} nodes {msg} ({elapsed})",
100 )
101 .unwrap()
102 .progress_chars("━╸─"),
103 );
104 bar.enable_steady_tick(Duration::from_millis(80));
105
106 let handler = ProgressHandler::new(state.clone(), bar.clone());
107 (bar, state, handler)
108}
109
110pub fn finish_progress(bar: &ProgressBar, state: &ProgressState, elapsed: Duration) {
112 let done = state.completed();
113 let failed = state.failed();
114 let ok = done - failed;
115 let total = state.total();
116
117 bar.finish_and_clear();
118
119 let elapsed_str = format!("{:.1}s", elapsed.as_secs_f64());
120
121 if failed == 0 {
122 eprintln!(
123 "{} {}/{} nodes completed in {}",
124 style("✓").green().bold(),
125 ok,
126 total,
127 style(elapsed_str).dim()
128 );
129 } else {
130 eprintln!(
131 "{} {}/{} ok, {} failed in {}",
132 style("✗").red().bold(),
133 ok,
134 total,
135 style(failed).red().bold(),
136 style(elapsed_str).dim()
137 );
138 }
139}
140
141pub fn print_gathered_header(nodes: &str, out: &mut impl Write) -> io::Result<()> {
152 let sep = "-".repeat(15);
153 writeln!(out, "{sep}")?;
154 writeln!(out, "{nodes}")?;
155 writeln!(out, "{sep}")?;
156 Ok(())
157}
158
159pub fn print_line_with_label(node: &str, line: &str, out: &mut impl Write) -> io::Result<()> {
161 writeln!(out, "{node}: {line}")
162}