mod actions;
mod app;
mod events;
mod ui;
use std::io;
use std::path::Path;
use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{Terminal, backend::CrosstermBackend};
use tokio::sync::mpsc;
use actions::{Action, ActionExecutor};
use app::App;
use events::{AppEvent, EventHandler};
pub fn run(socket_path: &Path) -> Result<(), String> {
let rt =
tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create runtime: {}", e))?;
rt.block_on(async { run_async(socket_path).await })
}
async fn run_async(socket_path: &Path) -> Result<(), String> {
let mut terminal = setup_terminal().map_err(|e| format!("Failed to setup terminal: {}", e))?;
let result = run_app(&mut terminal, socket_path).await;
if let Err(e) = restore_terminal(&mut terminal) {
eprintln!("Warning: failed to restore terminal: {}", e);
}
result
}
async fn run_app(
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
socket_path: &Path,
) -> Result<(), String> {
let (action_tx, mut action_rx) = mpsc::unbounded_channel::<Action>();
let (result_tx, result_rx) = mpsc::unbounded_channel();
let executor = ActionExecutor::new(socket_path.to_path_buf(), result_tx);
let mut events = EventHandler::new(result_rx);
let mut app = App::new(action_tx.clone());
executor.spawn(Action::FetchServices);
loop {
terminal
.draw(|f| ui::draw(f, &app))
.map_err(|e| format!("Draw error: {}", e))?;
while let Ok(action) = action_rx.try_recv() {
executor.spawn(action);
}
let event = events.next().await;
match event {
AppEvent::Tick => {
if app.should_refresh() {
executor.spawn(Action::FetchServices);
if let Some(name) = app.selected_service_name() {
executor.spawn(Action::FetchLogs(name.clone()));
executor.spawn(Action::FetchChildren(name));
}
}
app.update_pending_operations();
}
AppEvent::Render => {
}
AppEvent::Key(key) => {
if let Some(action) = app.handle_key(key) {
match action {
Action::Quit => break,
action => executor.spawn(action),
}
}
}
AppEvent::Mouse(_) => {
}
AppEvent::Resize(_, _) => {
}
AppEvent::ActionResult(result) => {
app.handle_action_result(result);
}
AppEvent::Error(msg) => {
app.set_status(msg, app::StatusLevel::Error);
}
AppEvent::Quit => break,
}
}
Ok(())
}
fn setup_terminal() -> io::Result<Terminal<CrosstermBackend<io::Stdout>>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
Terminal::new(backend)
}
fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
Ok(())
}