summaryrefslogtreecommitdiffstats
path: root/src/display/components/table.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/display/components/table.rs')
-rw-r--r--src/display/components/table.rs185
1 files changed, 185 insertions, 0 deletions
diff --git a/src/display/components/table.rs b/src/display/components/table.rs
new file mode 100644
index 0000000..6cfc2d7
--- /dev/null
+++ b/src/display/components/table.rs
@@ -0,0 +1,185 @@
+use ::std::collections::HashMap;
+
+use ::tui::backend::Backend;
+use ::tui::layout::Rect;
+use ::tui::style::{Color, Style};
+use ::tui::terminal::Frame;
+use ::tui::widgets::{Block, Borders, Row, Widget};
+
+use crate::display::{DisplayBandwidth, Bandwidth, UIState};
+use crate::network::Connection;
+
+use ::std::net::Ipv4Addr;
+use std::iter::FromIterator;
+
+const FIRST_WIDTH_BREAKPOINT: u16 = 50;
+const SECOND_WIDTH_BREAKPOINT: u16 = 71;
+const THIRD_WIDTH_BREAKPOINT: u16 = 95;
+
+const FIRST_COLUMN_WIDTHS: [u16; 4] = [20, 30, 40, 50];
+const SECOND_COLUMN_WIDTHS: [u16; 1] = [20];
+const THIRD_COLUMN_WIDTHS: [u16; 4] = [10, 20, 20, 20];
+
+
+fn display_upload_and_download(bandwidth: &impl Bandwidth) -> String {
+ format!(
+ "{}/{}",
+ DisplayBandwidth(bandwidth.get_total_bytes_uploaded() as f64),
+ DisplayBandwidth(bandwidth.get_total_bytes_downloaded() as f64)
+ )
+}
+
+fn display_ip_or_host(ip: Ipv4Addr, ip_to_host: &HashMap<Ipv4Addr, String>) -> String {
+ match ip_to_host.get(&ip) {
+ Some(host) => host.clone(),
+ None => ip.to_string(),
+ }
+}
+
+fn sort_by_bandwidth<'a, T>(
+ list: &'a mut Vec<(T, &impl Bandwidth)>,
+) -> &'a Vec<(T, &'a impl Bandwidth)> {
+ list.sort_by(|(_, a), (_, b)| {
+ let a_highest = if a.get_total_bytes_downloaded() > a.get_total_bytes_uploaded() {
+ a.get_total_bytes_downloaded()
+ } else {
+ a.get_total_bytes_uploaded()
+ };
+ let b_highest = if b.get_total_bytes_downloaded() > b.get_total_bytes_uploaded() {
+ b.get_total_bytes_downloaded()
+ } else {
+ b.get_total_bytes_uploaded()
+ };
+ b_highest.cmp(&a_highest)
+ });
+ list
+}
+
+fn display_connection_string(
+ connection: &Connection,
+ ip_to_host: &HashMap<Ipv4Addr, String>,
+) -> String {
+ format!(
+ ":{} => {}:{} ({})",
+ connection.local_port,
+ display_ip_or_host(connection.remote_socket.ip, ip_to_host),
+ connection.remote_socket.port,
+ connection.protocol,
+ )
+}
+
+pub struct Table<'a> {
+ title: &'a str,
+ column_names: &'a [&'a str],
+ rows: Vec<Vec<String>>,
+}
+
+impl <'a>Table<'a> {
+ pub fn create_connections_table(state: &UIState, ip_to_host: &HashMap<Ipv4Addr, String>) -> Self {
+ let mut connections_list = Vec::from_iter(&state.connections);
+ sort_by_bandwidth(&mut connections_list);
+ let connections_rows = connections_list
+ .iter()
+ .map(|(connection, connection_data)| {
+ vec![
+ display_connection_string(&connection, &ip_to_host),
+ connection_data.process_name.to_string(),
+ display_upload_and_download(*connection_data),
+ ]
+ })
+ .collect();
+ let connections_title = "Utilization by connection";
+ let connections_column_names = &["Connection", "Process", "Rate Up/Down"];
+ Table {
+ title: connections_title,
+ column_names: connections_column_names,
+ rows: connections_rows,
+ }
+ }
+ pub fn create_processes_table(state: &UIState) -> Self {
+ let mut processes_list = Vec::from_iter(&state.processes);
+ sort_by_bandwidth(&mut processes_list);
+ let processes_rows = processes_list
+ .iter()
+ .map(|(process_name, data_for_process)| {
+ vec![
+ process_name.to_string(),
+ data_for_process.connection_count.to_string(),
+ display_upload_and_download(*data_for_process),
+ ]
+ })
+ .collect();
+ let processes_title = "Utilization by process name";
+ let processes_column_names = &["Process", "Connection count", "Rate Up/Down"];
+ Table {
+ title: processes_title,
+ column_names: processes_column_names,
+ rows: processes_rows,
+ }
+ }
+ pub fn create_remote_ips_table(state: &UIState, ip_to_host: &HashMap<Ipv4Addr, String>) -> Self {
+ let mut remote_ips_list = Vec::from_iter(&state.remote_ips);
+ sort_by_bandwidth(&mut remote_ips_list);
+ let remote_ips_rows = remote_ips_list
+ .iter()
+ .map(|(remote_ip, data_for_remote_ip)| {
+ let remote_ip = display_ip_or_host(**remote_ip, &ip_to_host);
+ vec![
+ remote_ip,
+ data_for_remote_ip.connection_count.to_string(),
+ display_upload_and_download(*data_for_remote_ip),
+ ]
+ })
+ .collect();
+ let remote_ips_title = "Utilization by remote ip";
+ let remote_ips_column_names =
+ &["Remote Address", "Connection Count", "Rate Up/Down"];
+ Table {
+ title: remote_ips_title,
+ column_names: remote_ips_column_names,
+ rows: remote_ips_rows,
+ }
+ }
+ pub fn render(&self, frame: &mut Frame<impl Backend>, rect: Rect) {
+ // the second column is only rendered if there is enough room for it
+ // (over third breakpoint)
+ let widths = if rect.width < FIRST_WIDTH_BREAKPOINT {
+ vec![FIRST_COLUMN_WIDTHS[0], THIRD_COLUMN_WIDTHS[0]]
+ } else if rect.width < SECOND_WIDTH_BREAKPOINT {
+ vec![FIRST_COLUMN_WIDTHS[1], THIRD_COLUMN_WIDTHS[1]]
+ } else if rect.width < THIRD_WIDTH_BREAKPOINT {
+ vec![FIRST_COLUMN_WIDTHS[2], THIRD_COLUMN_WIDTHS[2]]
+ } else {
+ vec![FIRST_COLUMN_WIDTHS[3], SECOND_COLUMN_WIDTHS[0], THIRD_COLUMN_WIDTHS[2]]
+ };
+
+ let column_names = if rect.width < THIRD_WIDTH_BREAKPOINT {
+ vec![self.column_names[0], self.column_names[2]]
+ } else {
+ vec![
+ self.column_names[0],
+ self.column_names[1],
+ self.column_names[2],
+ ]
+ };
+
+ let rows = self.rows.iter().map(|row| {
+ if rect.width < THIRD_WIDTH_BREAKPOINT {
+ vec![&row[0], &row[2]]
+ } else {
+ vec![&row[0], &row[1], &row[2]]
+ }
+ });
+
+ let table_rows =
+ rows.map(|row| Row::StyledData(row.into_iter(), Style::default().fg(Color::White)));
+
+ ::tui::widgets::Table::new(column_names.into_iter(), table_rows)
+ .block(Block::default().title(self.title).borders(Borders::ALL))
+ .header_style(Style::default().fg(Color::Yellow))
+ .widths(&widths[..])
+ .style(Style::default().fg(Color::White))
+ .column_spacing(2)
+ .render(frame, rect);
+ }
+}