Skip to content

Commit

Permalink
Support showing and hiding anonymous ports
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
DeCarabas committed Aug 17, 2024
1 parent 3430cae commit eede5b0
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 0 deletions.
5 changes: 5 additions & 0 deletions doc/fwd.man.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,17 @@ The following commands are available while **fwd** is connected:
**Enter**
: 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**
: Enable or disable the selected port.

**l**
: Show or hide the log window.


# 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.
Expand Down
148 changes: 148 additions & 0 deletions src/client/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
*self.state.lock().unwrap()
}
Expand Down Expand Up @@ -223,6 +233,7 @@ pub struct UI {
show_help: bool,
alternate_screen: bool,
raw_mode: bool,
show_anonymous: bool,
clipboard: Option<ClipboardContext>,
}

Expand All @@ -247,6 +258,7 @@ impl UI {
config,
alternate_screen: false,
raw_mode: false,
show_anonymous: true,
clipboard,
}
}
Expand Down Expand Up @@ -334,6 +346,10 @@ impl UI {
ports.iter().map(|p| format!("{p}")).collect();
for (index, port) in ports.into_iter().enumerate() {
let listener = self.ports.get(&port).unwrap();
if !self.should_render_listener(listener) {
continue;
}

let (symbol, style) = match listener.state() {
State::Enabled => (" ✓ ", enabled_port_style),
State::Broken => (" ✗ ", broken_port_style),
Expand Down Expand Up @@ -379,6 +395,7 @@ impl UI {
Row::new(vec!["ESC / q", "Quit"]),
Row::new(vec!["? / h", "Show this help text"]),
Row::new(vec!["l", "Show fwd's logs"]),
Row::new(vec!["a", "Hide/show anonymous ports"]),
];

let border_lines = 3;
Expand Down Expand Up @@ -477,6 +494,19 @@ impl UI {
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) {
tokio::select! {
ev = console_events.next() => self.handle_console_event(ev),
Expand Down Expand Up @@ -584,6 +614,10 @@ impl UI {
_ = 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...
Expand Down Expand Up @@ -1250,4 +1284,118 @@ mod tests {

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);
}
}

0 comments on commit eede5b0

Please sign in to comment.