Support showing and hiding anonymous ports

I'm still not convinced that showing a big list of disabled ports is
the right thing to do so here's the ability to turn it off.
This commit is contained in:
John Doty 2024-08-17 08:03:41 -07:00
parent 3430cae957
commit eede5b0e50
2 changed files with 153 additions and 0 deletions

View file

@ -59,12 +59,17 @@ The following commands are available while **fwd** is connected:
**Enter** **Enter**
: Attempt to browse to localhost on the specified port with the default browser. : Attempt to browse to localhost on the specified port with the default browser.
**a**
: Hide or show anonymous ports.
: (See "identifying ports" below for more information on anonymous ports.)
**e** **e**
: Enable or disable the selected port. : Enable or disable the selected port.
**l** **l**
: Show or hide the log window. : Show or hide the log window.
# IDENTIFYING PORTS # IDENTIFYING PORTS
**fwd** enumerates all of the ports that the remote server is listening on, and attempts to identify the process that is listening on each port. **fwd** enumerates all of the ports that the remote server is listening on, and attempts to identify the process that is listening on each port.

View file

@ -155,6 +155,16 @@ impl Listener {
"" ""
} }
pub fn is_anonymous(&self) -> bool {
// Anonynous ports are not configured and came from the server but
// had no description there.
self.config.is_none()
&& match self.desc.as_ref() {
Some(desc) => desc.desc.is_empty(),
None => false,
}
}
fn state(&self) -> State { fn state(&self) -> State {
*self.state.lock().unwrap() *self.state.lock().unwrap()
} }
@ -223,6 +233,7 @@ pub struct UI {
show_help: bool, show_help: bool,
alternate_screen: bool, alternate_screen: bool,
raw_mode: bool, raw_mode: bool,
show_anonymous: bool,
clipboard: Option<ClipboardContext>, clipboard: Option<ClipboardContext>,
} }
@ -247,6 +258,7 @@ impl UI {
config, config,
alternate_screen: false, alternate_screen: false,
raw_mode: false, raw_mode: false,
show_anonymous: true,
clipboard, clipboard,
} }
} }
@ -334,6 +346,10 @@ impl UI {
ports.iter().map(|p| format!("{p}")).collect(); ports.iter().map(|p| format!("{p}")).collect();
for (index, port) in ports.into_iter().enumerate() { for (index, port) in ports.into_iter().enumerate() {
let listener = self.ports.get(&port).unwrap(); let listener = self.ports.get(&port).unwrap();
if !self.should_render_listener(listener) {
continue;
}
let (symbol, style) = match listener.state() { let (symbol, style) = match listener.state() {
State::Enabled => ("", enabled_port_style), State::Enabled => ("", enabled_port_style),
State::Broken => ("", broken_port_style), State::Broken => ("", broken_port_style),
@ -379,6 +395,7 @@ impl UI {
Row::new(vec!["ESC / q", "Quit"]), Row::new(vec!["ESC / q", "Quit"]),
Row::new(vec!["? / h", "Show this help text"]), Row::new(vec!["? / h", "Show this help text"]),
Row::new(vec!["l", "Show fwd's logs"]), Row::new(vec!["l", "Show fwd's logs"]),
Row::new(vec!["a", "Hide/show anonymous ports"]),
]; ];
let border_lines = 3; let border_lines = 3;
@ -477,6 +494,19 @@ impl UI {
Ok(()) Ok(())
} }
fn toggle_show_anonymous(&mut self) {
self.show_anonymous = !self.show_anonymous;
}
fn should_render_listener(&self, listener: &Listener) -> bool {
// Named/Configured ports are always rendered
!listener.is_anonymous()
// ...or we might be explicitly asked to render everything
|| self.show_anonymous
// ...or the port might be enabled or errored
|| listener.state() != State::Disabled
}
async fn handle_events(&mut self, console_events: &mut EventStream) { async fn handle_events(&mut self, console_events: &mut EventStream) {
tokio::select! { tokio::select! {
ev = console_events.next() => self.handle_console_event(ev), ev = console_events.next() => self.handle_console_event(ev),
@ -584,6 +614,10 @@ impl UI {
_ = open::that(format!("http://127.0.0.1:{}/", p)); _ = open::that(format!("http://127.0.0.1:{}/", p));
} }
} }
KeyEvent { code: KeyCode::Char('a'), .. } => {
self.toggle_show_anonymous()
}
_ => (), _ => (),
}, },
Some(Ok(_)) => (), // Don't care about this event... Some(Ok(_)) => (), // Don't care about this event...
@ -1250,4 +1284,118 @@ mod tests {
drop(sender); drop(sender);
} }
#[test]
fn listener_anonymous() {
let (sender, receiver) = mpsc::channel(64);
let mut config = ServerConfig::default();
config.insert(
8079,
PortConfig {
enabled: false,
description: Some("body once told me".to_string()),
},
);
let mut ui = UI::new(receiver, config);
ui.handle_internal_event(Some(UIEvent::Ports(vec![
PortDesc {
port: 8080,
desc: "python3 blaster.py".to_string(),
},
PortDesc { port: 8081, desc: "".to_string() },
PortDesc { port: 8082, desc: "".to_string() },
])));
// (Pretend that 8082 broke.)
ui.ports.get_mut(&8082).unwrap().state = State::Broken.boxed();
let listener = ui.ports.get(&8079).unwrap();
assert!(
!listener.is_anonymous(),
"Configured ports should not be anonymous"
);
let listener = ui.ports.get(&8080).unwrap();
assert!(
!listener.is_anonymous(),
"Ports with descriptions should not be anonymous"
);
let listener = ui.ports.get(&8081).unwrap();
assert!(
listener.is_anonymous(),
"Not configured, disabled, no description should be anonymous"
);
drop(sender);
}
#[test]
fn render_anonymous() {
let (sender, receiver) = mpsc::channel(64);
let mut config = ServerConfig::default();
config.insert(
8079,
PortConfig {
enabled: false,
description: Some("body once told me".to_string()),
},
);
let mut ui = UI::new(receiver, config);
ui.handle_internal_event(Some(UIEvent::Ports(vec![
PortDesc {
port: 8080,
desc: "python3 blaster.py".to_string(),
},
PortDesc { port: 8081, desc: "".to_string() },
PortDesc { port: 8082, desc: "".to_string() },
PortDesc { port: 8083, desc: "".to_string() },
])));
// (Pretend that 8082 broke.)
ui.ports.get_mut(&8082).unwrap().state = State::Broken.boxed();
// No showing anonymous ports!
ui.show_anonymous = false;
let listener = ui.ports.get(&8079).unwrap();
assert!(
ui.should_render_listener(listener),
"Configured ports should always be rendered"
);
let listener = ui.ports.get(&8080).unwrap();
assert!(
ui.should_render_listener(listener),
"Ports with descriptions should be rendered"
);
let listener = ui.ports.get(&8081).unwrap();
assert!(
!ui.should_render_listener(listener),
"Not configured, disabled, no description should be hidden"
);
ui.enable_disable_port(8081);
let listener = ui.ports.get(&8081).unwrap();
assert_eq!(listener.state(), State::Enabled);
assert!(
ui.should_render_listener(listener),
"Enabled ports should be rendered"
);
let listener = ui.ports.get(&8082).unwrap();
assert_eq!(listener.state(), State::Broken);
assert!(
ui.should_render_listener(listener),
"Broken ports should be rendered"
);
drop(sender);
}
} }