You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
9.2 KiB
313 lines
9.2 KiB
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; |
|
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, |
|
Jobs, |
|
Map, |
|
} |
|
|
|
impl UiTabs { |
|
pub fn variants() -> &'static [&'static str] { |
|
Self::VARIANTS |
|
} |
|
|
|
pub fn index(&self) -> usize { |
|
let ss = self.to_string(); |
|
Self::VARIANTS.iter().position(|el| **el == ss).unwrap() |
|
} |
|
|
|
pub fn next(&self) -> Self { |
|
let next_idx = (self.index() + 1) % Self::VARIANTS.len(); |
|
Self::from_str(Self::VARIANTS[next_idx]).unwrap() |
|
} |
|
|
|
pub fn prev(&self) -> Self { |
|
let vlen = Self::VARIANTS.len(); |
|
let next_idx = (self.index() + vlen - 1) % vlen; |
|
Self::from_str(Self::VARIANTS[next_idx]).unwrap() |
|
} |
|
} |
|
|
|
pub struct StatefulList<T: CRUD> { |
|
pub updated: bool, |
|
pub inner: Vec<T>, |
|
pub state: ListState, |
|
} |
|
|
|
impl<T: CRUD> StatefulList<T> { |
|
pub async fn update(&mut self) -> AResult<()> { |
|
if !self.updated { |
|
let new_values = <T as CRUD>::read().await?; |
|
self.inner = new_values; |
|
self.updated = true; |
|
} |
|
Ok(()) |
|
} |
|
|
|
pub async fn delete(&mut self) -> AResult<()> { |
|
if let Some(s) = self.state.selected() { |
|
let uid = self.inner[s].id(); |
|
<T as CRUD>::delete(uid).await?; |
|
} |
|
Ok(()) |
|
} |
|
|
|
pub fn get(&self, id: Uuid) -> Option<&T> { |
|
for item in self.inner.iter() { |
|
if item.id() == id { |
|
return Some(item); |
|
} |
|
} |
|
None |
|
} |
|
} |
|
|
|
impl<T: CRUD> Default for StatefulList<T> { |
|
fn default() -> Self { |
|
let mut state = ListState::default(); |
|
state.select(Some(0)); |
|
StatefulList { |
|
updated: false, |
|
inner: vec![], |
|
state, |
|
} |
|
} |
|
} |
|
|
|
pub struct MainWnd { |
|
pub active_tab: UiTabs, |
|
pub last_error: Option<String>, |
|
pub agents: StatefulList<Agent>, |
|
pub jobs: StatefulList<JobMeta>, |
|
pub map: StatefulList<AssignedJob>, |
|
} |
|
|
|
impl Default for MainWnd { |
|
fn default() -> Self { |
|
MainWnd { |
|
active_tab: UiTabs::Agents, |
|
last_error: None, |
|
agents: Default::default(), |
|
jobs: Default::default(), |
|
map: Default::default(), |
|
} |
|
} |
|
} |
|
|
|
impl MainWnd { |
|
pub fn next_tab(&mut self) { |
|
self.active_tab = self.active_tab.next() |
|
} |
|
|
|
pub fn prev_tab(&mut self) { |
|
self.active_tab = self.active_tab.prev() |
|
} |
|
|
|
fn check_err(&mut self, res: AResult<()>) -> bool { |
|
if let Err(e) = res { |
|
self.last_error = Some(e.to_string()); |
|
true |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
pub async fn check_updates(&mut self) { |
|
if !self.agents.updated || !self.jobs.updated || !self.map.updated { |
|
let state = self.tab_list_state(); |
|
if let None = state.selected() { |
|
state.select(Some(0)) |
|
} |
|
|
|
let (a, j, m) = join! { |
|
self.agents.update(), |
|
self.jobs.update(), |
|
self.map.update() |
|
}; |
|
for res in [a, j, m] { |
|
self.check_err(res); |
|
} |
|
} |
|
} |
|
|
|
pub fn tab_data(&self) -> Vec<String> { |
|
match self.active_tab { |
|
UiTabs::Agents => self |
|
.agents |
|
.inner |
|
.iter() |
|
.map(|i| format!("{}: {}-{}", crop(i.id, 6), i.username, i.hostname)) |
|
.collect(), |
|
UiTabs::Jobs => self |
|
.jobs |
|
.inner |
|
.iter() |
|
.map(|i| format!("{}: {}", crop(i.id, 6), i.alias.clone().unwrap_or_default())) |
|
.collect(), |
|
UiTabs::Map => self |
|
.map |
|
.inner |
|
.iter() |
|
.map(|i| { |
|
let job = self.jobs.get(i.job_id).unwrap(); |
|
let job_id = crop(i.job_id, 6); |
|
let job_ident = if let Some(alias) = job.alias.as_ref() { |
|
format!("{} ({})", alias, job_id) |
|
} else { |
|
format!("{}", job_id) |
|
}; |
|
let agent = self.agents.get(i.agent_id).unwrap(); |
|
let agent_id = crop(i.agent_id, 6); |
|
let agent_ident = if let Some(alias) = agent.alias.as_ref() { |
|
format!("{} ({})", alias, agent_id) |
|
} else { |
|
format!("{}-{} ({})", agent.username, agent.hostname, agent_id) |
|
}; |
|
format!("{}: {} for {}", crop(i.id, 6), job_ident, agent_ident) |
|
}) |
|
.collect(), |
|
} |
|
} |
|
|
|
pub fn tab_list_state(&mut self) -> &mut ListState { |
|
match self.active_tab { |
|
UiTabs::Agents => &mut self.agents.state, |
|
UiTabs::Jobs => &mut self.jobs.state, |
|
UiTabs::Map => &mut self.map.state, |
|
} |
|
} |
|
|
|
pub fn update_tab(&mut self) { |
|
match self.active_tab { |
|
UiTabs::Agents => self.agents.updated = false, |
|
UiTabs::Jobs => self.jobs.updated = false, |
|
UiTabs::Map => self.map.updated = false, |
|
} |
|
} |
|
|
|
pub fn on_down(&mut self) { |
|
let (list_len, list_state) = match self.active_tab { |
|
UiTabs::Agents => (self.agents.inner.len(), &mut self.agents.state), |
|
UiTabs::Jobs => (self.jobs.inner.len(), &mut self.jobs.state), |
|
UiTabs::Map => (self.map.inner.len(), &mut self.map.state), |
|
}; |
|
if list_len == 0 { |
|
list_state.select(None); |
|
} else { |
|
let selected = list_state.selected().unwrap_or(0); |
|
list_state.select(Some((selected + 1) % list_len)); |
|
} |
|
} |
|
|
|
pub fn on_up(&mut self) { |
|
let (list_len, list_state) = match self.active_tab { |
|
UiTabs::Agents => (self.agents.inner.len(), &mut self.agents.state), |
|
UiTabs::Jobs => (self.jobs.inner.len(), &mut self.jobs.state), |
|
UiTabs::Map => (self.map.inner.len(), &mut self.map.state), |
|
}; |
|
if list_len == 0 { |
|
list_state.select(None); |
|
} else { |
|
let selected = list_state.selected().unwrap_or(1); |
|
list_state.select(Some((selected + list_len - 1) % list_len)); |
|
} |
|
} |
|
|
|
pub async fn delete(&mut self) { |
|
let res = match self.active_tab { |
|
UiTabs::Agents => self.agents.delete().await, |
|
UiTabs::Jobs => self.jobs.delete().await, |
|
UiTabs::Map => self.map.delete().await, |
|
}; |
|
if !self.check_err(res) { |
|
self.on_up(); |
|
} |
|
} |
|
} |
|
|
|
#[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::<Vec<ListItem>>(); |
|
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<T: Display>(data: T, retain: usize) -> String { |
|
data.to_string()[..retain].to_string() |
|
}
|
|
|