@@ -1,16 +1,13 @@
|
||||
use serde_json::{json, Value};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::connection::get_socket_dir;
|
||||
use crate::install::get_dashboard_dir;
|
||||
|
||||
use super::chat::{chat_status_json, handle_chat_request, handle_models_request};
|
||||
use super::discovery::discover_sessions;
|
||||
use super::http::{serve_static_file, CORS_HEADERS, DASHBOARD_NOT_INSTALLED_HTML};
|
||||
use super::http::{serve_embedded_file, CORS_HEADERS};
|
||||
|
||||
pub async fn run_dashboard_server(port: u16) {
|
||||
let addr = format!("127.0.0.1:{}", port);
|
||||
@@ -22,23 +19,17 @@ pub async fn run_dashboard_server(port: u16) {
|
||||
}
|
||||
};
|
||||
|
||||
let dashboard_dir: Arc<PathBuf> = Arc::from(get_dashboard_dir());
|
||||
|
||||
loop {
|
||||
let Ok((stream, _addr)) = listener.accept().await else {
|
||||
break;
|
||||
};
|
||||
let dash_dir = dashboard_dir.clone();
|
||||
tokio::spawn(async move {
|
||||
handle_dashboard_connection(stream, dash_dir).await;
|
||||
handle_dashboard_connection(stream).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_dashboard_connection(
|
||||
mut stream: tokio::net::TcpStream,
|
||||
dashboard_dir: Arc<PathBuf>,
|
||||
) {
|
||||
async fn handle_dashboard_connection(mut stream: tokio::net::TcpStream) {
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
let mut buf = vec![0u8; 8192];
|
||||
@@ -118,14 +109,8 @@ async fn handle_dashboard_connection(
|
||||
"application/json; charset=utf-8",
|
||||
chat_status_json().into_bytes(),
|
||||
)
|
||||
} else if dashboard_dir.join("index.html").exists() {
|
||||
serve_static_file(&dashboard_dir, path)
|
||||
} else {
|
||||
(
|
||||
"200 OK",
|
||||
"text/html; charset=utf-8",
|
||||
DASHBOARD_NOT_INSTALLED_HTML.as_bytes().to_vec(),
|
||||
)
|
||||
serve_embedded_file(path)
|
||||
};
|
||||
|
||||
let response = format!(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use rust_embed::Embed;
|
||||
use serde_json::{json, Value};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -13,6 +13,10 @@ use super::chat::{chat_status_json, handle_chat_request, handle_models_request};
|
||||
use super::dashboard::spawn_session;
|
||||
use super::discovery::discover_sessions;
|
||||
|
||||
#[derive(Embed)]
|
||||
#[folder = "../packages/dashboard/out/"]
|
||||
struct DashboardAssets;
|
||||
|
||||
pub(super) const CORS_HEADERS: &str = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n";
|
||||
|
||||
/// Build CORS headers that reflect the request origin only when it passes
|
||||
@@ -42,7 +46,6 @@ fn parse_origin(peeked: &[u8]) -> Option<String> {
|
||||
pub(super) async fn handle_http_request(
|
||||
mut stream: tokio::net::TcpStream,
|
||||
peeked: &[u8],
|
||||
dashboard_dir: Option<&Path>,
|
||||
last_tabs: &Arc<RwLock<Vec<Value>>>,
|
||||
last_engine: &Arc<RwLock<String>>,
|
||||
session_name: &str,
|
||||
@@ -163,14 +166,7 @@ pub(super) async fn handle_http_request(
|
||||
chat_status_json().into_bytes(),
|
||||
)
|
||||
} else {
|
||||
match dashboard_dir {
|
||||
Some(dir) => serve_static_file(dir, path),
|
||||
None => (
|
||||
"200 OK",
|
||||
"text/html; charset=utf-8",
|
||||
DASHBOARD_NOT_INSTALLED_HTML.as_bytes().to_vec(),
|
||||
),
|
||||
}
|
||||
serve_embedded_file(path)
|
||||
};
|
||||
|
||||
let response = format!(
|
||||
@@ -282,25 +278,19 @@ pub(super) async fn relay_command_to_daemon(
|
||||
Ok(response_line.trim().to_string())
|
||||
}
|
||||
|
||||
pub(super) fn serve_static_file(
|
||||
dir: &Path,
|
||||
url_path: &str,
|
||||
) -> (&'static str, &'static str, Vec<u8>) {
|
||||
pub(super) fn serve_embedded_file(url_path: &str) -> (&'static str, &'static str, Vec<u8>) {
|
||||
let clean = url_path.trim_start_matches('/');
|
||||
let file_path = if clean.is_empty() {
|
||||
dir.join("index.html")
|
||||
let key = if clean.is_empty() {
|
||||
"index.html"
|
||||
} else {
|
||||
let joined = dir.join(clean);
|
||||
if joined.is_file() {
|
||||
joined
|
||||
} else {
|
||||
dir.join("index.html")
|
||||
}
|
||||
clean
|
||||
};
|
||||
|
||||
match std::fs::read(&file_path) {
|
||||
Ok(content) => {
|
||||
let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||
let file = DashboardAssets::get(key).or_else(|| DashboardAssets::get("index.html"));
|
||||
|
||||
match file {
|
||||
Some(content) => {
|
||||
let ext = key.rsplit('.').next().unwrap_or("");
|
||||
let ct = match ext {
|
||||
"html" => "text/html; charset=utf-8",
|
||||
"js" => "application/javascript; charset=utf-8",
|
||||
@@ -309,31 +299,17 @@ pub(super) fn serve_static_file(
|
||||
"svg" => "image/svg+xml",
|
||||
"png" => "image/png",
|
||||
"ico" => "image/x-icon",
|
||||
"woff2" => "font/woff2",
|
||||
"woff" => "font/woff",
|
||||
"txt" => "text/plain; charset=utf-8",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
("200 OK", ct, content)
|
||||
("200 OK", ct, content.data.to_vec())
|
||||
}
|
||||
Err(_) => (
|
||||
None => (
|
||||
"404 Not Found",
|
||||
"text/html; charset=utf-8",
|
||||
b"<html><body><p>404 Not Found</p></body></html>".to_vec(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const DASHBOARD_NOT_INSTALLED_HTML: &str = r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><meta charset="utf-8"><title>agent-browser</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #e5e5e5; }
|
||||
.card { text-align: center; max-width: 400px; }
|
||||
code { background: #262626; padding: 2px 8px; border-radius: 4px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>Dashboard not installed</h2>
|
||||
<p>Run <code>agent-browser dashboard install</code> to download the dashboard.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>"#;
|
||||
|
||||
@@ -9,7 +9,6 @@ pub use cdp_loop::{ack_screencast_frame, start_screencast, stop_screencast};
|
||||
pub use dashboard::run_dashboard_server;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
@@ -55,7 +54,6 @@ pub struct StreamServer {
|
||||
screencasting: Arc<Mutex<bool>>,
|
||||
viewport_width: Arc<Mutex<u32>>,
|
||||
viewport_height: Arc<Mutex<u32>>,
|
||||
dashboard_dir: Option<PathBuf>,
|
||||
last_tabs: Arc<RwLock<Vec<Value>>>,
|
||||
last_engine: Arc<RwLock<String>>,
|
||||
last_frame: Arc<RwLock<Option<String>>>,
|
||||
@@ -91,16 +89,6 @@ impl StreamServer {
|
||||
Self::start_inner(preferred_port, client_slot, session_id, allow_port_fallback).await
|
||||
}
|
||||
|
||||
/// Resolve the dashboard directory if it exists.
|
||||
fn resolve_dashboard_dir() -> Option<PathBuf> {
|
||||
let dir = dirs::home_dir()?.join(".agent-browser").join("dashboard");
|
||||
if dir.join("index.html").exists() {
|
||||
Some(dir)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify the background CDP listener that the client has changed (browser launched/closed).
|
||||
pub fn notify_client_changed(&self) {
|
||||
self.client_notify.notify_one();
|
||||
@@ -181,8 +169,6 @@ impl StreamServer {
|
||||
.map_err(|e| format!("Failed to get stream address: {}", e))?;
|
||||
let port = actual_addr.port();
|
||||
|
||||
let dashboard_dir = Self::resolve_dashboard_dir();
|
||||
|
||||
let (frame_tx, _) = broadcast::channel::<String>(64);
|
||||
let client_count = Arc::new(Mutex::new(0usize));
|
||||
let client_notify = Arc::new(Notify::new());
|
||||
@@ -205,7 +191,6 @@ impl StreamServer {
|
||||
|
||||
let vw_clone = viewport_width.clone();
|
||||
let vh_clone = viewport_height.clone();
|
||||
let dashboard_dir_clone = dashboard_dir.clone();
|
||||
let last_tabs_clone = last_tabs.clone();
|
||||
let last_engine_clone = last_engine.clone();
|
||||
let last_frame_clone = last_frame.clone();
|
||||
@@ -223,7 +208,6 @@ impl StreamServer {
|
||||
cdp_session_clone,
|
||||
vw_clone,
|
||||
vh_clone,
|
||||
dashboard_dir_clone,
|
||||
last_tabs_clone,
|
||||
last_engine_clone,
|
||||
last_frame_clone,
|
||||
@@ -277,7 +261,6 @@ impl StreamServer {
|
||||
screencasting,
|
||||
viewport_width,
|
||||
viewport_height,
|
||||
dashboard_dir,
|
||||
last_tabs,
|
||||
last_engine,
|
||||
last_frame,
|
||||
@@ -435,11 +418,6 @@ impl StreamServer {
|
||||
});
|
||||
let _ = self.frame_tx.send(msg.to_string());
|
||||
}
|
||||
|
||||
/// Whether the dashboard directory is available.
|
||||
pub fn has_dashboard(&self) -> bool {
|
||||
self.dashboard_dir.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn timestamp_ms() -> u64 {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use serde_json::{json, Value};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
@@ -24,7 +23,6 @@ pub(super) async fn accept_loop(
|
||||
cdp_session_id: Arc<RwLock<Option<String>>>,
|
||||
viewport_width: Arc<Mutex<u32>>,
|
||||
viewport_height: Arc<Mutex<u32>>,
|
||||
dashboard_dir: Option<PathBuf>,
|
||||
last_tabs: Arc<RwLock<Vec<Value>>>,
|
||||
last_engine: Arc<RwLock<String>>,
|
||||
last_frame: Arc<RwLock<Option<String>>>,
|
||||
@@ -32,7 +30,6 @@ pub(super) async fn accept_loop(
|
||||
mut shutdown_rx: watch::Receiver<bool>,
|
||||
session_name: String,
|
||||
) {
|
||||
let dashboard_dir = dashboard_dir.map(Arc::from);
|
||||
let session_name: Arc<str> = Arc::from(session_name);
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -53,7 +50,6 @@ pub(super) async fn accept_loop(
|
||||
let cdp_session_id = cdp_session_id.clone();
|
||||
let vw = viewport_width.clone();
|
||||
let vh = viewport_height.clone();
|
||||
let dd = dashboard_dir.clone();
|
||||
let lt = last_tabs.clone();
|
||||
let le = last_engine.clone();
|
||||
let lf = last_frame.clone();
|
||||
@@ -73,7 +69,6 @@ pub(super) async fn accept_loop(
|
||||
cdp_session_id,
|
||||
vw,
|
||||
vh,
|
||||
dd,
|
||||
lt,
|
||||
le,
|
||||
lf,
|
||||
@@ -112,7 +107,6 @@ async fn handle_connection(
|
||||
cdp_session_id: Arc<RwLock<Option<String>>>,
|
||||
viewport_width: Arc<Mutex<u32>>,
|
||||
viewport_height: Arc<Mutex<u32>>,
|
||||
dashboard_dir: Option<Arc<PathBuf>>,
|
||||
last_tabs: Arc<RwLock<Vec<Value>>>,
|
||||
last_engine: Arc<RwLock<String>>,
|
||||
last_frame: Arc<RwLock<Option<String>>>,
|
||||
@@ -148,15 +142,7 @@ async fn handle_connection(
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
handle_http_request(
|
||||
stream,
|
||||
&buf[..n],
|
||||
dashboard_dir.as_deref().map(|p| p.as_path()),
|
||||
&last_tabs,
|
||||
&last_engine,
|
||||
&session_name,
|
||||
)
|
||||
.await;
|
||||
handle_http_request(stream, &buf[..n], &last_tabs, &last_engine, &session_name).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user