1use axum::{
2 body::Body,
3 extract::State,
4 http::{header, StatusCode, Uri},
5 response::IntoResponse,
6 Json,
7};
8use rust_embed::RustEmbed;
9use serde::Serialize;
10
11#[derive(RustEmbed)]
12#[folder = "assets/webui"]
13struct Assets;
14
15#[derive(Serialize)]
16pub struct RuntimeConfig {
17 #[serde(rename = "backendUrl")]
18 pub backend_url: String,
19}
20
21pub async fn serve_runtime_config(State(config): State<crate::ServerConfig>) -> impl IntoResponse {
22 let backend_url = config.backend_url.unwrap_or_else(|| "/api".to_string());
25
26 Json(RuntimeConfig { backend_url })
27}
28
29pub async fn serve_ui_assets(uri: Uri) -> impl IntoResponse {
30 let mut path = uri.path().trim_start_matches("/ui/").to_string();
31
32 if path.is_empty() {
33 path = "index.html".to_string();
34 }
35
36 match Assets::get(&path) {
37 Some(content) => {
38 let mime = mime_guess::from_path(&path).first_or_octet_stream();
39 let mime_header = header::HeaderValue::from_str(mime.as_ref())
40 .unwrap_or_else(|_| header::HeaderValue::from_static("application/octet-stream"));
41 ([(header::CONTENT_TYPE, mime_header)], Body::from(content.data)).into_response()
42 }
43 None => {
44 if path.contains('.') {
50 StatusCode::NOT_FOUND.into_response()
51 } else {
52 match Assets::get("index.html") {
54 Some(content) => {
55 let mime = mime_guess::from_path("index.html").first_or_octet_stream();
56 let mime_header = header::HeaderValue::from_str(mime.as_ref())
57 .unwrap_or_else(|_| header::HeaderValue::from_static("text/html"));
58 ([(header::CONTENT_TYPE, mime_header)], Body::from(content.data))
59 .into_response()
60 }
61 None => StatusCode::NOT_FOUND.into_response(),
62 }
63 }
64 }
65 }
66}
67
68pub async fn root_redirect() -> impl IntoResponse {
69 axum::response::Redirect::to("/ui/")
70}
71
72pub async fn serve_ui_index() -> impl IntoResponse {
73 match Assets::get("index.html") {
74 Some(content) => {
75 let mime_header = header::HeaderValue::from_static("text/html; charset=utf-8");
76 ([(header::CONTENT_TYPE, mime_header)], Body::from(content.data)).into_response()
77 }
78 None => StatusCode::NOT_FOUND.into_response(),
79 }
80}