forked from NationTech/harmony
feat(widget): add help widget and improve score widget
- Introduced a new `Help` widget to display user instructions. - Improved the `ScoreListWidget` by removing unnecessary execution rendering methods and simplifying state transitions. - Cleaned up unused imports and refactored code for better readability.
This commit is contained in:
parent
6628e193e0
commit
f1f2c796c4
@ -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,
|
||||
|
||||
@ -35,7 +35,7 @@ impl Maestro {
|
||||
score_mut.append(&mut scores);
|
||||
}
|
||||
|
||||
pub async fn interpret<S: Score>(&self, score: &S) -> Result<Outcome, InterpretError> {
|
||||
pub async fn interpret(&self, score: Box<dyn Score>) -> Result<Outcome, InterpretError> {
|
||||
info!("Running score {score:?}");
|
||||
let interpret = score.create_interpret();
|
||||
info!("Launching interpret {interpret:?}");
|
||||
|
||||
@ -52,7 +52,7 @@ where
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct K8sResourceInterpret<K: Resource + std::fmt::Debug + Sync> {
|
||||
pub struct K8sResourceInterpret<K: Resource + std::fmt::Debug + Sync + Send> {
|
||||
pub score: K8sResourceScore<K>,
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ impl<
|
||||
+ DeserializeOwned
|
||||
+ serde::Serialize
|
||||
+ Default
|
||||
+ Send
|
||||
+ Sync,
|
||||
> Interpret for K8sResourceInterpret<K>
|
||||
where
|
||||
|
||||
@ -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<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
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<HarmonyTuiEvent>) {
|
||||
fn start_channel(
|
||||
maestro: Arc<Maestro>,
|
||||
) -> (tokio::task::JoinHandle<()>, mpsc::Sender<HarmonyTuiEvent>) {
|
||||
let (sender, mut receiver) = mpsc::channel::<HarmonyTuiEvent>(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<dyn std::error::Error>> {
|
||||
// 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<ScoreItem> {
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
20
harmony-rs/harmony_tui/src/widget/help.rs
Normal file
20
harmony-rs/harmony_tui/src/widget/help.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
pub mod score;
|
||||
pub mod help;
|
||||
|
||||
@ -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<ScoreItem>, sender : mpsc::Sender<HarmonyTuiEvent>) -> Self {
|
||||
pub(crate) fn new(scores: Vec<ScoreItem>, sender: mpsc::Sender<HarmonyTuiEvent>) -> 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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user