diff --git a/bin/u_panel/Cargo.toml b/bin/u_panel/Cargo.toml index 50395a0..14442d0 100644 --- a/bin/u_panel/Cargo.toml +++ b/bin/u_panel/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +backtrace = "0.3.61" structopt = "0.3.21" log = "^0.4" env_logger = "0.7.1" diff --git a/bin/u_panel/src/argparse.rs b/bin/u_panel/src/argparse.rs index dec2ccb..53c93bc 100644 --- a/bin/u_panel/src/argparse.rs +++ b/bin/u_panel/src/argparse.rs @@ -19,7 +19,7 @@ pub struct Args { enum Cmd { Agents(LD), Jobs(JobALD), - Jobmap(JobMapALD), + Map(JobMapALD), TUI, } @@ -127,7 +127,7 @@ pub async fn process_cmd(args: Args) -> UResult<()> { JobALD::LD(LD::List { uid }) => printer.print(CLIENT.get_jobs(uid).await), JobALD::LD(LD::Delete { uid }) => printer.print(CLIENT.del(Some(uid)).await), }, - Cmd::Jobmap(action) => match action { + Cmd::Map(action) => match action { JobMapALD::Add { agent_uid, job_idents, diff --git a/bin/u_panel/src/tui/impls.rs b/bin/u_panel/src/tui/impls.rs index 5b660d1..9e29a48 100644 --- a/bin/u_panel/src/tui/impls.rs +++ b/bin/u_panel/src/tui/impls.rs @@ -7,23 +7,19 @@ pub trait Id { fn id(&self) -> Uuid; } -impl Id for Agent { - fn id(&self) -> Uuid { - self.id - } -} - -impl Id for JobMeta { - fn id(&self) -> Uuid { - self.id - } -} - -impl Id for AssignedJob { - fn id(&self) -> Uuid { - self.id - } -} +#[macro_export] +macro_rules! impl_id { + ($($type:ty),+) => { + $( + impl Id for $type { + fn id(&self) -> Uuid { + self.id + } + })+ + }; +} + +impl_id!(Agent, JobMeta, AssignedJob); #[async_trait] pub trait CRUD: Id diff --git a/bin/u_panel/src/tui/mod.rs b/bin/u_panel/src/tui/mod.rs index 5742407..8ffd734 100644 --- a/bin/u_panel/src/tui/mod.rs +++ b/bin/u_panel/src/tui/mod.rs @@ -1,14 +1,13 @@ mod impls; -mod state; -mod ui; +mod windows; -use anyhow::Result; -use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}; +use anyhow::Result as AResult; +use backtrace::Backtrace; +use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event}; use crossterm::execute; use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }; -use state::State; use std::panic::set_hook; use std::process::exit; use std::{ @@ -18,36 +17,42 @@ use std::{ time::Duration, }; use tui::{backend::CrosstermBackend, Terminal}; +use windows::{MainWnd, WindowsHandler}; -type Frame<'f> = tui::Frame<'f, CrosstermBackend>; +pub type Backend = CrosstermBackend; +pub type Frame<'f> = tui::Frame<'f, Backend>; enum InputEvent { Key(I), Tick, } -pub async fn init_tui() -> Result<()> { +fn get_terminal() -> AResult> { + let backend = CrosstermBackend::new(stdout()); + Ok(Terminal::new(backend)?) +} + +pub async fn init_tui() -> AResult<()> { //TODO: fix this set_hook(Box::new(|p| { teardown().unwrap(); - eprintln!("{}", p); + get_terminal().unwrap().show_cursor().unwrap(); + eprintln!("{}\n{:?}", p, Backtrace::new()); exit(254); })); - let mut state = State::default(); - if let Err(e) = init(&mut state).await { + if let Err(e) = init().await { teardown()?; return Err(e); } Ok(()) } -async fn init(state: &mut State) -> Result<()> { - let mut stdout = stdout(); +async fn init() -> AResult<()> { enable_raw_mode()?; - execute!(&mut stdout, EnterAlternateScreen, EnableMouseCapture)?; + execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; + let mut wh = WindowsHandler::new(get_terminal()?); + wh.push::(); let (tx, rx) = mpsc::channel(); thread::spawn(move || loop { @@ -61,39 +66,23 @@ async fn init(state: &mut State) -> Result<()> { } }); - terminal.clear()?; + wh.clear()?; loop { - state.check_updates().await; - terminal.draw(|f| ui::draw(f, state))?; + wh.draw()?; + wh.update().await?; match rx.recv()? { - InputEvent::Key(Event::Key(key)) => match key.code { - KeyCode::Esc => { - teardown()?; - terminal.show_cursor()?; - break; - } - KeyCode::Left => state.prev_tab(), - KeyCode::Right => state.next_tab(), - KeyCode::Up => state.on_up(), - KeyCode::Down => state.on_down(), - KeyCode::Delete => { - state.delete().await; - state.update_tab(); - } - KeyCode::F(5) => state.update_tab(), - _ => (), - }, + InputEvent::Key(Event::Key(key)) => { + wh.handle_kbd(key.code).await?; + } InputEvent::Tick => (), _ => unreachable!(), } } - Ok(()) } -fn teardown() -> Result<()> { +fn teardown() -> AResult<()> { disable_raw_mode()?; execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)?; - eprintln!("teardown"); Ok(()) } diff --git a/bin/u_panel/src/tui/ui.rs b/bin/u_panel/src/tui/ui.rs deleted file mode 100644 index 8a97e65..0000000 --- a/bin/u_panel/src/tui/ui.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::{ - state::{State, UiTabs}, - Frame, -}; -use tui::layout::{Constraint, Direction, Layout}; -use tui::style::{Color, Modifier, Style}; -use tui::text::Spans; -use tui::widgets::{Block, Borders, List, ListItem, Tabs}; - -pub fn draw(f: &mut Frame, s: &mut State) { - let size = f.size(); - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(1) - .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) - .split(size); - let titles = UiTabs::variants() - .iter() - .cloned() - .map(Spans::from) - .collect(); - let tabs = Tabs::new(titles) - .block( - Block::default() - .title("The whole that you need to know") - .borders(Borders::ALL), - ) - .style(Style::default().fg(Color::White)) - .highlight_style(Style::default().fg(Color::Yellow)) - .divider("-") - .select(s.active_tab.index()); - f.render_widget(tabs, chunks[0]); - - let tab_data = s - .tab_data() - .into_iter() - .map(ListItem::new) - .collect::>(); - let list = List::new(tab_data) - .block(Block::default().borders(Borders::ALL)) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)); - f.render_stateful_widget(list, chunks[1], s.tab_list_state()); -} diff --git a/bin/u_panel/src/tui/state.rs b/bin/u_panel/src/tui/windows/main_wnd.rs similarity index 73% rename from bin/u_panel/src/tui/state.rs rename to bin/u_panel/src/tui/windows/main_wnd.rs index 57b1109..4cebc8f 100644 --- a/bin/u_panel/src/tui/state.rs +++ b/bin/u_panel/src/tui/windows/main_wnd.rs @@ -1,5 +1,7 @@ -use super::impls::CRUD; +use super::Window; +use crate::tui::{impls::CRUD, Frame}; use anyhow::Result as AResult; +use crossterm::event::KeyCode; use std::{fmt::Display, str::FromStr}; use strum::VariantNames; use tokio::join; @@ -7,6 +9,11 @@ use tui::widgets::ListState; use u_lib::models::{Agent, AssignedJob, JobMeta}; use uuid::Uuid; +use tui::layout::{Constraint, Direction, Layout}; +use tui::style::{Color, Modifier, Style}; +use tui::text::Spans; +use tui::widgets::{Block, Borders, List, ListItem, Tabs}; + #[derive(strum::Display, strum::EnumVariantNames, strum::EnumString)] pub enum UiTabs { Agents, @@ -82,7 +89,7 @@ impl Default for StatefulList { } } -pub struct State { +pub struct MainWnd { pub active_tab: UiTabs, pub last_error: Option, pub agents: StatefulList, @@ -90,9 +97,9 @@ pub struct State { pub map: StatefulList, } -impl Default for State { +impl Default for MainWnd { fn default() -> Self { - State { + MainWnd { active_tab: UiTabs::Agents, last_error: None, agents: Default::default(), @@ -102,7 +109,7 @@ impl Default for State { } } -impl State { +impl MainWnd { pub fn next_tab(&mut self) { self.active_tab = self.active_tab.next() } @@ -233,6 +240,74 @@ impl State { } } +#[async_trait] +impl Window for MainWnd { + async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()> { + match k { + KeyCode::Esc => { + /*teardown()?; + wh.show_cursor()?; + break;*/ + } + KeyCode::Left => self.prev_tab(), + KeyCode::Right => self.next_tab(), + KeyCode::Up => self.on_up(), + KeyCode::Down => self.on_down(), + KeyCode::Delete => { + self.delete().await; + self.update_tab(); + } + KeyCode::F(5) => self.update_tab(), + _ => (), + }; + Ok(()) + } + + async fn update(&mut self) -> AResult<()> { + self.check_updates().await; + Ok(()) + } + + async fn close(&mut self) { + //self.show_cursor() + } + + fn draw(&mut self, f: &mut Frame) { + let size = f.size(); + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) + .split(size); + let titles = UiTabs::variants() + .iter() + .cloned() + .map(Spans::from) + .collect(); + let tabs = Tabs::new(titles) + .block( + Block::default() + .title("The whole that you need to know") + .borders(Borders::ALL), + ) + .style(Style::default().fg(Color::White)) + .highlight_style(Style::default().fg(Color::Yellow)) + .divider("-") + .select(self.active_tab.index()); + f.render_widget(tabs, chunks[0]); + + let tab_data = self + .tab_data() + .into_iter() + .map(ListItem::new) + .collect::>(); + let list = List::new(tab_data) + .block(Block::default().borders(Borders::ALL)) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + f.render_stateful_widget(list, chunks[1], self.tab_list_state()); + } +} + fn crop(data: T, retain: usize) -> String { data.to_string()[..retain].to_string() } diff --git a/bin/u_panel/src/tui/windows/mod.rs b/bin/u_panel/src/tui/windows/mod.rs new file mode 100644 index 0000000..3cc7e31 --- /dev/null +++ b/bin/u_panel/src/tui/windows/mod.rs @@ -0,0 +1,94 @@ +mod main_wnd; +mod processing; +pub use main_wnd::MainWnd; + +use crate::tui::{Backend, Frame}; +use anyhow::Result as AResult; +use crossterm::event::KeyCode; +use std::cell::RefCell; +use std::process::exit; +use std::rc::Rc; +use tui::Terminal; + +type Wnd = Rc>; + +#[async_trait] +pub trait Window { + async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()>; + async fn update(&mut self) -> AResult<()>; + async fn close(&mut self); + fn draw(&mut self, f: &mut Frame); +} + +pub struct WindowsHandler { + queue: Vec, + term: Terminal, + redraw_all: bool, +} + +impl WindowsHandler { + fn get_last_wnd(&self) -> Wnd { + self.queue.last().expect("No windows found").clone() + } + + pub fn new(term: Terminal) -> Self { + Self { + term, + queue: vec![], + redraw_all: true, + } + } + + pub fn push(&mut self) { + let window = Rc::new(RefCell::new(W::default())); + self.queue.push(window); + } + + pub fn clear(&mut self) -> AResult<()> { + self.term.clear()?; + Ok(()) + } + + pub fn show_cursor(&mut self) -> AResult<()> { + self.term.show_cursor()?; + Ok(()) + } + + pub fn draw(&mut self) -> AResult<()> { + if self.redraw_all { + for wnd in self.queue.iter() { + self.term.draw(|f| wnd.borrow_mut().draw(f))?; + } + } else { + let wnd = self.get_last_wnd(); + self.term.draw(|f| wnd.borrow_mut().draw(f))?; + } + Ok(()) + } + + pub async fn close(&mut self) { + let wnd = self.queue.pop().unwrap(); + wnd.borrow_mut().close().await; + self.redraw_all = true; + if self.queue.is_empty() { + self.show_cursor().unwrap(); + exit(0); + } + } + + pub async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()> { + if let KeyCode::Esc = k { + self.close().await; + } else { + let current_wnd = self.get_last_wnd(); + current_wnd.borrow_mut().handle_kbd(k).await?; + } + Ok(()) + } + + pub async fn update(&mut self) -> AResult<()> { + let current_wnd = self.get_last_wnd(); + current_wnd.borrow_mut().update().await?; + Ok(()) + } +} diff --git a/bin/u_panel/src/tui/windows/processing.rs b/bin/u_panel/src/tui/windows/processing.rs new file mode 100644 index 0000000..e69de29