From fd31f08012d3847ae9029830b940e0a18995f2de Mon Sep 17 00:00:00 2001 From: Khadim Fall Date: Wed, 18 Jan 2023 22:11:26 +0100 Subject: [PATCH] Feature/logs (#4) * add fancy checkbox * add log settinsg * switched executable icon * added logger trait and struct * :) * smome little fixes : * working on ssh and ptcec * added log help * bump version * icon --- package.json | 2 +- src-tauri/Cargo.lock | 20 +++- src-tauri/Cargo.toml | 6 +- src-tauri/public/win/icon.ico | Bin 27946 -> 9648 bytes src-tauri/src/driver.rs | 4 +- src-tauri/src/driver/connect.rs | 8 +- src-tauri/src/driver/ptcec.rs | 83 ++++++++++++++-- src-tauri/src/driver/ssh.rs | 162 ++++++++++++++++++++++++++++---- src-tauri/src/logger.rs | 43 +++++++++ src-tauri/src/main.rs | 39 +++++++- src-tauri/src/settings.rs | 42 +++++++++ src-tauri/src/win_exe.rs | 40 +++----- src-tauri/tauri.conf.json | 2 +- src/lib/CheckBox.svelte | 72 ++++++++++++++ src/lib/Settings.svelte | 138 +++++++++++++++++++++++++-- src/models/settings.ts | 4 + src/routes/+page.svelte | 2 + static/check.svg | 1 + static/question.svg | 1 + 19 files changed, 596 insertions(+), 73 deletions(-) create mode 100644 src-tauri/src/logger.rs create mode 100644 src-tauri/src/settings.rs create mode 100644 src/lib/CheckBox.svelte create mode 100644 static/check.svg create mode 100644 static/question.svg diff --git a/package.json b/package.json index 2e1db1d..c83e741 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "drawbridge", "private": true, - "version": "0.0.3", + "version": "0.0.4", "type": "module", "scripts": { "dev": "vite dev", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0ed09ae..c3a47b8 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -621,13 +621,15 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "drawbridge" -version = "0.0.3" +version = "0.0.4" dependencies = [ "async-trait", "base64 0.21.0", "directories", "futures", + "humantime", "prost", + "rand 0.8.5", "regex", "rust-embed", "serde", @@ -1253,6 +1255,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.23" @@ -2714,6 +2722,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -3212,6 +3229,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8818bb6..233b4bb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "drawbridge" -version = "0.0.3" +version = "0.0.4" description = "A Tauri App" authors = ["Khadim Fall"] license = "" @@ -19,7 +19,7 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.2", features = ["dialog-open", "dialog-save", "shell-open", "updater", "window-all"] } tonic = "0.8.3" -tokio = { version = "1.24.1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.24.1", features = ["macros", "rt-multi-thread", "process"] } prost = "0.11.5" tokio-stream = "0.1.11" futures = "0.3.25" @@ -28,6 +28,8 @@ directories = "4.0.1" base64 = "0.21.0" regex = "1.7.1" rust-embed = "6.4.2" +humantime = "2.1.0" +rand = "0.8.5" [features] # by default Tauri runs in production mode diff --git a/src-tauri/public/win/icon.ico b/src-tauri/public/win/icon.ico index e81358e602625289652635d889ec48a08b81f78c..7e01c2fcebfdb4293e64721009fa9372329acddd 100644 GIT binary patch literal 9648 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 literal 27946 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 -> Result<(), Box>; + async fn run(&self, args: Vec, log: Option) -> Result<(), Box>; } pub fn get_driver(driver_name: String) -> Option> { diff --git a/src-tauri/src/driver/connect.rs b/src-tauri/src/driver/connect.rs index 8b6d6b4..9a22eba 100644 --- a/src-tauri/src/driver/connect.rs +++ b/src-tauri/src/driver/connect.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use serde_json::Value; -use crate::storage; +use crate::{storage, logger}; use super::{Driver, ptcec, ssh}; @@ -9,7 +9,7 @@ pub struct ConnectDriver {} #[async_trait] impl Driver for ConnectDriver { - async fn run(&self, args: Vec) -> Result<(), Box> { + async fn run(&self, args: Vec, logger: Option) -> Result<(), Box> { if args.len() != 3 { println!("Invalid number of arguments. Please use: drawbridge connect "); std::process::exit(1); @@ -40,7 +40,7 @@ impl Driver for ConnectDriver { r["engine"].as_str().unwrap().to_string(), r["mode"].as_str().unwrap().to_string(), r["token"].as_str().unwrap().to_string(), - ]).await; + ], logger).await; } if driver_name.eq("ssh") { @@ -52,7 +52,7 @@ impl Driver for ConnectDriver { r["url"].as_str().unwrap().to_string(), r["runCommand"].as_str().unwrap().to_string(), r["privateKeyFile"].as_str().unwrap().to_string(), - ]).await; + ], logger).await; } println!("Driver {} not found.", driver_name); diff --git a/src-tauri/src/driver/ptcec.rs b/src-tauri/src/driver/ptcec.rs index db95801..73221a2 100644 --- a/src-tauri/src/driver/ptcec.rs +++ b/src-tauri/src/driver/ptcec.rs @@ -3,33 +3,37 @@ use tonic::transport::Channel; use tonic::{Request, metadata::AsciiMetadataValue}; use proto::pub_client::PubClient; use proto::{ StreamEngineInitRequest, StreamEngineRequest, stream_engine_request }; +use std::sync::{Arc}; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::mpsc; use tokio::io::{stdin, AsyncBufReadExt, BufReader}; use tokio_stream::StreamExt; use std::str::{self}; use tokio_stream::wrappers::ReceiverStream; +use tokio::sync::Mutex; pub mod proto { tonic::include_proto!("proto"); } +use crate::logger; + use super::Driver; pub struct PtcecDriver {} #[async_trait] impl Driver for PtcecDriver { - async fn run(&self, args: Vec) -> Result<(), Box> { + async fn run(&self, args: Vec, logger: Option) -> Result<(), Box> { if args.len() != 6 { println!("Invalid number of arguments. Please use: drawbridge ptcec "); std::process::exit(1); } - return ptcec_run(args[2].clone(), args[3].clone(), args[4].clone(), args[5].clone(), gen_session_id()).await; + return ptcec_run(args[2].clone(), args[3].clone(), args[4].clone(), args[5].clone(), gen_session_id(), logger).await; } } -async fn ptcec_run(url: String, engine: String, mode: String, token: String, session_id: String) -> Result<(), Box> { +async fn ptcec_run(url: String, engine: String, mode: String, token: String, session_id: String, logger: Option) -> Result<(), Box> { let channel = Channel::from_shared(url).unwrap().connect().await?; let mut auth_header = "Basic ".to_owned(); auth_header.push_str(&token); @@ -44,7 +48,17 @@ async fn ptcec_run(url: String, engine: String, mode: String, token: String, ses let (tx, rx) = mpsc::channel(80); + // Logger + let logger_o_ptr = Arc::new(Mutex::new(logger)); + let logger_ptr_a = Arc::clone(&logger_o_ptr); + // Initialize + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_info(&format!("Initialize PTCEC with Engine={} Mode={} SessionId={}", &engine, &mode, &session_id).to_string()).is_err() {/* ignored */} + } + drop(guard); + } let init_req = StreamEngineRequest { data: Some(stream_engine_request::Data::Init(StreamEngineInitRequest{ engine: engine, @@ -54,15 +68,49 @@ async fn ptcec_run(url: String, engine: String, mode: String, token: String, ses }; tx.send(init_req).await.unwrap(); + + let logger_ptr_b = Arc::clone(&logger_o_ptr); tokio::spawn(async move { let stdin = stdin(); let mut reader = BufReader::new(stdin); let mut line = String::new(); loop { - reader.read_line(&mut line).await.unwrap(); - tx.send(StreamEngineRequest { + match reader.read_line(&mut line).await { + Err(e) => { + let mut guard = logger_ptr_b.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to read stdin: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + break; + }, + _ => (), + } + + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + let mut logline = line.clone(); + logline.pop(); + if guard.as_mut().unwrap().debug_outgoing(&logline).is_err() {/* ignored */} + } + drop(guard); + } + + match tx.send(StreamEngineRequest { data: Some(stream_engine_request::Data::Stdin(line.as_bytes().to_vec())), - }).await.unwrap(); + }).await { + Err(e) => { + let mut guard = logger_ptr_b.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to send to remote: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + break; + }, + _ => (), + } line.clear(); } }); @@ -73,12 +121,27 @@ async fn ptcec_run(url: String, engine: String, mode: String, token: String, ses .await .unwrap() .into_inner(); - - + + let logger_ptr_c = Arc::clone(&logger_o_ptr); while let Some(item) = stream.next().await { match str::from_utf8(item.unwrap().stdout.as_ref()) { - Ok(v) => println!("{}", v), - Err(_) => {/* ignored */}, + Ok(v) => { + let mut guard = logger_ptr_c.lock().await; + if guard.is_some() { + if guard.as_mut().unwrap().debug_incomming(&v.clone()).is_err() {/* ignored */} + } + drop(guard); + println!("{}", v) + }, + Err(e) => { + let mut guard = logger_ptr_c.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to parse stdout: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + break; + }, }; } diff --git a/src-tauri/src/driver/ssh.rs b/src-tauri/src/driver/ssh.rs index b7d49d8..c4ca1af 100644 --- a/src-tauri/src/driver/ssh.rs +++ b/src-tauri/src/driver/ssh.rs @@ -1,15 +1,18 @@ -use std::process::{Stdio, Command}; - +use std::{process::{Stdio}, sync::Arc}; +use tokio::{process::Command, io::{BufReader, AsyncBufReadExt, BufWriter, self, AsyncWriteExt}}; +use tokio::sync::Mutex; use async_trait::async_trait; +use crate::logger; + use super::Driver; pub struct SshDriver {} #[async_trait] impl Driver for SshDriver { - async fn run(&self, args: Vec) -> Result<(), Box> { + async fn run(&self, args: Vec, logger: Option) -> Result<(), Box> { if args.len() != 4 && args.len() != 5 { println!("Invalid number of arguments. Please use: drawbridge ssh [private-key-file]"); std::process::exit(1); @@ -20,7 +23,7 @@ impl Driver for SshDriver { private_key_file = args[4].clone(); } - match start_ssh_driver(args[2].clone(), args[3].clone(), private_key_file).await { + match start_ssh_driver(args[2].clone(), args[3].clone(), private_key_file, logger).await { Err(e) => { println!("Something failed: {}", e); std::process::exit(1); @@ -30,27 +33,154 @@ impl Driver for SshDriver { } } -// Starts the ssh bridge driver -async fn start_ssh_driver(host: String, run_command: String, private_key_file: String) -> Result<(), Box> { - let mut args = Vec::new(); +async fn start_ssh_driver(host: String, run_command: String, private_key_file: String, logger: Option) -> Result<(), Box> { + // Logger + let logger_o_ptr = Arc::new(Mutex::new(logger)); + let logger_ptr_a = Arc::clone(&logger_o_ptr); + let logger_ptr_b = Arc::clone(&logger_o_ptr); + // Args + let mut args = Vec::new(); let priv_key_arg = "-i".to_owned(); - if !private_key_file.eq("") { args.push(&priv_key_arg); args.push(&private_key_file); } - args.push(&host); args.push(&run_command); - let mut child = Command::new("ssh") + // Start Process + let mut guard = logger_ptr_b.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_info(&format!("Initialize SSH with Command=ssh Args={:?}", &args).to_string()).is_err() {/* ignored */} + } + drop(guard); + } + let mut child = match Command::new("ssh") .args(&args) - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .spawn()?; + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .kill_on_drop(true) + .spawn() + { + Ok(c) => c, + Err(e) => return Err(format!("Unable to start process `ssh`. {}", e).into()), + }; - child.wait()?; + let stdout = child.stdout.take().expect("child did not have a handle to stdout"); + let stderr = child.stderr.take().expect("child did not have a handle to stderr"); + let stdin = child.stdin.take().expect("child did not have a handle to stdin"); - return Ok(()); -} + let mut stdout_reader = BufReader::new(stdout).lines(); + let mut stderr_reader = BufReader::new(stderr).lines(); + let mut stdin_writer = BufWriter::new(stdin); + let mut stdin_reader = BufReader::new(io::stdin()).lines(); + + loop { + tokio::select! { + result = stdin_reader.next_line() => { + match result { + Ok(Some(mut line)) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_outgoing(&line.clone()).is_err() {/* ignored */} + } + drop(guard); + }; + line.push_str("\n"); + + match stdin_writer.write_all(line.as_bytes()).await { + Ok(_) => {}, + Err(e) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to write to remote stdin: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + }, + }; + + match stdin_writer.flush().await { + Ok(_) => {}, + Err(e) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to flush to remote stdin: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + }, + }; + }, + Err(e) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to read from local stdin: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + break; + }, + _ => (), + } + } + result = stdout_reader.next_line() => { + match result { + Ok(Some(line)) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_incomming(&line.clone()).is_err() {/* ignored */} + } + drop(guard); + } + println!("{}", line); + }, + Err(e) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to read from remote stdout: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + break; + }, + _ => (), + } + } + result = stderr_reader.next_line() => { + match result { + Ok(Some(line)) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_incomming(&line.clone()).is_err() {/* ignored */} + } + drop(guard); + } + println!("{}", line); + }, + Err(e) => { + let mut guard = logger_ptr_a.lock().await; { + if guard.is_some() { + if guard.as_mut().unwrap().debug_error(&format!("Failed to read from remote stderr: {}", e)).is_err() {/* ignored */} + } + drop(guard); + }; + break; + }, + _ => (), + } + } + result = child.wait() => { + match result { + Ok(exit_code) => println!("Child process exited with {}", exit_code), + _ => (), + } + break // child process exited + } + }; + }; + + return Ok(()) +} \ No newline at end of file diff --git a/src-tauri/src/logger.rs b/src-tauri/src/logger.rs new file mode 100644 index 0000000..ad1d051 --- /dev/null +++ b/src-tauri/src/logger.rs @@ -0,0 +1,43 @@ +use std::{time::SystemTime, path::PathBuf}; + +pub struct Logger { + file: std::fs::File, +} + +impl Logger { + + pub fn new(log_file: PathBuf) -> Result> { + let file = std::fs::File::create(&log_file)?; + Ok(Logger { + file, + }) + } + + pub fn debug_outgoing(&mut self, message: &str) -> Result<(), Box> { + self.write(&format!("|>| {}", message)) + } + + pub fn debug_incomming(&mut self, message: &str) -> Result<(), Box> { + self.write(&format!("|<| {}", message)) + } + + pub fn debug_info(&mut self, message: &str) -> Result<(), Box> { + self.write(&format!("|i| {}", message)) + } + + pub fn debug_error(&mut self, message: &str) -> Result<(), Box> { + self.write(&format!("|!| {}", message)) + } + + fn write(&mut self, line: &str) -> Result<(), Box> { + use std::io::Write; + + let sys_time = SystemTime::now(); + let full_line = format!("[{:?}] {}\n", humantime::format_rfc3339(sys_time).to_string(), line); + self.file.write(full_line.as_bytes())?; + self.file.flush()?; + return Ok(()); + } + +} + diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e39a56a..cc2195a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -11,7 +11,9 @@ mod win_exe; mod driver; mod storage; -use std::env; +mod settings; +mod logger; +use std::{env, time::SystemTime}; #[tauri::command] fn load_data(key: String) -> String { @@ -106,13 +108,46 @@ fn start_tauri() { .expect("error while running tauri application"); } +fn get_logger() -> Option { + use std::path::Path; + use rand::distributions::{Alphanumeric, DistString}; + + let logs_enabled = settings::get_setting_bool("enableLogs".to_string()); + if logs_enabled.is_none() || logs_enabled == Some(false) { + return None; + } + + match settings::get_setting_str("logFile".to_string()) { + Some(mut path) => { + if path.eq("") { + return None; + } + + let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + path = path.replace("{rnd}", &Alphanumeric.sample_string(&mut rand::thread_rng(), 6)); + path = path.replace("{ts}", &format!("{}", ts.as_nanos())); + + let path = Path::new(&path); + match logger::Logger::new(path.to_path_buf()) { + Ok(logger) => Some(logger), + Err(_) => None + } + }, + None => { + return None; + } + } +} + // Starts the bridge cli #[tokio::main] async fn start_bridge(args: Vec) -> Result<(), Box> { let driver = driver::get_driver(args[1].clone()); + let logger: Option = get_logger(); if driver.is_some() { - return driver.unwrap().run(args).await; + // Todo: add logger initialization + return driver.unwrap().run(args, logger).await; } println!("Invalid driver name. Please use ptcec or ssh."); diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs new file mode 100644 index 0000000..c53ce0f --- /dev/null +++ b/src-tauri/src/settings.rs @@ -0,0 +1,42 @@ +use serde_json::Value; +use crate::storage; + +pub fn get_settings() -> Option { + let data_res = storage::load("settings".to_string()); + match data_res { + Ok(data) => { + let settings: Value = serde_json::from_str(&data).unwrap(); + return Some(settings); + }, + + Err(_) => { + return None; + } + } +} + +pub fn get_setting_str(key: String) -> Option { + match get_settings() { + Some(settings) => { + let val = settings[key].as_str(); + return Some(val.unwrap().to_string()); + }, + + None => { + return None; + } + } +} + +pub fn get_setting_bool(key: String) -> Option { + match get_settings() { + Some(settings) => { + let val = settings[key].as_bool(); + return Some(val.unwrap()); + }, + + None => { + return None; + } + } +} \ No newline at end of file diff --git a/src-tauri/src/win_exe.rs b/src-tauri/src/win_exe.rs index b9e6471..b4245d5 100644 --- a/src-tauri/src/win_exe.rs +++ b/src-tauri/src/win_exe.rs @@ -2,46 +2,32 @@ use std::io::Write; use std::path::Path; use std::process::Command; use regex::Regex; -use serde_json::Value; use std::fs; use std::fs::metadata; -use crate::storage; +use crate::settings; mod asset; static NET_FRAMEWORK_PATH: &str = "C:\\Windows\\Microsoft.NET\\Framework"; fn get_cs_compiler_path() -> Result> { - let data_res = storage::load("settings".to_string()); - match data_res { - Ok(data) => { - let settings: Value = serde_json::from_str(&data).unwrap(); - - let custom_cs_compiler_path = settings["csCompilerPath"].as_str(); - match custom_cs_compiler_path { - Some(path) => { - if path.eq("") { - return try_find_cs_compiler(); - } - - let path = Path::new(path); - if path.exists() { - return Ok(path.to_path_buf()); - } - println!("{}", path.to_string_lossy()); - return Err("Custom c# compiler does not exists.".into()); - }, - None => { - return try_find_cs_compiler(); - } + match settings::get_setting_str("csCompilerPath".to_string()) { + Some(path) => { + if path.eq("") { + return try_find_cs_compiler(); } - }, - Err(_) => { + let path = Path::new(&path); + if path.exists() { + return Ok(path.to_path_buf()); + } + println!("{}", path.to_string_lossy()); + return Err("Custom c# compiler does not exists.".into()); + }, + None => { return try_find_cs_compiler(); } } - } fn try_find_cs_compiler() -> Result> { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index dfb8360..23b54ed 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "drawbridge", - "version": "0.0.3" + "version": "0.0.4" }, "tauri": { "allowlist": { diff --git a/src/lib/CheckBox.svelte b/src/lib/CheckBox.svelte new file mode 100644 index 0000000..af74d57 --- /dev/null +++ b/src/lib/CheckBox.svelte @@ -0,0 +1,72 @@ + + + + + \ No newline at end of file diff --git a/src/lib/Settings.svelte b/src/lib/Settings.svelte index b37375b..edd4a37 100644 --- a/src/lib/Settings.svelte +++ b/src/lib/Settings.svelte @@ -1,12 +1,13 @@