pull/1/head
plazmoid 3 years ago
parent bda30e2a72
commit 5df2b09ceb
  1. 7
      bin/u_panel/Cargo.toml
  2. 10
      bin/u_panel/src/argparse.rs
  3. 5
      bin/u_panel/src/main.rs
  4. 138
      bin/u_panel/src/tui/mod.rs
  5. 31
      bin/u_panel/src/tui/retval.rs
  6. 33
      bin/u_panel/src/tui/utils.rs
  7. 15
      bin/u_panel/src/tui/windows/confirm.rs
  8. 14
      bin/u_panel/src/tui/windows/main_wnd.rs
  9. 67
      bin/u_panel/src/tui/windows/mod.rs
  10. 8
      scripts/build_musl_libs.sh

@ -14,7 +14,7 @@ env_logger = "0.7.1"
uuid = "0.6.5"
serde_json = "1.0.4"
serde = { version = "1.0.114", features = ["derive"] }
tokio = "1.11.0"
tokio = { version = "1.11.0", features = ["rt", "rt-multi-thread"] }
# be = { version = "*", path = "./be" }
u_lib = { version = "*", path = "../../lib/u_lib" }
tui = { version = "0.16", default-features = false, features = ['crossterm'] }
@ -24,3 +24,8 @@ strum = { version = "0.22.0", features = ["derive"] }
async-trait = "0.1.51"
once_cell = "1.8.0"
crossbeam = "0.8.1"
async-channel = "1.6.1"
tracing = "0.1.29"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"]}
signal-hook = "0.3.12"
tracing-appender = "0.2.0"

@ -17,7 +17,13 @@ enum Cmd {
Agents(LD),
Jobs(JobALD),
Map(JobMapALD),
TUI,
TUI(TUIArgs),
}
#[derive(StructOpt, Debug)]
pub struct TUIArgs {
#[structopt(long)]
pub nogui: bool,
}
#[derive(StructOpt, Debug)]
@ -126,7 +132,7 @@ pub async fn process_cmd(args: Args) -> UResult<()> {
JobMapALD::List { uid } => printer.print(CLIENT.get_agent_jobs(uid).await),
JobMapALD::Delete { uid } => printer.print(CLIENT.del(Some(uid)).await),
},
Cmd::TUI => crate::tui::init_tui()
Cmd::TUI(args) => crate::tui::init_tui(&args)
.await
.map_err(|e| UError::TUIError(e.to_string()))?,
}

@ -4,6 +4,9 @@ mod tui;
#[macro_use]
extern crate async_trait;
#[macro_use]
extern crate tracing;
use argparse::{process_cmd, Args};
use once_cell::sync::Lazy;
use std::env;
@ -17,7 +20,7 @@ pub static CLIENT: Lazy<ClientHandler> = Lazy::new(|| {
ClientHandler::new(None).password(token.clone())
});
#[tokio::main]
#[tokio::main(flavor = "multi_thread")]
async fn main() {
init_env();
let args: Args = Args::from_args();

@ -3,6 +3,7 @@ mod retval;
mod utils;
mod windows;
use crate::argparse::TUIArgs;
use anyhow::Result as AResult;
use backtrace::Backtrace;
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode};
@ -12,15 +13,17 @@ use crossterm::terminal::{
};
use once_cell::sync::Lazy;
use retval::{RetVal, ReturnValue};
use std::panic::set_hook;
use std::process::exit;
use std::{
env,
io::{stdout, Stdout},
panic::set_hook,
process::exit,
sync::atomic::{AtomicBool, Ordering},
thread,
time::Duration,
};
use tui::{backend::CrosstermBackend, Terminal};
use utils::Channel;
use utils::{AsyncChannel, Channel};
use windows::{MainWnd, SharedWnd, WindowsHandler, WndId};
pub type Backend = CrosstermBackend<Stdout>;
@ -30,9 +33,12 @@ const EVENT_GEN_PERIOD: Duration = Duration::from_millis(120);
static GENERAL_EVENT_CHANNEL: Lazy<Channel<GEvent>> = Lazy::new(|| Channel::new());
static ACTIVE_LOOP: AtomicBool = AtomicBool::new(true);
enum GEvent {
CreateWnd(SharedWnd),
CloseWnd { wid: WndId, force: bool },
Exit,
Key(KeyCode),
Tick,
}
@ -42,28 +48,31 @@ fn get_terminal() -> AResult<Terminal<Backend>> {
Ok(Terminal::new(backend)?)
}
pub async fn init_tui() -> AResult<()> {
//TODO: fix this
set_hook(Box::new(|p| {
teardown().unwrap();
get_terminal().unwrap().show_cursor().unwrap();
eprintln!("{}\n{:?}", p, Backtrace::new());
exit(254);
}));
if let Err(e) = init().await {
teardown()?;
return Err(e);
}
Ok(())
}
pub async fn init_tui(args: &TUIArgs) -> AResult<()> {
init_logger();
info!("Initializing u_panel");
async fn init() -> AResult<()> {
enable_raw_mode()?;
execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?;
let gui = !args.nogui;
init_signal_handlers(gui);
init_panic_handler(gui);
term_setup(gui)?;
info!("Starting loop");
let result = init_loop(args).await;
info!("Exiting");
term_teardown(gui)?;
result
}
async fn init_loop(args: &TUIArgs) -> AResult<()> {
let gui = !args.nogui;
if gui {
WindowsHandler::lock().await.clear()?;
}
WindowsHandler::lock().await.push_new::<MainWnd>().await;
thread::spawn(move || loop {
thread::spawn(move || {
while is_running() {
if event::poll(EVENT_GEN_PERIOD).unwrap() {
match event::read().unwrap() {
Event::Key(key) => GENERAL_EVENT_CHANNEL.send(GEvent::Key(key.code)),
@ -72,33 +81,106 @@ async fn init() -> AResult<()> {
} else {
GENERAL_EVENT_CHANNEL.send(GEvent::Tick)
}
}
});
WindowsHandler::lock().await.clear()?;
loop {
while is_running() {
match GENERAL_EVENT_CHANNEL.recv() {
GEvent::Tick => {
let mut wh = WindowsHandler::lock().await;
wh.update().await?;
if gui {
wh.draw().await?;
}
}
GEvent::CloseWnd { wid, force } => {
let mut wh = WindowsHandler::lock().await;
wh.close(wid, force).await;
WindowsHandler::lock().await.close(wid, force).await;
}
GEvent::Key(key) => {
WindowsHandler::lock().await.send_handle_kbd(key);
info!(?key, "pressed");
if let KeyCode::Char('q') = key {
break_global();
} else {
WindowsHandler::lock().await.send_handle_kbd(key).await;
}
}
GEvent::CreateWnd(wnd) => {
WindowsHandler::lock().await.push_dyn(wnd).await;
}
GEvent::Exit => {
break_global();
}
}
}
Ok(())
}
#[inline]
fn is_running() -> bool {
ACTIVE_LOOP.load(Ordering::Relaxed)
}
fn break_global() {
ACTIVE_LOOP.store(false, Ordering::Relaxed);
}
fn teardown() -> AResult<()> {
fn init_signal_handlers(gui: bool) {
use signal_hook::{
consts::{SIGINT, SIGTERM},
iterator::Signals,
};
thread::spawn(move || {
let mut signals = Signals::new(&[SIGINT, SIGTERM]).unwrap();
for sig in signals.forever() {
match sig {
SIGINT => break_global(),
SIGTERM => {
break_global();
term_teardown(gui).ok();
exit(3);
}
_ => (),
}
}
});
}
fn init_logger() {
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::EnvFilter;
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "info")
}
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(|| RollingFileAppender::new(Rotation::NEVER, ".", "u_panel.log"))
.init();
}
fn init_panic_handler(gui: bool) {
set_hook(Box::new(move |panic_info| {
term_teardown(gui).ok();
eprintln!("{}\n{:?}", panic_info, Backtrace::new());
exit(254);
}));
}
fn term_setup(gui: bool) -> AResult<()> {
enable_raw_mode()?;
if gui {
execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?;
}
Ok(())
}
fn term_teardown(gui: bool) -> AResult<()> {
disable_raw_mode()?;
if gui {
get_terminal()?.show_cursor()?;
execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
}
Ok(())
}

@ -1,42 +1,25 @@
use crate::tui::Channel;
use once_cell::sync::Lazy;
use std::any::{type_name, Any};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::sync::Mutex;
type RV = Box<dyn Any + Send>;
static LAST_RETVAL: Lazy<Mutex<Option<RV>>> = Lazy::new(|| Mutex::new(None));
static RETVAL: Lazy<Channel<RV>> = Lazy::new(|| Channel::new());
pub struct ReturnValue;
impl ReturnValue {
pub async fn set(t: RV) {
*LAST_RETVAL.lock().await = Some(t);
pub fn set(t: RV) {
RETVAL.send(t);
}
pub async fn get<T: 'static>() -> Box<T> {
ReturnValue
.await
pub fn get<T: 'static>() -> Box<T> {
RETVAL
.recv()
.downcast::<T>()
.expect(&format!("wrong type {}", type_name::<T>()))
}
}
impl Future for ReturnValue {
type Output = Box<dyn Any>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Poll::Ready(mut rv) = unsafe { Pin::new_unchecked(&mut LAST_RETVAL.lock()) }.poll(cx)
{
if let Some(rv) = rv.take() {
return Poll::Ready(rv);
}
}
Poll::Pending
}
}
pub trait RetVal {
fn retval(&self) -> RV;
}

@ -1,3 +1,6 @@
use async_channel::{
unbounded as unbounded_async, Receiver as AsyncReceiver, Sender as AsyncSender,
};
use crossbeam::channel::{unbounded, Receiver, Sender};
pub struct Channel<T> {
@ -29,3 +32,33 @@ impl<T> Default for Channel<T> {
Channel::new()
}
}
pub struct AsyncChannel<T> {
pub tx: AsyncSender<T>,
pub rx: AsyncReceiver<T>,
}
impl<T> AsyncChannel<T> {
pub fn new() -> Self {
let (tx, rx) = unbounded_async::<T>();
Self { tx, rx }
}
pub async fn send(&self, msg: T) {
self.tx.send(msg).await.unwrap()
}
pub async fn recv(&self) -> T {
self.rx.recv().await.unwrap()
}
pub fn is_empty(&self) -> bool {
self.rx.is_empty()
}
}
impl<T> Default for AsyncChannel<T> {
fn default() -> Self {
AsyncChannel::new()
}
}

@ -1,7 +1,10 @@
use super::{Window, WndId};
use crate::tui::{Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL};
use crate::tui::{
windows::WndEvent, AsyncChannel, Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL,
};
use anyhow::Result as AResult;
use crossterm::event::KeyCode;
use once_cell::sync::OnceCell;
use std::any::Any;
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::style::{Modifier, Style};
@ -79,7 +82,6 @@ impl Window for ConfirmWnd {
fn draw(&mut self, f: &mut Frame) {
let size = f.size();
let rect = centered_rect(60, 40, size);
f.render_widget(Clear, rect);
let chunks = Layout::default()
.direction(Direction::Vertical)
@ -98,12 +100,17 @@ impl Window for ConfirmWnd {
List::new(options).highlight_style(Style::default().add_modifier(Modifier::BOLD));
f.render_stateful_widget(list, chunks[1], &mut self.state);
}
fn get_chan(&self) -> &'static AsyncChannel<WndEvent> {
static EV_CHAN: OnceCell<AsyncChannel<WndEvent>> = OnceCell::new();
EV_CHAN.get_or_init(|| AsyncChannel::new())
}
}
pub async fn confirm_wnd(msg: impl Into<String>) -> bool {
pub async fn confirm_wnd(msg: impl Into<String>) -> String {
let wnd = ConfirmWnd::new_yn(msg.into(), None);
GENERAL_EVENT_CHANNEL.send(GEvent::CreateWnd(wnd.into_shared()));
*ReturnValue::get().await
*ReturnValue::get()
}
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {

@ -1,7 +1,12 @@
use super::{confirm_wnd, Window};
use crate::tui::{impls::CRUD, windows::WndId, Frame, RetVal};
use crate::tui::{
impls::CRUD,
windows::{WndEvent, WndId},
AsyncChannel, Frame, RetVal,
};
use anyhow::Result as AResult;
use crossterm::event::KeyCode;
use once_cell::sync::OnceCell;
use std::any::Any;
use std::{fmt::Display, str::FromStr};
use strum::VariantNames;
@ -256,7 +261,7 @@ impl Window for MainWnd {
KeyCode::Up => self.on_up(),
KeyCode::Down => self.on_down(),
KeyCode::Delete => {
if confirm_wnd("Delete?").await {
if &confirm_wnd("Delete?").await == "Yes" {
self.delete().await;
self.update_tab();
}
@ -310,6 +315,11 @@ impl Window for MainWnd {
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
f.render_stateful_widget(list, chunks[1], self.tab_list_state());
}
fn get_chan(&self) -> &'static AsyncChannel<WndEvent> {
static EV_CHAN: OnceCell<AsyncChannel<WndEvent>> = OnceCell::new();
EV_CHAN.get_or_init(|| AsyncChannel::new())
}
}
impl RetVal for MainWnd {

@ -1,18 +1,17 @@
mod confirm;
mod main_wnd;
use async_channel::Sender;
pub use confirm::{confirm_wnd, ConfirmWnd};
use crossbeam::channel::Sender;
pub use main_wnd::MainWnd;
use crate::tui::{
get_terminal, impls::Id, teardown, utils::Channel, Backend, Frame, GEvent, RetVal, ReturnValue,
get_terminal, impls::Id, AsyncChannel, Backend, Frame, GEvent, RetVal, ReturnValue,
GENERAL_EVENT_CHANNEL,
};
use anyhow::Result as AResult;
use crossterm::event::KeyCode;
use once_cell::sync::{Lazy, OnceCell};
use std::collections::BTreeMap;
use std::process::exit;
use std::sync::Arc;
use std::sync::{Mutex as StdMutex, MutexGuard as StdMutexGuard};
use tokio::sync::{Mutex, MutexGuard};
@ -24,11 +23,10 @@ static WINDOWS: Lazy<Arc<Mutex<WindowsHandler>>> =
static LAST_WND_ID: OnceCell<StdMutex<WndId>> = OnceCell::new();
static WND_EVENT_CHANNEL: Lazy<Channel<WndEvent>> = Lazy::new(|| Channel::new());
pub type SharedWnd = Arc<Mutex<dyn Window>>;
enum WndEvent {
#[derive(Clone, Debug)]
pub enum WndEvent {
Key(KeyCode),
}
@ -36,7 +34,7 @@ enum WndEvent {
pub struct WndId(u64);
impl WndId {
pub fn last_wid() -> StdMutexGuard<'static, WndId> {
fn last_wid() -> StdMutexGuard<'static, WndId> {
LAST_WND_ID
.get_or_init(|| StdMutex::new(WndId(0)))
.lock()
@ -54,25 +52,25 @@ impl Default for WndId {
#[async_trait]
pub trait Window: Id<WndId> + RetVal + Send {
async fn check_wnd_events(&mut self) {
if !WND_EVENT_CHANNEL.is_empty() {
match WND_EVENT_CHANNEL.recv() {
async fn handle_event(&mut self, ev: WndEvent) {
match ev {
WndEvent::Key(k) => {
self.handle_kbd(k).await.unwrap();
}
}
}
}
async fn close(&mut self, force: bool) {
let rv = self.retval();
ReturnValue::set(rv).await;
ReturnValue::set(rv);
GENERAL_EVENT_CHANNEL.send(GEvent::CloseWnd {
wid: self.id(),
force,
});
}
fn get_chan(&self) -> &'static AsyncChannel<WndEvent>;
fn into_shared(self) -> SharedWnd
where
Self: Sized + 'static,
@ -90,30 +88,35 @@ pub trait Window: Id<WndId> + RetVal + Send {
}
pub struct WndLoop {
upd_tx: Sender<()>,
upd_tx: Sender<WndEvent>,
updater: JoinHandle<()>,
window: SharedWnd,
}
impl WndLoop {
pub fn with_wnd(window: SharedWnd) -> Self {
let Channel { tx, rx } = Channel::new();
pub async fn with_wnd(window: SharedWnd) -> Self {
let wnd = window.clone();
let AsyncChannel { tx, rx } = wnd.lock().await.get_chan();
let wnd_loop = async move {
loop {
rx.recv().unwrap();
wnd.lock().await.check_wnd_events().await;
match rx.recv().await {
Ok(ev) => wnd.lock().await.handle_event(ev).await,
Err(_) => break,
}
}
};
WndLoop {
upd_tx: tx,
upd_tx: tx.clone(),
window,
updater: task::spawn(wnd_loop),
}
}
pub fn send_update(&self) {
self.upd_tx.send(()).unwrap();
pub async fn send(&self, ev: WndEvent) {
let wnd_id = self.window.lock().await.id();
let event = ev.clone();
debug!(?event, ?wnd_id, "sending");
self.upd_tx.send(ev).await.expect("big zhopa");
}
}
@ -151,7 +154,8 @@ impl WindowsHandler {
pub async fn push_dyn(&mut self, window: SharedWnd) -> SharedWnd {
let wid = window.lock().await.id();
self.queue.insert(wid, WndLoop::with_wnd(window.clone()));
self.queue
.insert(wid, WndLoop::with_wnd(window.clone()).await);
window
}
@ -160,19 +164,12 @@ impl WindowsHandler {
Ok(())
}
pub fn show_cursor(&mut self) -> AResult<()> {
self.term.show_cursor()?;
Ok(())
}
pub async fn draw(&mut self) -> AResult<()> {
let wids_to_redraw = if self.redraw_all {
self.redraw_all = false;
let mut wids = self.queue.keys().cloned().collect::<Vec<WndId>>();
wids.sort();
wids
self.queue.keys().collect::<Vec<&WndId>>()
} else {
vec![*WndId::last_wid()]
vec![self.queue.keys().last().unwrap()]
};
for wid in wids_to_redraw {
let mut wnd_locked = match self.queue.get(&wid) {
@ -202,19 +199,17 @@ impl WindowsHandler {
}
if self.queue.is_empty() {
self.show_cursor().unwrap();
teardown().unwrap();
exit(0);
GENERAL_EVENT_CHANNEL.send(GEvent::Exit);
}
}
pub fn send_handle_kbd(&self, k: KeyCode) {
WND_EVENT_CHANNEL.send(WndEvent::Key(k));
pub async fn send_handle_kbd(&self, k: KeyCode) {
let current_wnd = self.get_last_wnd();
current_wnd.send(WndEvent::Key(k)).await;
}
pub async fn update(&mut self) -> AResult<()> {
let current_wnd = self.get_last_wnd();
current_wnd.send_update();
current_wnd.window.lock().await.handle_update().await?;
Ok(())
}

@ -5,11 +5,13 @@ ARGS=$@
STATIC_LIBS=./static
DOCKER_EXCHG=/musl-share
IMAGE=unki/musllibs
mkdir -p $STATIC_LIBS
cd $ROOTDIR/images && docker build -t $IMAGE . -f musl-libs.Dockerfile
docker run \
if [[ ! -d ./static ]]; then
mkdir $STATIC_LIBS
cd $ROOTDIR/images && docker build -t $IMAGE . -f musl-libs.Dockerfile
docker run \
-v $ROOTDIR/$STATIC_LIBS:$DOCKER_EXCHG \
-w /volume \
-it \
$IMAGE \
bash -c "[[ \$(ls -A $DOCKER_EXCHG) ]] || cp /musl/. $DOCKER_EXCHG -r"
fi

Loading…
Cancel
Save