use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent},
execute,
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
LeaveAlternateScreen,
},
};
use gradient_tui_fork::{
backend::CrosstermBackend,
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::List,
Terminal,
};
use std::{
io::stdout,
sync::mpsc,
thread,
time::{Duration, Instant},
};
enum Event<I> {
Input(I),
Tick,
}
use crate::{
configuration::configuration::Config,
terminal_ui::{
cli::{
config_newtype::{AsyncFrom, ConfigurationNewtype},
ui, App,
},
debug::{LoggableNT, PrintInfo},
state::State,
util,
},
utility::{
log::{Loggable, LoggableType},
options::CliOptions,
},
PaintLogsCallbacks,
};
use eyre::{ContextCompat, Result};
use std::panic;
use std::sync::{Arc, Mutex};
use thiserror::Error;
use tokio::runtime::Runtime;
use tokio::sync::Mutex as AsyncMutex;
#[derive(Error, Debug)]
pub enum FrontendError {
#[error("Failed to start frontend due to io error")]
GenericFrontend(#[from] std::io::Error),
#[error("Failed to start frontend due to channel error")]
Input(#[from] std::sync::mpsc::RecvError),
}
pub async fn spawn_frontend(
_app: Option<App<'_>>,
runtime: Runtime,
) -> Result<()> {
enable_raw_mode()?;
let mut unwrapped_app = _app.wrap_err("foo")?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let (tx, rx) = mpsc::channel();
let tick_rate = Duration::from_millis(50);
thread::spawn(move || -> Result<()> {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).unwrap() {
if let CEvent::Key(key) = event::read()? {
tx.send(Event::Input(key))?;
}
}
if last_tick.elapsed() >= tick_rate {
tx.send(Event::Tick)?;
last_tick = Instant::now();
}
}
});
terminal.clear()?;
panic::set_hook({
let captured_state = unwrapped_app.state.clone();
Box::new(move |panic_info| {
log::error!("Panic: {}", panic_info);
captured_state
.add_message(PrintInfo::Plain(panic_info.to_string()));
})
});
loop {
let all_plugins: Vec<Spans> = unwrapped_app
.state
.configuration
.lock()
.await
.rules
.iter()
.map(|x| x.into_inner())
.filter_map(|rule| {
rule.plugins.as_ref().map(|plugins| {
let functions = plugins.functions.lock().unwrap();
let function_names: Vec<String> = functions.keys().cloned().collect();
(&rule.name, function_names)
})
})
.flat_map(|(rule_name, function_names)| {
let rule_name = rule_name.to_string();
function_names.into_iter().map(move |func_name| {
Spans::from(vec![
Span::styled(
format!("[{}] ", &rule_name),
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
),
Span::raw(func_name)
])
})
})
.collect();
let wrapper = ConfigurationNewtype(unwrapped_app.state.configuration.clone());
let list = List::async_from(wrapper).await;
terminal
.draw(|f| ui::draw(f, &mut unwrapped_app, all_plugins, list))?;
match rx.recv()? {
Event::Input(event) => {
util::match_keybinds(event, &mut unwrapped_app).await?
}
Event::Tick => unwrapped_app.on_tick()?,
};
if unwrapped_app.should_quit {
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
runtime.shutdown_background();
terminal.show_cursor()?;
break;
}
}
Ok(())
}
pub fn define_log_callbacks(state: Arc<State>) -> PaintLogsCallbacks {
let inner_state = Arc::clone(&state);
let log = Box::new(move |message: &Loggable| match &message.message_type {
LoggableType::IncomingRequestAtFfips(i) => {
inner_state
.add_traffic_info(LoggableNT(
LoggableType::IncomingRequestAtFfips(i.clone()),
));
}
LoggableType::OutGoingResponseFromFips(i) => {
inner_state
.add_traffic_info(LoggableNT(
LoggableType::OutGoingResponseFromFips(i.clone()),
));
}
LoggableType::OutgoingRequestToServer(i) => {
inner_state
.add_traffic_info(LoggableNT(
LoggableType::OutgoingRequestToServer(i.clone()),
));
}
LoggableType::IncomingResponseFromServer(i) => {
inner_state
.add_traffic_info(LoggableNT(
LoggableType::IncomingResponseFromServer(i.clone()),
));
}
LoggableType::Plain => {
inner_state
.add_message(PrintInfo::Plain(message.message.clone()));
}
});
PaintLogsCallbacks(log)
}
pub async fn setup(
configuration: Arc<AsyncMutex<Config>>,
options: CliOptions,
) -> (
Option<Arc<State>>,
Option<App<'static>>,
Arc<PaintLogsCallbacks>,
) {
let state = Arc::new(State {
messages: Mutex::new(Vec::new()),
configuration,
traffic_info: Mutex::new(vec![]),
});
let app = App::new(true, Arc::clone(&state), options);
let logging = Arc::new(define_log_callbacks(Arc::clone(&state)));
(Some(state), Some(app), logging)
}