mod app;
mod domain;
mod error;
mod event;
mod http_client;
mod infrastructure;
mod terminal;
mod ui;
use app::App;
use event::{Event, EventHandler};
use ratatui::widgets::ScrollbarState;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use terminal::TuiWrapper;
use crate::tui::domain::{
executors::{ExecutorDetailsPopup, ExecutorsData},
jobs::{JobsData, stages::JobStagesPopup},
metrics::MetricsData,
};
use crate::tui::{error::TuiError, event::UiData, infrastructure::Settings};
pub type TuiResult<OK> = Result<OK, TuiError>;
pub async fn tui_main(tui_mode: Arc<AtomicBool>) -> TuiResult<()> {
tui_mode.store(true, Ordering::Release);
tracing::info!("Starting the Ballista TUI application");
let config = Settings::new()?;
tracing::debug!("TUI configuration: {:?}", config);
let mut tui_wrapper = TuiWrapper::new()?;
let mut events = EventHandler::new(Duration::from_millis(config.tick_interval_ms));
let mut app = App::new(config)?;
let (app_tx, mut app_rx) = tokio::sync::mpsc::channel(16);
app.set_event_tx(app_tx);
let _ = ui::load_executors_data(&app).await;
loop {
tui_wrapper.terminal.draw(|f| ui::render(f, &app))?;
tokio::select! {
maybe_event = events.next() => {
match maybe_event {
Some(Event::Key(key)) => app.on_key(key).await?,
Some(Event::Tick) => app.on_tick().await,
Some(evt) => tracing::debug!("Unexpected event: {evt:?}"),
None => break,
}
}
Some(app_event) = app_rx.recv() => {
if let Event::DataLoaded { data } = app_event {
match data {
UiData::Executors(state, executors, jobs) => {
let old_scrollbar_position = app.executors_data.scrollbar_state.get_position();
let scrollbar_state = ScrollbarState::new(executors.len()).position(old_scrollbar_position);
app.executors_data = ExecutorsData {
executors,
scrollbar_state,
table_state: app.executors_data.table_state,
sort_column: app.executors_data.sort_column,
sort_order: app.executors_data.sort_order,
scheduler_state: state,
jobs,
};
app.executors_data.sort();
},
UiData::Metrics(metrics) => {
let old_scrollbar_position = app.metrics_data.scrollbar_state.get_position();
let scrollbar_state = ScrollbarState::new(metrics.len()).position(old_scrollbar_position);
app.metrics_data = MetricsData {
metrics,
scrollbar_state,
table_state: app.metrics_data.table_state,
sort_column: app.metrics_data.sort_column,
sort_order: app.metrics_data.sort_order
};
app.metrics_data.sort();
}
UiData::Jobs(jobs) => {
let old_scrollbar_position = app.jobs_data.scrollbar_state.get_position();
let scrollbar_state = ScrollbarState::new(jobs.len()).position(old_scrollbar_position);
app.jobs_data = JobsData {
jobs,
scrollbar_state,
table_state: app.jobs_data.table_state,
sort_column: app.jobs_data.sort_column,
sort_order: app.jobs_data.sort_order
};
}
UiData::JobDetails(details) => {
app.job_details = Some(details);
}
UiData::JobStagesGraph(graph) => {
app.job_dot_popup = Some(graph);
}
UiData::JobStagesData(job_id, stages) => {
app.job_stages_popup = Some(JobStagesPopup::new(job_id, stages));
}
UiData::ExecutorDetails(executor) => {
app.executor_details_popup =
Some(ExecutorDetailsPopup::new(executor));
}
}
}
}
}
tokio::task::yield_now().await;
if app.should_quit() {
tracing::info!("Stopping the Ballista TUI application!");
break;
}
}
Ok(())
}