Skip to content

Commit

Permalink
feat: Allow Custom Columns TUI
Browse files Browse the repository at this point in the history
feat: Allow custom TUI Columns

feat: Allow custom TUI Column

feat: allow customizing columns rebased (WIP)

feat: Allow Custom Columns TUI
  • Loading branch information
trkelly23 committed Dec 13, 2023
1 parent 0bb1eeb commit 9d02f00
Show file tree
Hide file tree
Showing 12 changed files with 506 additions and 60 deletions.
28 changes: 28 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ use trippy::tracing::{

mod binding;
mod cmd;
mod columns;
mod constants;
mod file;
mod theme;

pub use binding::{TuiBindings, TuiKeyBinding};
pub use cmd::Args;
pub use columns::{TuiColumn, TuiColumns};
pub use constants::MAX_HOPS;
pub use theme::{TuiColor, TuiTheme};

Expand Down Expand Up @@ -237,6 +239,7 @@ pub struct TrippyConfig {
pub tui_privacy_max_ttl: u8,
pub tui_address_mode: AddressMode,
pub tui_as_mode: AsMode,
pub tui_custom_columns: TuiColumns,
pub tui_icmp_extension_mode: IcmpExtensionMode,
pub tui_geoip_mode: GeoIpMode,
pub tui_max_addrs: Option<u8>,
Expand Down Expand Up @@ -445,6 +448,14 @@ impl TrippyConfig {
constants::DEFAULT_TUI_AS_MODE,
);

let columns = cfg_layer(
args.tui_custom_columns,
cfg_file_tui.tui_custom_columns,
String::from(constants::DEFAULT_CUSTOM_COLUMNS),
);

let tui_custom_columns = TuiColumns::try_from(columns.as_str())?;

let tui_icmp_extension_mode = cfg_layer(
args.tui_icmp_extension_mode,
cfg_file_tui.tui_icmp_extension_mode,
Expand Down Expand Up @@ -570,6 +581,7 @@ impl TrippyConfig {
validate_report_cycles(report_cycles)?;
validate_dns(dns_resolve_method, dns_lookup_as_info)?;
validate_geoip(tui_geoip_mode, &geoip_mmdb_file)?;
validate_tui_custom_columns(&tui_custom_columns)?;
let tui_theme_items = args
.tui_theme_colors
.into_iter()
Expand Down Expand Up @@ -611,6 +623,7 @@ impl TrippyConfig {
tui_privacy_max_ttl,
tui_address_mode,
tui_as_mode,
tui_custom_columns,
tui_icmp_extension_mode,
tui_geoip_mode,
tui_max_addrs,
Expand Down Expand Up @@ -677,6 +690,7 @@ impl Default for TrippyConfig {
log_format: constants::DEFAULT_LOG_FORMAT,
log_filter: String::from(constants::DEFAULT_LOG_FILTER),
log_span_events: constants::DEFAULT_LOG_SPAN_EVENTS,
tui_custom_columns: TuiColumns::default(),
}
}
}
Expand Down Expand Up @@ -778,6 +792,20 @@ fn validate_privilege(
}
}

fn validate_tui_custom_columns(tui_custom_columns: &TuiColumns) -> anyhow::Result<()> {
let duplicates = tui_custom_columns.find_duplicates();
if tui_custom_columns.0.is_empty() {
Err(anyhow!(
"Missing or no custom columns - The command line or config file value is blank"
))
} else if duplicates.is_empty() {
Ok(())
} else {
let dup_str = duplicates.iter().join(", ");
Err(anyhow!("Duplicate custom columns: {dup_str}"))
}
}

fn validate_logging(mode: Mode, verbose: bool) -> anyhow::Result<()> {
if matches!(mode, Mode::Tui) && verbose {
Err(anyhow!("cannot enable verbose logging in tui mode"))
Expand Down
4 changes: 4 additions & 0 deletions src/config/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ pub struct Args {
#[arg(value_enum, long)]
pub tui_as_mode: Option<AsMode>,

/// Custom columns to be displayed in the TUI hops table [default: HOLSRAVBWDT]
#[arg(long)]
pub tui_custom_columns: Option<String>,

/// How to render ICMP extensions [default: off]
#[arg(value_enum, long)]
pub tui_icmp_extension_mode: Option<IcmpExtensionMode>,
Expand Down
198 changes: 198 additions & 0 deletions src/config/columns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use anyhow::anyhow;
use itertools::Itertools;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};

/// The columns to display in the hops table of the TUI.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TuiColumns(pub Vec<TuiColumn>);

impl TryFrom<&str> for TuiColumns {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self(
value
.chars()
.map(TuiColumn::try_from)
.collect::<Result<Vec<_>, Self::Error>>()?,
))
}
}

impl Default for TuiColumns {
fn default() -> Self {
Self::try_from(super::constants::DEFAULT_CUSTOM_COLUMNS).expect("custom columns")
}
}

impl TuiColumns {
/// Validate the columns.
///
/// Returns any duplicate columns.
pub fn find_duplicates(&self) -> Vec<String> {
let (_, duplicates) = self.0.iter().fold(
(HashSet::<TuiColumn>::new(), Vec::new()),
|(mut all, mut dups), column| {
if all.iter().contains(column) {
dups.push(column.to_string());
} else {
all.insert(*column);
}
(all, dups)
},
);
duplicates
}
}

/// A TUI hops table column.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum TuiColumn {
/// The ttl for a hop.
Ttl,
/// The hostname for a hostname.
Host,
/// The packet loss % for a hop.
LossPct,
/// The number of probes sent for a hop.
Sent,
/// The number of responses received for a hop.
Received,
/// The last RTT for a hop.
Last,
/// The rolling average RTT for a hop.
Average,
/// The best RTT for a hop.
Best,
/// The worst RTT for a hop.
Worst,
/// The stddev of RTT for a hop.
StdDev,
/// The status of a hop.
Status,
}

impl TryFrom<char> for TuiColumn {
type Error = anyhow::Error;

fn try_from(value: char) -> Result<Self, Self::Error> {
match value.to_ascii_lowercase() {
'h' => Ok(Self::Ttl),
'o' => Ok(Self::Host),
'l' => Ok(Self::LossPct),
's' => Ok(Self::Sent),
'r' => Ok(Self::Received),
'a' => Ok(Self::Last),
'v' => Ok(Self::Average),
'b' => Ok(Self::Best),
'w' => Ok(Self::Worst),
'd' => Ok(Self::StdDev),
't' => Ok(Self::Status),
c => Err(anyhow!(format!("unknown column code: {c}"))),
}
}
}

impl Display for TuiColumn {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ttl => write!(f, "h"),
Self::Host => write!(f, "o"),
Self::LossPct => write!(f, "l"),
Self::Sent => write!(f, "s"),
Self::Received => write!(f, "r"),
Self::Last => write!(f, "a"),
Self::Average => write!(f, "v"),
Self::Best => write!(f, "b"),
Self::Worst => write!(f, "w"),
Self::StdDev => write!(f, "d"),
Self::Status => write!(f, "t"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_try_from_char_for_tui_column() {
assert_eq!(TuiColumn::try_from('h').unwrap(), TuiColumn::Ttl);
assert_eq!(TuiColumn::try_from('o').unwrap(), TuiColumn::Host);
assert_eq!(TuiColumn::try_from('l').unwrap(), TuiColumn::LossPct);
assert_eq!(TuiColumn::try_from('s').unwrap(), TuiColumn::Sent);
assert_eq!(TuiColumn::try_from('r').unwrap(), TuiColumn::Received);
assert_eq!(TuiColumn::try_from('a').unwrap(), TuiColumn::Last);
assert_eq!(TuiColumn::try_from('v').unwrap(), TuiColumn::Average);
assert_eq!(TuiColumn::try_from('b').unwrap(), TuiColumn::Best);
assert_eq!(TuiColumn::try_from('w').unwrap(), TuiColumn::Worst);
assert_eq!(TuiColumn::try_from('d').unwrap(), TuiColumn::StdDev);
assert_eq!(TuiColumn::try_from('t').unwrap(), TuiColumn::Status);

// Negative test for an unknown character
assert!(TuiColumn::try_from('x').is_err());
}

#[test]
fn test_display_formatting_for_tui_column() {
assert_eq!(format!("{}", TuiColumn::Ttl), "h");
assert_eq!(format!("{}", TuiColumn::Host), "o");
assert_eq!(format!("{}", TuiColumn::LossPct), "l");
assert_eq!(format!("{}", TuiColumn::Sent), "s");
assert_eq!(format!("{}", TuiColumn::Received), "r");
assert_eq!(format!("{}", TuiColumn::Last), "a");
assert_eq!(format!("{}", TuiColumn::Average), "v");
assert_eq!(format!("{}", TuiColumn::Best), "b");
assert_eq!(format!("{}", TuiColumn::Worst), "w");
assert_eq!(format!("{}", TuiColumn::StdDev), "d");
assert_eq!(format!("{}", TuiColumn::Status), "t");
}

#[test]
fn test_try_from_str_for_tui_columns() {
let valid_input = "hol";
let tui_columns = TuiColumns::try_from(valid_input).unwrap();
assert_eq!(
tui_columns,
TuiColumns(vec![TuiColumn::Ttl, TuiColumn::Host, TuiColumn::LossPct])
);

// Test for invalid characters in the input
let invalid_input = "xyz";
assert!(TuiColumns::try_from(invalid_input).is_err());
}

#[test]
fn test_default_for_tui_columns() {
let default_columns = TuiColumns::default();
assert_eq!(
default_columns,
TuiColumns(vec![
TuiColumn::Ttl,
TuiColumn::Host,
TuiColumn::LossPct,
TuiColumn::Sent,
TuiColumn::Received,
TuiColumn::Last,
TuiColumn::Average,
TuiColumn::Best,
TuiColumn::Worst,
TuiColumn::StdDev,
TuiColumn::Status
])
);
}

#[test]
fn test_find_duplicates_for_tui_columns() {
let columns_with_duplicates = TuiColumns(vec![
TuiColumn::Ttl,
TuiColumn::Host,
TuiColumn::LossPct,
TuiColumn::Host, // Duplicate
]);

let duplicates = columns_with_duplicates.find_duplicates();
assert_eq!(duplicates, vec!["o".to_string()]);
}
}
3 changes: 3 additions & 0 deletions src/config/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ pub const DEFAULT_TUI_PRESERVE_SCREEN: bool = false;
/// The default value for `tui-as-mode`.
pub const DEFAULT_TUI_AS_MODE: AsMode = AsMode::Asn;

/// The default value for `tui-custom-columns`.
pub const DEFAULT_CUSTOM_COLUMNS: &str = "HOLSRAVBWDT";

/// The default value for `tui-icmp-extension-mode`.
pub const DEFAULT_TUI_ICMP_EXTENSION_MODE: IcmpExtensionMode = IcmpExtensionMode::Off;

Expand Down
2 changes: 2 additions & 0 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ pub struct ConfigTui {
pub tui_geoip_mode: Option<GeoIpMode>,
pub tui_max_addrs: Option<u8>,
pub geoip_mmdb_file: Option<String>,
pub tui_custom_columns: Option<String>,
}

impl Default for ConfigTui {
Expand All @@ -235,6 +236,7 @@ impl Default for ConfigTui {
tui_privacy_max_ttl: Some(super::constants::DEFAULT_TUI_PRIVACY_MAX_TTL),
tui_address_mode: Some(super::constants::DEFAULT_TUI_ADDRESS_MODE),
tui_as_mode: Some(super::constants::DEFAULT_TUI_AS_MODE),
tui_custom_columns: Some(String::from(super::constants::DEFAULT_CUSTOM_COLUMNS)),
tui_icmp_extension_mode: Some(super::constants::DEFAULT_TUI_ICMP_EXTENSION_MODE),
tui_geoip_mode: Some(super::constants::DEFAULT_TUI_GEOIP_MODE),
tui_max_addrs: Some(super::constants::DEFAULT_TUI_MAX_ADDRS),
Expand Down
1 change: 1 addition & 0 deletions src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use trippy::dns::DnsResolver;
use tui_app::TuiApp;

mod binding;
mod columns;
mod config;
mod render;
mod theme;
Expand Down
Loading

0 comments on commit 9d02f00

Please sign in to comment.