adguardian/widgets/
table.rs

1
2use tui::{
3  style::{Color, Modifier, Style},
4  widgets::{Block, Borders, Cell, Row, Table},
5  text::{Span},
6  layout::{Constraint},
7};
8use chrono::{DateTime, Utc};
9
10use crate::fetch::fetch_query_log::{Query, Question};
11pub fn make_query_table(data: &[Query], width: u16) -> Table<'_> {
12  let rows = data.iter().map(|query| {
13      let time = Cell::from(
14          time_ago(query.time.as_str()).unwrap_or("unknown".to_string())
15      ).style(Style::default().fg(Color::Gray));
16      
17      let question = Cell::from(make_request_cell(&query.question).unwrap())
18          .style(Style::default().add_modifier(Modifier::BOLD));
19
20      let client = Cell::from(query.client.as_str())
21          .style(Style::default().fg(Color::Blue));
22
23      let (time_taken, elapsed_color) = make_time_taken_and_color(&query.elapsed_ms).unwrap();
24      let elapsed_ms = Cell::from(time_taken).style(Style::default().fg(elapsed_color));
25
26      let (status_txt, status_color) = block_status_text(&query.reason, query.cached);
27      let status = Cell::from(status_txt).style(Style::default().fg(status_color));
28
29      let upstream = Cell::from(query.upstream.as_str()).style(Style::default().fg(Color::Blue));
30
31      let color = make_row_color(&query.reason);
32      Row::new(vec![time, question, status, elapsed_ms, client, upstream])
33          .style(Style::default().fg(color))
34  }).collect::<Vec<Row>>();
35
36  
37  let title = Span::styled(
38    "Query Log",
39    Style::default().add_modifier(Modifier::BOLD),
40  );
41
42  let block = Block::default()
43      .title(title)
44      .borders(Borders::ALL);
45
46  let mut headers = vec![
47      Cell::from(Span::raw("Time")),
48      Cell::from(Span::raw("Request")),
49      Cell::from(Span::raw("Status")),
50      Cell::from(Span::raw("Time Taken")),
51  ];
52
53  if width > 120 {
54      headers.extend(vec![
55          Cell::from(Span::raw("Client")),
56          Cell::from(Span::raw("Upstream DNS")),
57      ]);
58      
59      let widths = &[
60          Constraint::Percentage(15),
61          Constraint::Percentage(35),
62          Constraint::Percentage(10),
63          Constraint::Percentage(10),
64          Constraint::Percentage(15),
65          Constraint::Percentage(15),
66      ];
67
68      Table::new(rows)
69          .header(Row::new(headers))
70          .widths(widths)
71          .block(block)
72  } else {
73      let widths = &[
74          Constraint::Percentage(20),
75          Constraint::Percentage(40),
76          Constraint::Percentage(20),
77          Constraint::Percentage(20),
78      ];
79
80      Table::new(rows)
81          .header(Row::new(headers))
82          .widths(widths)
83          .block(block)
84  }
85}
86
87// Given a timestamp, return a string representing how long ago that was
88fn time_ago(timestamp: &str) -> Result<String, anyhow::Error> {
89  let datetime = DateTime::parse_from_rfc3339(timestamp)?;
90  let datetime_utc = datetime.with_timezone(&Utc);
91  let now = Utc::now();
92
93  let duration = now - datetime_utc;
94
95  if duration.num_minutes() < 1 {
96      Ok(format!("{} sec ago", duration.num_seconds()))
97  } else {
98      Ok(format!("{} min ago", duration.num_minutes()))
99  }
100}
101
102// Return cell showing info about the request made in a given query
103fn make_request_cell(q: &Question) -> Result<String, anyhow::Error> {
104  Ok(format!("[{}] {} - {}", q.class, q.question_type, q.name))
105}
106
107// Return a cell showing the time taken for a query, and a color based on time
108fn make_time_taken_and_color(elapsed: &str) -> Result<(String, Color), anyhow::Error> {
109  let elapsed_f64 = elapsed.parse::<f64>()?;
110  let rounded_elapsed = (elapsed_f64 * 100.0).round() / 100.0;
111  let time_taken = format!("{:.2} ms", rounded_elapsed);
112  let color = if elapsed_f64 < 1.0 {
113      Color::Green
114  } else if (1.0..=20.0).contains(&elapsed_f64) {
115      Color::Yellow
116  } else {
117      Color::Red
118  };
119  Ok((time_taken, color))
120}
121
122// Return color for a row, based on the allow/block reason
123fn make_row_color(reason: &str) -> Color {
124  if reason == "NotFilteredNotFound" {
125      Color::Green
126  } else if reason == "FilteredBlackList" {
127      Color::Red
128  } else {
129      Color::Yellow
130  }
131}
132
133// Return text and color for the status cell based on allow/ block reason
134fn block_status_text(reason: &str, cached: bool) -> (String, Color) {
135  let (text, color) =
136  if reason == "FilteredBlackList" {
137      ("Blacklisted".to_string(), Color::Red)
138  } else if cached {
139      ("Cached".to_string(), Color::Cyan)
140  } else if reason == "NotFilteredNotFound" {
141      ("Allowed".to_string(), Color::Green)
142  } else {
143      ("Other Block".to_string(), Color::Yellow)
144  };
145  (text, color)
146}
147