1use crate::{data::DockerStats, utils::*};
2use byte_unit::Byte;
3use colored::Colorize;
4use std::io::{self, Write};
5
6pub struct StatsDisplay {
7 width: usize,
8 compact: bool,
9 full: bool
10}
11
12impl StatsDisplay {
13 pub fn new(width: usize, compact: bool, full: bool) -> Self {
14 print!("\x1B[?25l");
16 let _ = io::stdout().flush();
17 Self { width, compact, full }
18 }
19
20 fn out_line(&self, line: &str) {
22 print!("\x1B[2K\r{}\n", line);
24 }
25
26 }
28
29impl Drop for StatsDisplay {
30 fn drop(&mut self) {
31 print!("\x1B[?25h");
33 let _ = io::stdout().flush();
34 }
35}
36
37impl StatsDisplay {
38 pub fn print_stats(&self, containers: &[DockerStats]) {
39 print!("\x1B[H");
41
42 let mut max = 100f32;
43
44 if containers.is_empty() {
45 self.out_line("Waiting for container stats...");
46 } else {
47 for stats in containers {
49 let mem_perc = perc_to_float(&stats.mem_perc);
50 let cpu_perc = perc_to_float(&stats.cpu_perc);
51 max = max.max(mem_perc).max(cpu_perc);
52 }
53
54 for (i, stats) in containers.iter().enumerate() {
55 self.print_container_stats(stats, i, containers.len(), max);
56 }
57 }
58
59 self.out_line("Press Ctrl+C to exit");
60
61 print!("\x1B[J");
63
64 let _ = io::stdout().flush();
66 }
67
68 fn print_container_stats(&self, stats: &DockerStats, index: usize, total: usize, max: f32) {
69 if !self.compact || index == 0 {
71 self.out_line(&format!("┌─ {} {}┐", stats.name, filler("─", self.width, stats.name.len() + 5)));
72 } else {
73 self.out_line(&format!("├─ {} {}┤", stats.name, fill_on_even("─", self.width, stats.name.len() + 5)));
74 }
75
76 let mem_perc = perc_to_float(&stats.mem_perc);
77 let cpu_perc = perc_to_float(&stats.cpu_perc);
78
79 let scale_factor = (self.width - 18) as f32 / max;
81 let cpu_perc_scaled = (cpu_perc * scale_factor) as usize;
82 let cpu_padding = filler(" ", 7, stats.cpu_perc.len());
83 let cpu_status = usize_to_status(cpu_perc_scaled, self.width);
84 let cpu_fill = filler("░", self.width, cpu_perc_scaled + 18).dimmed();
85
86 self.out_line(&format!("│ CPU | {cpu_padding}{} {cpu_status}{cpu_fill} │", stats.cpu_perc));
87
88 let mem_usage_len = stats.mem_usage.len() + 1;
90 let scale_factor = (self.width - (18 + mem_usage_len)) as f32 / max;
91 let mem_perc_scaled = (mem_perc * scale_factor) as usize;
92 let mem_padding = filler(" ", 7, stats.mem_perc.len());
93 let mem_status = usize_to_status(mem_perc_scaled, self.width - (18 + mem_usage_len));
94 let mem_fill = filler("░", self.width, mem_perc_scaled + (18 + mem_usage_len)).dimmed();
95 let mem_spacing = filler(" ", mem_usage_len, mem_usage_len);
96
97 self.out_line(&format!(
98 "│ RAM | {mem_padding}{} {mem_status}{mem_fill}{mem_spacing} {} │",
99 stats.mem_perc, stats.mem_usage
100 ));
101
102 if self.full {
103 self.print_full_stats(stats);
104 }
105
106 if !self.compact || index == total - 1 {
107 self.out_line(&format!("└{}┘", filler("─", self.width, 2)));
108 }
109 }
110
111 fn print_full_stats(&self, stats: &DockerStats) {
112 self.out_line(&format!("│{}│", fill_on_even("─", self.width, 2).dimmed()));
113
114 if let Ok(net) = self.parse_network_stats(&stats.net_io) {
116 self.out_line(&format!(
117 "│ NET | {}{}{} │",
118 filler("▒", self.width - 11, net[0]).green(),
119 "░".dimmed(),
120 filler("▒", self.width - 11, net[1]).red()
121 ));
122 }
123
124 if let Ok(io) = self.parse_block_stats(&stats.block_io) {
126 self.out_line(&format!(
127 "│ IO | {}{}{} │",
128 filler("▒", io[0], 0).white(),
129 "░".dimmed(),
130 filler("▒", io[1], 0).black()
131 ));
132 }
133 }
134
135 fn parse_network_stats(&self, net_io: &str) -> Result<Vec<usize>, Box<dyn std::error::Error>> {
136 let parts: Vec<&str> = net_io.split(" / ").collect();
137 if parts.len() != 2 {
138 return Ok(balanced_split(self.width - 11));
139 }
140
141 let bytes = vec![
142 Byte::parse_str(parts[0], true)?.as_u128(),
143 Byte::parse_str(parts[1], true)?.as_u128(),
144 ];
145
146 Ok(scale_between(bytes, 1, self.width - 12).unwrap_or_else(|| balanced_split(self.width - 11)))
147 }
148
149 fn parse_block_stats(&self, block_io: &str) -> Result<Vec<usize>, Box<dyn std::error::Error>> {
150 let parts: Vec<&str> = block_io.split(" / ").collect();
151 if parts.len() != 2 {
152 return Ok(balanced_split(self.width - 11));
153 }
154
155 let bytes = vec![
156 Byte::parse_str(parts[0], true)?.as_u128(),
157 Byte::parse_str(parts[1], true)?.as_u128(),
158 ];
159
160 Ok(scale_between(bytes, 1, self.width - 12).unwrap_or_else(|| balanced_split(self.width - 11)))
161 }
162}