use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
prelude::*,
widgets::{Block, Borders, List, ListItem, Paragraph, Row, Table},
Frame,
};
use super::Theme;
use crate::app::App;
pub fn render(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(6), Constraint::Length(5), Constraint::Min(8), ])
.split(area);
render_connection_info(frame, chunks[0], app, theme);
render_dns_info(frame, chunks[1], app, theme);
render_interfaces(frame, chunks[2], app, theme);
}
fn render_connection_info(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
let block = Block::default()
.title(" Connection Info ")
.title_style(Style::default().fg(theme.primary))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border))
.style(Style::default().bg(theme.background));
let inner = block.inner(area);
frame.render_widget(block, area);
let info = if let Some(ref conn) = app.connection_info {
vec![
Line::from(vec![
Span::styled(" Public IP: ", Style::default().fg(theme.muted)),
Span::styled(&conn.ip, Style::default().fg(theme.foreground)),
]),
Line::from(vec![
Span::styled(" ISP: ", Style::default().fg(theme.muted)),
Span::styled(
conn.isp.as_deref().unwrap_or("Unknown"),
Style::default().fg(theme.foreground),
),
]),
Line::from(vec![
Span::styled(" Location: ", Style::default().fg(theme.muted)),
Span::styled(
conn.location.as_deref().unwrap_or("Unknown"),
Style::default().fg(theme.foreground),
),
]),
]
} else {
vec![Line::from(vec![Span::styled(
" Loading connection info...",
Style::default().fg(theme.muted),
)])]
};
let paragraph = Paragraph::new(info);
frame.render_widget(paragraph, inner);
}
fn render_dns_info(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
let block = Block::default()
.title(" DNS Resolution ")
.title_style(Style::default().fg(theme.primary))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border))
.style(Style::default().bg(theme.background));
let inner = block.inner(area);
frame.render_widget(block, area);
let info = if let Some(ref dns) = app.dns_result {
vec![
Line::from(vec![
Span::styled(" Domain: ", Style::default().fg(theme.muted)),
Span::styled(&dns.domain, Style::default().fg(theme.foreground)),
]),
Line::from(vec![
Span::styled(" Resolution Time:", Style::default().fg(theme.muted)),
Span::styled(
format!(" {:.2} ms", dns.resolution_time_ms),
Style::default().fg(theme.success),
),
]),
]
} else {
vec![Line::from(vec![Span::styled(
" Press 'r' to run DNS test",
Style::default().fg(theme.muted),
)])]
};
let paragraph = Paragraph::new(info);
frame.render_widget(paragraph, inner);
}
fn render_interfaces(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
let block = Block::default()
.title(" Network Interfaces ")
.title_style(Style::default().fg(theme.primary))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border))
.style(Style::default().bg(theme.background));
let inner = block.inner(area);
frame.render_widget(block, area);
if app.interfaces.is_empty() {
let placeholder =
Paragraph::new(" Loading interfaces...").style(Style::default().fg(theme.muted));
frame.render_widget(placeholder, inner);
return;
}
let header = Row::new(vec!["Name", "IP Address", "Status", "MAC"])
.style(Style::default().fg(theme.primary).bold())
.bottom_margin(1);
let rows: Vec<Row> = app
.interfaces
.iter()
.map(|iface| {
let row_style = if iface.is_up {
Style::default().fg(theme.foreground)
} else {
Style::default().fg(theme.muted)
};
Row::new(vec![
iface.name.clone(),
iface.ip.clone(),
if iface.is_up { "UP" } else { "DOWN" }.to_string(),
iface.mac.clone().unwrap_or_else(|| "-".to_string()),
])
.style(row_style)
})
.collect();
let table = Table::new(
rows,
[
Constraint::Percentage(20),
Constraint::Percentage(30),
Constraint::Percentage(15),
Constraint::Percentage(35),
],
)
.header(header);
frame.render_widget(table, inner);
}
#[allow(dead_code)]
pub fn render_traceroute(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
let block = Block::default()
.title(" Traceroute ")
.title_style(Style::default().fg(theme.primary))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border))
.style(Style::default().bg(theme.background));
let inner = block.inner(area);
frame.render_widget(block, area);
if app.traceroute_hops.is_empty() {
let placeholder = Paragraph::new(" No traceroute data. Press 't' to trace.")
.style(Style::default().fg(theme.muted));
frame.render_widget(placeholder, inner);
return;
}
let items: Vec<ListItem> = app
.traceroute_hops
.iter()
.map(|hop| {
let text = if let Some(ref addr) = hop.address {
format!(
"{:>2}. {:>15} {:.2} ms",
hop.hop_number, addr, hop.latency_ms
)
} else {
format!("{:>2}. * Request timed out", hop.hop_number)
};
ListItem::new(text).style(Style::default().fg(theme.foreground))
})
.collect();
let list = List::new(items);
frame.render_widget(list, inner);
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_compiles() {
assert!(true);
}
}