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 animals;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
pub mod keys;
|
||||||
pub mod ssh;
|
pub mod ssh;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
pub mod tui;
|
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 tracing::{debug, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
keys::Input,
|
||||||
terminal::TerminalHandle,
|
terminal::TerminalHandle,
|
||||||
tui::Renderer,
|
tui::Renderer,
|
||||||
tunnel::{Tunnel, TunnelAccess, Tunnels},
|
tunnel::{Tunnel, TunnelAccess, Tunnels},
|
||||||
|
@ -25,6 +26,7 @@ pub struct Handler {
|
||||||
|
|
||||||
terminal: Option<Terminal<CrosstermBackend<TerminalHandle>>>,
|
terminal: Option<Terminal<CrosstermBackend<TerminalHandle>>>,
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
|
selected: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
|
@ -33,12 +35,13 @@ impl Handler {
|
||||||
all_tunnels,
|
all_tunnels,
|
||||||
tunnels: IndexMap::new(),
|
tunnels: IndexMap::new(),
|
||||||
user: None,
|
user: None,
|
||||||
terminal: Default::default(),
|
terminal: None,
|
||||||
renderer: Default::default(),
|
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 {
|
for (_address, tunnel) in &self.tunnels {
|
||||||
if let Some(tunnel) = tunnel {
|
if let Some(tunnel) = tunnel {
|
||||||
tunnel.set_access(access.clone()).await;
|
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 {
|
let rect = Rect {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -73,10 +76,11 @@ impl Handler {
|
||||||
Ok(())
|
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 {
|
if let Some(terminal) = &mut self.terminal {
|
||||||
trace!("redraw");
|
trace!("redraw");
|
||||||
self.renderer.update_table(&self.tunnels).await;
|
self.renderer.update_table(&self.tunnels).await;
|
||||||
|
self.renderer.select(self.selected);
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
self.renderer.render(frame);
|
self.renderer.render(frame);
|
||||||
})?;
|
})?;
|
||||||
|
@ -87,12 +91,37 @@ impl Handler {
|
||||||
Ok(())
|
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 {
|
match input {
|
||||||
'q' => {
|
Input::Char('q') => {
|
||||||
self.close()?;
|
self.close()?;
|
||||||
return Ok(false);
|
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);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
@ -100,6 +129,34 @@ impl Handler {
|
||||||
|
|
||||||
Ok(true)
|
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
|
/// Quickly create http tunnels for development
|
||||||
|
@ -150,13 +207,10 @@ impl russh::server::Handler for Handler {
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
_session: &mut Session,
|
_session: &mut Session,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
let Some(input) = data.first().cloned() else {
|
let input: Input = data.into();
|
||||||
return Ok(());
|
trace!(?input, "data");
|
||||||
};
|
|
||||||
|
|
||||||
trace!(input, "data");
|
if self.handle_input(input).await? {
|
||||||
|
|
||||||
if self.handle_input(input as char).await? {
|
|
||||||
self.redraw().await?;
|
self.redraw().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +233,7 @@ impl russh::server::Handler for Handler {
|
||||||
debug!("{args:?}");
|
debug!("{args:?}");
|
||||||
if args.public {
|
if args.public {
|
||||||
trace!("Making tunnels public");
|
trace!("Making tunnels public");
|
||||||
self.set_access(TunnelAccess::Public).await;
|
self.set_access_all(TunnelAccess::Public).await;
|
||||||
self.redraw().await?;
|
self.redraw().await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/tui.rs
11
src/tui.rs
|
@ -5,7 +5,7 @@ use indexmap::IndexMap;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
layout::{Constraint, Flex, Rect},
|
layout::{Constraint, Flex, Rect},
|
||||||
style::{Color, Style, Stylize as _},
|
style::{Style, Stylize as _},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Cell, HighlightSpacing, Row, Table, TableState},
|
widgets::{Cell, HighlightSpacing, Row, Table, TableState},
|
||||||
};
|
};
|
||||||
|
@ -61,7 +61,8 @@ impl Renderer {
|
||||||
"{} ({})",
|
"{} ({})",
|
||||||
std::env!("CARGO_PKG_NAME"),
|
std::env!("CARGO_PKG_NAME"),
|
||||||
std::env!("CARGO_PKG_VERSION")
|
std::env!("CARGO_PKG_VERSION")
|
||||||
);
|
)
|
||||||
|
.bold();
|
||||||
let title = Line::from(title).centered();
|
let title = Line::from(title).centered();
|
||||||
frame.render_widget(title, rect);
|
frame.render_widget(title, rect);
|
||||||
}
|
}
|
||||||
|
@ -82,7 +83,7 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_table(&mut self, frame: &mut Frame<'_>, rect: Rect) {
|
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 header_style = Style::default().bold().reversed();
|
||||||
let row_style = Style::default();
|
let row_style = Style::default();
|
||||||
|
|
||||||
|
@ -116,4 +117,8 @@ impl Renderer {
|
||||||
|
|
||||||
frame.render_stateful_widget(t, rect, &mut self.table_state);
|
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) {
|
pub async fn set_access(&self, access: TunnelAccess) {
|
||||||
*self.access.write().await = access;
|
*self.access.write().await = access;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn is_public(&self) -> bool {
|
||||||
|
matches!(*self.access.read().await, TunnelAccess::Public)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user