adguardian/widgets/
table.rs1
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
87fn 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
102fn make_request_cell(q: &Question) -> Result<String, anyhow::Error> {
104 Ok(format!("[{}] {} - {}", q.class, q.question_type, q.name))
105}
106
107fn 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
122fn 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
133fn 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