diff --git a/crates/trippy-tui/src/config.rs b/crates/trippy-tui/src/config.rs index 01fb1521a..727e95d2c 100644 --- a/crates/trippy-tui/src/config.rs +++ b/crates/trippy-tui/src/config.rs @@ -20,7 +20,7 @@ mod constants; mod file; mod theme; -use crate::config::file::ConfigTui; +use crate::config::file::{ConfigBindings, ConfigTui}; pub use binding::{TuiBindings, TuiCommandItem, TuiKeyBinding}; pub use cmd::Args; pub use columns::{TuiColumn, TuiColumns}; @@ -364,7 +364,7 @@ impl TrippyConfig { let cfg_file_tui = cfg_file.tui.unwrap_or_default(); let cfg_file_dns = cfg_file.dns.unwrap_or_default(); let cfg_file_report = cfg_file.report.unwrap_or_default(); - validate_deprecated(&cfg_file_tui)?; + validate_deprecated(&cfg_file_tui, &cfg_file_tui_bindings)?; let mode = cfg_layer(args.mode, cfg_file_trace.mode, constants::DEFAULT_MODE); let unprivileged = cfg_layer_bool_flag( args.unprivileged, @@ -811,11 +811,16 @@ const fn cfg_layer_bool_flag(fst: bool, snd: Option, default: bool) -> boo } /// Check for deprecated fields. -fn validate_deprecated(cfg_file_tui: &ConfigTui) -> anyhow::Result<()> { +fn validate_deprecated( + cfg_file_tui: &ConfigTui, + cfg_file_tui_bindings: &ConfigBindings, +) -> anyhow::Result<()> { if cfg_file_tui.deprecated_tui_max_samples.is_some() { Err(anyhow!("tui-max-samples in [tui] section is deprecated, use max-samples in [strategy] section instead")) } else if cfg_file_tui.deprecated_tui_max_flows.is_some() { Err(anyhow!("tui-max-flows in [tui] section is deprecated, use max-flows in [strategy] section instead")) + } else if cfg_file_tui_bindings.deprecated_toggle_privacy.is_some() { + Err(anyhow!("toggle-privacy in [bindings] section is deprecated, use expand-privacy and contract-privacy instead")) } else { Ok(()) } @@ -1646,6 +1651,7 @@ mod tests { #[test_case("trip example.com --tui-max-samples foo", Err(anyhow!("error: unexpected argument '--tui-max-samples' found")); "deprecated tui max samples")] #[test_case("trip example.com --tui-max-flows foo", Err(anyhow!("error: unexpected argument '--tui-max-flows' found")); "deprecated tui max flows")] + #[test_case("trip example.com --tui-key-bindings toggle-privacy=o", Err(anyhow!("error: invalid value 'toggle-privacy=o' for '--tui-key-bindings ': toggle-privacy is deprecated, use expand-privacy and contract-privacy instead")); "deprecated toggle-privacy key binding")] fn test_deprecated(cmd: &str, expected: anyhow::Result) { compare_lines(parse_config(cmd), expected, Some(0)); } diff --git a/crates/trippy-tui/src/config/binding.rs b/crates/trippy-tui/src/config/binding.rs index 349c962d4..4658849e7 100644 --- a/crates/trippy-tui/src/config/binding.rs +++ b/crates/trippy-tui/src/config/binding.rs @@ -33,7 +33,8 @@ pub struct TuiBindings { pub toggle_chart: TuiKeyBinding, pub toggle_map: TuiKeyBinding, pub toggle_flows: TuiKeyBinding, - pub toggle_privacy: TuiKeyBinding, + pub expand_privacy: TuiKeyBinding, + pub contract_privacy: TuiKeyBinding, pub expand_hosts: TuiKeyBinding, pub contract_hosts: TuiKeyBinding, pub expand_hosts_max: TuiKeyBinding, @@ -77,7 +78,8 @@ impl Default for TuiBindings { toggle_chart: TuiKeyBinding::new(KeyCode::Char('c')), toggle_map: TuiKeyBinding::new(KeyCode::Char('m')), toggle_flows: TuiKeyBinding::new(KeyCode::Char('f')), - toggle_privacy: TuiKeyBinding::new(KeyCode::Char('p')), + expand_privacy: TuiKeyBinding::new(KeyCode::Char('p')), + contract_privacy: TuiKeyBinding::new(KeyCode::Char('o')), expand_hosts: TuiKeyBinding::new(KeyCode::Char(']')), contract_hosts: TuiKeyBinding::new(KeyCode::Char('[')), expand_hosts_max: TuiKeyBinding::new(KeyCode::Char('}')), @@ -135,7 +137,8 @@ impl TuiBindings { (self.toggle_chart, TuiCommandItem::ToggleChart), (self.toggle_map, TuiCommandItem::ToggleMap), (self.toggle_flows, TuiCommandItem::ToggleFlows), - (self.toggle_privacy, TuiCommandItem::TogglePrivacy), + (self.expand_privacy, TuiCommandItem::ExpandPrivacy), + (self.contract_privacy, TuiCommandItem::ContractPrivacy), (self.expand_hosts, TuiCommandItem::ExpandHosts), (self.expand_hosts_max, TuiCommandItem::ExpandHostsMax), (self.contract_hosts, TuiCommandItem::ContractHosts), @@ -263,10 +266,14 @@ impl From<(HashMap, ConfigBindings)> for TuiBindi .get(&TuiCommandItem::ToggleFlows) .or(cfg.toggle_flows.as_ref()) .unwrap_or(&Self::default().toggle_flows), - toggle_privacy: *cmd_items - .get(&TuiCommandItem::TogglePrivacy) - .or(cfg.toggle_privacy.as_ref()) - .unwrap_or(&Self::default().toggle_privacy), + expand_privacy: *cmd_items + .get(&TuiCommandItem::ExpandPrivacy) + .or(cfg.expand_privacy.as_ref()) + .unwrap_or(&Self::default().expand_privacy), + contract_privacy: *cmd_items + .get(&TuiCommandItem::ContractPrivacy) + .or(cfg.contract_privacy.as_ref()) + .unwrap_or(&Self::default().contract_privacy), toggle_map: *cmd_items .get(&TuiCommandItem::ToggleMap) .or(cfg.toggle_map.as_ref()) @@ -589,7 +596,14 @@ pub enum TuiCommandItem { /// Toggle the flows panel. ToggleFlows, /// Toggle hop privacy mode. - TogglePrivacy, + /// + /// Deprecated: use `ExpandPrivacy` and `ContractPrivacy` instead. + #[strum(serialize = "toggle-privacy")] + DeprecatedTogglePrivacy, + /// Expand hop privacy. + ExpandPrivacy, + /// Contract hop privacy. + ContractPrivacy, /// Expand hosts. ExpandHosts, /// Expand hosts to max. diff --git a/crates/trippy-tui/src/config/cmd.rs b/crates/trippy-tui/src/config/cmd.rs index 2d5de8d9f..647714fa5 100644 --- a/crates/trippy-tui/src/config/cmd.rs +++ b/crates/trippy-tui/src/config/cmd.rs @@ -292,6 +292,11 @@ fn parse_tui_binding_value(value: &str) -> anyhow::Result<(TuiCommandItem, TuiKe .ok_or_else(|| anyhow!("invalid binding value: expected format `item=value`"))?; let item = TuiCommandItem::try_from(&value[..pos])?; let binding = TuiKeyBinding::try_from(&value[pos + 1..])?; + if item == TuiCommandItem::DeprecatedTogglePrivacy { + return Err(anyhow!( + "toggle-privacy is deprecated, use expand-privacy and contract-privacy instead" + )); + } Ok((item, binding)) } diff --git a/crates/trippy-tui/src/config/file.rs b/crates/trippy-tui/src/config/file.rs index 9edafab6b..73447a562 100644 --- a/crates/trippy-tui/src/config/file.rs +++ b/crates/trippy-tui/src/config/file.rs @@ -377,7 +377,10 @@ pub struct ConfigBindings { pub toggle_freeze: Option, pub toggle_chart: Option, pub toggle_flows: Option, - pub toggle_privacy: Option, + #[serde(rename = "toggle-privacy")] + pub deprecated_toggle_privacy: Option, + pub expand_privacy: Option, + pub contract_privacy: Option, pub toggle_map: Option, pub expand_hosts: Option, pub contract_hosts: Option, @@ -419,7 +422,9 @@ impl Default for ConfigBindings { toggle_freeze: Some(bindings.toggle_freeze), toggle_chart: Some(bindings.toggle_chart), toggle_flows: Some(bindings.toggle_flows), - toggle_privacy: Some(bindings.toggle_privacy), + deprecated_toggle_privacy: None, + expand_privacy: Some(bindings.expand_privacy), + contract_privacy: Some(bindings.contract_privacy), toggle_map: Some(bindings.toggle_map), expand_hosts: Some(bindings.expand_hosts), contract_hosts: Some(bindings.contract_hosts), diff --git a/crates/trippy-tui/src/frontend.rs b/crates/trippy-tui/src/frontend.rs index 4d1f4e3c3..3f1011611 100644 --- a/crates/trippy-tui/src/frontend.rs +++ b/crates/trippy-tui/src/frontend.rs @@ -195,8 +195,10 @@ fn run_app( app.toggle_map(); } else if bindings.toggle_flows.check(key) { app.toggle_flows(); - } else if bindings.toggle_privacy.check(key) { - app.toggle_privacy(); + } else if bindings.expand_privacy.check(key) { + app.expand_privacy(); + } else if bindings.contract_privacy.check(key) { + app.contract_privacy(); } else if bindings.contract_hosts_min.check(key) { app.contract_hosts_min(); } else if bindings.expand_hosts_max.check(key) { diff --git a/crates/trippy-tui/src/frontend/binding.rs b/crates/trippy-tui/src/frontend/binding.rs index 8bdd5123a..8e0189b98 100644 --- a/crates/trippy-tui/src/frontend/binding.rs +++ b/crates/trippy-tui/src/frontend/binding.rs @@ -29,7 +29,8 @@ pub struct Bindings { pub toggle_chart: KeyBinding, pub toggle_map: KeyBinding, pub toggle_flows: KeyBinding, - pub toggle_privacy: KeyBinding, + pub expand_privacy: KeyBinding, + pub contract_privacy: KeyBinding, pub expand_hosts: KeyBinding, pub contract_hosts: KeyBinding, pub expand_hosts_max: KeyBinding, @@ -70,7 +71,8 @@ impl From for Bindings { toggle_chart: KeyBinding::from(value.toggle_chart), toggle_map: KeyBinding::from(value.toggle_map), toggle_flows: KeyBinding::from(value.toggle_flows), - toggle_privacy: KeyBinding::from(value.toggle_privacy), + expand_privacy: KeyBinding::from(value.expand_privacy), + contract_privacy: KeyBinding::from(value.contract_privacy), expand_hosts: KeyBinding::from(value.expand_hosts), contract_hosts: KeyBinding::from(value.contract_hosts), expand_hosts_max: KeyBinding::from(value.expand_hosts_max), diff --git a/crates/trippy-tui/src/frontend/render/header.rs b/crates/trippy-tui/src/frontend/render/header.rs index eed331285..4bae1ab1f 100644 --- a/crates/trippy-tui/src/frontend/render/header.rs +++ b/crates/trippy-tui/src/frontend/render/header.rs @@ -82,11 +82,7 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) { .tui_config .max_addrs .map_or_else(|| String::from(t!("auto")), |m| m.to_string()); - let privacy = if app.hide_private_hops && app.tui_config.privacy_max_ttl > 0 { - t!("on") - } else { - t!("off") - }; + let privacy = app.tui_config.privacy_max_ttl; let source = render_source(app); let dest = render_destination(app); let target = format!("{source} -> {dest}"); diff --git a/crates/trippy-tui/src/frontend/render/settings.rs b/crates/trippy-tui/src/frontend/render/settings.rs index 1f3edd447..6beff5108 100644 --- a/crates/trippy-tui/src/frontend/render/settings.rs +++ b/crates/trippy-tui/src/frontend/render/settings.rs @@ -371,7 +371,8 @@ fn format_binding_settings(app: &TuiApp) -> Vec { SettingsItem::new("toggle-chart", format!("{}", binds.toggle_chart)), SettingsItem::new("toggle-map", format!("{}", binds.toggle_map)), SettingsItem::new("toggle-flows", format!("{}", binds.toggle_flows)), - SettingsItem::new("toggle-privacy", format!("{}", binds.toggle_privacy)), + SettingsItem::new("expand-privacy", format!("{}", binds.expand_privacy)), + SettingsItem::new("contract-privacy", format!("{}", binds.contract_privacy)), SettingsItem::new("expand-hosts", format!("{}", binds.expand_hosts)), SettingsItem::new("expand-hosts-max", format!("{}", binds.expand_hosts_max)), SettingsItem::new("contract-hosts", format!("{}", binds.contract_hosts)), @@ -523,7 +524,7 @@ pub fn settings_tabs() -> [(String, usize); 7] { (t!("settings_tab_trace_title").to_string(), 17), (t!("settings_tab_dns_title").to_string(), 5), (t!("settings_tab_geoip_title").to_string(), 1), - (t!("settings_tab_bindings_title").to_string(), 36), + (t!("settings_tab_bindings_title").to_string(), 37), (t!("settings_tab_theme_title").to_string(), 31), (t!("settings_tab_columns_title").to_string(), 0), ] diff --git a/crates/trippy-tui/src/frontend/render/table.rs b/crates/trippy-tui/src/frontend/render/table.rs index 17679a3a0..88842b260 100644 --- a/crates/trippy-tui/src/frontend/render/table.rs +++ b/crates/trippy-tui/src/frontend/render/table.rs @@ -262,7 +262,7 @@ fn render_hostname( geoip_lookup: &GeoIpLookup, ) -> (Cell<'static>, u16) { let (hostname, count) = if hop.total_recv() > 0 { - if app.hide_private_hops && app.tui_config.privacy_max_ttl >= hop.ttl() { + if app.tui_config.privacy_max_ttl >= hop.ttl() { (format!("**{}**", t!("hidden")), 1) } else { match app.tui_config.max_addrs { @@ -512,7 +512,7 @@ fn render_hostname_with_details( config: &TuiConfig, ) -> (Cell<'static>, u16) { let rendered = if hop.total_recv() > 0 { - if app.hide_private_hops && config.privacy_max_ttl >= hop.ttl() { + if config.privacy_max_ttl >= hop.ttl() { format!("**{}**", t!("hidden")) } else { let index = app.selected_hop_address; diff --git a/crates/trippy-tui/src/frontend/render/world.rs b/crates/trippy-tui/src/frontend/render/world.rs index 4b23df9d5..1c09ec207 100644 --- a/crates/trippy-tui/src/frontend/render/world.rs +++ b/crates/trippy-tui/src/frontend/render/world.rs @@ -51,7 +51,7 @@ fn render_map_canvas(f: &mut Frame<'_>, app: &TuiApp, rect: Rect, entries: &[Map .hops .iter() .any(|hop| *hop > app.tui_config.privacy_max_ttl); - if !app.hide_private_hops || any_show { + if any_show { render_map_canvas_pin(ctx, entry); render_map_canvas_radius(ctx, entry, theme.map_radius); render_map_canvas_selected( @@ -146,7 +146,7 @@ fn render_map_info_panel(f: &mut Frame<'_>, app: &TuiApp, rect: Rect, entries: & } }) .collect::>(); - let info = if app.hide_private_hops && app.tui_config.privacy_max_ttl >= selected_hop.ttl() { + let info = if app.tui_config.privacy_max_ttl >= selected_hop.ttl() { format!("**{}**", t!("hidden")) } else { match locations.as_slice() { diff --git a/crates/trippy-tui/src/frontend/tui_app.rs b/crates/trippy-tui/src/frontend/tui_app.rs index 17b59afc3..5947bf3ec 100644 --- a/crates/trippy-tui/src/frontend/tui_app.rs +++ b/crates/trippy-tui/src/frontend/tui_app.rs @@ -38,8 +38,6 @@ pub struct TuiApp { pub show_settings: bool, pub show_hop_details: bool, pub show_flows: bool, - /// Whether private hops should be shown or not. - pub hide_private_hops: bool, pub show_chart: bool, pub show_map: bool, pub frozen_start: Option, @@ -70,7 +68,6 @@ impl TuiApp { show_settings: false, show_hop_details: false, show_flows: false, - hide_private_hops: true, show_chart: false, show_map: false, frozen_start: None, @@ -382,8 +379,17 @@ impl TuiApp { } } - pub fn toggle_privacy(&mut self) { - self.hide_private_hops = !self.hide_private_hops; + pub fn expand_privacy(&mut self) { + let hop_count = self.tracer_data().hops_for_flow(self.selected_flow).len(); + if usize::from(self.tui_config.privacy_max_ttl) < hop_count { + self.tui_config.privacy_max_ttl += 1; + } + } + + pub fn contract_privacy(&mut self) { + if self.tui_config.privacy_max_ttl > 0 { + self.tui_config.privacy_max_ttl -= 1; + } } pub fn toggle_asinfo(&mut self) { diff --git a/crates/trippy-tui/tests/resources/snapshots/trippy_tui__print__tests__output@tui_binding_commands_match.snap b/crates/trippy-tui/tests/resources/snapshots/trippy_tui__print__tests__output@tui_binding_commands_match.snap index 9de8c55c1..21d0a648a 100644 --- a/crates/trippy-tui/tests/resources/snapshots/trippy_tui__print__tests__output@tui_binding_commands_match.snap +++ b/crates/trippy-tui/tests/resources/snapshots/trippy_tui__print__tests__output@tui_binding_commands_match.snap @@ -1,4 +1,4 @@ --- source: crates/trippy-tui/src/print.rs --- -TUIbindingcommands:toggle-help,toggle-help-alt,toggle-settings,toggle-settings-tui,toggle-settings-trace,toggle-settings-dns,toggle-settings-geoip,toggle-settings-bindings,toggle-settings-theme,toggle-settings-columns,next-hop,previous-hop,next-trace,previous-trace,next-hop-address,previous-hop-address,address-mode-ip,address-mode-host,address-mode-both,toggle-freeze,toggle-chart,toggle-map,toggle-flows,toggle-privacy,expand-hosts,expand-hosts-max,contract-hosts,contract-hosts-min,chart-zoom-in,chart-zoom-out,clear-trace-data,clear-dns-cache,clear-selection,toggle-as-info,toggle-hop-details,quit +TUIbindingcommands:toggle-help,toggle-help-alt,toggle-settings,toggle-settings-tui,toggle-settings-trace,toggle-settings-dns,toggle-settings-geoip,toggle-settings-bindings,toggle-settings-theme,toggle-settings-columns,next-hop,previous-hop,next-trace,previous-trace,next-hop-address,previous-hop-address,address-mode-ip,address-mode-host,address-mode-both,toggle-freeze,toggle-chart,toggle-map,toggle-flows,toggle-privacy,expand-privacy,contract-privacy,expand-hosts,expand-hosts-max,contract-hosts,contract-hosts-min,chart-zoom-in,chart-zoom-out,clear-trace-data,clear-dns-cache,clear-selection,toggle-as-info,toggle-hop-details,quit diff --git a/trippy-config-sample.toml b/trippy-config-sample.toml index 355a54304..859a99925 100644 --- a/trippy-config-sample.toml +++ b/trippy-config-sample.toml @@ -324,7 +324,7 @@ tui-preserve-screen = false # The Tui refresh rate [default: 100ms] tui-refresh-rate = "100ms" -# The maximum ttl of hops which will be masked for privacy [default: 1] +# The maximum ttl of hops which will be masked for privacy [default: 0] tui-privacy-max-ttl = 0 # The locale to use for Tui [default: auto] @@ -408,7 +408,8 @@ toggle-freeze = "ctrl+f" toggle-chart = "c" toggle-map = "m" toggle-flows = "f" -toggle-privacy = "p" +expand-privacy = "p" +contract-privacy = "o" expand-hosts = "]" expand-hosts-max = "}" contract-hosts = "["