Fix 404 errors for static assets with bot name prefixes
All checks were successful
BotUI CI / build (push) Successful in 3m42s

Removed root-level static routes (/:dir/*path) that conflicted with
bot name prefixes like /edu. Now all assets are served under /suite/
path, allowing the fallback handler to properly detect and strip
bot prefixes before resolving file paths.

Fixes:
- /edu/suite/js/api-client.js now returns 200 OK
- /edu/suite/css/apps-extended.css now returns 200 OK
- /edu/suite/admin/admin.js now returns 200 OK
- Base href injection working correctly for bot prefixes
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-02-06 16:24:56 -03:00
parent 1a1de27fd7
commit 661edc09fa

View file

@ -164,7 +164,11 @@ pub async fn index(OriginalUri(uri): OriginalUri) -> Response {
let mut start_idx = 1; let mut start_idx = 1;
let known_dirs = ["suite", "js", "css", "vendor", "assets", "public", "partials", "settings", "auth", "about", "drive", "chat", "tasks", "admin", "mail", "calendar", "meet", "docs", "sheet", "slides", "paper", "research", "sources", "learn", "analytics", "dashboards", "monitoring", "people", "crm", "tickets", "billing", "products", "video", "player", "canvas", "social", "project", "goals", "workspace", "designer"]; let known_dirs = ["suite", "js", "css", "vendor", "assets", "public", "partials", "settings", "auth", "about", "drive", "chat", "tasks", "admin", "mail", "calendar", "meet", "docs", "sheet", "slides", "paper", "research", "sources", "learn", "analytics", "dashboards", "monitoring", "people", "crm", "tickets", "billing", "products", "video", "player", "canvas", "social", "project", "goals", "workspace", "designer"];
if path_parts.len() > start_idx && !known_dirs.contains(&path_parts[start_idx]) { // Skip bot name if present (first segment is not a known dir, second segment is)
if path_parts.len() > start_idx + 1
&& !known_dirs.contains(&path_parts[start_idx])
&& known_dirs.contains(&path_parts[start_idx + 1])
{
start_idx += 1; start_idx += 1;
} }
@ -175,7 +179,7 @@ pub async fn index(OriginalUri(uri): OriginalUri) -> Response {
let full_path = get_ui_root().join(&fs_path); let full_path = get_ui_root().join(&fs_path);
debug!("index: Serving static file: {} -> {:?} (fs_path: {})", path, full_path, fs_path); info!("index: Serving static file: {} -> {:?} (fs_path: {})", path, full_path, fs_path);
#[cfg(feature = "embed-ui")] #[cfg(feature = "embed-ui")]
{ {
@ -1149,36 +1153,28 @@ async fn handle_embedded_root_asset(
fn add_static_routes(router: Router<AppState>, _suite_path: &Path) -> Router<AppState> { fn add_static_routes(router: Router<AppState>, _suite_path: &Path) -> Router<AppState> {
#[cfg(feature = "embed-ui")] #[cfg(feature = "embed-ui")]
{ {
let mut r = router let mut r = router.route("/suite/:dir/*path", get(handle_embedded_asset));
.route("/suite/:dir/*path", get(handle_embedded_asset))
.route("/:dir/*path", get(handle_embedded_asset));
// Add root files // Add root files only under /suite/
for file in ROOT_FILES { for file in ROOT_FILES {
r = r r = r.route(&format!("/suite/{}", file), get(handle_embedded_root_asset));
.route(&format!("/{}", file), get(handle_embedded_root_asset))
.route(&format!("/suite/{}", file), get(handle_embedded_root_asset));
} }
r r
} }
#[cfg(not(feature = "embed-ui"))] #[cfg(not(feature = "embed-ui"))]
{ {
let mut r = router; let mut r = router;
// Serve suite directories at BOTH root level and /suite/{dir} path // Only serve suite directories under /suite/{dir} path
// This allows HTML files to reference js/vendor/file.js directly // Root-level paths (/:dir) are handled by fallback to support bot prefixes
for dir in SUITE_DIRS { for dir in SUITE_DIRS {
let path = _suite_path.join(dir); let path = _suite_path.join(dir);
info!("Adding route for /{} -> {:?}", dir, path);
r = r.nest_service(&format!("/{dir}"), ServeDir::new(path.clone()));
info!("Adding route for /suite/{} -> {:?}", dir, path); info!("Adding route for /suite/{} -> {:?}", dir, path);
r = r.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone())); r = r.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone()));
} }
for file in ROOT_FILES { for file in ROOT_FILES {
let path = _suite_path.join(file); let path = _suite_path.join(file);
r = r r = r.nest_service(&format!("/suite/{}", file), ServeFile::new(path));
.nest_service(&format!("/{}", file), ServeFile::new(path.clone()))
.nest_service(&format!("/suite/{}", file), ServeFile::new(path));
} }
r r
} }
@ -1190,18 +1186,16 @@ pub fn configure_router() -> Router {
let mut router = Router::new() let mut router = Router::new()
.route("/health", get(health)) .route("/health", get(health))
.route("/favicon.ico", get(serve_favicon))
.nest("/api", create_api_router()) .nest("/api", create_api_router())
.nest("/ui", create_ui_router()) .nest("/ui", create_ui_router())
.nest("/ws", create_ws_router()) .nest("/ws", create_ws_router())
.nest("/apps", create_apps_router()) .nest("/apps", create_apps_router())
.route("/favicon.ico", get(serve_favicon)); .route("/", get(index))
.route("/minimal", get(serve_minimal))
.route("/suite", get(serve_suite));
router = add_static_routes(router, &suite_path); router = add_static_routes(router, &suite_path);
router router.fallback(get(index)).with_state(state)
.route("/", get(index))
.route("/minimal", get(serve_minimal))
.route("/suite", get(serve_suite))
.fallback(get(index))
.with_state(state)
} }