Skip to content

Commit

Permalink
feat(tui): add dynamic configuraton bar (#1349)
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Oct 16, 2024
1 parent 623c706 commit 423c350
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 86 deletions.
4 changes: 4 additions & 0 deletions crates/trippy-tui/src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ pub struct ConfigThemeColors {
pub map_info_panel_border_color: Option<TuiColor>,
pub map_info_panel_bg_color: Option<TuiColor>,
pub map_info_panel_text_color: Option<TuiColor>,
pub dynamic_bar_bg_color: Option<TuiColor>,
pub dynamic_bar_text_color: Option<TuiColor>,
}

impl Default for ConfigThemeColors {
Expand Down Expand Up @@ -348,6 +350,8 @@ impl Default for ConfigThemeColors {
map_info_panel_border_color: Some(theme.map_info_panel_border),
map_info_panel_bg_color: Some(theme.map_info_panel_bg),
map_info_panel_text_color: Some(theme.map_info_panel_text),
dynamic_bar_bg_color: Some(theme.dynamic_bar_bg),
dynamic_bar_text_color: Some(theme.dynamic_bar_text),
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/trippy-tui/src/config/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ pub struct TuiTheme {
pub map_info_panel_bg: TuiColor,
/// The color of text in the map info panel.
pub map_info_panel_text: TuiColor,
/// The color of the dynamic bar background.
pub dynamic_bar_bg: TuiColor,
/// The color of the dynamic bar text.
pub dynamic_bar_text: TuiColor,
}

impl Default for TuiTheme {
Expand Down Expand Up @@ -114,6 +118,8 @@ impl Default for TuiTheme {
map_info_panel_border: TuiColor::Gray,
map_info_panel_bg: TuiColor::Black,
map_info_panel_text: TuiColor::Gray,
dynamic_bar_bg: TuiColor::White,
dynamic_bar_text: TuiColor::Black,
}
}
}
Expand Down Expand Up @@ -251,6 +257,14 @@ impl From<(HashMap<TuiThemeItem, TuiColor>, ConfigThemeColors)> for TuiTheme {
.get(&TuiThemeItem::MapInfoPanelTextColor)
.or(cfg.map_info_panel_text_color.as_ref())
.unwrap_or(&Self::default().map_info_panel_text),
dynamic_bar_bg: *color_map
.get(&TuiThemeItem::DynamicBarBgColor)
.or(cfg.dynamic_bar_bg_color.as_ref())
.unwrap_or(&Self::default().dynamic_bar_bg),
dynamic_bar_text: *color_map
.get(&TuiThemeItem::DynamicBarTextColor)
.or(cfg.dynamic_bar_text_color.as_ref())
.unwrap_or(&Self::default().dynamic_bar_text),
}
}
}
Expand Down Expand Up @@ -324,6 +338,10 @@ pub enum TuiThemeItem {
MapInfoPanelBgColor,
/// The color of text in the map info panel.
MapInfoPanelTextColor,
/// The color of the dynamic bar background.
DynamicBarBgColor,
/// The color of the dynamic bar text.
DynamicBarTextColor,
}

/// A TUI color.
Expand Down
1 change: 1 addition & 0 deletions crates/trippy-tui/src/frontend/render.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod app;
pub mod bar;
pub mod body;
pub mod bsod;
pub mod chart;
Expand Down
27 changes: 17 additions & 10 deletions crates/trippy-tui/src/frontend/render/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::frontend::render::{body, flows, footer, header, help, settings, tabs};
use crate::frontend::render::{bar, body, flows, footer, header, help, settings, tabs};
use crate::frontend::tui_app::TuiApp;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Frame;
Expand All @@ -25,14 +25,15 @@ use ratatui::Frame;
/// | History | Frequency |
/// | | |
/// ------------------------------------
/// ===== dynamic configuration bar ======
///
/// - Header: the title, configuration, destination, clock and keyboard controls
/// - Tab: a tab for each target being traced (shown if > 1 target requested, can't be used with
/// flows)
/// - Header: the title, target, clock and basic keyboard controls
/// - Tab: a tab for each target (shown if > 1 target requested, can't be used with flows)
/// - Flows: a navigable chart of individual trace flows (toggled on/off, can't be used with tabs)
/// - Hops: a table where each row represents a single hop (time-to-live) in the trace
/// - History: a graph of historic round-trip ping samples for the target host
/// - Frequency: a histogram of sample frequencies by round-trip time for the target host
/// - Configuration bar: a bar showing the current value for dynamically configurable items
///
/// On startup a splash screen is shown in place of the hops table, until the completion of the
/// first round.
Expand All @@ -53,13 +54,16 @@ pub fn render(f: &mut Frame<'_>, app: &mut TuiApp) {
tabs::render(f, chunks[1], app);
body::render(f, chunks[2], app);
footer::render(f, chunks[3], app);
bar::render(f, chunks[4], app);
} else if app.show_flows {
flows::render(f, chunks[1], app);
body::render(f, chunks[2], app);
footer::render(f, chunks[3], app);
bar::render(f, chunks[4], app);
} else {
body::render(f, chunks[1], app);
footer::render(f, chunks[2], app);
bar::render(f, chunks[3], app);
}
if app.show_settings {
settings::render(f, app);
Expand All @@ -68,22 +72,25 @@ pub fn render(f: &mut Frame<'_>, app: &mut TuiApp) {
}
}

const LAYOUT_WITHOUT_TABS: [Constraint; 3] = [
Constraint::Length(5),
const LAYOUT_WITHOUT_TABS: [Constraint; 4] = [
Constraint::Length(4),
Constraint::Min(10),
Constraint::Length(6),
Constraint::Length(1),
];

const LAYOUT_WITH_TABS: [Constraint; 4] = [
Constraint::Length(5),
const LAYOUT_WITH_TABS: [Constraint; 5] = [
Constraint::Length(4),
Constraint::Length(3),
Constraint::Min(10),
Constraint::Length(6),
Constraint::Length(1),
];

const LAYOUT_WITH_FLOWS: [Constraint; 4] = [
Constraint::Length(5),
const LAYOUT_WITH_FLOWS: [Constraint; 5] = [
Constraint::Length(4),
Constraint::Length(6),
Constraint::Min(10),
Constraint::Length(6),
Constraint::Length(1),
];
120 changes: 120 additions & 0 deletions crates/trippy-tui/src/frontend/render/bar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::config::AddressMode;
use crate::frontend::tui_app::TuiApp;
use crate::t;
use ratatui::layout::{Alignment, Rect};
use ratatui::prelude::{Line, Span, Style};
use ratatui::style::Stylize;
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use std::borrow::Cow;
use std::net::IpAddr;
use trippy_core::{PrivilegeMode, Protocol};
use trippy_dns::ResolveMethod;

pub fn render(f: &mut Frame<'_>, rect: Rect, app: &TuiApp) {
let protocol = match app.tracer_config().data.protocol() {
Protocol::Icmp => format!(
"{}/{}",
t!("icmp"),
fmt_target_family(app.tracer_config().data.target_addr()),
),
Protocol::Udp => format!(
"{}/{}/{}",
t!("udp"),
fmt_target_family(app.tracer_config().data.target_addr()),
app.tracer_config().data.multipath_strategy(),
),
Protocol::Tcp => format!(
"{}/{}",
t!("tcp"),
fmt_target_family(app.tracer_config().data.target_addr()),
),
};

let privilege_mode = fmt_privilege_mode(app.tracer_config().data.privilege_mode());

let as_mode = match app.resolver.config().resolve_method {
ResolveMethod::System => Span::styled("✖asn", Style::default().dim()),
ResolveMethod::Resolv | ResolveMethod::Google | ResolveMethod::Cloudflare => {
if app.tui_config.lookup_as_info {
Span::styled("✓asn", Style::default())
} else {
Span::styled("✖asn", Style::default().dim())
}
}
};

let details = if app.show_hop_details {
Span::styled(format!("✓{}", t!("details")), Style::default())
} else {
Span::styled(format!("✖{}", t!("details")), Style::default().dim())
};

let auto = t!("auto");
let width = auto.len();
let max_hosts = app
.tui_config
.max_addrs
.map_or_else(|| Span::raw(auto), |m| Span::raw(format!("{m:width$}")));

let privacy = if app.tui_config.privacy_max_ttl > 0 {
Span::styled(format!("✓{}", t!("privacy")), Style::default())
} else {
Span::styled(format!("✖{}", t!("privacy")), Style::default().dim())
};

let address_mode = match app.tui_config.address_mode {
AddressMode::Ip => Span::styled(" ip ", Style::default()),
AddressMode::Host => Span::styled("host", Style::default()),
AddressMode::Both => Span::styled("both", Style::default()),
};

let left_line = Line::from(vec![
Span::raw(" ["),
Span::raw(protocol),
Span::raw("] ["),
Span::raw(privilege_mode),
Span::raw("] ["),
as_mode,
Span::raw("] ["),
details,
Span::raw("] ["),
privacy,
Span::raw("]"),
]);

let right_line = Line::from(vec![
Span::raw(" ["),
address_mode,
Span::raw("] ["),
max_hosts,
Span::raw("] "),
]);

let bar_style = Style::default()
.bg(app.tui_config.theme.dynamic_bar_bg)
.fg(app.tui_config.theme.dynamic_bar_text);
let left = Paragraph::new(left_line)
.style(bar_style)
.alignment(Alignment::Left);
let right = Paragraph::new(right_line)
.style(bar_style)
.alignment(Alignment::Right);

f.render_widget(right, rect);
f.render_widget(left, rect);
}

fn fmt_privilege_mode(privilege_mode: PrivilegeMode) -> Cow<'static, str> {
match privilege_mode {
PrivilegeMode::Privileged => t!("privileged"),
PrivilegeMode::Unprivileged => t!("unprivileged"),
}
}

const fn fmt_target_family(target: IpAddr) -> &'static str {
match target {
IpAddr::V4(_) => "v4",
IpAddr::V6(_) => "v6",
}
}
78 changes: 4 additions & 74 deletions crates/trippy-tui/src/frontend/render/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
use ratatui::Frame;
use std::borrow::Cow;
use std::net::IpAddr;
use std::time::Duration;
use trippy_core::{Hop, PortDirection, PrivilegeMode, Protocol};
use trippy_dns::{ResolveMethod, Resolver};
use trippy_core::{Hop, PortDirection};
use trippy_dns::Resolver;

/// Render the title, config, target, clock and keyboard controls.
/// Render the title, target, clock and basic keyboard controls.
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
let header_block = Block::default()
Expand Down Expand Up @@ -42,47 +40,7 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
.style(Style::default())
.block(header_block.clone())
.alignment(Alignment::Right);
let protocol = match app.tracer_config().data.protocol() {
Protocol::Icmp => format!(
"{}({}, {})",
t!("icmp"),
fmt_target_family(app.tracer_config().data.target_addr()),
fmt_privilege_mode(app.tracer_config().data.privilege_mode())
),
Protocol::Udp => format!(
"{}({}, {}, {})",
t!("udp"),
fmt_target_family(app.tracer_config().data.target_addr()),
app.tracer_config().data.multipath_strategy(),
fmt_privilege_mode(app.tracer_config().data.privilege_mode())
),
Protocol::Tcp => format!(
"{}({}, {})",
t!("tcp"),
fmt_target_family(app.tracer_config().data.target_addr()),
fmt_privilege_mode(app.tracer_config().data.privilege_mode())
),
};
let details = if app.show_hop_details {
String::from(t!("on"))
} else {
String::from(t!("off"))
};
let as_info = match app.resolver.config().resolve_method {
ResolveMethod::System => String::from(t!("na")),
ResolveMethod::Resolv | ResolveMethod::Google | ResolveMethod::Cloudflare => {
if app.tui_config.lookup_as_info {
String::from(t!("on"))
} else {
String::from(t!("off"))
}
}
};
let max_hosts = app
.tui_config
.max_addrs
.map_or_else(|| String::from(t!("auto")), |m| m.to_string());
let privacy = app.tui_config.privacy_max_ttl;

let source = render_source(app);
let dest = render_destination(app);
let target = format!("{source} -> {dest}");
Expand Down Expand Up @@ -113,20 +71,6 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
),
Span::raw(target),
]),
Line::from(vec![
Span::styled(
format!("{}: ", t!("config")),
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw(format!(
"{}={protocol} {}={as_info} {}={details} {}={max_hosts}, {}={privacy}",
t!("protocol"),
t!("as-info"),
t!("details"),
t!("max-hosts"),
t!("privacy")
)),
]),
Line::from(vec![
Span::styled(
format!("{}: ", t!("status")),
Expand All @@ -145,20 +89,6 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
f.render_widget(left, rect);
}

fn fmt_privilege_mode(privilege_mode: PrivilegeMode) -> Cow<'static, str> {
match privilege_mode {
PrivilegeMode::Privileged => t!("privileged"),
PrivilegeMode::Unprivileged => t!("unprivileged"),
}
}

const fn fmt_target_family(target: IpAddr) -> &'static str {
match target {
IpAddr::V4(_) => "v4",
IpAddr::V6(_) => "v6",
}
}

/// Render the source address of the trace.
fn render_source(app: &TuiApp) -> String {
if let Some(src_addr) = app.tracer_config().data.source_addr() {
Expand Down
10 changes: 9 additions & 1 deletion crates/trippy-tui/src/frontend/render/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,14 @@ fn format_theme_settings(app: &TuiApp) -> Vec<SettingsItem> {
"map-info-panel-text-color",
theme::fmt_color(theme.map_info_panel_text),
),
SettingsItem::new(
"dynamic-bar-bg-color",
theme::fmt_color(theme.dynamic_bar_bg),
),
SettingsItem::new(
"dynamic-bar-text-color",
theme::fmt_color(theme.dynamic_bar_text),
),
]
}

Expand All @@ -525,7 +533,7 @@ pub fn settings_tabs() -> [(String, usize); 7] {
(t!("settings_tab_dns_title").to_string(), 5),
(t!("settings_tab_geoip_title").to_string(), 1),
(t!("settings_tab_bindings_title").to_string(), 37),
(t!("settings_tab_theme_title").to_string(), 31),
(t!("settings_tab_theme_title").to_string(), 33),
(t!("settings_tab_columns_title").to_string(), 0),
]
}
Expand Down
Loading

0 comments on commit 423c350

Please sign in to comment.