main
rs 118 lines 4.05 KB
Raw
1 mod app;
2 mod ui;
3
4 use std::time::Duration;
5
6 use anyhow::Result;
7 use crossterm::event::{self, Event, KeyCode, KeyEventKind};
8
9 pub use app::App;
10
11 /// Launch the live process dashboard.
12 ///
13 /// Initialises the Ratatui terminal, runs the event loop, and restores
14 /// the terminal on exit (including on panic, via Ratatui's built-in hook).
15 pub fn run_dashboard() -> Result<()> {
16 let mut terminal = ratatui::init();
17 let result = event_loop(&mut terminal);
18 ratatui::restore();
19 result
20 }
21
22 // ── Main event loop ───────────────────────────────────────────────────────────
23
24 fn event_loop(terminal: &mut ratatui::DefaultTerminal) -> Result<()> {
25 let mut app = App::new();
26
27 // Populate with live data before the first draw.
28 app.refresh();
29
30 loop {
31 // Draw a frame.
32 terminal.draw(|frame| ui::render(frame, &mut app))?;
33
34 // Poll for keyboard / resize events with a short timeout so the
35 // 2-second refresh timer can fire promptly.
36 if event::poll(Duration::from_millis(100))? {
37 match event::read()? {
38 Event::Key(key) if key.kind == KeyEventKind::Press => {
39 handle_key(&mut app, key.code);
40 }
41 Event::Resize(_, _) => {
42 // Terminal resize is handled automatically by ratatui on
43 // the next draw; we just need to trigger a redraw.
44 }
45 _ => {}
46 }
47 }
48
49 // Periodic refresh: pull a fresh snapshot from the daemon every 2 s.
50 if app.last_refresh.elapsed() >= Duration::from_secs(2) {
51 app.refresh();
52 }
53
54 if app.should_quit {
55 break;
56 }
57 }
58
59 Ok(())
60 }
61
62 // ── Keyboard handler ──────────────────────────────────────────────────────────
63
64 fn handle_key(app: &mut App, code: KeyCode) {
65 // Esc always cancels any pending confirmation and, if nothing is pending,
66 // quits the dashboard.
67 if code == KeyCode::Esc {
68 if app.confirm_delete {
69 app.confirm_delete = false;
70 } else {
71 app.should_quit = true;
72 }
73 return;
74 }
75
76 match code {
77 // ── Navigation ────────────────────────────────────────────────────
78 KeyCode::Down | KeyCode::Char('j') => {
79 app.confirm_delete = false;
80 app.select_next();
81 }
82 KeyCode::Up | KeyCode::Char('k') => {
83 app.confirm_delete = false;
84 app.select_prev();
85 }
86
87 // ── Process actions ───────────────────────────────────────────────
88 KeyCode::Char('r') => {
89 app.confirm_delete = false;
90 app.restart_selected();
91 }
92 KeyCode::Char('s') => {
93 app.confirm_delete = false;
94 app.toggle_stop();
95 }
96 KeyCode::Char('d') => {
97 // First press → arms confirmation; second press → executes.
98 app.delete_selected();
99 }
100
101 // ── Log pane ──────────────────────────────────────────────────────
102 KeyCode::Char('e') => {
103 app.confirm_delete = false;
104 app.toggle_log_source();
105 }
106
107 // ── Quit ──────────────────────────────────────────────────────────
108 KeyCode::Char('q') => {
109 if app.confirm_delete {
110 app.confirm_delete = false;
111 } else {
112 app.should_quit = true;
113 }
114 }
115
116 _ => {}
117 }
118 }