some ui progress

pull/1/head
plazmoid 3 years ago
parent ec3f78b8cd
commit c8dc747bcc
  1. 2
      bin/u_panel/Cargo.toml
  2. 28
      bin/u_panel/src/argparse.rs
  3. 3
      bin/u_panel/src/main.rs
  4. 63
      bin/u_panel/src/tui/impls.rs
  5. 29
      bin/u_panel/src/tui/mod.rs
  6. 195
      bin/u_panel/src/tui/state.rs
  7. 23
      bin/u_panel/src/tui/ui.rs

@ -20,3 +20,5 @@ tui = { version = "0.16", default-features = false, features = ['crossterm'] }
crossterm = "0.22.1" crossterm = "0.22.1"
anyhow = "1.0.44" anyhow = "1.0.44"
strum = { version = "0.22.0", features = ["derive"] } strum = { version = "0.22.0", features = ["derive"] }
async-trait = "0.1.51"
once_cell = "1.8.0"

@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use std::env; use std::env;
use std::fmt; use std::fmt;
use structopt::StructOpt; use structopt::StructOpt;
@ -74,6 +75,11 @@ enum LD {
}, },
} }
pub static CLIENT: Lazy<ClientHandler> = Lazy::new(|| {
let token = env::var("ADMIN_AUTH_TOKEN").expect("access token is not set");
ClientHandler::new(None).password(token.clone())
});
fn parse_uuid(src: &str) -> Result<Uuid, String> { fn parse_uuid(src: &str) -> Result<Uuid, String> {
Uuid::parse_str(src).map_err(|e| e.to_string()) Uuid::parse_str(src).map_err(|e| e.to_string())
} }
@ -100,13 +106,11 @@ pub async fn process_cmd(args: Args) -> UResult<()> {
} }
} }
let token = env::var("ADMIN_AUTH_TOKEN").map_err(|_| UError::WrongToken)?;
let cli_handler = ClientHandler::new(None).password(token.clone());
let printer = Printer { json: args.json }; let printer = Printer { json: args.json };
match args.cmd { match args.cmd {
Cmd::Agents(action) => match action { Cmd::Agents(action) => match action {
LD::List { uid } => printer.print(cli_handler.get_agents(uid).await), LD::List { uid } => printer.print(CLIENT.get_agents(uid).await),
LD::Delete { uid } => printer.print(cli_handler.del(Some(uid)).await), LD::Delete { uid } => printer.print(CLIENT.del(Some(uid)).await),
}, },
Cmd::Jobs(action) => match action { Cmd::Jobs(action) => match action {
JobALD::Add { JobALD::Add {
@ -118,21 +122,23 @@ pub async fn process_cmd(args: Args) -> UResult<()> {
.with_shell(cmd.join(" ")) .with_shell(cmd.join(" "))
.with_alias(alias) .with_alias(alias)
.build()?; .build()?;
printer.print(cli_handler.upload_jobs(&[job]).await); printer.print(CLIENT.upload_jobs(&[job]).await);
} }
JobALD::LD(LD::List { uid }) => printer.print(cli_handler.get_jobs(uid).await), JobALD::LD(LD::List { uid }) => printer.print(CLIENT.get_jobs(uid).await),
JobALD::LD(LD::Delete { uid }) => printer.print(cli_handler.del(Some(uid)).await), JobALD::LD(LD::Delete { uid }) => printer.print(CLIENT.del(Some(uid)).await),
}, },
Cmd::Jobmap(action) => match action { Cmd::Jobmap(action) => match action {
JobMapALD::Add { JobMapALD::Add {
agent_uid, agent_uid,
job_idents, job_idents,
} => printer.print(cli_handler.set_jobs(Some(agent_uid), &job_idents).await), } => printer.print(CLIENT.set_jobs(Some(agent_uid), &job_idents).await),
JobMapALD::List { uid } => printer.print(cli_handler.get_agent_jobs(uid).await), JobMapALD::List { uid } => printer.print(CLIENT.get_agent_jobs(uid).await),
JobMapALD::Delete { uid } => printer.print(cli_handler.del(Some(uid)).await), JobMapALD::Delete { uid } => printer.print(CLIENT.del(Some(uid)).await),
}, },
//Cmd::Server => be::serve().unwrap(), //Cmd::Server => be::serve().unwrap(),
Cmd::TUI => crate::tui::init_tui(token).map_err(|e| UError::TUIError(e.to_string()))?, Cmd::TUI => crate::tui::init_tui()
.await
.map_err(|e| UError::TUIError(e.to_string()))?,
} }
Ok(()) Ok(())
} }

@ -1,6 +1,9 @@
mod argparse; mod argparse;
mod tui; mod tui;
#[macro_use]
extern crate async_trait;
use argparse::{process_cmd, Args}; use argparse::{process_cmd, Args};
use std::process; use std::process;
use structopt::StructOpt; use structopt::StructOpt;

@ -0,0 +1,63 @@
use crate::argparse::CLIENT;
use u_lib::models::{Agent, AssignedJob, JobMeta};
use u_lib::UResult;
use uuid::Uuid;
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
}
}
#[async_trait]
pub trait CRUD: Id
where
Self: Sized,
{
async fn read() -> UResult<Vec<Self>>;
async fn delete(uid: Uuid) -> UResult<i32> {
CLIENT.del(Some(uid)).await
}
//TODO: other crud
}
#[async_trait]
impl CRUD for Agent {
async fn read() -> UResult<Vec<Agent>> {
CLIENT.get_agents(None).await.map(|r| r.into_builtin_vec())
}
}
#[async_trait]
impl CRUD for AssignedJob {
async fn read() -> UResult<Vec<AssignedJob>> {
CLIENT
.get_agent_jobs(None)
.await
.map(|r| r.into_builtin_vec())
}
}
#[async_trait]
impl CRUD for JobMeta {
async fn read() -> UResult<Vec<JobMeta>> {
CLIENT.get_jobs(None).await.map(|r| r.into_builtin_vec())
}
}

@ -1,3 +1,4 @@
mod impls;
mod state; mod state;
mod ui; mod ui;
@ -20,22 +21,27 @@ use tui::{backend::CrosstermBackend, Terminal};
type Frame<'f> = tui::Frame<'f, CrosstermBackend<Stdout>>; type Frame<'f> = tui::Frame<'f, CrosstermBackend<Stdout>>;
pub fn init_tui(token: String) -> Result<()> { enum InputEvent<I> {
Key(I),
Tick,
}
pub async fn init_tui() -> Result<()> {
//TODO: fix this //TODO: fix this
set_hook(Box::new(|p| { set_hook(Box::new(|p| {
teardown().unwrap(); teardown().unwrap();
eprintln!("{}", p); eprintln!("{}", p);
exit(254); exit(254);
})); }));
let mut state = State::new(token); let mut state = State::default();
if let Err(e) = init(&mut state) { if let Err(e) = init(&mut state).await {
teardown()?; teardown()?;
return Err(e); return Err(e);
} }
Ok(()) Ok(())
} }
fn init(state: &mut State) -> Result<()> { async fn init(state: &mut State) -> Result<()> {
let mut stdout = stdout(); let mut stdout = stdout();
enable_raw_mode()?; enable_raw_mode()?;
execute!(&mut stdout, EnterAlternateScreen, EnableMouseCapture)?; execute!(&mut stdout, EnterAlternateScreen, EnableMouseCapture)?;
@ -47,18 +53,21 @@ fn init(state: &mut State) -> Result<()> {
thread::spawn(move || loop { thread::spawn(move || loop {
if event::poll(Duration::from_millis(10)).unwrap() { if event::poll(Duration::from_millis(10)).unwrap() {
match event::read().unwrap() { match event::read().unwrap() {
key @ Event::Key(_) => tx.send(key).unwrap(), key @ Event::Key(_) => tx.send(InputEvent::Key(key)).unwrap(),
_ => (), _ => (),
} }
} else {
tx.send(InputEvent::Tick).unwrap()
} }
}); });
terminal.clear()?; terminal.clear()?;
loop { loop {
state.check_updates().await;
terminal.draw(|f| ui::draw(f, state))?; terminal.draw(|f| ui::draw(f, state))?;
match rx.recv()? { match rx.recv()? {
Event::Key(key) => match key.code { InputEvent::Key(Event::Key(key)) => match key.code {
KeyCode::Esc => { KeyCode::Esc => {
teardown()?; teardown()?;
terminal.show_cursor()?; terminal.show_cursor()?;
@ -66,8 +75,16 @@ fn init(state: &mut State) -> Result<()> {
} }
KeyCode::Left => state.prev_tab(), KeyCode::Left => state.prev_tab(),
KeyCode::Right => state.next_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::Tick => (),
_ => unreachable!(), _ => unreachable!(),
} }
} }

@ -1,5 +1,11 @@
use std::str::FromStr; use super::impls::CRUD;
use anyhow::Result as AResult;
use std::{fmt::Display, str::FromStr};
use strum::VariantNames; use strum::VariantNames;
use tokio::join;
use tui::widgets::ListState;
use u_lib::models::{Agent, AssignedJob, JobMeta};
use uuid::Uuid;
#[derive(strum::Display, strum::EnumVariantNames, strum::EnumString)] #[derive(strum::Display, strum::EnumVariantNames, strum::EnumString)]
pub enum UiTabs { pub enum UiTabs {
@ -30,19 +36,73 @@ impl UiTabs {
} }
} }
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 State { pub struct State {
pub token: String,
pub active_tab: UiTabs, pub active_tab: UiTabs,
pub last_error: Option<String>,
pub agents: StatefulList<Agent>,
pub jobs: StatefulList<JobMeta>,
pub map: StatefulList<AssignedJob>,
} }
impl State { impl Default for State {
pub fn new(token: String) -> Self { fn default() -> Self {
State { State {
token,
active_tab: UiTabs::Agents, active_tab: UiTabs::Agents,
last_error: None,
agents: Default::default(),
jobs: Default::default(),
map: Default::default(),
} }
} }
}
impl State {
pub fn next_tab(&mut self) { pub fn next_tab(&mut self) {
self.active_tab = self.active_tab.next() self.active_tab = self.active_tab.next()
} }
@ -50,4 +110,129 @@ impl State {
pub fn prev_tab(&mut self) { pub fn prev_tab(&mut self) {
self.active_tab = self.active_tab.prev() 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();
}
}
}
fn crop<T: Display>(data: T, retain: usize) -> String {
data.to_string()[..retain].to_string()
} }

@ -2,11 +2,18 @@ use super::{
state::{State, UiTabs}, state::{State, UiTabs},
Frame, Frame,
}; };
use tui::style::{Color, Style}; use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Modifier, Style};
use tui::text::Spans; use tui::text::Spans;
use tui::widgets::{Block, Borders, Tabs}; use tui::widgets::{Block, Borders, List, ListItem, Tabs};
pub fn draw(f: &mut Frame, s: &mut State) { 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() let titles = UiTabs::variants()
.iter() .iter()
.cloned() .cloned()
@ -22,5 +29,15 @@ pub fn draw(f: &mut Frame, s: &mut State) {
.highlight_style(Style::default().fg(Color::Yellow)) .highlight_style(Style::default().fg(Color::Yellow))
.divider("-") .divider("-")
.select(s.active_tab.index()); .select(s.active_tab.index());
f.render_widget(tabs, f.size()) f.render_widget(tabs, chunks[0]);
let tab_data = s
.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], s.tab_list_state());
} }

Loading…
Cancel
Save