mirror of
https://github.com/house-of-vanity/khm.git
synced 2025-08-21 14:27:14 +00:00
Added web ui
This commit is contained in:
65
Cargo.lock
generated
65
Cargo.lock
generated
@@ -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"
|
||||
|
@@ -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"
|
||||
|
90
src/web.rs
90
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<Vec<String>>) -> Result<HttpResponse> {
|
||||
info!("API request for available flows");
|
||||
@@ -15,13 +27,11 @@ pub async fn get_flows_api(allowed_flows: web::Data<Vec<String>>) -> Result<Http
|
||||
// API endpoint to delete a specific key by server name
|
||||
pub async fn delete_key_by_server(
|
||||
flows: web::Data<Flows>,
|
||||
flow_id: web::Path<String>,
|
||||
server: web::Path<String>,
|
||||
path: web::Path<(String, String)>,
|
||||
db_client: web::Data<std::sync::Arc<Client>>,
|
||||
allowed_flows: web::Data<Vec<String>>,
|
||||
) -> Result<HttpResponse> {
|
||||
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<String>) -> Result<HttpResponse> {
|
||||
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<String>) -> Result<HttpResponse>
|
||||
|
||||
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<HttpResponse> {
|
||||
// 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user