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

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()
}