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