Improved input handling and allow user to change tunnel access in interface
This commit is contained in:
parent
750713b6b0
commit
321e5842d3
22
src/keys.rs
Normal file
22
src/keys.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
#[derive(Debug)]
|
||||
pub enum Input {
|
||||
Char(char),
|
||||
Up,
|
||||
Down,
|
||||
Esc,
|
||||
Enter,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Input {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
match value {
|
||||
[c] if c.is_ascii_graphic() => Input::Char(*c as char),
|
||||
[27] => Input::Esc,
|
||||
[27, 91, 65] => Input::Up,
|
||||
[27, 91, 66] => Input::Down,
|
||||
[13] => Input::Enter,
|
||||
_ => Input::Other,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
pub mod animals;
|
||||
pub mod auth;
|
||||
pub mod helper;
|
||||
pub mod keys;
|
||||
pub mod ssh;
|
||||
pub mod terminal;
|
||||
pub mod tui;
|
||||
|
|
80
src/ssh.rs
80
src/ssh.rs
|
@ -12,6 +12,7 @@ use tokio::net::ToSocketAddrs;
|
|||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::{
|
||||
keys::Input,
|
||||
terminal::TerminalHandle,
|
||||
tui::Renderer,
|
||||
tunnel::{Tunnel, TunnelAccess, Tunnels},
|
||||
|
@ -25,6 +26,7 @@ pub struct Handler {
|
|||
|
||||
terminal: Option<Terminal<CrosstermBackend<TerminalHandle>>>,
|
||||
renderer: Renderer,
|
||||
selected: Option<usize>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
|
@ -33,12 +35,13 @@ impl Handler {
|
|||
all_tunnels,
|
||||
tunnels: IndexMap::new(),
|
||||
user: None,
|
||||
terminal: Default::default(),
|
||||
terminal: None,
|
||||
renderer: Default::default(),
|
||||
selected: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_access(&mut self, access: TunnelAccess) {
|
||||
async fn set_access_all(&mut self, access: TunnelAccess) {
|
||||
for (_address, tunnel) in &self.tunnels {
|
||||
if let Some(tunnel) = tunnel {
|
||||
tunnel.set_access(access.clone()).await;
|
||||
|
@ -46,7 +49,7 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn resize(&mut self, width: u32, height: u32) -> std::io::Result<()> {
|
||||
async fn resize(&mut self, width: u32, height: u32) -> std::io::Result<()> {
|
||||
let rect = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -73,10 +76,11 @@ impl Handler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn redraw(&mut self) -> std::io::Result<()> {
|
||||
async fn redraw(&mut self) -> std::io::Result<()> {
|
||||
if let Some(terminal) = &mut self.terminal {
|
||||
trace!("redraw");
|
||||
self.renderer.update_table(&self.tunnels).await;
|
||||
self.renderer.select(self.selected);
|
||||
terminal.draw(|frame| {
|
||||
self.renderer.render(frame);
|
||||
})?;
|
||||
|
@ -87,12 +91,37 @@ impl Handler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_input(&mut self, input: char) -> std::io::Result<bool> {
|
||||
async fn set_access_selection(&mut self, access: TunnelAccess) {
|
||||
if let Some(selected) = self.selected {
|
||||
if let Some((_, Some(tunnel))) = self.tunnels.get_index_mut(selected) {
|
||||
tunnel.set_access(access).await;
|
||||
} else {
|
||||
warn!("Selection was invalid");
|
||||
}
|
||||
} else {
|
||||
self.set_access_all(access).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_input(&mut self, input: Input) -> std::io::Result<bool> {
|
||||
match input {
|
||||
'q' => {
|
||||
Input::Char('q') => {
|
||||
self.close()?;
|
||||
return Ok(false);
|
||||
}
|
||||
Input::Char('k') | Input::Up => self.previous_row(),
|
||||
Input::Char('j') | Input::Down => self.next_row(),
|
||||
Input::Esc => self.selected = None,
|
||||
Input::Char('P') => {
|
||||
self.set_access_selection(TunnelAccess::Public).await;
|
||||
}
|
||||
Input::Char('p') => {
|
||||
if let Some(user) = self.user.clone() {
|
||||
self.set_access_selection(TunnelAccess::Private(user)).await;
|
||||
} else {
|
||||
warn!("User not set");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
|
@ -100,6 +129,34 @@ impl Handler {
|
|||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.selected {
|
||||
Some(i) => {
|
||||
if i < self.tunnels.len() - 1 {
|
||||
i + 1
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.selected = Some(i);
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.selected {
|
||||
Some(i) => {
|
||||
if i > 0 {
|
||||
i - 1
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
None => self.tunnels.len() - 1,
|
||||
};
|
||||
self.selected = Some(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// Quickly create http tunnels for development
|
||||
|
@ -150,13 +207,10 @@ impl russh::server::Handler for Handler {
|
|||
data: &[u8],
|
||||
_session: &mut Session,
|
||||
) -> Result<(), Self::Error> {
|
||||
let Some(input) = data.first().cloned() else {
|
||||
return Ok(());
|
||||
};
|
||||
let input: Input = data.into();
|
||||
trace!(?input, "data");
|
||||
|
||||
trace!(input, "data");
|
||||
|
||||
if self.handle_input(input as char).await? {
|
||||
if self.handle_input(input).await? {
|
||||
self.redraw().await?;
|
||||
}
|
||||
|
||||
|
@ -179,7 +233,7 @@ impl russh::server::Handler for Handler {
|
|||
debug!("{args:?}");
|
||||
if args.public {
|
||||
trace!("Making tunnels public");
|
||||
self.set_access(TunnelAccess::Public).await;
|
||||
self.set_access_all(TunnelAccess::Public).await;
|
||||
self.redraw().await?;
|
||||
}
|
||||
}
|
||||
|
|
11
src/tui.rs
11
src/tui.rs
|
@ -5,7 +5,7 @@ use indexmap::IndexMap;
|
|||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Flex, Rect},
|
||||
style::{Color, Style, Stylize as _},
|
||||
style::{Style, Stylize as _},
|
||||
text::{Line, Span},
|
||||
widgets::{Cell, HighlightSpacing, Row, Table, TableState},
|
||||
};
|
||||
|
@ -61,7 +61,8 @@ impl Renderer {
|
|||
"{} ({})",
|
||||
std::env!("CARGO_PKG_NAME"),
|
||||
std::env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
)
|
||||
.bold();
|
||||
let title = Line::from(title).centered();
|
||||
frame.render_widget(title, rect);
|
||||
}
|
||||
|
@ -82,7 +83,7 @@ impl Renderer {
|
|||
}
|
||||
|
||||
pub fn render_table(&mut self, frame: &mut Frame<'_>, rect: Rect) {
|
||||
let highlight_style = Style::default().bg(Color::Blue);
|
||||
let highlight_style = Style::default().bold();
|
||||
let header_style = Style::default().bold().reversed();
|
||||
let row_style = Style::default();
|
||||
|
||||
|
@ -116,4 +117,8 @@ impl Renderer {
|
|||
|
||||
frame.render_stateful_widget(t, rect, &mut self.table_state);
|
||||
}
|
||||
|
||||
pub fn select(&mut self, index: Option<usize>) {
|
||||
self.table_state.select(index);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,10 @@ impl Tunnel {
|
|||
pub async fn set_access(&self, access: TunnelAccess) {
|
||||
*self.access.write().await = access;
|
||||
}
|
||||
|
||||
pub async fn is_public(&self) -> bool {
|
||||
matches!(*self.access.read().await, TunnelAccess::Public)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
Loading…
Reference in New Issue
Block a user