commit 6c855b24f7929ca590fa4d9d9c7cee990742dcac Author: antifallobst Date: Mon Sep 11 00:54:26 2023 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a0038a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7190971 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,461 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "ncadmin" +version = "0.1.0" +dependencies = [ + "anyhow", + "crossterm", + "tokio", + "tui", + "tui-textarea", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "syn" +version = "2.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tui" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" +dependencies = [ + "bitflags", + "cassowary", + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "tui-textarea" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437ad97a57d66f7231dab16f51ede1ff5a3aac68c83eb10fa3a178e454b63cae" +dependencies = [ + "crossterm", + "tui", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0826a03 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ncadmin" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +tokio = { version = "1.32", features = ["macros", "rt-multi-thread"] } +tui = {version = "0.19"} +tui-textarea = { version = "0.2", features = ["crossterm"] } +crossterm = { version = "0.25" } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..50409ba --- /dev/null +++ b/src/main.rs @@ -0,0 +1,15 @@ +mod ui; +mod state; + +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + + let stdout = ui::prepare()?; + + let mut login = ui::login::UI::new(stdout)?; + login.run().await?; + + Ok(()) +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..39889a7 --- /dev/null +++ b/src/state.rs @@ -0,0 +1 @@ +pub struct State {} \ No newline at end of file diff --git a/src/ui/login.rs b/src/ui/login.rs new file mode 100644 index 0000000..a10ab5a --- /dev/null +++ b/src/ui/login.rs @@ -0,0 +1,220 @@ +use std::io::Stdout; +use anyhow::Result; +use tui::{ + backend::CrosstermBackend, + widgets::{Block, Borders, Paragraph}, + style::{Color, Modifier, Style}, + text::Span, + layout::{Constraint, Direction, Layout, Alignment}, + Terminal, +}; +use tui_textarea::TextArea; +use crossterm::{ + event::{Event, KeyEvent, KeyCode, DisableMouseCapture}, + execute, + terminal::{disable_raw_mode, LeaveAlternateScreen}, +}; + +trait TextAreaExt { + fn disable(&mut self); + fn enable(&mut self); +} + +impl TextAreaExt for TextArea<'_> { + fn disable(&mut self) { + self.set_cursor_line_style(Style::default()); + self.set_cursor_style(Style::default()); + let b = self + .block() + .cloned() + .unwrap_or_else(|| Block::default().borders(Borders::ALL)); + self.set_block(b.style(Style::default().fg(Color::DarkGray))); + } + + fn enable(&mut self) { + self.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED)); + self.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); + let b = self + .block() + .cloned() + .unwrap_or_else(|| Block::default().borders(Borders::ALL)); + self.set_block(b.style(Style::default())); + } +} + +enum InputPosition { + Username, + Password, + Ok, +} + +pub struct UI<'a> { + terminal: Terminal>, + + position: InputPosition, + + username: TextArea<'a>, + password: TextArea<'a>, + password_data: TextArea<'a>, +} + +impl Drop for UI<'_> { + fn drop(&mut self) { + disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode"); + execute!( + self.terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + ).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)"); + self.terminal + .show_cursor() + .expect("While destructing UI -> Failed to re-enable cursor"); + } +} + +impl UI<'_> { + pub fn new(stdout: Stdout) -> Result { + let backend = CrosstermBackend::new(stdout); + let terminal = Terminal::new(backend)?; + + let mut username = TextArea::default(); + let mut password = TextArea::default(); + let password_data = TextArea::default(); + + username.set_block(Block::default().title("Username").borders(Borders::ALL)); + password.set_block(Block::default().title("Password").borders(Borders::ALL)); + + username.enable(); + password.disable(); + + Ok(Self{ + terminal, + position: InputPosition::Username, + username, + password, + password_data + }) + } + + pub async fn run(&mut self) -> Result { + loop { + let event = crossterm::event::read()?; + + match event { + Event::Key(KeyEvent { + code: KeyCode::Esc, .. + }) => { + return Err(anyhow::Error::msg("Canceled by user")); + } + Event::Key(KeyEvent { + code: KeyCode::Tab, .. + }) => { + self.cycle(); + } + Event::Key(KeyEvent { + code: KeyCode::Enter, + .. + }) => { + match self.position { + InputPosition::Ok => { + let username = self.username.lines()[0].clone(); + let password = self.password_data.lines()[0].clone(); + } + _ => self.cycle(), + } + } + input => match self.position { + InputPosition::Username => { + self.username.input(input.to_owned()); + } + InputPosition::Password => { + let textarea_input = tui_textarea::Input::from(input.to_owned()); + self.password_data.input(textarea_input.clone()); + match textarea_input.key { + tui_textarea::Key::Char(_) => { + self.password.input(tui_textarea::Input { + key: tui_textarea::Key::Char('*'), + ctrl: false, + alt: false, + }); + } + _ => { + self.password.input(textarea_input); + } + } + } + _ => () + } + } + + self.draw().await?; + } + } + + fn cycle(&mut self) { + self.position = match self.position { + InputPosition::Username => { + self.username.disable(); + self.password.enable(); + InputPosition::Password + } + InputPosition::Password => { + self.username.disable(); + self.password.disable(); + InputPosition::Ok + } + InputPosition::Ok => { + self.username.enable(); + self.password.disable(); + InputPosition::Username + } + }; + } + + async fn draw(&mut self) -> Result<()> { + // define a 32 * 6 chunk in the middle of the screen + let mut chunk = self.terminal.size()?; + chunk.x = (chunk.width / 2) - 24; + chunk.y = (chunk.height / 2) - 5; + chunk.height = 9; + chunk.width = 28; + + let mut inner_chunk = chunk.clone(); + inner_chunk.x += 1; + inner_chunk.y += 1; + inner_chunk.height -= 1; + inner_chunk.width -= 2; + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Length(3), // 1. Username: + Constraint::Length(3), // 2. Password: + Constraint::Length(1), // 3. OK + ] + .as_ref(), + ) + .split(inner_chunk); + + let content_ok = match self.position { + InputPosition::Ok => { + Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)) + } + _ => Span::styled("OK", Style::default().fg(Color::DarkGray)), + }; + + let block = Block::default().title("Login").borders(Borders::ALL); + + let ok = Paragraph::new(content_ok).alignment(Alignment::Center); + + self.terminal.draw(|f| { + f.render_widget(block, chunk); + f.render_widget(self.username.widget(), chunks[0]); + f.render_widget(self.password.widget(), chunks[1]); + f.render_widget(ok.clone(), chunks[2]); + })?; + + Ok(()) + } +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..f9806c5 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,16 @@ +pub mod login; + +use std::{io, io::Stdout}; +use anyhow::{Context, Result}; +use crossterm::{ + event::EnableMouseCapture, + execute, + terminal::{enable_raw_mode, EnterAlternateScreen}, +}; + +pub fn prepare() -> Result { + enable_raw_mode().context("Failed to enable raw mode")?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + Ok(stdout) +} \ No newline at end of file