use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span},
widgets::Paragraph,
Frame,
};
use crate::app::{App, Snapshot};
use crate::ui::{
graph, palette as p,
widgets::{human_bytes, human_rate, panel},
};
pub fn draw(f: &mut Frame, area: Rect, app: &App, snap: &Snapshot) {
let v = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(8), Constraint::Min(0)])
.split(area);
draw_aggregate(f, v[0], app, snap);
draw_iface_table(f, v[1], snap);
}
fn draw_aggregate(f: &mut Frame, area: Rect, app: &App, snap: &Snapshot) {
let block = panel("Aggregate throughput (rx + tx)");
let inner = block.inner(area);
f.render_widget(block, area);
let series: Vec<f32> = app
.history
.net_rate
.to_vec()
.iter()
.map(|v| *v as f32)
.collect();
let peak = series.iter().cloned().fold(1.0f32, f32::max);
let normalized: Vec<f32> = series.iter().map(|v| (v / peak).min(1.0)).collect();
let cols = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(70), Constraint::Percentage(30)])
.split(inner);
graph::render(f, cols[0], &normalized, app.graph_style, p::brand());
let total: f64 = snap.net.iter().map(|i| i.rx_rate + i.tx_rate).sum();
let rx: f64 = snap.net.iter().map(|i| i.rx_rate).sum();
let tx: f64 = snap.net.iter().map(|i| i.tx_rate).sum();
let counters = vec![
Line::from(vec![
Span::styled("now ", Style::default().fg(p::text_muted())),
Span::styled(
human_rate(total),
Style::default().fg(p::brand()).add_modifier(Modifier::BOLD),
),
]),
Line::from(vec![
Span::styled("rx ", Style::default().fg(p::text_muted())),
Span::styled(human_rate(rx), Style::default().fg(p::status_good())),
]),
Line::from(vec![
Span::styled("tx ", Style::default().fg(p::text_muted())),
Span::styled(human_rate(tx), Style::default().fg(p::brand())),
]),
Line::from(vec![
Span::styled("peak ", Style::default().fg(p::text_muted())),
Span::styled(
human_rate(peak as f64),
Style::default().fg(p::text_primary()),
),
]),
Line::from(vec![
Span::styled("ifaces ", Style::default().fg(p::text_muted())),
Span::styled(
snap.net.len().to_string(),
Style::default().fg(p::text_primary()),
),
]),
];
f.render_widget(
Paragraph::new(counters).style(Style::default().bg(p::bg())),
cols[1],
);
}
fn draw_iface_table(f: &mut Frame, area: Rect, snap: &Snapshot) {
let block = panel("Interfaces (via netwatch-sdk)");
let inner = block.inner(area);
f.render_widget(block, area);
let mut lines: Vec<Line> = vec![Line::from(vec![
Span::styled(format!("{:<14} ", "NAME"), header_style()),
Span::styled(format!("{:<6} ", "STATE"), header_style()),
Span::styled(format!("{:>12} ", "RX/s"), header_style()),
Span::styled(format!("{:>12} ", "TX/s"), header_style()),
Span::styled(format!("{:>14} ", "RX TOTAL"), header_style()),
Span::styled(format!("{:>14}", "TX TOTAL"), header_style()),
])];
let take = inner.height.saturating_sub(1) as usize;
for iface in snap.net.iter().take(take) {
let state_color = if iface.is_up {
p::status_good()
} else {
p::border()
};
lines.push(Line::from(vec![
Span::styled(
format!("{:<14.14} ", iface.name),
Style::default().fg(p::text_primary()),
),
Span::styled(
format!("{:<6} ", if iface.is_up { "UP" } else { "DOWN" }),
Style::default().fg(state_color),
),
Span::styled(
format!("{:>12} ", human_rate(iface.rx_rate)),
Style::default().fg(p::status_good()),
),
Span::styled(
format!("{:>12} ", human_rate(iface.tx_rate)),
Style::default().fg(p::brand()),
),
Span::styled(
format!("{:>14} ", human_bytes(iface.rx_bytes)),
Style::default().fg(p::text_muted()),
),
Span::styled(
format!("{:>14}", human_bytes(iface.tx_bytes)),
Style::default().fg(p::text_muted()),
),
]));
}
f.render_widget(
Paragraph::new(lines).style(Style::default().bg(p::bg())),
inner,
);
}
fn header_style() -> Style {
Style::default()
.fg(p::text_muted())
.add_modifier(Modifier::BOLD)
}