Compare commits
4 commits
7410ec5143
...
99d377d4ce
| Author | SHA1 | Date | |
|---|---|---|---|
| 99d377d4ce | |||
| 34340e2575 | |||
|
|
290dcff9b6 | ||
|
|
381c008665 |
1 changed files with 151 additions and 5 deletions
156
src/client/ui.rs
156
src/client/ui.rs
|
|
@ -19,10 +19,11 @@ use tokio::sync::oneshot;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::{Backend, CrosstermBackend},
|
backend::{Backend, CrosstermBackend},
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Margin, Rect},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
widgets::{
|
widgets::{
|
||||||
Block, Borders, List, ListItem, ListState, Row, Table, TableState,
|
Block, Borders, Clear, List, ListItem, ListState, Paragraph, Row,
|
||||||
|
Table, TableState,
|
||||||
},
|
},
|
||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
|
|
@ -148,6 +149,7 @@ pub struct UI {
|
||||||
selection: TableState,
|
selection: TableState,
|
||||||
running: bool,
|
running: bool,
|
||||||
show_logs: bool,
|
show_logs: bool,
|
||||||
|
show_help: bool,
|
||||||
alternate_screen: bool,
|
alternate_screen: bool,
|
||||||
raw_mode: bool,
|
raw_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +162,7 @@ impl UI {
|
||||||
socks_port: None,
|
socks_port: None,
|
||||||
running: true,
|
running: true,
|
||||||
show_logs: false,
|
show_logs: false,
|
||||||
|
show_help: false,
|
||||||
selection: TableState::default(),
|
selection: TableState::default(),
|
||||||
lines: VecDeque::with_capacity(1024),
|
lines: VecDeque::with_capacity(1024),
|
||||||
config,
|
config,
|
||||||
|
|
@ -234,6 +237,9 @@ impl UI {
|
||||||
if self.show_logs {
|
if self.show_logs {
|
||||||
self.render_logs(frame, chunks[1]);
|
self.render_logs(frame, chunks[1]);
|
||||||
}
|
}
|
||||||
|
if self.show_help {
|
||||||
|
self.render_help(frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_ports<B: Backend>(&mut self, frame: &mut Frame<B>, size: Rect) {
|
fn render_ports<B: Backend>(&mut self, frame: &mut Frame<B>, size: Rect) {
|
||||||
|
|
@ -248,6 +254,7 @@ impl UI {
|
||||||
let listener = self.ports.get(&port).unwrap();
|
let listener = self.ports.get(&port).unwrap();
|
||||||
rows.push(
|
rows.push(
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
|
if listener.enabled { " ✓ " } else { "" },
|
||||||
&port_strings[index][..],
|
&port_strings[index][..],
|
||||||
match &listener.desc {
|
match &listener.desc {
|
||||||
Some(port_desc) => &port_desc.desc,
|
Some(port_desc) => &port_desc.desc,
|
||||||
|
|
@ -265,11 +272,14 @@ impl UI {
|
||||||
// TODO: I don't know how to express the lengths I want here.
|
// TODO: I don't know how to express the lengths I want here.
|
||||||
// That last length is extremely wrong but guaranteed to work I
|
// That last length is extremely wrong but guaranteed to work I
|
||||||
// guess.
|
// guess.
|
||||||
let widths =
|
let widths = vec![
|
||||||
vec![Constraint::Length(5), Constraint::Length(size.width)];
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(5),
|
||||||
|
Constraint::Length(size.width),
|
||||||
|
];
|
||||||
|
|
||||||
let port_list = Table::new(rows)
|
let port_list = Table::new(rows)
|
||||||
.header(Row::new(vec!["Port", "Description"]))
|
.header(Row::new(vec!["fwd", "Port", "Description"]))
|
||||||
.block(Block::default().title("Ports").borders(Borders::ALL))
|
.block(Block::default().title("Ports").borders(Borders::ALL))
|
||||||
.column_spacing(1)
|
.column_spacing(1)
|
||||||
.widths(&widths)
|
.widths(&widths)
|
||||||
|
|
@ -278,6 +288,58 @@ impl UI {
|
||||||
frame.render_stateful_widget(port_list, size, &mut self.selection);
|
frame.render_stateful_widget(port_list, size, &mut self.selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_help<B: Backend>(&mut self, frame: &mut Frame<B>) {
|
||||||
|
let keybindings = vec![
|
||||||
|
Row::new(vec!["↑ / k", "Move cursor up"]),
|
||||||
|
Row::new(vec!["↓ / j", "Move cursor down"]),
|
||||||
|
Row::new(vec!["e", "Enable/disable forwarding"]),
|
||||||
|
Row::new(vec!["RET", "Open port in web browser"]),
|
||||||
|
Row::new(vec!["ESC / q", "Quit"]),
|
||||||
|
Row::new(vec!["? / h", "Show this help text"]),
|
||||||
|
Row::new(vec!["l", "Show fwd's logs"]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let help_intro = 4;
|
||||||
|
let border_lines = 3;
|
||||||
|
|
||||||
|
let help_popup_area = centered_rect(
|
||||||
|
65,
|
||||||
|
keybindings.len() as u16 + help_intro + border_lines,
|
||||||
|
frame.size(),
|
||||||
|
);
|
||||||
|
let inner_area =
|
||||||
|
help_popup_area.inner(&Margin { vertical: 1, horizontal: 1 });
|
||||||
|
|
||||||
|
let constraints = vec![
|
||||||
|
Constraint::Length(help_intro),
|
||||||
|
Constraint::Length(inner_area.height - help_intro),
|
||||||
|
];
|
||||||
|
let help_parts = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(constraints)
|
||||||
|
.split(inner_area);
|
||||||
|
|
||||||
|
let keybindings = Table::new(keybindings)
|
||||||
|
.widths(&[Constraint::Length(7), Constraint::Length(40)])
|
||||||
|
.column_spacing(1)
|
||||||
|
.block(Block::default().title("Keys").borders(Borders::TOP));
|
||||||
|
|
||||||
|
let exp = Paragraph::new(
|
||||||
|
"fwd automatically listens for connections on the same ports\nas the target, and forwards connections on those ports to the\ntarget.",
|
||||||
|
);
|
||||||
|
|
||||||
|
// outer box
|
||||||
|
frame.render_widget(Clear, help_popup_area); //this clears out the background
|
||||||
|
let helpbox = Block::default().title("Help").borders(Borders::ALL);
|
||||||
|
frame.render_widget(helpbox, help_popup_area);
|
||||||
|
|
||||||
|
// explanation
|
||||||
|
frame.render_widget(exp, help_parts[0]);
|
||||||
|
|
||||||
|
// keybindings
|
||||||
|
frame.render_widget(keybindings, help_parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
fn render_logs<B: Backend>(&mut self, frame: &mut Frame<B>, size: Rect) {
|
fn render_logs<B: Backend>(&mut self, frame: &mut Frame<B>, size: Rect) {
|
||||||
let items: Vec<_> =
|
let items: Vec<_> =
|
||||||
self.lines.iter().map(|l| ListItem::new(&l[..])).collect();
|
self.lines.iter().map(|l| ListItem::new(&l[..])).collect();
|
||||||
|
|
@ -362,6 +424,37 @@ impl UI {
|
||||||
fn handle_console_event(
|
fn handle_console_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
ev: Option<Result<Event, std::io::Error>>,
|
ev: Option<Result<Event, std::io::Error>>,
|
||||||
|
) {
|
||||||
|
if self.show_help {
|
||||||
|
self.handle_console_event_help(ev)
|
||||||
|
} else {
|
||||||
|
self.handle_console_event_main(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_console_event_help(
|
||||||
|
&mut self,
|
||||||
|
ev: Option<Result<Event, std::io::Error>>,
|
||||||
|
) {
|
||||||
|
match ev {
|
||||||
|
Some(Ok(Event::Key(ev))) => match ev {
|
||||||
|
KeyEvent { code: KeyCode::Esc, .. }
|
||||||
|
| KeyEvent { code: KeyCode::Char('q'), .. }
|
||||||
|
| KeyEvent { code: KeyCode::Char('?'), .. }
|
||||||
|
| KeyEvent { code: KeyCode::Char('h'), .. } => {
|
||||||
|
self.show_help = false;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Some(Ok(_)) => (), // Don't care about this event...
|
||||||
|
Some(Err(_)) => (), // Hmmmmmm.....?
|
||||||
|
None => (), // ....no events? what?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_console_event_main(
|
||||||
|
&mut self,
|
||||||
|
ev: Option<Result<Event, std::io::Error>>,
|
||||||
) {
|
) {
|
||||||
match ev {
|
match ev {
|
||||||
Some(Ok(Event::Key(ev))) => match ev {
|
Some(Ok(Event::Key(ev))) => match ev {
|
||||||
|
|
@ -374,6 +467,10 @@ impl UI {
|
||||||
| KeyEvent { code: KeyCode::Char('q'), .. } => {
|
| KeyEvent { code: KeyCode::Char('q'), .. } => {
|
||||||
self.running = false;
|
self.running = false;
|
||||||
}
|
}
|
||||||
|
KeyEvent { code: KeyCode::Char('?'), .. }
|
||||||
|
| KeyEvent { code: KeyCode::Char('h'), .. } => {
|
||||||
|
self.show_help = !self.show_help;
|
||||||
|
}
|
||||||
KeyEvent { code: KeyCode::Char('l'), .. } => {
|
KeyEvent { code: KeyCode::Char('l'), .. } => {
|
||||||
self.show_logs = !self.show_logs;
|
self.show_logs = !self.show_logs;
|
||||||
}
|
}
|
||||||
|
|
@ -506,6 +603,28 @@ impl Drop for UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// helper function to create a centered rect using up certain percentage of the available rect `r`
|
||||||
|
fn centered_rect(width_chars: u16, height_chars: u16, r: Rect) -> Rect {
|
||||||
|
let left = r.left()
|
||||||
|
+ if width_chars > r.width {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(r.width - width_chars) / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
let top = r.top()
|
||||||
|
+ if height_chars > r.height {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(r.height - height_chars) / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = width_chars.min(r.width);
|
||||||
|
let height = height_chars.min(r.height);
|
||||||
|
|
||||||
|
Rect::new(left, top, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -669,4 +788,31 @@ mod tests {
|
||||||
|
|
||||||
drop(sender);
|
drop(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_centered_rect() {
|
||||||
|
// Normal old centering.
|
||||||
|
let frame = Rect::new(0, 0, 128, 128);
|
||||||
|
let centered = centered_rect(10, 10, frame);
|
||||||
|
assert_eq!(centered.left(), (128 - centered.width) / 2);
|
||||||
|
assert_eq!(centered.top(), (128 - centered.height) / 2);
|
||||||
|
assert_eq!(centered.width, 10);
|
||||||
|
assert_eq!(centered.height, 10);
|
||||||
|
|
||||||
|
// Clip the width and height to the box
|
||||||
|
let frame = Rect::new(0, 0, 5, 5);
|
||||||
|
let centered = centered_rect(10, 10, frame);
|
||||||
|
assert_eq!(centered.left(), 0);
|
||||||
|
assert_eq!(centered.top(), 0);
|
||||||
|
assert_eq!(centered.width, 5);
|
||||||
|
assert_eq!(centered.height, 5);
|
||||||
|
|
||||||
|
// Deal with non zero-zero origins.
|
||||||
|
let frame = Rect::new(10, 10, 128, 128);
|
||||||
|
let centered = centered_rect(10, 10, frame);
|
||||||
|
assert_eq!(centered.left(), 10 + (128 - centered.width) / 2);
|
||||||
|
assert_eq!(centered.top(), 10 + (128 - centered.height) / 2);
|
||||||
|
assert_eq!(centered.width, 10);
|
||||||
|
assert_eq!(centered.height, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue