move to vanilla js/html with not-quite-hot-reloading
This commit is contained in:
parent
ccb3966078
commit
aab2d73f9d
|
@ -1,16 +1,11 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use rocket::{
|
||||
fairing::{Fairing, Info, Kind},
|
||||
http::{ContentType, Header, Status},
|
||||
outcome::IntoOutcome,
|
||||
response::Responder,
|
||||
route::{Handler, Outcome},
|
||||
http::Header,
|
||||
serde::json::Json,
|
||||
Data, Request, Response, State,
|
||||
Request, Response, State,
|
||||
};
|
||||
|
||||
use teslatte::{
|
||||
vehicles::{Endpoint, GetVehicleData, VehicleData},
|
||||
FleetVehicleApi,
|
||||
|
@ -18,6 +13,10 @@ use teslatte::{
|
|||
|
||||
use crate::{config::Config, Coords, FleetApiAuth};
|
||||
|
||||
use self::static_handler::UiStatic;
|
||||
|
||||
mod static_handler;
|
||||
|
||||
pub struct ServerState {
|
||||
pub config: Config,
|
||||
pub auth: FleetApiAuth,
|
||||
|
@ -29,79 +28,22 @@ pub async fn launch_server(state: ServerState) {
|
|||
}
|
||||
|
||||
fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
||||
let fileserver: Vec<rocket::Route> = if cfg!(debug_assertions) {
|
||||
rocket::fs::FileServer::from(format!(
|
||||
"{}/webapp",
|
||||
std::env::var("CARGO_MANIFEST_DIR").unwrap()
|
||||
))
|
||||
.into()
|
||||
} else {
|
||||
UiStatic {}.into()
|
||||
};
|
||||
rocket::build()
|
||||
.attach(Cors)
|
||||
.manage(state)
|
||||
.mount("/", UiStatic {})
|
||||
.mount("/", fileserver)
|
||||
.mount("/", routes![home, charge_state, flash])
|
||||
}
|
||||
|
||||
static UI_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../client/dist");
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct UiStatic {}
|
||||
|
||||
impl From<UiStatic> for Vec<rocket::Route> {
|
||||
fn from(server: UiStatic) -> Self {
|
||||
vec![rocket::Route::ranked(
|
||||
None,
|
||||
rocket::http::Method::Get,
|
||||
"/<path..>",
|
||||
server,
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Handler for UiStatic {
|
||||
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
|
||||
use rocket::http::uri::fmt::Path;
|
||||
|
||||
let path = req
|
||||
.segments::<rocket::http::uri::Segments<'_, Path>>(0..)
|
||||
.ok()
|
||||
.and_then(|segments| segments.to_path_buf(true).ok());
|
||||
|
||||
match path {
|
||||
Some(p) => {
|
||||
if p.as_os_str() == "" {
|
||||
let index = UI_DIR.get_file("index.html").map(|v| RawHtml {
|
||||
data: v.contents().to_vec(),
|
||||
name: PathBuf::from("index.html"),
|
||||
});
|
||||
|
||||
index.respond_to(req).or_forward((data, Status::NotFound))
|
||||
} else {
|
||||
let file = UI_DIR.get_file(&p).map(|v| RawHtml {
|
||||
data: v.contents().to_vec(),
|
||||
name: p,
|
||||
});
|
||||
file.respond_to(req).or_forward((data, Status::NotFound))
|
||||
}
|
||||
}
|
||||
None => Outcome::forward(data, Status::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RawHtml {
|
||||
data: Vec<u8>,
|
||||
name: PathBuf,
|
||||
}
|
||||
|
||||
impl<'r> Responder<'r, 'static> for RawHtml {
|
||||
fn respond_to(self, request: &'r Request<'_>) -> rocket::response::Result<'static> {
|
||||
let mut response = self.data.respond_to(request)?;
|
||||
if let Some(ext) = self.name.extension() {
|
||||
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
|
||||
response.set_header(ct);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/home")]
|
||||
async fn home(state: &State<ServerState>) -> Option<String> {
|
||||
let coords = state.get_coords().await.ok()?;
|
||||
|
|
75
server/src/server/static_handler.rs
Normal file
75
server/src/server/static_handler.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use include_dir::{include_dir, Dir};
|
||||
use rocket::{
|
||||
http::{ContentType, Status},
|
||||
outcome::IntoOutcome,
|
||||
response::Responder,
|
||||
route::{Handler, Outcome},
|
||||
Data, Request,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
static UI_DIR_FILES: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/webapp");
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) struct UiStatic {}
|
||||
|
||||
impl From<UiStatic> for Vec<rocket::Route> {
|
||||
fn from(server: UiStatic) -> Self {
|
||||
vec![rocket::Route::ranked(
|
||||
None,
|
||||
rocket::http::Method::Get,
|
||||
"/<path..>",
|
||||
server,
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Handler for UiStatic {
|
||||
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
|
||||
use rocket::http::uri::fmt::Path;
|
||||
|
||||
let path = req
|
||||
.segments::<rocket::http::uri::Segments<'_, Path>>(0..)
|
||||
.ok()
|
||||
.and_then(|segments| segments.to_path_buf(true).ok());
|
||||
|
||||
match path {
|
||||
Some(p) => {
|
||||
if p.as_os_str() == "" {
|
||||
let index = UI_DIR_FILES.get_file("index.html").map(|v| RawHtml {
|
||||
data: v.contents().to_vec(),
|
||||
name: PathBuf::from("index.html"),
|
||||
});
|
||||
|
||||
index.respond_to(req).or_forward((data, Status::NotFound))
|
||||
} else {
|
||||
let file = UI_DIR_FILES.get_file(&p).map(|v| RawHtml {
|
||||
data: v.contents().to_vec(),
|
||||
name: p,
|
||||
});
|
||||
file.respond_to(req).or_forward((data, Status::NotFound))
|
||||
}
|
||||
}
|
||||
None => Outcome::forward(data, Status::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RawHtml {
|
||||
data: Vec<u8>,
|
||||
name: PathBuf,
|
||||
}
|
||||
|
||||
impl<'r> Responder<'r, 'static> for RawHtml {
|
||||
fn respond_to(self, request: &'r Request<'_>) -> rocket::response::Result<'static> {
|
||||
let mut response = self.data.respond_to(request)?;
|
||||
if let Some(ext) = self.name.extension() {
|
||||
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
|
||||
response.set_header(ct);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
40
server/webapp/index.html
Normal file
40
server/webapp/index.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Tesla Charge Control</title>
|
||||
|
||||
<script type="text/javascript">
|
||||
const api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port;
|
||||
|
||||
function flash() {
|
||||
fetch(api_url + "/flash", { method: "POST" })
|
||||
.then((response) => console.log(response));
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
fetch(api_url + "/charge-state")
|
||||
.then((response) => response.json())
|
||||
.then((json) => update_charge_state(json));
|
||||
}
|
||||
|
||||
function update_charge_state(charge_state) {
|
||||
var info_div = document.getElementById("info");
|
||||
while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) }
|
||||
var arr = ["Battery " + charge_state.battery_level + "%", "Range: " + (charge_state.battery_range * 1.60934).toFixed(1) + "km", "Charging at " + charge_state.charge_amps + " amps"];
|
||||
for (line in arr) {
|
||||
el = document.createElement('p');
|
||||
el.appendChild(document.createTextNode(arr[line]));
|
||||
info_div.appendChild(el)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
<button onclick="refresh()">refresh</button>
|
||||
<div id="info"></div>
|
||||
<button onclick="flash()">flash</button>
|
||||
|
||||
</html>
|
Loading…
Reference in a new issue