diff --git a/src/client/ui.rs b/src/client/ui.rs index c6cfa9f..062550a 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -10,16 +10,19 @@ use crossterm::{ }, }; use log::{error, info, Level, Metadata, Record}; -use std::collections::vec_deque::VecDeque; use std::collections::{HashMap, HashSet}; use std::io::stdout; +use std::{ + collections::vec_deque::VecDeque, + sync::{Arc, Mutex}, +}; use tokio::sync::mpsc; use tokio::sync::oneshot; use tokio_stream::StreamExt; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout, Margin, Rect}, - style::{Color, Style}, + style::{Color, Modifier, Style}, widgets::{ Block, Borders, List, ListItem, ListState, Row, Table, TableState, }, @@ -67,9 +70,22 @@ impl log::Log for Logger { fn flush(&self) {} } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum State { + Enabled, + Broken, + Disabled, +} + +impl State { + fn boxed(self) -> Arc> { + Arc::new(Mutex::new(self)) + } +} + #[derive(Debug)] struct Listener { - enabled: bool, + state: std::sync::Arc>, stop: Option>, desc: Option, } @@ -80,7 +96,15 @@ impl Listener { desc: PortDesc, enabled: bool, ) -> Listener { - let mut listener = Listener { enabled, stop: None, desc: Some(desc) }; + let mut listener = Listener { + state: if enabled { + State::Enabled.boxed() + } else { + State::Disabled.boxed() + }, + stop: None, + desc: Some(desc), + }; if enabled { listener.start(socks_port); } @@ -88,15 +112,19 @@ impl Listener { } pub fn enabled(&self) -> bool { - self.enabled + *self.state.lock().unwrap() == State::Enabled + } + + fn state(&self) -> State { + *self.state.lock().unwrap() } pub fn set_enabled(&mut self, socks_port: Option, enabled: bool) { if enabled { - self.enabled = true; + self.state = State::Enabled.boxed(); self.start(socks_port); } else { - self.enabled = false; + self.state = State::Enabled.boxed(); self.stop = None; } } @@ -112,19 +140,22 @@ impl Listener { } pub fn start(&mut self, socks_port: Option) { - if self.enabled { + if self.enabled() { if let (Some(desc), Some(socks_port), None) = (&self.desc, socks_port, &self.stop) { info!("Starting port {port} to {socks_port}", port = desc.port); let (l, stop) = oneshot::channel(); let port = desc.port; + let state = self.state.clone(); tokio::spawn(async move { let result = tokio::select! { r = client_listen(port, socks_port) => r, _ = stop => Ok(()), }; if let Err(e) = result { + let mut sg = state.lock().unwrap(); + *sg = State::Broken; error!("Error listening on port {port}: {e:?}"); } else { info!("Stopped listening on port {port}"); @@ -243,6 +274,8 @@ impl UI { fn render_ports(&mut self, frame: &mut Frame, size: Rect) { let enabled_port_style = Style::default(); let disabled_port_style = Style::default().fg(Color::DarkGray); + let broken_port_style = + Style::default().fg(Color::Red).add_modifier(Modifier::DIM); let mut rows = Vec::new(); let ports = self.get_ui_ports(); @@ -250,20 +283,21 @@ impl UI { ports.iter().map(|p| format!("{p}")).collect(); for (index, port) in ports.into_iter().enumerate() { let listener = self.ports.get(&port).unwrap(); + let (symbol, style) = match listener.state() { + State::Enabled => (" ✓ ", enabled_port_style), + State::Broken => (" ✗ ", broken_port_style), + State::Disabled => ("", disabled_port_style), + }; rows.push( Row::new(vec![ - if listener.enabled { " ✓ " } else { "" }, + symbol, &port_strings[index][..], match &listener.desc { Some(port_desc) => &port_desc.desc, None => "", }, ]) - .style(if listener.enabled { - enabled_port_style - } else { - disabled_port_style - }), + .style(style), ); } @@ -357,7 +391,11 @@ impl UI { } fn enable_disable_port(&mut self, port: u16) { - if let Some(listener) = self.ports.get_mut(&port) { + let state = self.ports.get(&port).map(Listener::state); + if state == Some(State::Broken) { + // try turning it off and on again, it will at least get logs visible + self.ports.remove(&port); + } else if let Some(listener) = self.ports.get_mut(&port) { listener.set_enabled(self.socks_port, !listener.enabled()); } }