mirror of
https://github.com/Noratrieb/haesli.git
synced 2026-01-14 19:55:03 +01:00
embedded
This commit is contained in:
parent
d7b574cef4
commit
85ef2aa7fd
6 changed files with 395 additions and 52 deletions
149
amqp_dashboard/src/archive.rs
Normal file
149
amqp_dashboard/src/archive.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{header, Request, Response, StatusCode},
|
||||
};
|
||||
use mime_guess::mime;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{Debug, Formatter},
|
||||
future,
|
||||
io::Cursor,
|
||||
path::Path,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tracing::trace;
|
||||
use zip::ZipArchive;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum StaticFileKind {
|
||||
File { mime: mime::Mime },
|
||||
Directory,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StaticFile {
|
||||
data: Vec<u8>,
|
||||
kind: StaticFileKind,
|
||||
}
|
||||
|
||||
impl Debug for StaticFile {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StaticFile")
|
||||
.field("kind", &self.kind)
|
||||
.field("data", &format!("[{} bytes]", self.data.len()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StaticFileService {
|
||||
files: HashMap<String, StaticFile>,
|
||||
}
|
||||
|
||||
impl StaticFileService {
|
||||
pub fn new(zip: &[u8]) -> Self {
|
||||
let mut archive = ZipArchive::new(Cursor::new(zip)).unwrap();
|
||||
|
||||
let mut files = HashMap::with_capacity(archive.len());
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i).unwrap();
|
||||
let mut data = Vec::with_capacity(usize::try_from(file.size()).unwrap());
|
||||
std::io::copy(&mut file, &mut data).unwrap();
|
||||
|
||||
trace!(name = %file.name(), size = %file.size(),"Unpacking dashboard frontend file");
|
||||
|
||||
let path = Path::new(file.name());
|
||||
|
||||
let kind = if file.is_dir() {
|
||||
StaticFileKind::Directory
|
||||
} else {
|
||||
let mime = match path.extension() {
|
||||
Some(ext) => {
|
||||
mime_guess::from_ext(&ext.to_string_lossy()).first_or_octet_stream()
|
||||
}
|
||||
None => mime::APPLICATION_OCTET_STREAM,
|
||||
};
|
||||
StaticFileKind::File { mime }
|
||||
};
|
||||
|
||||
files.insert(
|
||||
file.name().trim_start_matches('/').to_owned(),
|
||||
StaticFile { data, kind },
|
||||
);
|
||||
}
|
||||
|
||||
trace!(?files, "Created StaticFileService");
|
||||
|
||||
Self { files }
|
||||
}
|
||||
|
||||
fn call_inner(&mut self, req: Request<Body>) -> Result<Response<Body>, anyhow::Error> {
|
||||
trace!(?req, "Got request for static file");
|
||||
|
||||
let path = req.uri().path().trim_start_matches('/');
|
||||
|
||||
let entry = self.files.get(path);
|
||||
|
||||
match entry {
|
||||
Some(file) => {
|
||||
if let StaticFileKind::File { mime } = &file.kind {
|
||||
trace!(%path, "Found file");
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, mime.essence_str())
|
||||
.body(Body::from(file.data.clone()))?)
|
||||
} else {
|
||||
trace!(%path, "Found directory, trying to append index.html");
|
||||
|
||||
let new_path = if path.is_empty() {
|
||||
"index.html".to_owned()
|
||||
} else {
|
||||
let new_path = path.trim_end_matches('/');
|
||||
format!("{}/index.html", new_path)
|
||||
};
|
||||
|
||||
match self.files.get(&new_path) {
|
||||
Some(file) => {
|
||||
trace!(%new_path, "Found index.html");
|
||||
if let StaticFileKind::File { mime } = &file.kind {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, mime.essence_str())
|
||||
.body(Body::from(file.data.clone()))?)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
None => {
|
||||
trace!(%new_path, "Did not find index.html");
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::empty())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
trace!(%path, "Did not find file");
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::empty())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tower::Service<Request<Body>> for StaticFileService {
|
||||
type Response = Response<Body>;
|
||||
type Error = anyhow::Error;
|
||||
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
future::ready(self.call_inner(req))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +1,42 @@
|
|||
#![warn(rust_2018_idioms)]
|
||||
|
||||
mod archive;
|
||||
|
||||
use crate::archive::StaticFileService;
|
||||
use amqp_core::GlobalData;
|
||||
use axum::{
|
||||
body::{boxed, Full},
|
||||
http::Method,
|
||||
response::{Html, IntoResponse, Response},
|
||||
routing::get,
|
||||
http::{Method, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::{get, get_service},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::info;
|
||||
use tracing::{error, info};
|
||||
|
||||
const INDEX_HTML: &str = include_str!("../assets/index.html");
|
||||
const SCRIPT_JS: &str = include_str!("../assets/script.js");
|
||||
const STYLE_CSS: &str = include_str!("../assets/style.css");
|
||||
// const INDEX_HTML: &str = include_str!("../assets/index.html");
|
||||
// const SCRIPT_JS: &str = include_str!("../assets/script.js");
|
||||
// const STYLE_CSS: &str = include_str!("../assets/style.css");
|
||||
|
||||
const DATA_ZIP: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/frontend.zip"));
|
||||
|
||||
pub async fn dashboard(global_data: GlobalData) {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods(vec![Method::GET])
|
||||
.allow_origin(Any);
|
||||
|
||||
let static_file_service =
|
||||
get_service(StaticFileService::new(DATA_ZIP)).handle_error(|error| async move {
|
||||
error!(?error, "Error in static file service");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(get_index_html))
|
||||
.route("/script.js", get(get_script_js))
|
||||
.route("/style.css", get(get_style_css))
|
||||
.route("/api/data", get(move || get_data(global_data)).layer(cors));
|
||||
//.route("/", get(get_index_html))
|
||||
//.route("/script.js", get(get_script_js))
|
||||
//.route("/style.css", get(get_style_css))
|
||||
.route("/api/data", get(move || get_data(global_data)).layer(cors))
|
||||
.fallback(static_file_service);
|
||||
|
||||
let socket_addr = "0.0.0.0:8080".parse().unwrap();
|
||||
|
||||
|
|
@ -37,24 +48,24 @@ pub async fn dashboard(global_data: GlobalData) {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
async fn get_index_html() -> impl IntoResponse {
|
||||
info!("Requesting index.html");
|
||||
Html(INDEX_HTML)
|
||||
}
|
||||
|
||||
async fn get_script_js() -> Response {
|
||||
Response::builder()
|
||||
.header("content-type", "application/javascript")
|
||||
.body(boxed(Full::from(SCRIPT_JS)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn get_style_css() -> Response {
|
||||
Response::builder()
|
||||
.header("content-type", "text/css")
|
||||
.body(boxed(Full::from(STYLE_CSS)))
|
||||
.unwrap()
|
||||
}
|
||||
//async fn get_index_html() -> impl IntoResponse {
|
||||
// info!("Requesting index.html");
|
||||
// Html(INDEX_HTML)
|
||||
//}
|
||||
//
|
||||
//async fn get_script_js() -> Response {
|
||||
// Response::builder()
|
||||
// .header("content-type", "application/javascript")
|
||||
// .body(boxed(Full::from(SCRIPT_JS)))
|
||||
// .unwrap()
|
||||
//}
|
||||
//
|
||||
//async fn get_style_css() -> Response {
|
||||
// Response::builder()
|
||||
// .header("content-type", "text/css")
|
||||
// .body(boxed(Full::from(STYLE_CSS)))
|
||||
// .unwrap()
|
||||
//}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Data {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue