1use colored::*;
2use console::Term;
3use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
4use rand::RngExt as _;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8use std::io::{self, Write};
9use std::thread;
10use std::time::Duration;
11
12use netrunner_core::TestConfig;
13
14#[derive(Clone)]
16pub struct BandwidthMonitor {
17 pub speed_history: Arc<RwLock<Vec<f64>>>,
18 pub current_speed: Arc<RwLock<f64>>,
19 pub peak_speed: Arc<RwLock<f64>>,
20 pub is_final: Arc<RwLock<bool>>,
21 pub throbber_frame: Arc<RwLock<usize>>,
22 #[allow(dead_code)]
23 pub title: String,
24 pub label: String,
25}
26
27impl BandwidthMonitor {
28 pub fn new(title: String, label: String) -> Self {
29 Self {
30 speed_history: Arc::new(RwLock::new(Vec::new())),
31 current_speed: Arc::new(RwLock::new(0.0)),
32 peak_speed: Arc::new(RwLock::new(0.0)),
33 is_final: Arc::new(RwLock::new(false)),
34 throbber_frame: Arc::new(RwLock::new(0)),
35 title,
36 label,
37 }
38 }
39
40 pub async fn update(&self, speed: f64) {
41 let mut history = self.speed_history.write().await;
42 let mut current = self.current_speed.write().await;
43 let mut peak = self.peak_speed.write().await;
44 let mut frame = self.throbber_frame.write().await;
45
46 *current = speed;
47 *peak = peak.max(speed);
48 history.push(speed);
49
50 *frame = (*frame + 1) % 10;
52
53 if history.len() > 100 {
55 history.remove(0);
56 }
57 }
58
59 pub async fn mark_final(&self) {
60 let mut is_final = self.is_final.write().await;
61 *is_final = true;
62 }
63
64 pub async fn render_live(&self) -> io::Result<()> {
65 let history = self.speed_history.read().await;
66 let current = self.current_speed.read().await;
67 let peak = self.peak_speed.read().await;
68 let is_final = self.is_final.read().await;
69 let frame = self.throbber_frame.read().await;
70
71 let throbber_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
73 let indicator = if *is_final {
74 "✓"
75 } else {
76 &throbber_chars[*frame].to_string()
77 };
78
79 println!(
80 "{} {}: {}",
81 indicator.bright_cyan(),
82 self.label.bright_blue().bold(),
83 format!("{:.1} Mbps", current).bright_green().bold()
84 );
85 println!();
86 println!(
87 "{} {}",
88 "Peak:".bright_cyan(),
89 format!("{:.1} Mbps", peak).bright_cyan()
90 );
91 println!();
92
93 let max_val = if history.is_empty() {
95 1.0
96 } else {
97 history.iter().cloned().fold(0.0f64, f64::max).max(1.0)
98 };
99 let width = 80; let height = 8; for row in (0..height).rev() {
104 let threshold = (row as f64 / height as f64) * max_val;
105 print!("│");
106
107 if history.is_empty() {
108 for _ in 0..width {
110 print!(" ");
111 }
112 } else {
113 let samples_to_show = history.len().min(width);
115 let start_idx = history.len().saturating_sub(width);
116
117 for i in start_idx..history.len() {
118 let speed = history[i];
119 let char = if speed >= threshold { "█" } else { " " };
120 print!("{}", char.bright_yellow());
121 }
122
123 for _ in 0..(width - samples_to_show) {
125 print!(" ");
126 }
127 }
128
129 println!();
130 }
131
132 print!("└");
134 for _ in 0..width {
135 print!("─");
136 }
137 println!();
138
139 std::io::stdout().flush()?;
140 Ok(())
141 }
142
143 pub async fn render_live_update(&self) -> io::Result<()> {
144 let history = self.speed_history.read().await;
145 let current = self.current_speed.read().await;
146 let peak = self.peak_speed.read().await;
147 let is_final = self.is_final.read().await;
148 let frame = self.throbber_frame.read().await;
149
150 print!("\x1B[13A"); for _ in 0..13 {
153 print!("\x1B[2K"); print!("\x1B[1B"); }
156 print!("\x1B[13A"); let throbber_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
160 let indicator = if *is_final {
161 "✓"
162 } else {
163 &throbber_chars[*frame].to_string()
164 };
165
166 println!(
167 "{} {}: {}",
168 indicator.bright_cyan(),
169 self.label.bright_blue().bold(),
170 format!("{:.1} Mbps", current).bright_green().bold()
171 );
172 println!();
173 println!(
174 "{} {}",
175 "Peak:".bright_cyan(),
176 format!("{:.1} Mbps", peak).bright_cyan()
177 );
178 println!();
179
180 let max_val = if history.is_empty() {
182 1.0
183 } else {
184 history.iter().cloned().fold(0.0f64, f64::max).max(1.0)
185 };
186 let width = 80;
187 let height = 8;
188
189 for row in (0..height).rev() {
191 let threshold = (row as f64 / height as f64) * max_val;
192 print!("│");
193
194 if history.is_empty() {
195 for _ in 0..width {
196 print!(" ");
197 }
198 } else {
199 let samples_to_show = history.len().min(width);
200 let start_idx = history.len().saturating_sub(width);
201
202 for i in start_idx..history.len() {
203 let speed = history[i];
204 let char = if speed >= threshold { "█" } else { " " };
205 print!("{}", char.bright_yellow());
206 }
207
208 for _ in 0..(width - samples_to_show) {
209 print!(" ");
210 }
211 }
212
213 println!();
214 }
215
216 print!("└");
218 for _ in 0..width {
219 print!("─");
220 }
221 println!();
222
223 std::io::stdout().flush()?;
224 Ok(())
225 }
226}
227
228pub struct UI {
229 term: Term,
230 multi_progress: MultiProgress,
231}
232
233impl UI {
234 pub fn new(_config: TestConfig) -> Self {
235 Self {
236 term: Term::stdout(),
237 multi_progress: MultiProgress::new(),
238 }
239 }
240
241 pub fn clear_screen(&self) -> io::Result<()> {
242 self.term.clear_screen()
243 }
244
245 pub fn show_welcome_banner(&self) -> io::Result<()> {
246 self.term.clear_screen()?;
247
248 let banner = r#"
249 _ _ ______ _______ _____ _ _ _ _ _ _ ______ _____
250| \ | | ____|__ __| __ \| | | | \ | | \ | | ____| __ \
251| \| | |__ | | | |__) | | | | \| | \| | |__ | |__) |
252| . ` | __| | | | _ /| | | | . ` | . ` | __| | _ /
253| |\ | |____ | | | | \ \| |__| | |\ | |\ | |____| | \ \
254|_| \_|______| |_| |_| \_\\____/|_| \_|_| \_|______|_| \_\
255
256 "#;
257
258 println!("{}", banner.bright_cyan());
259
260 println!("{}", "SYSTEM STATUS".bright_magenta().bold());
261 println!("{}", "⟨⟨⟨ NEURAL INTERFACE: ONLINE ⟩⟩⟩".bright_green());
262 println!("{}", "⟨⟨⟨ NETWORK SCANNER: INITIALIZED ⟩⟩⟩".bright_green());
263 println!("{}", "⟨⟨⟨ QUANTUM DIAGNOSTICS: READY ⟩⟩⟩".bright_green());
264 println!();
265 println!(
266 "{}",
267 ">>> JACK IN AND ANALYZE YOUR DIGITAL HIGHWAY <<<"
268 .bright_yellow()
269 .bold()
270 );
271 println!(
272 "{}",
273 ">>> DATA FLOWS | PACKET STREAMS | NEURAL PATHS <<<".bright_blue()
274 );
275 println!();
276
277 Ok(())
278 }
279
280 pub fn create_progress_bar(&self, len: u64, message: &str) -> ProgressBar {
281 let pb = self.multi_progress.add(ProgressBar::new(len));
282 pb.set_style(
283 ProgressStyle::default_bar()
284 .template(
285 "{spinner:.green} {msg} [{bar:40.cyan/blue}] {percent}% [{elapsed_precise}]",
286 )
287 .unwrap()
288 .progress_chars("━━╸─"),
289 );
290 pb.set_message(message.to_string());
291 pb
292 }
293
294 pub fn create_speed_test_spinner(&self, message: &str) -> ProgressBar {
295 let pb = self.multi_progress.add(ProgressBar::new_spinner());
296 pb.set_style(
297 ProgressStyle::default_spinner()
298 .template("{spinner:.bright_cyan} {msg}")
299 .unwrap()
300 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
301 );
302 pb.set_message(message.to_string());
303 pb.enable_steady_tick(Duration::from_millis(80));
304 pb
305 }
306
307 pub fn create_download_spinner(&self, message: &str) -> ProgressBar {
308 let pb = self.multi_progress.add(ProgressBar::new_spinner());
309 pb.set_style(
310 ProgressStyle::default_spinner()
311 .template("{spinner:.bright_green} {msg}")
312 .unwrap()
313 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
314 );
315 pb.set_message(message.to_string());
316 pb.enable_steady_tick(Duration::from_millis(80));
317 pb
318 }
319
320 pub fn create_upload_spinner(&self, message: &str) -> ProgressBar {
321 let pb = self.multi_progress.add(ProgressBar::new_spinner());
322 pb.set_style(
323 ProgressStyle::default_spinner()
324 .template("{spinner:.bright_blue} {msg}")
325 .unwrap()
326 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
327 );
328 pb.set_message(message.to_string());
329 pb.enable_steady_tick(Duration::from_millis(80));
330 pb
331 }
332
333 pub fn create_ping_spinner(&self, message: &str) -> ProgressBar {
334 let pb = self.multi_progress.add(ProgressBar::new_spinner());
335 pb.set_style(
336 ProgressStyle::default_spinner()
337 .template("{spinner:.bright_magenta} {msg}")
338 .unwrap()
339 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
340 );
341 pb.set_message(message.to_string());
342 pb.enable_steady_tick(Duration::from_millis(80));
343 pb
344 }
345
346 pub fn create_spinner(&self, message: &str) -> ProgressBar {
347 let pb = self.multi_progress.add(ProgressBar::new_spinner());
348 pb.set_style(
349 ProgressStyle::default_spinner()
350 .template("{spinner:.bright_green} {msg}")
351 .unwrap()
352 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
353 );
354 pb.set_message(message.to_string());
355 pb.enable_steady_tick(Duration::from_millis(100));
356 pb
357 }
358
359 pub fn create_cyberpunk_spinner(&self, message: &str) -> ProgressBar {
360 let pb = self.multi_progress.add(ProgressBar::new_spinner());
361 pb.set_style(
362 ProgressStyle::default_spinner()
363 .template("{spinner:.bright_cyan} {msg}")
364 .unwrap()
365 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
366 );
367 pb.set_message(message.to_string());
368 pb.enable_steady_tick(Duration::from_millis(80));
369 pb
370 }
371
372 pub fn create_dna_helix_spinner(&self, message: &str) -> ProgressBar {
373 let pb = self.multi_progress.add(ProgressBar::new_spinner());
374 pb.set_style(
375 ProgressStyle::default_spinner()
376 .template("{spinner:.bright_green} {msg}")
377 .unwrap()
378 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
379 );
380 pb.set_message(message.to_string());
381 pb.enable_steady_tick(Duration::from_millis(80));
382 pb
383 }
384
385 pub fn create_rocket_spinner(&self, message: &str) -> ProgressBar {
386 let pb = self.multi_progress.add(ProgressBar::new_spinner());
387 pb.set_style(
388 ProgressStyle::default_spinner()
389 .template("{spinner:.bright_yellow} {msg}")
390 .unwrap()
391 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
392 );
393 pb.set_message(message.to_string());
394 pb.enable_steady_tick(Duration::from_millis(80));
395 pb
396 }
397
398 pub fn create_wave_spinner(&self, message: &str) -> ProgressBar {
399 let pb = self.multi_progress.add(ProgressBar::new_spinner());
400 pb.set_style(
401 ProgressStyle::default_spinner()
402 .template("{spinner:.bright_cyan} {msg}")
403 .unwrap()
404 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
405 );
406 pb.set_message(message.to_string());
407 pb.enable_steady_tick(Duration::from_millis(80));
408 pb
409 }
410
411 pub fn create_network_scanner_bar(&self, message: &str) -> ProgressBar {
412 let pb = self.multi_progress.add(ProgressBar::new_spinner());
413 pb.set_style(
414 ProgressStyle::default_spinner()
415 .template("{spinner:.bright_yellow} {msg}")
416 .unwrap()
417 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
418 );
419 pb.set_message(message.to_string());
420 pb.enable_steady_tick(Duration::from_millis(80));
421 pb
422 }
423
424 pub fn create_pacman_spinner(&self, message: &str) -> ProgressBar {
425 let pb = self.multi_progress.add(ProgressBar::new_spinner());
426 pb.set_style(
427 ProgressStyle::default_spinner()
428 .template("{spinner:.bright_yellow} {msg}")
429 .unwrap()
430 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
431 );
432 pb.set_message(message.to_string());
433 pb.enable_steady_tick(Duration::from_millis(80));
434 pb
435 }
436
437 pub fn show_section_header(&self, title: &str) -> io::Result<()> {
438 println!();
439 println!(
440 "{}",
441 format!(">>> {} <<<", title.to_uppercase())
442 .bright_magenta()
443 .bold()
444 );
445 Ok(())
446 }
447
448 pub fn show_error(&self, message: &str) -> io::Result<()> {
449 println!("{} {}", "ERROR:".bright_red().bold(), message.bright_red());
450 Ok(())
451 }
452
453 pub fn show_info(&self, message: &str) -> io::Result<()> {
454 println!("{} {}", "INFO:".bright_blue().bold(), message.bright_blue());
455 Ok(())
456 }
457
458 pub fn show_typing_effect(&self, text: &str) -> io::Result<()> {
459 for char in text.chars() {
460 print!("{}", char.to_string().bright_green());
461 std::io::stdout().flush()?;
462 thread::sleep(Duration::from_millis(50));
463 }
464 println!();
465 Ok(())
466 }
467
468 pub fn show_matrix_effect(&self, lines: usize) -> io::Result<()> {
469 let matrix_chars = ["0", "1", "⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"];
470
471 for _ in 0..lines {
472 print!("{}", "█".bright_green());
473 for _ in 0..60 {
474 let idx = rand::rng().random_range(0..matrix_chars.len());
475 print!("{}", matrix_chars[idx].bright_green());
476 thread::sleep(Duration::from_millis(20));
477 }
478 println!();
479 }
480 Ok(())
481 }
482
483 pub fn show_pulse_text(&self, text: &str, pulses: usize) -> io::Result<()> {
484 for _ in 0..pulses {
485 print!("\r{}", text.bright_cyan().bold());
486 std::io::stdout().flush()?;
487 thread::sleep(Duration::from_millis(500));
488
489 print!("\r{}", text.bright_blue());
490 std::io::stdout().flush()?;
491 thread::sleep(Duration::from_millis(500));
492 }
493 println!();
494 Ok(())
495 }
496
497 pub fn show_connection_establishing(&self) -> io::Result<()> {
498 let steps = [
499 "⟨⟨⟨ INITIALIZING NEURAL INTERFACE ⟩⟩⟩",
500 "⟨⟨⟨ SCANNING NETWORK TOPOLOGY ⟩⟩⟩",
501 "⟨⟨⟨ ESTABLISHING QUANTUM TUNNEL ⟩⟩⟩",
502 "⟨⟨⟨ CALIBRATING DATA STREAMS ⟩⟩⟩",
503 "⟨⟨⟨ CONNECTION ESTABLISHED ⟩⟩⟩",
504 ];
505
506 for step in steps.iter() {
507 println!("{}", step.bright_magenta());
508 thread::sleep(Duration::from_millis(800));
509 }
510 println!();
511 Ok(())
512 }
513
514 pub fn create_bandwidth_monitor(&self, title: &str, label: &str) -> BandwidthMonitor {
515 BandwidthMonitor::new(title.to_string(), label.to_string())
516 }
517}