diff --git a/harmony-rs/harmony/src/domain/interpret/mod.rs b/harmony-rs/harmony/src/domain/interpret/mod.rs index 0860727..928eb09 100644 --- a/harmony-rs/harmony/src/domain/interpret/mod.rs +++ b/harmony-rs/harmony/src/domain/interpret/mod.rs @@ -31,7 +31,7 @@ impl std::fmt::Display for InterpretName { } #[async_trait] -pub trait Interpret: std::fmt::Debug { +pub trait Interpret: std::fmt::Debug + Send { async fn execute( &self, inventory: &Inventory, diff --git a/harmony-rs/harmony/src/domain/maestro/mod.rs b/harmony-rs/harmony/src/domain/maestro/mod.rs index fb6c890..ed28173 100644 --- a/harmony-rs/harmony/src/domain/maestro/mod.rs +++ b/harmony-rs/harmony/src/domain/maestro/mod.rs @@ -35,7 +35,7 @@ impl Maestro { score_mut.append(&mut scores); } - pub async fn interpret(&self, score: &S) -> Result { + pub async fn interpret(&self, score: Box) -> Result { info!("Running score {score:?}"); let interpret = score.create_interpret(); info!("Launching interpret {interpret:?}"); diff --git a/harmony-rs/harmony/src/modules/k8s/resource.rs b/harmony-rs/harmony/src/modules/k8s/resource.rs index 070f818..ccf40b1 100644 --- a/harmony-rs/harmony/src/modules/k8s/resource.rs +++ b/harmony-rs/harmony/src/modules/k8s/resource.rs @@ -52,7 +52,7 @@ where } #[derive(Debug)] -pub struct K8sResourceInterpret { +pub struct K8sResourceInterpret { pub score: K8sResourceScore, } @@ -64,6 +64,7 @@ impl< + DeserializeOwned + serde::Serialize + Default + + Send + Sync, > Interpret for K8sResourceInterpret where diff --git a/harmony-rs/harmony_tui/src/lib.rs b/harmony-rs/harmony_tui/src/lib.rs index 756ab19..0387d49 100644 --- a/harmony-rs/harmony_tui/src/lib.rs +++ b/harmony-rs/harmony_tui/src/lib.rs @@ -1,10 +1,11 @@ mod ratatui_utils; mod widget; -use log::{debug, error, info, trace, warn}; +use log::{debug, info}; use tokio::sync::mpsc; use tokio_stream::StreamExt; -use widget::score::ScoreListWidget; +use tui_logger::{TuiWidgetEvent, TuiWidgetState}; +use widget::{help::HelpWidget, score::ScoreListWidget}; use std::{sync::Arc, time::Duration}; @@ -48,10 +49,10 @@ pub async fn init(maestro: Maestro) -> Result<(), Box> { } pub struct HarmonyTUI { - maestro: Maestro, score: ScoreListWidget, should_quit: bool, channel_handle: tokio::task::JoinHandle<()>, + tui_state: TuiWidgetState, } #[derive(Debug)] @@ -61,23 +62,34 @@ enum HarmonyTuiEvent { impl HarmonyTUI { pub fn new(maestro: Maestro) -> Self { - let (handle, sender) = Self::start_channel(); + let maestro = Arc::new(maestro); + let (handle, sender) = Self::start_channel(maestro.clone()); let score = ScoreListWidget::new(Self::scores_list(&maestro), sender); HarmonyTUI { - maestro, should_quit: false, score, channel_handle: handle, + tui_state: TuiWidgetState::new(), } } - fn start_channel() -> (tokio::task::JoinHandle<()>, mpsc::Sender) { + fn start_channel( + maestro: Arc, + ) -> (tokio::task::JoinHandle<()>, mpsc::Sender) { let (sender, mut receiver) = mpsc::channel::(32); let handle = tokio::spawn(async move { info!("Starting message channel receiver loop"); while let Some(event) = receiver.recv().await { info!("Received event {event:#?}"); + match event { + HarmonyTuiEvent::LaunchScore(score_item) => { + info!( + "Interpretation result {:#?}", + maestro.interpret(score_item.0).await + ) + } + } } info!("STOPPING message channel receiver loop"); }); @@ -86,9 +98,9 @@ impl HarmonyTUI { pub async fn init(mut self) -> Result<(), Box> { // Set max_log_level to Trace - tui_logger::init_logger(log::LevelFilter::Trace).unwrap(); + tui_logger::init_logger(log::LevelFilter::Info).unwrap(); // Set default level for unknown targets to Trace - tui_logger::set_default_level(log::LevelFilter::Trace); + tui_logger::set_default_level(log::LevelFilter::Info); color_eyre::install()?; let mut terminal = ratatui::init(); @@ -116,22 +128,30 @@ impl HarmonyTUI { let size = frame.area(); frame.set_cursor_position(Position::new(size.x / 2, size.y / 2)); + let [app_area, help_area] = + Layout::vertical([Constraint::Percentage(100), Constraint::Min(4)]) + .areas(frame.area()); + + let help_block = Block::default().borders(Borders::TOP); + frame.render_widget(&help_block, help_area); + frame.render_widget(HelpWidget::new(), help_block.inner(help_area)); + let [list_area, output_area] = Layout::horizontal([Constraint::Min(30), Constraint::Percentage(100)]) - .areas(frame.area()); + .areas(app_area); + let block = Block::default().borders(Borders::RIGHT); frame.render_widget(&block, list_area); self.score.render(list_area, frame); - frame.render_widget( - tui_logger::TuiLoggerWidget::default() - .style_error(Style::default().fg(Color::Red)) - .style_warn(Style::default().fg(Color::LightRed)) - .style_info(Style::default().fg(Color::LightGreen)) - .style_debug(Style::default().fg(Color::Gray)) - .style_trace(Style::default().fg(Color::Gray)), - output_area, - ) + let tui_logger = tui_logger::TuiLoggerWidget::default() + .style_error(Style::default().fg(Color::Red)) + .style_warn(Style::default().fg(Color::LightRed)) + .style_info(Style::default().fg(Color::LightGreen)) + .style_debug(Style::default().fg(Color::Gray)) + .style_trace(Style::default().fg(Color::Gray)) + .state(&self.tui_state); + frame.render_widget(tui_logger, output_area) } fn scores_list(maestro: &Maestro) -> Vec { @@ -144,10 +164,14 @@ impl HarmonyTUI { } async fn handle_event(&mut self, event: &Event) { + debug!("Got event {event:?}"); if let Event::Key(key) = event { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true, + KeyCode::PageUp => self.tui_state.transition(TuiWidgetEvent::PrevPageKey), + KeyCode::PageDown => self.tui_state.transition(TuiWidgetEvent::NextPageKey), + KeyCode::Char('G') => self.tui_state.transition(TuiWidgetEvent::EscapeKey), _ => self.score.handle_event(event).await, } } diff --git a/harmony-rs/harmony_tui/src/widget/help.rs b/harmony-rs/harmony_tui/src/widget/help.rs new file mode 100644 index 0000000..f418161 --- /dev/null +++ b/harmony-rs/harmony_tui/src/widget/help.rs @@ -0,0 +1,20 @@ +use ratatui::widgets::{Paragraph, Widget, Wrap}; + +pub(crate) struct HelpWidget; +impl HelpWidget { + pub(crate) fn new() -> Self { + Self + } +} + +impl Widget for HelpWidget { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) + where + Self: Sized { + let text = Paragraph::new("Usage => q/Esc: Quit | j/↑ :Select UP | k/↓: Select Down | Enter: Launch Score | PageUp/PageDown: Scroll Logs | g/Home: Logs top | Shift+G/End: Logs bottom") + .centered() + .wrap(Wrap { trim: false }); + + Widget::render(text, area, buf) + } +} diff --git a/harmony-rs/harmony_tui/src/widget/mod.rs b/harmony-rs/harmony_tui/src/widget/mod.rs index ba2fe89..a6f65d5 100644 --- a/harmony-rs/harmony_tui/src/widget/mod.rs +++ b/harmony-rs/harmony_tui/src/widget/mod.rs @@ -1 +1,2 @@ pub mod score; +pub mod help; diff --git a/harmony-rs/harmony_tui/src/widget/score.rs b/harmony-rs/harmony_tui/src/widget/score.rs index c2bce21..2e9f2d3 100644 --- a/harmony-rs/harmony_tui/src/widget/score.rs +++ b/harmony-rs/harmony_tui/src/widget/score.rs @@ -1,26 +1,22 @@ -use std::sync::{ Arc, RwLock}; +use std::sync::{Arc, RwLock}; use crossterm::event::{Event, KeyCode, KeyEventKind}; -use log::{debug, error, info, trace, warn}; +use log::{info, warn}; use ratatui::{ - layout::{Constraint, Rect}, + layout::Rect, style::{Style, Stylize}, - widgets::{Block, Clear, List, ListState, Paragraph, StatefulWidget, Widget}, + widgets::{List, ListState, StatefulWidget, Widget}, Frame, }; use tokio::sync::mpsc; use crate::{HarmonyTuiEvent, ScoreItem}; -use crate::ratatui_utils::center; - #[derive(Debug)] enum ExecutionState { INITIATED, - CONFIRMED, - CANCELED, RUNNING, - COMPLETED, + CANCELED, } #[derive(Debug)] @@ -39,7 +35,7 @@ pub(crate) struct ScoreListWidget { } impl ScoreListWidget { - pub(crate) fn new(scores: Vec, sender : mpsc::Sender) -> Self { + pub(crate) fn new(scores: Vec, sender: mpsc::Sender) -> Self { let mut list_state = ListState::default(); list_state.select_first(); let list_state = Arc::new(RwLock::new(list_state)); @@ -80,16 +76,8 @@ impl ScoreListWidget { pub(crate) fn render(&self, area: Rect, frame: &mut Frame) { frame.render_widget(self, area); - self.render_execution(frame); } - pub(crate) fn render_execution(&self, frame: &mut Frame) { - if let None = self.execution { - return; - } - - let execution = self.execution.as_ref().unwrap(); - } fn clear_execution(&mut self) { match self.execution.take() { @@ -104,13 +92,12 @@ impl ScoreListWidget { if let Some(execution) = &mut self.execution { match confirm { true => { - execution.state = ExecutionState::CONFIRMED; + execution.state = ExecutionState::RUNNING; info!("Launch execution {:?}", execution); - warn!("Launch execution"); - error!("Launch execution"); - trace!("Launch execution"); - debug!("Launch execution"); - self.sender.send(HarmonyTuiEvent::LaunchScore(execution.score.clone())).await.expect("Should be able to send message"); + self.sender + .send(HarmonyTuiEvent::LaunchScore(execution.score.clone())) + .await + .expect("Should be able to send message"); } false => { execution.state = ExecutionState::CANCELED;