diff --git a/harmony-rs/Cargo.lock b/harmony-rs/Cargo.lock index a70ed41..7f255e4 100644 --- a/harmony-rs/Cargo.lock +++ b/harmony-rs/Cargo.lock @@ -993,6 +993,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1113,6 +1122,7 @@ dependencies = [ "ratatui", "tokio", "tokio-stream", + "tui-logger", ] [[package]] @@ -3537,6 +3547,20 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui-logger" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a5249b5e5df37af5389721794f9839dad0b3f7b92445f24528199acf2f1805" +dependencies = [ + "chrono", + "fxhash", + "lazy_static", + "log", + "parking_lot", + "ratatui", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/harmony-rs/harmony/src/domain/score.rs b/harmony-rs/harmony/src/domain/score.rs index bd7c397..7b2c790 100644 --- a/harmony-rs/harmony/src/domain/score.rs +++ b/harmony-rs/harmony/src/domain/score.rs @@ -1,6 +1,6 @@ use super::interpret::Interpret; -pub trait Score: std::fmt::Debug { +pub trait Score: std::fmt::Debug + Send + Sync { fn create_interpret(&self) -> Box; fn name(&self) -> String; fn clone_box(&self) -> Box; diff --git a/harmony-rs/harmony/src/modules/k8s/resource.rs b/harmony-rs/harmony/src/modules/k8s/resource.rs index 9f68cd1..070f818 100644 --- a/harmony-rs/harmony/src/modules/k8s/resource.rs +++ b/harmony-rs/harmony/src/modules/k8s/resource.rs @@ -32,6 +32,7 @@ impl< + Default + serde::Serialize + 'static + + Send + Clone, > Score for K8sResourceScore where diff --git a/harmony-rs/harmony_tui/Cargo.toml b/harmony-rs/harmony_tui/Cargo.toml index 09a2d30..0da7982 100644 --- a/harmony-rs/harmony_tui/Cargo.toml +++ b/harmony-rs/harmony_tui/Cargo.toml @@ -12,3 +12,4 @@ ratatui = "0.29.0" crossterm = { version = "0.28.1", features = [ "event-stream" ] } color-eyre = "0.6.3" tokio-stream = "0.1.17" +tui-logger = "0.14.1" diff --git a/harmony-rs/harmony_tui/src/lib.rs b/harmony-rs/harmony_tui/src/lib.rs index c8561d6..756ab19 100644 --- a/harmony-rs/harmony_tui/src/lib.rs +++ b/harmony-rs/harmony_tui/src/lib.rs @@ -1,15 +1,19 @@ +mod ratatui_utils; mod widget; +use log::{debug, error, info, trace, warn}; +use tokio::sync::mpsc; use tokio_stream::StreamExt; use widget::score::ScoreListWidget; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind}; use harmony::{maestro::Maestro, score::Score}; use ratatui::{ self, layout::{Constraint, Layout, Position}, + style::{Color, Style}, widgets::{Block, Borders, ListItem}, Frame, }; @@ -47,22 +51,48 @@ pub struct HarmonyTUI { maestro: Maestro, score: ScoreListWidget, should_quit: bool, + channel_handle: tokio::task::JoinHandle<()>, +} + +#[derive(Debug)] +enum HarmonyTuiEvent { + LaunchScore(ScoreItem), } impl HarmonyTUI { pub fn new(maestro: Maestro) -> Self { - let score = ScoreListWidget::new(Self::scores_list(&maestro)); + let (handle, sender) = Self::start_channel(); + let score = ScoreListWidget::new(Self::scores_list(&maestro), sender); HarmonyTUI { maestro, should_quit: false, score, + channel_handle: handle, } } + fn start_channel() -> (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:#?}"); + } + info!("STOPPING message channel receiver loop"); + }); + (handle, sender) + } + pub async fn init(mut self) -> Result<(), Box> { + // Set max_log_level to Trace + tui_logger::init_logger(log::LevelFilter::Trace).unwrap(); + // Set default level for unknown targets to Trace + tui_logger::set_default_level(log::LevelFilter::Trace); + color_eyre::install()?; let mut terminal = ratatui::init(); + terminal.hide_cursor()?; // TODO improve performance here // Refreshing the entire terminal 10 times a second does not seem very smart @@ -73,7 +103,7 @@ impl HarmonyTUI { while !self.should_quit { tokio::select! { _ = interval.tick() => { terminal.draw(|frame| self.render(frame))?; }, - Some(Ok(event)) = events.next() => self.handle_event(&event), + Some(Ok(event)) = events.next() => self.handle_event(&event).await, } } @@ -93,6 +123,15 @@ impl HarmonyTUI { 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, + ) } fn scores_list(maestro: &Maestro) -> Vec { @@ -104,17 +143,12 @@ impl HarmonyTUI { .collect() } - fn handle_event(&mut self, event: &Event) { + async fn handle_event(&mut self, 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::Char('j') | KeyCode::Down => self.score.scroll_down(), - KeyCode::Char('k') | KeyCode::Up => self.score.scroll_up(), - KeyCode::Enter => self.score.launch_execution(), - KeyCode::Char('y') => self.score.confirm(true), - KeyCode::Char('n') => self.score.confirm(false), - _ => {} + _ => self.score.handle_event(event).await, } } } diff --git a/harmony-rs/harmony_tui/src/widget/ratatui_utils.rs b/harmony-rs/harmony_tui/src/ratatui_utils.rs similarity index 100% rename from harmony-rs/harmony_tui/src/widget/ratatui_utils.rs rename to harmony-rs/harmony_tui/src/ratatui_utils.rs diff --git a/harmony-rs/harmony_tui/src/widget/mod.rs b/harmony-rs/harmony_tui/src/widget/mod.rs index 63f8cd5..ba2fe89 100644 --- a/harmony-rs/harmony_tui/src/widget/mod.rs +++ b/harmony-rs/harmony_tui/src/widget/mod.rs @@ -1,2 +1 @@ pub mod score; -pub mod ratatui_utils; diff --git a/harmony-rs/harmony_tui/src/widget/score.rs b/harmony-rs/harmony_tui/src/widget/score.rs index abd4fa4..c2bce21 100644 --- a/harmony-rs/harmony_tui/src/widget/score.rs +++ b/harmony-rs/harmony_tui/src/widget/score.rs @@ -1,16 +1,18 @@ -use std::sync::{Arc, RwLock}; +use std::sync::{ Arc, RwLock}; -use log::warn; +use crossterm::event::{Event, KeyCode, KeyEventKind}; +use log::{debug, error, info, trace, warn}; use ratatui::{ layout::{Constraint, Rect}, style::{Style, Stylize}, widgets::{Block, Clear, List, ListState, Paragraph, StatefulWidget, Widget}, Frame, }; +use tokio::sync::mpsc; -use crate::ScoreItem; +use crate::{HarmonyTuiEvent, ScoreItem}; -use super::ratatui_utils::center; +use crate::ratatui_utils::center; #[derive(Debug)] enum ExecutionState { @@ -33,10 +35,11 @@ pub(crate) struct ScoreListWidget { scores: Vec, execution: Option, execution_history: Vec, + sender: mpsc::Sender, } impl ScoreListWidget { - pub(crate) fn new(scores: Vec) -> 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)); @@ -45,6 +48,7 @@ impl ScoreListWidget { list_state, execution: None, execution_history: vec![], + sender, } } @@ -60,6 +64,9 @@ impl ScoreListWidget { state: ExecutionState::INITIATED, score: score.clone(), }); + info!("{:#?}\n\nConfirm Execution (Press y/n)", score.0); + } else { + warn!("No Score selected, nothing to launch"); } } @@ -82,25 +89,6 @@ impl ScoreListWidget { } let execution = self.execution.as_ref().unwrap(); - - // let confirm = Paragraph::new(format!("{:#?}", execution.score.0)).block( - // Block::default() - // .borders(Borders::ALL) - // .title("Confirm Execution") - // .border_type(BorderType::Rounded), - // ); - // let rect = frame.area().inner(Margin::new(5, 5)); - // frame.render_widget(confirm, rect); - - let area = center( - frame.area(), - Constraint::Percentage(80), - Constraint::Percentage(80), // top and bottom border + content - ); - let popup = Paragraph::new(format!("{:#?}", execution.score.0)) - .block(Block::bordered().title("Confirm Execution (Press y/n)")); - frame.render_widget(Clear, area); - frame.render_widget(popup, area); } fn clear_execution(&mut self) { @@ -112,20 +100,41 @@ impl ScoreListWidget { } } - pub(crate) fn confirm(&mut self, confirm: bool) { + pub(crate) async fn confirm(&mut self, confirm: bool) { if let Some(execution) = &mut self.execution { match confirm { true => { execution.state = ExecutionState::CONFIRMED; - todo!("launch execution {:#?}", execution); - }, + 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"); + } false => { execution.state = ExecutionState::CANCELED; + info!("Execution cancelled"); self.clear_execution(); } } } } + + pub(crate) async fn handle_event(&mut self, event: &Event) { + if let Event::Key(key) = event { + if key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Char('j') | KeyCode::Down => self.scroll_down(), + KeyCode::Char('k') | KeyCode::Up => self.scroll_up(), + KeyCode::Enter => self.launch_execution(), + KeyCode::Char('y') => self.confirm(true).await, + KeyCode::Char('n') => self.confirm(false).await, + _ => {} + } + } + } + } } impl Widget for &ScoreListWidget {