use std::io;
use std::time::Duration;
use crossterm::event::{self as ce, Event as CtEvent, KeyEventKind};
use crossterm::execute;
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Direction, Layout};
use tokio::sync::{mpsc, oneshot};
pub mod format;
pub mod keymap;
pub mod render_chrome;
pub mod render_flow;
pub mod render_hosts;
pub mod render_intercept;
pub mod render_overview;
pub mod render_techniques;
pub mod state;
pub mod yank;
pub use state::{Event, MAX_BODY_EXCERPT};
use state::{State, Tab};
#[derive(Debug, Clone)]
pub struct DashboardConfig {
pub bind_addr: String,
pub mode: String,
pub tls_stack_label: String,
pub body_padding_bytes: usize,
pub conn_reuse: bool,
}
pub async fn run(
cfg: DashboardConfig,
mut events: mpsc::Receiver<Event>,
quit_tx: oneshot::Sender<()>,
) -> io::Result<()> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut term = Terminal::new(backend)?;
let mut state = State::new();
let mut redraw = tokio::time::interval(Duration::from_millis(150));
let mut quit = Some(quit_tx);
loop {
while let Ok(ev) = events.try_recv() {
state.record(&ev);
}
state.tick_toast();
tokio::select! {
_ = redraw.tick() => {
term.draw(|f| draw(f, &cfg, &state))?;
}
ev = events.recv() => {
match ev {
Some(ev) => state.record(&ev),
None => break, }
}
}
if ce::poll(Duration::from_millis(0))?
&& let CtEvent::Key(k) = ce::read()?
&& k.kind == KeyEventKind::Press
&& keymap::handle_key(&mut state, k.code, k.modifiers, &mut quit)
{
break;
}
}
disable_raw_mode()?;
execute!(term.backend_mut(), LeaveAlternateScreen)?;
term.show_cursor()?;
Ok(())
}
fn draw(f: &mut ratatui::Frame, cfg: &DashboardConfig, state: &State) {
let area = f.area();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), Constraint::Length(2), Constraint::Min(5), Constraint::Length(1), ])
.split(area);
render_chrome::draw_header(f, chunks[0], cfg, state);
render_chrome::draw_tabs(f, chunks[1], state);
match state.tab {
Tab::Flow => render_flow::draw(f, chunks[2], state),
Tab::Overview => render_overview::draw(f, chunks[2], cfg, state),
Tab::Hosts => render_hosts::draw(f, chunks[2], state),
Tab::Techniques => render_techniques::draw(f, chunks[2], state),
Tab::Intercept => render_intercept::draw(f, chunks[2], state),
}
render_chrome::draw_footer(f, chunks[3], state);
}