seedelf_cli/
web_server.rs

1use colored::Colorize;
2use mime_guess::MimeGuess;
3use rust_embed::RustEmbed;
4use std::net::SocketAddr;
5use warp::{Filter, http::Response};
6
7#[derive(RustEmbed)]
8#[folder = "static/"]
9struct Asset;
10
11/// Helper to build a `warp::http::Response<Vec<u8>>` with the correct Content-Type.
12fn serve_bytes(path: &'static str, data: Vec<u8>) -> impl warp::Reply {
13    let mime = MimeGuess::from_path(path).first_or_octet_stream();
14    Response::builder()
15        .header("content-type", mime.as_ref())
16        .body(data)
17}
18
19pub async fn run_web_server(message: String, network_flag: bool) {
20    let addr: SocketAddr = ([127, 0, 0, 1], 44203).into();
21    println!(
22        "{} {}",
23        "\nStarting server at".bright_cyan(),
24        format!("http://{addr}/").bright_white()
25    );
26    println!("{}", "Hit Ctrl-C To Stop Server".bright_yellow());
27
28    // HTML route with injection
29    let html = warp::path::end().map(move || {
30        let file = Asset::get("index.html").expect("index.html not found");
31        let mut html = String::from_utf8(file.data.into_owned()).unwrap();
32
33        let dyn_msg = format!(r#"{{ "message": "{message}" }}"#);
34        html = html.replace(r#"{ "message": "ACAB000000000000" }"#, &dyn_msg);
35
36        let net_repl = if network_flag {
37            r#"{ "network": "preprod." }"#
38        } else {
39            r#"{ "network": "" }"#
40        };
41        html = html.replace(r#"{ "network": "FADECAFE00000000" }"#, net_repl);
42
43        warp::reply::html(html)
44    });
45
46    // JS, CSS, and favicon routes
47    let js = warp::path("index.js").map(|| {
48        let file = Asset::get("index.js").expect("index.js not found");
49        serve_bytes("index.js", file.data.into_owned())
50    });
51    let css = warp::path("index.css").map(|| {
52        let file = Asset::get("index.css").expect("index.css not found");
53        serve_bytes("index.css", file.data.into_owned())
54    });
55    let ico = warp::path("favicon.ico").map(|| {
56        let file = Asset::get("favicon.ico").expect("favicon.ico not found");
57        serve_bytes("favicon.ico", file.data.into_owned())
58    });
59
60    // Combine all routes
61    let routes = html.or(js).or(css).or(ico);
62
63    // Run server with graceful shutdown
64    let (_addr, server) = warp::serve(routes).bind_with_graceful_shutdown(addr, async {
65        tokio::signal::ctrl_c()
66            .await
67            .expect("Failed to install Ctrl-C handler");
68        println!("\n{}", "Shutdown signal received...".red());
69    });
70
71    server.await;
72    println!("{}", "Server has stopped.".bright_purple());
73}