fix(ui): improve asset discovery and error logging
Some checks failed
GBCI / build (push) Failing after 11s
Some checks failed
GBCI / build (push) Failing after 11s
This commit is contained in:
parent
12c1e3210f
commit
22fa29b3ec
1 changed files with 245 additions and 123 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::{
|
extract::{
|
||||||
|
|
@ -12,17 +11,18 @@ use axum::{
|
||||||
};
|
};
|
||||||
use futures_util::{SinkExt, StreamExt};
|
use futures_util::{SinkExt, StreamExt};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
#[cfg(feature = "embed-ui")]
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
#[cfg(not(feature = "embed-ui"))]
|
#[cfg(not(feature = "embed-ui"))]
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio_tungstenite::{
|
use tokio_tungstenite::{
|
||||||
connect_async_tls_with_config, tungstenite, tungstenite::protocol::Message as TungsteniteMessage,
|
connect_async_tls_with_config, tungstenite,
|
||||||
|
tungstenite::protocol::Message as TungsteniteMessage,
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "embed-ui"))]
|
#[cfg(not(feature = "embed-ui"))]
|
||||||
use tower_http::services::{ServeDir, ServeFile};
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
#[cfg(feature = "embed-ui")]
|
|
||||||
use rust_embed::RustEmbed;
|
|
||||||
|
|
||||||
#[cfg(feature = "embed-ui")]
|
#[cfg(feature = "embed-ui")]
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
|
|
@ -38,10 +38,9 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"assets",
|
"assets",
|
||||||
"partials",
|
"partials",
|
||||||
// Core & Support
|
// Core & Support
|
||||||
"settings",
|
"settings",
|
||||||
"auth",
|
"auth",
|
||||||
"about",
|
"about",
|
||||||
|
|
||||||
// Core Apps
|
// Core Apps
|
||||||
#[cfg(feature = "drive")]
|
#[cfg(feature = "drive")]
|
||||||
"drive",
|
"drive",
|
||||||
|
|
@ -55,7 +54,6 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"calendar",
|
"calendar",
|
||||||
#[cfg(feature = "meet")]
|
#[cfg(feature = "meet")]
|
||||||
"meet",
|
"meet",
|
||||||
|
|
||||||
// Document Apps
|
// Document Apps
|
||||||
#[cfg(feature = "paper")]
|
#[cfg(feature = "paper")]
|
||||||
"paper",
|
"paper",
|
||||||
|
|
@ -65,7 +63,6 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"slides",
|
"slides",
|
||||||
#[cfg(feature = "docs")]
|
#[cfg(feature = "docs")]
|
||||||
"docs",
|
"docs",
|
||||||
|
|
||||||
// Research & Learning
|
// Research & Learning
|
||||||
#[cfg(feature = "research")]
|
#[cfg(feature = "research")]
|
||||||
"research",
|
"research",
|
||||||
|
|
@ -73,7 +70,6 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"sources",
|
"sources",
|
||||||
#[cfg(feature = "learn")]
|
#[cfg(feature = "learn")]
|
||||||
"learn",
|
"learn",
|
||||||
|
|
||||||
// Analytics
|
// Analytics
|
||||||
#[cfg(feature = "analytics")]
|
#[cfg(feature = "analytics")]
|
||||||
"analytics",
|
"analytics",
|
||||||
|
|
@ -81,7 +77,6 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"dashboards",
|
"dashboards",
|
||||||
#[cfg(feature = "monitoring")]
|
#[cfg(feature = "monitoring")]
|
||||||
"monitoring",
|
"monitoring",
|
||||||
|
|
||||||
// Admin & Tools
|
// Admin & Tools
|
||||||
#[cfg(feature = "admin")]
|
#[cfg(feature = "admin")]
|
||||||
"admin",
|
"admin",
|
||||||
|
|
@ -89,7 +84,6 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"attendant",
|
"attendant",
|
||||||
#[cfg(feature = "tools")]
|
#[cfg(feature = "tools")]
|
||||||
"tools",
|
"tools",
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
#[cfg(feature = "video")]
|
#[cfg(feature = "video")]
|
||||||
"video",
|
"video",
|
||||||
|
|
@ -97,7 +91,6 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"player",
|
"player",
|
||||||
#[cfg(feature = "canvas")]
|
#[cfg(feature = "canvas")]
|
||||||
"canvas",
|
"canvas",
|
||||||
|
|
||||||
// Social
|
// Social
|
||||||
#[cfg(feature = "social")]
|
#[cfg(feature = "social")]
|
||||||
"social",
|
"social",
|
||||||
|
|
@ -107,13 +100,11 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
"crm",
|
"crm",
|
||||||
#[cfg(feature = "tickets")]
|
#[cfg(feature = "tickets")]
|
||||||
"tickets",
|
"tickets",
|
||||||
|
|
||||||
// Business
|
// Business
|
||||||
#[cfg(feature = "billing")]
|
#[cfg(feature = "billing")]
|
||||||
"billing",
|
"billing",
|
||||||
#[cfg(feature = "products")]
|
#[cfg(feature = "products")]
|
||||||
"products",
|
"products",
|
||||||
|
|
||||||
// Development
|
// Development
|
||||||
#[cfg(feature = "designer")]
|
#[cfg(feature = "designer")]
|
||||||
"designer",
|
"designer",
|
||||||
|
|
@ -126,11 +117,18 @@ const SUITE_DIRS: &[&str] = &[
|
||||||
];
|
];
|
||||||
|
|
||||||
const ROOT_FILES: &[&str] = &[
|
const ROOT_FILES: &[&str] = &[
|
||||||
"designer.html", "designer.css", "designer.js",
|
"designer.html",
|
||||||
"editor.html", "editor.css", "editor.js",
|
"designer.css",
|
||||||
|
"designer.js",
|
||||||
|
"editor.html",
|
||||||
|
"editor.css",
|
||||||
|
"editor.js",
|
||||||
"home.html",
|
"home.html",
|
||||||
"base.html", "base-layout.html", "base-layout.css",
|
"base.html",
|
||||||
"default.gbui", "single.gbui",
|
"base-layout.html",
|
||||||
|
"base-layout.css",
|
||||||
|
"default.gbui",
|
||||||
|
"single.gbui",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub async fn index() -> impl IntoResponse {
|
pub async fn index() -> impl IntoResponse {
|
||||||
|
|
@ -138,13 +136,30 @@ pub async fn index() -> impl IntoResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ui_root() -> PathBuf {
|
pub fn get_ui_root() -> PathBuf {
|
||||||
if Path::new("ui").exists() {
|
let candidates = [
|
||||||
PathBuf::from("ui")
|
"ui",
|
||||||
} else if Path::new("botui/ui").exists() {
|
"botui/ui",
|
||||||
PathBuf::from("botui/ui")
|
"../botui/ui",
|
||||||
} else {
|
"../../botui/ui",
|
||||||
PathBuf::from("ui")
|
"../../../botui/ui",
|
||||||
|
];
|
||||||
|
|
||||||
|
for path_str in candidates {
|
||||||
|
let path = PathBuf::from(path_str);
|
||||||
|
if path.exists() {
|
||||||
|
info!("Found UI root at: {:?}", path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback to "ui" but log a warning
|
||||||
|
let default = PathBuf::from("ui");
|
||||||
|
error!(
|
||||||
|
"Could not find 'ui' directory in candidates: {:?}. Defaulting to 'ui' (CWD: {:?})",
|
||||||
|
candidates,
|
||||||
|
std::env::current_dir()
|
||||||
|
);
|
||||||
|
default
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn serve_minimal() -> impl IntoResponse {
|
pub async fn serve_minimal() -> impl IntoResponse {
|
||||||
|
|
@ -157,8 +172,15 @@ pub async fn serve_minimal() -> impl IntoResponse {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "embed-ui"))]
|
#[cfg(not(feature = "embed-ui"))]
|
||||||
{
|
{
|
||||||
fs::read_to_string(get_ui_root().join("minimal/index.html"))
|
let path = get_ui_root().join("minimal/index.html");
|
||||||
.map_err(|e| e.to_string())
|
fs::read_to_string(&path).map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"Failed to read {:?} (CWD: {:?}): {}",
|
||||||
|
path,
|
||||||
|
std::env::current_dir(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -185,7 +207,15 @@ pub async fn serve_suite() -> impl IntoResponse {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "embed-ui"))]
|
#[cfg(not(feature = "embed-ui"))]
|
||||||
{
|
{
|
||||||
fs::read_to_string(get_ui_root().join("suite/index.html")).map_err(|e| e.to_string())
|
let path = get_ui_root().join("suite/index.html");
|
||||||
|
fs::read_to_string(&path).map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"Failed to read {:?} (CWD: {:?}): {}",
|
||||||
|
path,
|
||||||
|
std::env::current_dir(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -195,64 +225,157 @@ pub async fn serve_suite() -> impl IntoResponse {
|
||||||
let mut html = raw_html;
|
let mut html = raw_html;
|
||||||
|
|
||||||
// Core Apps
|
// Core Apps
|
||||||
#[cfg(not(feature = "chat"))] { html = remove_section(&html, "chat"); }
|
#[cfg(not(feature = "chat"))]
|
||||||
#[cfg(not(feature = "mail"))] { html = remove_section(&html, "mail"); }
|
{
|
||||||
#[cfg(not(feature = "calendar"))] { html = remove_section(&html, "calendar"); }
|
html = remove_section(&html, "chat");
|
||||||
#[cfg(not(feature = "drive"))] { html = remove_section(&html, "drive"); }
|
|
||||||
#[cfg(not(feature = "tasks"))] { html = remove_section(&html, "tasks"); }
|
|
||||||
#[cfg(not(feature = "meet"))] { html = remove_section(&html, "meet"); }
|
|
||||||
|
|
||||||
// Documents
|
|
||||||
#[cfg(not(feature = "docs"))] { html = remove_section(&html, "docs"); }
|
|
||||||
#[cfg(not(feature = "sheet"))] { html = remove_section(&html, "sheet"); }
|
|
||||||
#[cfg(not(feature = "slides"))] { html = remove_section(&html, "slides"); }
|
|
||||||
#[cfg(not(feature = "paper"))] { html = remove_section(&html, "paper"); }
|
|
||||||
|
|
||||||
// Research
|
|
||||||
#[cfg(not(feature = "research"))] { html = remove_section(&html, "research"); }
|
|
||||||
#[cfg(not(feature = "sources"))] { html = remove_section(&html, "sources"); }
|
|
||||||
#[cfg(not(feature = "learn"))] { html = remove_section(&html, "learn"); }
|
|
||||||
|
|
||||||
// Analytics
|
|
||||||
#[cfg(not(feature = "analytics"))] { html = remove_section(&html, "analytics"); }
|
|
||||||
#[cfg(not(feature = "dashboards"))] { html = remove_section(&html, "dashboards"); }
|
|
||||||
#[cfg(not(feature = "monitoring"))] { html = remove_section(&html, "monitoring"); }
|
|
||||||
|
|
||||||
// Business
|
|
||||||
#[cfg(not(feature = "people"))] {
|
|
||||||
html = remove_section(&html, "people");
|
|
||||||
html = remove_section(&html, "crm");
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "billing"))] { html = remove_section(&html, "billing"); }
|
#[cfg(not(feature = "mail"))]
|
||||||
#[cfg(not(feature = "products"))] { html = remove_section(&html, "products"); }
|
{
|
||||||
#[cfg(not(feature = "tickets"))] { html = remove_section(&html, "tickets"); }
|
html = remove_section(&html, "mail");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "calendar"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "calendar");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "drive"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "drive");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "tasks"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "tasks");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "meet"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "meet");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Documents
|
||||||
|
#[cfg(not(feature = "docs"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "docs");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sheet"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "sheet");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "slides"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "slides");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "paper"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "paper");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Research
|
||||||
|
#[cfg(not(feature = "research"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "research");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sources"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "sources");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "learn"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "learn");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analytics
|
||||||
|
#[cfg(not(feature = "analytics"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "analytics");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "dashboards"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "dashboards");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "monitoring"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "monitoring");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Business
|
||||||
|
#[cfg(not(feature = "people"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "people");
|
||||||
|
html = remove_section(&html, "crm");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "billing"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "billing");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "products"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "products");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "tickets"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "tickets");
|
||||||
|
}
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
#[cfg(not(feature = "video"))] { html = remove_section(&html, "video"); }
|
#[cfg(not(feature = "video"))]
|
||||||
#[cfg(not(feature = "player"))] { html = remove_section(&html, "player"); }
|
{
|
||||||
#[cfg(not(feature = "canvas"))] { html = remove_section(&html, "canvas"); }
|
html = remove_section(&html, "video");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "player"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "player");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "canvas"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "canvas");
|
||||||
|
}
|
||||||
|
|
||||||
// Social & Project
|
// Social & Project
|
||||||
#[cfg(not(feature = "social"))] { html = remove_section(&html, "social"); }
|
#[cfg(not(feature = "social"))]
|
||||||
#[cfg(not(feature = "project"))] { html = remove_section(&html, "project"); }
|
{
|
||||||
#[cfg(not(feature = "goals"))] { html = remove_section(&html, "goals"); }
|
html = remove_section(&html, "social");
|
||||||
#[cfg(not(feature = "workspace"))] { html = remove_section(&html, "workspace"); }
|
}
|
||||||
|
#[cfg(not(feature = "project"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "project");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "goals"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "goals");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "workspace"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "workspace");
|
||||||
|
}
|
||||||
|
|
||||||
// Admin/Tools
|
// Admin/Tools
|
||||||
#[cfg(not(feature = "admin"))] {
|
#[cfg(not(feature = "admin"))]
|
||||||
html = remove_section(&html, "admin");
|
{
|
||||||
|
html = remove_section(&html, "admin");
|
||||||
}
|
}
|
||||||
// Mapped security to tools feature
|
// Mapped security to tools feature
|
||||||
#[cfg(not(feature = "tools"))] {
|
#[cfg(not(feature = "tools"))]
|
||||||
|
{
|
||||||
html = remove_section(&html, "security");
|
html = remove_section(&html, "security");
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "attendant"))] { html = remove_section(&html, "attendant"); }
|
#[cfg(not(feature = "attendant"))]
|
||||||
#[cfg(not(feature = "designer"))] { html = remove_section(&html, "designer"); }
|
{
|
||||||
#[cfg(not(feature = "editor"))] { html = remove_section(&html, "editor"); }
|
html = remove_section(&html, "attendant");
|
||||||
#[cfg(not(feature = "settings"))] { html = remove_section(&html, "settings"); }
|
}
|
||||||
|
#[cfg(not(feature = "designer"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "designer");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "editor"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "editor");
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "settings"))]
|
||||||
|
{
|
||||||
|
html = remove_section(&html, "settings");
|
||||||
|
}
|
||||||
|
|
||||||
(StatusCode::OK, [("content-type", "text/html")], Html(html))
|
(StatusCode::OK, [("content-type", "text/html")], Html(html))
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to load suite UI: {e}");
|
error!("Failed to load suite UI: {e}");
|
||||||
(
|
(
|
||||||
|
|
@ -268,29 +391,29 @@ pub async fn serve_suite() -> impl IntoResponse {
|
||||||
pub fn remove_section(html: &str, section: &str) -> String {
|
pub fn remove_section(html: &str, section: &str) -> String {
|
||||||
let start_marker = format!("<!-- SECTION:{} -->", section);
|
let start_marker = format!("<!-- SECTION:{} -->", section);
|
||||||
let end_marker = format!("<!-- ENDSECTION:{} -->", section);
|
let end_marker = format!("<!-- ENDSECTION:{} -->", section);
|
||||||
|
|
||||||
let mut result = String::with_capacity(html.len());
|
let mut result = String::with_capacity(html.len());
|
||||||
let mut current_pos = 0;
|
let mut current_pos = 0;
|
||||||
|
|
||||||
// Process multiple occurrences of the section
|
// Process multiple occurrences of the section
|
||||||
while let Some(start_idx) = html[current_pos..].find(&start_marker) {
|
while let Some(start_idx) = html[current_pos..].find(&start_marker) {
|
||||||
let abs_start = current_pos + start_idx;
|
let abs_start = current_pos + start_idx;
|
||||||
// Append content up to the marker
|
// Append content up to the marker
|
||||||
result.push_str(&html[current_pos..abs_start]);
|
result.push_str(&html[current_pos..abs_start]);
|
||||||
|
|
||||||
// Find end marker
|
// Find end marker
|
||||||
if let Some(end_idx) = html[abs_start..].find(&end_marker) {
|
if let Some(end_idx) = html[abs_start..].find(&end_marker) {
|
||||||
// Skip past the end marker
|
// Skip past the end marker
|
||||||
current_pos = abs_start + end_idx + end_marker.len();
|
current_pos = abs_start + end_idx + end_marker.len();
|
||||||
} else {
|
} else {
|
||||||
// No end marker? This shouldn't happen with our script,
|
// No end marker? This shouldn't happen with our script,
|
||||||
// but if it does, just skip the start marker and continue
|
// but if it does, just skip the start marker and continue
|
||||||
// or consume everything?
|
// or consume everything?
|
||||||
// Safety: Skip start marker only
|
// Safety: Skip start marker only
|
||||||
current_pos = abs_start + start_marker.len();
|
current_pos = abs_start + start_marker.len();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append remaining content
|
// Append remaining content
|
||||||
result.push_str(&html[current_pos..]);
|
result.push_str(&html[current_pos..]);
|
||||||
result
|
result
|
||||||
|
|
@ -518,7 +641,9 @@ async fn handle_task_progress_ws_proxy(
|
||||||
let backend_result =
|
let backend_result =
|
||||||
connect_async_tls_with_config(&backend_url, None, false, Some(connector)).await;
|
connect_async_tls_with_config(&backend_url, None, false, Some(connector)).await;
|
||||||
|
|
||||||
let backend_socket: tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> = match backend_result {
|
let backend_socket: tokio_tungstenite::WebSocketStream<
|
||||||
|
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
|
||||||
|
> = match backend_result {
|
||||||
Ok((socket, _)) => socket,
|
Ok((socket, _)) => socket,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to connect to backend task-progress WebSocket: {e}");
|
error!("Failed to connect to backend task-progress WebSocket: {e}");
|
||||||
|
|
@ -535,33 +660,29 @@ async fn handle_task_progress_ws_proxy(
|
||||||
while let Some(msg) = client_rx.next().await {
|
while let Some(msg) = client_rx.next().await {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(AxumMessage::Text(text)) => {
|
Ok(AxumMessage::Text(text)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Text(text))
|
backend_tx.send(TungsteniteMessage::Text(text)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Binary(data)) => {
|
Ok(AxumMessage::Binary(data)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Binary(data))
|
backend_tx.send(TungsteniteMessage::Binary(data)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Ping(data)) => {
|
Ok(AxumMessage::Ping(data)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Ping(data))
|
backend_tx.send(TungsteniteMessage::Ping(data)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Pong(data)) => {
|
Ok(AxumMessage::Pong(data)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Pong(data))
|
backend_tx.send(TungsteniteMessage::Pong(data)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -572,13 +693,18 @@ async fn handle_task_progress_ws_proxy(
|
||||||
};
|
};
|
||||||
|
|
||||||
let backend_to_client = async {
|
let backend_to_client = async {
|
||||||
while let Some(msg) = backend_rx.next().await as Option<Result<TungsteniteMessage, tungstenite::Error>> {
|
while let Some(msg) =
|
||||||
|
backend_rx.next().await as Option<Result<TungsteniteMessage, tungstenite::Error>>
|
||||||
|
{
|
||||||
match msg {
|
match msg {
|
||||||
Ok(TungsteniteMessage::Text(text)) => {
|
Ok(TungsteniteMessage::Text(text)) => {
|
||||||
// Log manifest_update messages for debugging
|
// Log manifest_update messages for debugging
|
||||||
let is_manifest = text.contains("manifest_update");
|
let is_manifest = text.contains("manifest_update");
|
||||||
if is_manifest {
|
if is_manifest {
|
||||||
info!("[WS_PROXY] Forwarding manifest_update to client: {}...", &text[..text.len().min(200)]);
|
info!(
|
||||||
|
"[WS_PROXY] Forwarding manifest_update to client: {}...",
|
||||||
|
&text[..text.len().min(200)]
|
||||||
|
);
|
||||||
} else if text.contains("task_progress") {
|
} else if text.contains("task_progress") {
|
||||||
debug!("[WS_PROXY] Forwarding task_progress to client");
|
debug!("[WS_PROXY] Forwarding task_progress to client");
|
||||||
}
|
}
|
||||||
|
|
@ -650,7 +776,9 @@ async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQu
|
||||||
let backend_result =
|
let backend_result =
|
||||||
connect_async_tls_with_config(&backend_url, None, false, Some(connector)).await;
|
connect_async_tls_with_config(&backend_url, None, false, Some(connector)).await;
|
||||||
|
|
||||||
let backend_socket: tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> = match backend_result {
|
let backend_socket: tokio_tungstenite::WebSocketStream<
|
||||||
|
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
|
||||||
|
> = match backend_result {
|
||||||
Ok((socket, _)) => socket,
|
Ok((socket, _)) => socket,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to connect to backend WebSocket: {e}");
|
error!("Failed to connect to backend WebSocket: {e}");
|
||||||
|
|
@ -667,33 +795,29 @@ async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQu
|
||||||
while let Some(msg) = client_rx.next().await {
|
while let Some(msg) = client_rx.next().await {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(AxumMessage::Text(text)) => {
|
Ok(AxumMessage::Text(text)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Text(text))
|
backend_tx.send(TungsteniteMessage::Text(text)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Binary(data)) => {
|
Ok(AxumMessage::Binary(data)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Binary(data))
|
backend_tx.send(TungsteniteMessage::Binary(data)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Ping(data)) => {
|
Ok(AxumMessage::Ping(data)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Ping(data))
|
backend_tx.send(TungsteniteMessage::Ping(data)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Pong(data)) => {
|
Ok(AxumMessage::Pong(data)) => {
|
||||||
let res: Result<(), tungstenite::Error> = backend_tx
|
let res: Result<(), tungstenite::Error> =
|
||||||
.send(TungsteniteMessage::Pong(data))
|
backend_tx.send(TungsteniteMessage::Pong(data)).await;
|
||||||
.await;
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -761,7 +885,8 @@ async fn serve_favicon() -> impl IntoResponse {
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[("content-type", "image/x-icon")],
|
[("content-type", "image/x-icon")],
|
||||||
content.data,
|
content.data,
|
||||||
).into_response(),
|
)
|
||||||
|
.into_response(),
|
||||||
None => StatusCode::NOT_FOUND.into_response(),
|
None => StatusCode::NOT_FOUND.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -769,11 +894,9 @@ async fn serve_favicon() -> impl IntoResponse {
|
||||||
{
|
{
|
||||||
let favicon_path = get_ui_root().join("suite/public/favicon.ico");
|
let favicon_path = get_ui_root().join("suite/public/favicon.ico");
|
||||||
match tokio::fs::read(&favicon_path).await {
|
match tokio::fs::read(&favicon_path).await {
|
||||||
Ok(bytes) => (
|
Ok(bytes) => {
|
||||||
StatusCode::OK,
|
(StatusCode::OK, [("content-type", "image/x-icon")], bytes).into_response()
|
||||||
[("content-type", "image/x-icon")],
|
}
|
||||||
bytes,
|
|
||||||
).into_response(),
|
|
||||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -806,7 +929,7 @@ async fn handle_embedded_root_asset(
|
||||||
axum::extract::Path(filename): axum::extract::Path<String>,
|
axum::extract::Path(filename): axum::extract::Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if !ROOT_FILES.contains(&filename.as_str()) {
|
if !ROOT_FILES.contains(&filename.as_str()) {
|
||||||
return StatusCode::NOT_FOUND.into_response();
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
let asset_path = format!("suite/{}", filename);
|
let asset_path = format!("suite/{}", filename);
|
||||||
|
|
@ -829,11 +952,12 @@ fn add_static_routes(router: Router<AppState>, _suite_path: &Path) -> Router<App
|
||||||
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));
|
.route("/:dir/*path", get(handle_embedded_asset));
|
||||||
|
|
||||||
// Add root files
|
// Add root files
|
||||||
for file in ROOT_FILES {
|
for file in ROOT_FILES {
|
||||||
r = r.route(&format!("/{}", file), get(handle_embedded_root_asset))
|
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
|
||||||
}
|
}
|
||||||
|
|
@ -846,7 +970,7 @@ fn add_static_routes(router: Router<AppState>, _suite_path: &Path) -> Router<App
|
||||||
.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone()))
|
.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone()))
|
||||||
.nest_service(&format!("/{dir}"), ServeDir::new(path));
|
.nest_service(&format!("/{dir}"), ServeDir::new(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -874,7 +998,5 @@ pub fn configure_router() -> Router {
|
||||||
|
|
||||||
router = add_static_routes(router, &suite_path);
|
router = add_static_routes(router, &suite_path);
|
||||||
|
|
||||||
router
|
router.fallback(get(index)).with_state(state)
|
||||||
.fallback(get(index))
|
|
||||||
.with_state(state)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue