Skip to content

Commit

Permalink
use ratatui
Browse files Browse the repository at this point in the history
  • Loading branch information
janstarke committed May 7, 2024
1 parent 3face18 commit 29ae9df
Show file tree
Hide file tree
Showing 11 changed files with 515 additions and 440 deletions.
385 changes: 162 additions & 223 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ ipgrep = []
ts2date = ["regex"]
lnk2bodyfile = ["lnk"]
pf2bodyfile = ["num", "libc", "frnsc-prefetch", "forensic-rs"]
evtxview = ["cursive", "cursive_table_view", "cursive-async-view"]
evtxview = ["ratatui", "crossterm", "ouroboros"]

regdump = ["nt_hive2"]
hivescan = ["nt_hive2"]
Expand Down Expand Up @@ -185,9 +185,8 @@ frnsc-prefetch = {version="0.9", optional=true}
forensic-rs = {version="0.9.1", optional=true}

# evtxview
cursive = {version="0.20", optional=true}
cursive_table_view = {version="0.14", optional=true}
cursive-async-view = {version="^0", optional=true}
ratatui = {version="0.26.2", optional=true}
crossterm = {version="^0", optional=true}

[dev-dependencies]

Expand Down
209 changes: 209 additions & 0 deletions src/bin/evtxview/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use std::io;

use crate::{
cli::Cli,
tui::{self, EvtxTable},
};
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
prelude::*,
style::palette::tailwind,
widgets::{block::*, *},
};

const PALETTES: [tailwind::Palette; 4] = [
tailwind::BLUE,
tailwind::EMERALD,
tailwind::INDIGO,
tailwind::RED,
];
const INFO_TEXT: &str =
"(Esc) quit | (↑) move up | (↓) move down | (→) next color | (←) previous color";

pub struct App {
evtx_table: EvtxTable,
exit: bool,
state: TableState,
scroll_state: ScrollbarState,
colors: TableColors,
}

struct TableColors {
buffer_bg: Color,
_header_bg: Color,
_header_fg: Color,
row_fg: Color,
selected_style_fg: Color,
_normal_row_color: Color,
_alt_row_color: Color,
footer_border_color: Color,
}

impl TableColors {
const fn new(color: &tailwind::Palette) -> Self {
Self {
buffer_bg: tailwind::SLATE.c950,
_header_bg: color.c900,
_header_fg: tailwind::SLATE.c200,
row_fg: tailwind::SLATE.c200,
selected_style_fg: color.c400,
_normal_row_color: tailwind::SLATE.c950,
_alt_row_color: tailwind::SLATE.c900,
footer_border_color: color.c400,
}
}
}

impl App {
pub fn new(cli: Cli) -> Self {
let evtx_table = EvtxTable::try_from(cli.evtx_file.path().path()).unwrap();
let table_len = evtx_table.len();
Self {
evtx_table,
exit: Default::default(),
state: TableState::default().with_selected(0),
scroll_state: ScrollbarState::new(table_len - 1),
colors: TableColors::new(&PALETTES[0]),
}
}
/// runs the application's main loop until the user quits
pub fn run(&mut self, terminal: &mut tui::Tui) -> io::Result<()> {
while !self.exit {
terminal.draw(|frame| self.render_frame(frame))?;
self.handle_events()?;
}
Ok(())
}

fn render_frame(&mut self, frame: &mut Frame) {
let rects =
Layout::vertical([Constraint::Min(5), Constraint::Length(3)]).split(frame.size());
let cols = Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(rects[0]);
frame.render_widget(Clear, rects[0]);
self.render_table(frame, cols[0]);
self.render_scrollbar(frame, cols[0]);
self.render_content(frame, cols[1]);
self.render_footer(frame, rects[1]);
}

fn render_table(&mut self, frame: &mut Frame, area: Rect) {
let selected_style = Style::default()
.add_modifier(Modifier::REVERSED)
.fg(self.colors.selected_style_fg);
let table = self.evtx_table.table().highlight_style(selected_style);
frame.render_stateful_widget(table, area, &mut self.state);
}
fn render_content(&mut self, frame: &mut Frame, area: Rect) {
match self.state.selected() {
Some(i) => {
match self.evtx_table.content(i) {
Some(value) => {
match serde_json::to_string_pretty(value) {
Ok(content) => {
frame.render_widget(Paragraph::new(content), area)
}
Err(why) => {
frame.render_widget(Paragraph::new(format!("{why}")), area)}
}
}
None => frame.render_widget(Clear, area),
}
},
None => frame.render_widget(Clear, area),
}
}

fn render_scrollbar(&mut self, frame: &mut Frame, area: Rect) {
frame.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_symbol(None)
.end_symbol(None),
area.inner(&Margin {
vertical: 1,
horizontal: 1,
}),
&mut self.scroll_state,
)
}

fn render_footer(&mut self, frame: &mut Frame, area: Rect) {
let info_footer = Paragraph::new(Line::from(INFO_TEXT))
.style(
Style::new()
.fg(self.colors.row_fg)
.bg(self.colors.buffer_bg),
)
.centered()
.block(
Block::bordered()
.border_type(BorderType::Double)
.border_style(Style::new().fg(self.colors.footer_border_color)),
);
frame.render_widget(info_footer, area);
}

fn handle_events(&mut self) -> io::Result<()> {
match event::read()? {
// it's important to check that the event is a key press event as
// crossterm also emits key release and repeat events on Windows.
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
self.handle_key_event(key_event)
}
_ => {}
};
Ok(())
}
fn handle_key_event(&mut self, key_event: KeyEvent) {
match key_event.code {
KeyCode::Char('q') => self.exit(),
KeyCode::Char('g') => self.set_selected(0),
KeyCode::Char('G') => self.set_selected(self.evtx_table.len()-1),
KeyCode::Down => self.next(1),
KeyCode::Up => self.previous(1),
KeyCode::PageDown => self.next(10),
KeyCode::PageUp => self.previous(10),
_ => {}
}
}
fn exit(&mut self) {
self.exit = true;
}

fn set_selected(&mut self, idx: usize) {
self.state.select(Some(idx));
self.scroll_state = self.scroll_state.position(idx);
}

fn next(&mut self, steps: usize) {
assert_ne!(steps, 0);
let i = match self.state.selected() {
Some(i) => {
(i + steps) % self.evtx_table.len()
}
None => 0,
};
self.set_selected(i);
}

fn previous(&mut self, steps: usize) {
assert_ne!(steps, 0);
let i = match self.state.selected() {
Some(i) => {
if i > steps {
i - steps
} else {
let steps = steps % self.evtx_table.len();
if steps == 0 {
0
} else {
self.evtx_table.len() - steps
}
}
}
None => 0,
};
self.set_selected(i);
}
}
4 changes: 2 additions & 2 deletions src/bin/evtxview/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ pub(crate) enum SortOrder {
/// Display one or more events from an evtx file
#[derive(Parser)]
#[clap(name=env!("CARGO_BIN_NAME"), author, version,long_about=None)]
pub(crate) struct Cli {
pub struct Cli {
/// Name of the evtx files to read from
#[clap(value_hint=ValueHint::FilePath)]
pub(crate) evtx_file: InputPath,

#[clap(flatten)]
verbose: clap_verbosity_flag::Verbosity,
pub(crate) verbose: clap_verbosity_flag::Verbosity,
}

impl HasVerboseFlag for Cli {
Expand Down
7 changes: 0 additions & 7 deletions src/bin/evtxview/evtx_column.rs

This file was deleted.

37 changes: 0 additions & 37 deletions src/bin/evtxview/evtx_line.rs

This file was deleted.

99 changes: 0 additions & 99 deletions src/bin/evtxview/evtx_view.rs

This file was deleted.

Loading

0 comments on commit 29ae9df

Please sign in to comment.