Added tunnel renaming
This commit is contained in:
parent
d944efc24a
commit
f7a79c8411
147
src/handler.rs
147
src/handler.rs
|
@ -26,6 +26,8 @@ pub struct Handler {
|
|||
terminal: Option<Terminal<CrosstermBackend<TerminalHandle>>>,
|
||||
renderer: Renderer,
|
||||
selected: Option<usize>,
|
||||
|
||||
rename_buffer: Option<String>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
|
@ -38,6 +40,7 @@ impl Handler {
|
|||
terminal: None,
|
||||
renderer: Default::default(),
|
||||
selected: None,
|
||||
rename_buffer: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +81,7 @@ impl Handler {
|
|||
trace!("redraw");
|
||||
self.renderer.update(&self.tunnels, self.selected).await;
|
||||
terminal.draw(|frame| {
|
||||
self.renderer.render(frame);
|
||||
self.renderer.render(frame, &self.rename_buffer);
|
||||
})?;
|
||||
} else {
|
||||
warn!("Redraw called without valid terminal");
|
||||
|
@ -98,62 +101,102 @@ impl Handler {
|
|||
}
|
||||
|
||||
async fn handle_input(&mut self, input: Input) -> std::io::Result<bool> {
|
||||
match input {
|
||||
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");
|
||||
if self.rename_buffer.is_some() {
|
||||
match input {
|
||||
Input::Char(c) if c.is_alphanumeric() => {
|
||||
self.rename_buffer
|
||||
.as_mut()
|
||||
.expect("input buffer should be some")
|
||||
.push(c.to_ascii_lowercase());
|
||||
}
|
||||
Input::Backspace => {
|
||||
self.rename_buffer
|
||||
.as_mut()
|
||||
.expect("input buffer should be some")
|
||||
.pop();
|
||||
}
|
||||
Input::Enter => {
|
||||
debug!("Input accepted");
|
||||
if let Some(selected) = self.selected
|
||||
&& let Some(tunnel) = self.tunnels.get_mut(selected)
|
||||
&& let Some(buffer) = self.rename_buffer.take()
|
||||
{
|
||||
*tunnel = self.all_tunnels.rename_tunnel(tunnel.clone(), buffer).await;
|
||||
} else {
|
||||
warn!("Trying to rename invalid tunnel");
|
||||
}
|
||||
}
|
||||
Input::Esc => {
|
||||
debug!("Input rejected");
|
||||
self.rename_buffer = None;
|
||||
}
|
||||
_ => return Ok(false),
|
||||
}
|
||||
Input::Char('r') => {
|
||||
let Some(selected) = self.selected else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let Some(tunnel) = self.tunnels.get_mut(selected) else {
|
||||
warn!("Trying to retry invalid tunnel");
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
*tunnel = self.all_tunnels.retry_tunnel(tunnel.clone()).await;
|
||||
}
|
||||
Input::Delete => {
|
||||
let Some(selected) = self.selected else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
if selected >= self.tunnels.len() {
|
||||
warn!("Trying to delete tunnel out of bounds");
|
||||
debug!("Input: {:?}", self.rename_buffer);
|
||||
} else {
|
||||
match input {
|
||||
Input::Char('q') => {
|
||||
self.close()?;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let tunnel = self.tunnels.remove(selected);
|
||||
self.all_tunnels.remove_tunnel(tunnel).await;
|
||||
|
||||
if self.tunnels.is_empty() {
|
||||
self.selected = None;
|
||||
} else {
|
||||
self.selected = Some(min(self.tunnels.len() - 1, selected));
|
||||
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::CtrlP => {
|
||||
self.set_access_selection(TunnelAccess::Protected).await;
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
Input::Char('p') => {
|
||||
if let Some(user) = self.user.clone() {
|
||||
self.set_access_selection(TunnelAccess::Private(user)).await;
|
||||
} else {
|
||||
warn!("User not set");
|
||||
}
|
||||
}
|
||||
Input::Char('R') => {
|
||||
let Some(selected) = self.selected else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let Some(tunnel) = self.tunnels.get_mut(selected) else {
|
||||
warn!("Trying to retry invalid tunnel");
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
*tunnel = self.all_tunnels.retry_tunnel(tunnel.clone()).await;
|
||||
}
|
||||
Input::Char('r') => {
|
||||
if self.selected.is_some() {
|
||||
trace!("Renaming tunnel");
|
||||
self.rename_buffer = Some(String::new());
|
||||
}
|
||||
}
|
||||
Input::Delete => {
|
||||
let Some(selected) = self.selected else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
if selected >= self.tunnels.len() {
|
||||
warn!("Trying to delete tunnel out of bounds");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let tunnel = self.tunnels.remove(selected);
|
||||
self.all_tunnels.remove_tunnel(tunnel).await;
|
||||
|
||||
if self.tunnels.is_empty() {
|
||||
self.selected = None;
|
||||
} else {
|
||||
self.selected = Some(min(self.tunnels.len() - 1, selected));
|
||||
}
|
||||
}
|
||||
Input::CtrlP => {
|
||||
self.set_access_selection(TunnelAccess::Protected).await;
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub enum Input {
|
|||
Delete,
|
||||
Esc,
|
||||
Enter,
|
||||
Backspace,
|
||||
CtrlP,
|
||||
Other,
|
||||
}
|
||||
|
@ -23,6 +24,7 @@ impl From<&[u8]> for Input {
|
|||
[13] => Input::Enter,
|
||||
// NOTE: Actual char is DLE, this happens to map to ctrl-p
|
||||
[16] => Input::CtrlP,
|
||||
[127] => Input::Backspace,
|
||||
other => {
|
||||
trace!("{other:?}");
|
||||
Input::Other
|
||||
|
|
40
src/tui.rs
40
src/tui.rs
|
@ -1,12 +1,14 @@
|
|||
use std::cmp;
|
||||
use std::cmp::{self, max};
|
||||
|
||||
use futures::StreamExt;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
layout::{Constraint, Flex, Layout, Position, Rect},
|
||||
style::{Style, Stylize as _},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Table, TableState},
|
||||
widgets::{
|
||||
Block, BorderType, Cell, Clear, HighlightSpacing, Paragraph, Row, Table, TableState,
|
||||
},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
|
@ -33,7 +35,7 @@ impl Renderer {
|
|||
self.table_state.select(index);
|
||||
}
|
||||
|
||||
fn compute_footer_text<'a>(&self, rect: Rect) -> (u16, Paragraph<'a>) {
|
||||
pub fn compute_footer_text<'a>(&self, rect: Rect) -> (u16, Paragraph<'a>) {
|
||||
let width = rect.width as usize - 2;
|
||||
|
||||
let commands = if self.table_state.selected().is_some() {
|
||||
|
@ -43,11 +45,13 @@ impl Renderer {
|
|||
command("↓/j", "move down"),
|
||||
command("↑/k", "move up"),
|
||||
vec![],
|
||||
command("del", "remove"),
|
||||
command("r", "rename"),
|
||||
command("shift-r", "retry"),
|
||||
vec![],
|
||||
command("p", "make private"),
|
||||
command("ctrl-p", "make protected"),
|
||||
command("shift-p", "make public"),
|
||||
command("del", "remove"),
|
||||
command("r", "retry"),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
|
@ -89,7 +93,7 @@ impl Renderer {
|
|||
(height as u16, Paragraph::new(text).centered().block(block))
|
||||
}
|
||||
|
||||
pub fn render(&mut self, frame: &mut Frame) {
|
||||
pub fn render(&mut self, frame: &mut Frame, input: &Option<String>) {
|
||||
self.render_title(frame, frame.area());
|
||||
|
||||
let mut area = frame.area().inner(ratatui::layout::Margin {
|
||||
|
@ -104,6 +108,28 @@ impl Renderer {
|
|||
|
||||
self.render_table(frame, chunks[0]);
|
||||
frame.render_widget(footer, chunks[1]);
|
||||
|
||||
if let Some(input) = input {
|
||||
self.render_rename(frame, area, input);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_rename(&self, frame: &mut Frame, area: Rect, input: &str) {
|
||||
let vertical = Layout::vertical([Constraint::Length(3)]).flex(Flex::Center);
|
||||
let horizontal = Layout::horizontal([Constraint::Max(max(20, input.width() as u16 + 4))])
|
||||
.flex(Flex::Center);
|
||||
let [area] = vertical.areas(area);
|
||||
let [area] = horizontal.areas(area);
|
||||
|
||||
let title = Line::from("New name").centered();
|
||||
let block = Block::bordered().title(title);
|
||||
let text = Paragraph::new(format!(" {input}")).block(block);
|
||||
|
||||
frame.render_widget(Clear, area);
|
||||
|
||||
frame.render_widget(text, area);
|
||||
|
||||
frame.set_cursor_position(Position::new(area.x + input.width() as u16 + 2, area.y + 1));
|
||||
}
|
||||
|
||||
pub fn render_title(&self, frame: &mut Frame, rect: Rect) {
|
||||
|
|
|
@ -41,6 +41,7 @@ pub enum TunnelAccess {
|
|||
pub struct Tunnel {
|
||||
handle: Handle,
|
||||
name: String,
|
||||
address: String,
|
||||
domain: Option<String>,
|
||||
port: u32,
|
||||
access: Arc<RwLock<TunnelAccess>>,
|
||||
|
@ -50,7 +51,7 @@ impl Tunnel {
|
|||
pub async fn open_tunnel(&self) -> Result<Channel<Msg>, russh::Error> {
|
||||
trace!(tunnel = self.name, "Opening tunnel");
|
||||
self.handle
|
||||
.channel_open_forwarded_tcpip(&self.name, self.port, &self.name, self.port)
|
||||
.channel_open_forwarded_tcpip(&self.address, self.port, &self.address, self.port)
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -92,9 +93,11 @@ impl Tunnels {
|
|||
port: u32,
|
||||
user: impl Into<String>,
|
||||
) -> Tunnel {
|
||||
let address = name.into();
|
||||
let mut tunnel = Tunnel {
|
||||
handle,
|
||||
name: name.into(),
|
||||
name: address.clone(),
|
||||
address,
|
||||
domain: Some(self.domain.clone()),
|
||||
port,
|
||||
access: Arc::new(RwLock::new(TunnelAccess::Private(user.into()))),
|
||||
|
@ -150,6 +153,14 @@ impl Tunnels {
|
|||
|
||||
self.add_tunnel(tunnel).await
|
||||
}
|
||||
|
||||
pub async fn rename_tunnel(&mut self, tunnel: Tunnel, name: impl Into<String>) -> Tunnel {
|
||||
let mut tunnel = self.remove_tunnel(tunnel).await;
|
||||
tunnel.name = name.into();
|
||||
tunnel.domain = Some(self.domain.clone());
|
||||
|
||||
self.add_tunnel(tunnel).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Request<Incoming>> for Tunnels {
|
||||
|
|
Loading…
Reference in New Issue
Block a user