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 anyhow::{Context, Result};
|
||||||
use include_dir::{include_dir, Dir};
|
|
||||||
use rocket::{
|
use rocket::{
|
||||||
fairing::{Fairing, Info, Kind},
|
fairing::{Fairing, Info, Kind},
|
||||||
http::{ContentType, Header, Status},
|
http::Header,
|
||||||
outcome::IntoOutcome,
|
|
||||||
response::Responder,
|
|
||||||
route::{Handler, Outcome},
|
|
||||||
serde::json::Json,
|
serde::json::Json,
|
||||||
Data, Request, Response, State,
|
Request, Response, State,
|
||||||
};
|
};
|
||||||
|
|
||||||
use teslatte::{
|
use teslatte::{
|
||||||
vehicles::{Endpoint, GetVehicleData, VehicleData},
|
vehicles::{Endpoint, GetVehicleData, VehicleData},
|
||||||
FleetVehicleApi,
|
FleetVehicleApi,
|
||||||
|
@ -18,6 +13,10 @@ use teslatte::{
|
||||||
|
|
||||||
use crate::{config::Config, Coords, FleetApiAuth};
|
use crate::{config::Config, Coords, FleetApiAuth};
|
||||||
|
|
||||||
|
use self::static_handler::UiStatic;
|
||||||
|
|
||||||
|
mod static_handler;
|
||||||
|
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub auth: FleetApiAuth,
|
pub auth: FleetApiAuth,
|
||||||
|
@ -29,79 +28,22 @@ pub async fn launch_server(state: ServerState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
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()
|
rocket::build()
|
||||||
.attach(Cors)
|
.attach(Cors)
|
||||||
.manage(state)
|
.manage(state)
|
||||||
.mount("/", UiStatic {})
|
.mount("/", fileserver)
|
||||||
.mount("/", routes![home, charge_state, flash])
|
.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")]
|
#[get("/home")]
|
||||||
async fn home(state: &State<ServerState>) -> Option<String> {
|
async fn home(state: &State<ServerState>) -> Option<String> {
|
||||||
let coords = state.get_coords().await.ok()?;
|
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