From 3fa43d276d2282105c67f1f123e2e5c49a7cb657 Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Fri, 18 Jul 2025 18:06:26 +0300 Subject: [PATCH] Added web ui --- Cargo.lock | 65 ++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/web.rs | 90 ++++++++++++++++++------------------------------------ 3 files changed, 95 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b78d63..0356ab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1064,7 +1064,7 @@ dependencies = [ [[package]] name = "khm" -version = "0.4.2" +version = "0.5.0" dependencies = [ "actix-web", "base64 0.21.7", @@ -1075,6 +1075,7 @@ dependencies = [ "log", "regex", "reqwest", + "rust-embed", "serde", "serde_json", "tokio", @@ -1575,6 +1576,40 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-embed" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1649,6 +1684,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.23" @@ -2134,6 +2178,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2258,6 +2312,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 8264ea9..370c0d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ clap = { version = "4", features = ["derive"] } chrono = "0.4.38" reqwest = { version = "0.12", features = ["json"] } hostname = "0.3" +rust-embed = "8.0" diff --git a/src/web.rs b/src/web.rs index bf1784f..956eeaf 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,11 +1,23 @@ use actix_web::{web, HttpResponse, Result}; use log::info; +use rust_embed::RustEmbed; +use serde::{Deserialize, Serialize}; use serde_json::json; use std::collections::HashSet; use tokio_postgres::Client; use crate::server::{get_keys_from_db, Flows}; +#[derive(RustEmbed)] +#[folder = "static/"] +struct StaticAssets; + +#[derive(Deserialize)] +struct DeleteKeyPath { + flow_id: String, + server: String, +} + // API endpoint to get list of available flows pub async fn get_flows_api(allowed_flows: web::Data>) -> Result { info!("API request for available flows"); @@ -15,13 +27,11 @@ pub async fn get_flows_api(allowed_flows: web::Data>) -> Result, - flow_id: web::Path, - server: web::Path, + path: web::Path<(String, String)>, db_client: web::Data>, allowed_flows: web::Data>, ) -> Result { - let flow_id_str = flow_id.into_inner(); - let server_name = server.into_inner(); + let (flow_id_str, server_name) = path.into_inner(); info!("API request to delete key for server '{}' in flow '{}'", server_name, flow_id_str); @@ -130,36 +140,16 @@ async fn delete_key_from_db( Ok(std::cmp::max(flow_delete_count, total_deleted)) } -// Serve static files +// Serve static files from embedded assets pub async fn serve_static_file(path: web::Path) -> Result { let file_path = path.into_inner(); - // Get the path to the static directory relative to the executable - let exe_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from(".")); - let exe_dir = exe_path.parent().unwrap_or_else(|| std::path::Path::new(".")); - let static_dir = exe_dir.join("static"); - - // Fallback to current directory if static dir doesn't exist next to executable - let static_dir = if static_dir.exists() { - static_dir - } else { - std::path::PathBuf::from("static") - }; - - let full_path = static_dir.join(&file_path); - - // Security check - ensure the path is within the static directory - if !full_path.starts_with(&static_dir) { - return Ok(HttpResponse::Forbidden().body("Access denied")); - } - - if !full_path.exists() { - return Ok(HttpResponse::NotFound().body(format!("File not found: {}", full_path.display()))); - } - - match tokio::fs::read(&full_path).await { - Ok(contents) => { - let content_type = match full_path.extension().and_then(|s| s.to_str()) { + match StaticAssets::get(&file_path) { + Some(content) => { + let content_type = match std::path::Path::new(&file_path) + .extension() + .and_then(|s| s.to_str()) + { Some("html") => "text/html; charset=utf-8", Some("css") => "text/css; charset=utf-8", Some("js") => "application/javascript; charset=utf-8", @@ -171,44 +161,24 @@ pub async fn serve_static_file(path: web::Path) -> Result Ok(HttpResponse::Ok() .content_type(content_type) - .body(contents)) + .body(content.data.as_ref().to_vec())) } - Err(e) => { - info!("Failed to read file {}: {}", full_path.display(), e); - Ok(HttpResponse::InternalServerError().body("Failed to read file")) + None => { + Ok(HttpResponse::NotFound().body(format!("File not found: {}", file_path))) } } } -// Serve the main web interface +// Serve the main web interface from embedded assets pub async fn serve_web_interface() -> Result { - // Get the path to the static directory relative to the executable - let exe_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from(".")); - let exe_dir = exe_path.parent().unwrap_or_else(|| std::path::Path::new(".")); - let static_dir = exe_dir.join("static"); - - // Fallback to current directory if static dir doesn't exist next to executable - let static_dir = if static_dir.exists() { - static_dir - } else { - std::path::PathBuf::from("static") - }; - - let index_path = static_dir.join("index.html"); - - if !index_path.exists() { - return Ok(HttpResponse::NotFound().body(format!("Web interface not found: {}", index_path.display()))); - } - - match tokio::fs::read(&index_path).await { - Ok(contents) => { + match StaticAssets::get("index.html") { + Some(content) => { Ok(HttpResponse::Ok() .content_type("text/html; charset=utf-8") - .body(contents)) + .body(content.data.as_ref().to_vec())) } - Err(e) => { - info!("Failed to read index.html: {}", e); - Ok(HttpResponse::InternalServerError().body("Failed to load web interface")) + None => { + Ok(HttpResponse::NotFound().body("Web interface not found")) } } }