-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add explorer flag and build folder #2997
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -28,11 +28,13 @@ use tracing::{info, Subscriber}; | |||
use tracing_log::LogTracer; | ||||
use tracing_subscriber::{fmt, EnvFilter}; | ||||
use url::Url; | ||||
use katana_rpc::cors::HeaderValue; | ||||
|
||||
use crate::file::NodeArgsConfig; | ||||
use crate::options::*; | ||||
use crate::utils; | ||||
use crate::utils::{parse_seed, LogFormat}; | ||||
use crate::explorer::ExplorerServer; | ||||
|
||||
pub(crate) const LOG_TARGET: &str = "katana::cli"; | ||||
|
||||
|
@@ -110,11 +112,17 @@ pub struct NodeArgs { | |||
#[cfg(feature = "slot")] | ||||
#[command(flatten)] | ||||
pub slot: SlotOptions, | ||||
|
||||
#[command(flatten)] | ||||
pub explorer: ExplorerOptions, | ||||
} | ||||
|
||||
impl NodeArgs { | ||||
pub async fn execute(&self) -> Result<()> { | ||||
// Initialize logging first | ||||
self.init_logging()?; | ||||
|
||||
// Finally start the node | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
self.start_node().await | ||||
} | ||||
|
||||
|
@@ -130,6 +138,29 @@ impl NodeArgs { | |||
// Launch the node | ||||
let handle = node.launch().await.context("failed to launch node")?; | ||||
|
||||
// Then start the explorer if enabled | ||||
if self.explorer.explorer { | ||||
let build_dir = self.explorer.explorer_build_dir | ||||
.clone() | ||||
.unwrap_or_else(|| { | ||||
PathBuf::from("crates/katana") | ||||
.join("explorer") | ||||
.join("build") | ||||
}); | ||||
|
||||
if !build_dir.exists() { | ||||
anyhow::bail!("Explorer build directory not found at {:?}. Please build the explorer first or specify a different path with --explorer-build-dir", build_dir); | ||||
} | ||||
|
||||
let explorer = ExplorerServer::new( | ||||
self.explorer.explorer_port, | ||||
build_dir, | ||||
)?; | ||||
|
||||
explorer.start()?; | ||||
} | ||||
|
||||
|
||||
// Wait until an OS signal (ie SIGINT, SIGTERM) is received or the node is shutdown. | ||||
tokio::select! { | ||||
_ = dojo_utils::signal::wait_signals() => { | ||||
|
@@ -216,12 +247,22 @@ impl NodeArgs { | |||
modules | ||||
}; | ||||
|
||||
let mut cors_origins = self.server.http_cors_origins.clone(); | ||||
|
||||
// Add explorer URL to CORS origins if explorer is enabled | ||||
if self.explorer.explorer { | ||||
cors_origins.push( | ||||
HeaderValue::from_str(&format!("http://127.0.0.1:{}", self.explorer.explorer_port)) | ||||
.context("Failed to create CORS header")? | ||||
); | ||||
} | ||||
|
||||
Ok(RpcConfig { | ||||
apis: modules, | ||||
port: self.server.http_port, | ||||
addr: self.server.http_addr, | ||||
max_connections: self.server.max_connections, | ||||
cors_origins: self.server.http_cors_origins.clone(), | ||||
cors_origins: cors_origins, | ||||
max_event_page_size: Some(self.server.max_event_page_size), | ||||
max_proof_keys: Some(self.server.max_proof_keys), | ||||
}) | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// crates/katana/cli/src/explorer.rs | ||
use std::path::PathBuf; | ||
use anyhow::{Result}; | ||
use tiny_http::{Server, Response}; | ||
use std::thread; | ||
use tracing::info; | ||
|
||
pub struct ExplorerServer { | ||
port: u16, | ||
build_dir: PathBuf, | ||
} | ||
|
||
impl ExplorerServer { | ||
pub fn new(port: u16, build_dir: PathBuf) -> Result<Self> { | ||
Ok(Self { | ||
port, | ||
build_dir, | ||
}) | ||
} | ||
|
||
pub fn start(&self) -> Result<()> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible at this point to inject the Katana url (which can be modified by the user) into the env somehow, which would make the explorer always indexing by default the katana that has just started? |
||
// Create the server | ||
let addr = format!("127.0.0.1:{}", self.port); | ||
let server = Server::http(&addr) | ||
.map_err(|e| anyhow::anyhow!("Failed to start explorer server: {}", e))?; | ||
|
||
// Handle requests in a separate thread | ||
let build_dir = self.build_dir.clone(); | ||
thread::spawn(move || { | ||
info!( | ||
target: "katana", | ||
"Explorer server started. addr=http://{}", | ||
addr, | ||
); | ||
|
||
for request in server.incoming_requests() { | ||
let path = request.url().to_string(); | ||
info!( | ||
target: "katana::explorer", | ||
"Received request for: {}", | ||
path | ||
); | ||
|
||
let file_path = if path == "/" { | ||
build_dir.join("index.html") | ||
} else { | ||
build_dir.join(&path[1..]) | ||
}; | ||
|
||
if let Ok(content) = std::fs::read(&file_path) { | ||
let content_type = match file_path.extension().and_then(|s| s.to_str()) { | ||
Some("html") => "text/html", | ||
Some("js") => "application/javascript", | ||
Some("css") => "text/css", | ||
Some("png") => "image/png", | ||
Some("svg") => "image/svg+xml", | ||
Some("json") => "application/json", | ||
_ => "application/octet-stream", | ||
}; | ||
|
||
let response = Response::from_data(content) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
.with_header(tiny_http::Header { | ||
field: "Content-Type".parse().unwrap(), | ||
value: content_type.parse().unwrap(), | ||
}) | ||
.with_header(tiny_http::Header { | ||
field: "Access-Control-Allow-Origin".parse().unwrap(), | ||
value: "*".parse().unwrap(), | ||
}) | ||
.with_header(tiny_http::Header { | ||
field: "Access-Control-Allow-Methods".parse().unwrap(), | ||
value: "GET, POST, OPTIONS".parse().unwrap(), | ||
}) | ||
.with_header(tiny_http::Header { | ||
field: "Access-Control-Allow-Headers".parse().unwrap(), | ||
value: "Content-Type".parse().unwrap(), | ||
}); | ||
|
||
let _ = request.respond(response); | ||
} else { | ||
// If file not found, serve index.html for SPA routing | ||
if let Ok(content) = std::fs::read(build_dir.join("index.html")) { | ||
let response = Response::from_data(content) | ||
.with_header(tiny_http::Header { | ||
field: "Content-Type".parse().unwrap(), | ||
value: "text/html".parse().unwrap(), | ||
}); | ||
let _ = request.respond(response); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
Ok(()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those comments don't add any relevant information.