268 lines
7 KiB
Rust
268 lines
7 KiB
Rust
use rocket::{get, post, routes, serde::json::Json, State};
|
|
|
|
use crate::storage::AllControllers;
|
|
|
|
mod static_handler;
|
|
|
|
pub struct ServerState {
|
|
primary_name: String,
|
|
data: AllControllers,
|
|
tx_to_controllers: crate::controller::MultiTx,
|
|
}
|
|
|
|
impl ServerState {
|
|
pub fn new(
|
|
primary_name: &impl ToString,
|
|
data: AllControllers,
|
|
tx_to_controllers: crate::controller::MultiTx,
|
|
) -> Self {
|
|
let primary_name = primary_name.to_string();
|
|
Self {
|
|
primary_name,
|
|
data,
|
|
tx_to_controllers,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
|
// serve the html from disk if running in a debug build
|
|
// this allows editing the webpage without having to rebuild the executable
|
|
// but in release builds, bundle the entire webapp folder into the exe
|
|
let fileserver: Vec<rocket::Route> = if cfg!(debug_assertions) {
|
|
rocket::fs::FileServer::from(format!(
|
|
"{}/webapp",
|
|
std::env::var("CARGO_MANIFEST_DIR").unwrap()
|
|
))
|
|
.into()
|
|
} else {
|
|
static_handler::UiStatic {}.into()
|
|
};
|
|
rocket::build()
|
|
.attach(Cors)
|
|
.manage(state)
|
|
.mount("/", fileserver)
|
|
.mount(
|
|
"/",
|
|
routes![
|
|
metrics,
|
|
interfaces,
|
|
all_interfaces,
|
|
all_interfaces_full,
|
|
all_interfaces_settings,
|
|
primary_interface,
|
|
interface,
|
|
interface_full,
|
|
interface_settings,
|
|
get_control,
|
|
enable_control,
|
|
disable_control
|
|
],
|
|
)
|
|
}
|
|
|
|
#[get("/interfaces")]
|
|
fn interfaces(state: &State<ServerState>) -> Json<Vec<String>> {
|
|
Json(state.data.controller_names().cloned().collect())
|
|
}
|
|
|
|
#[get("/interfaces/primary")]
|
|
async fn primary_interface(
|
|
state: &State<ServerState>,
|
|
) -> Result<Json<crate::controller::CommonData>, ServerError> {
|
|
let s = state
|
|
.data
|
|
.get(&state.primary_name)
|
|
.ok_or(ServerError::InvalidPrimaryName)?
|
|
.read_state()
|
|
.await;
|
|
|
|
Ok(Json(s.as_ref().ok_or(ServerError::NoData)?.common()))
|
|
}
|
|
|
|
#[get("/interfaces/data")]
|
|
async fn all_interfaces(
|
|
state: &State<ServerState>,
|
|
) -> Json<Vec<(String, crate::controller::CommonData)>> {
|
|
let mut data = Vec::new();
|
|
|
|
for (k, v) in state.data.all_data() {
|
|
let v = v.read_state().await;
|
|
if let Some(v) = v.as_ref() {
|
|
data.push((k.clone(), v.common().clone()));
|
|
}
|
|
}
|
|
|
|
Json(data)
|
|
}
|
|
|
|
#[get("/interfaces/data/full")]
|
|
async fn all_interfaces_full(
|
|
state: &State<ServerState>,
|
|
) -> Json<Vec<(String, crate::controller::ControllerState)>> {
|
|
let mut data = Vec::new();
|
|
|
|
for (k, v) in state.data.all_data() {
|
|
let v = v.read_state().await;
|
|
if let Some(v) = v.as_ref() {
|
|
data.push((k.clone(), v.clone()));
|
|
}
|
|
}
|
|
|
|
Json(data)
|
|
}
|
|
|
|
#[get("/interfaces/settings")]
|
|
async fn all_interfaces_settings(
|
|
state: &State<ServerState>,
|
|
) -> Json<Vec<(String, crate::controller::ControllerSettings)>> {
|
|
let mut data = Vec::new();
|
|
|
|
for (k, v) in state.data.all_data() {
|
|
let v = v.read_settings().await;
|
|
if let Some(v) = v.as_ref() {
|
|
data.push((k.clone(), v.clone()));
|
|
}
|
|
}
|
|
|
|
Json(data)
|
|
}
|
|
|
|
#[get("/interface/<name>")]
|
|
async fn interface(
|
|
name: &str,
|
|
state: &State<ServerState>,
|
|
) -> Result<Json<crate::controller::CommonData>, ServerError> {
|
|
let data = state
|
|
.data
|
|
.get(name)
|
|
.ok_or(ServerError::NotFound)?
|
|
.read_state()
|
|
.await;
|
|
Ok(Json(data.as_ref().ok_or(ServerError::NoData)?.common()))
|
|
}
|
|
|
|
#[get("/interface/<name>/full")]
|
|
async fn interface_full(
|
|
name: &str,
|
|
state: &State<ServerState>,
|
|
) -> Result<Json<crate::controller::ControllerState>, ServerError> {
|
|
let data = state
|
|
.data
|
|
.get(name)
|
|
.ok_or(ServerError::NotFound)?
|
|
.read_state()
|
|
.await;
|
|
Ok(Json(data.as_ref().ok_or(ServerError::NoData)?.clone()))
|
|
}
|
|
|
|
#[get("/interface/<name>/settings")]
|
|
async fn interface_settings(
|
|
name: &str,
|
|
state: &State<ServerState>,
|
|
) -> Result<Json<crate::controller::ControllerSettings>, ServerError> {
|
|
let data = state
|
|
.data
|
|
.get(name)
|
|
.ok_or(ServerError::NotFound)?
|
|
.read_settings()
|
|
.await;
|
|
Ok(Json(data.as_ref().ok_or(ServerError::NoData)?.clone()))
|
|
}
|
|
|
|
#[get("/metrics")]
|
|
fn metrics() -> Result<String, ServerError> {
|
|
Ok(
|
|
prometheus::TextEncoder::new()
|
|
.encode_to_string(&prometheus::default_registry().gather())?,
|
|
)
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct ControlState {
|
|
enabled: bool,
|
|
}
|
|
|
|
#[get("/control")]
|
|
async fn get_control() -> Json<ControlState> {
|
|
let enabled = crate::config::access_config()
|
|
.await
|
|
.enable_secondary_control;
|
|
Json(ControlState { enabled })
|
|
}
|
|
|
|
#[post("/control/enable")]
|
|
async fn enable_control() {
|
|
log::warn!("enabling control");
|
|
crate::config::write_to_config()
|
|
.await
|
|
.enable_secondary_control = true;
|
|
}
|
|
|
|
#[post("/control/disable")]
|
|
async fn disable_control(state: &State<ServerState>) {
|
|
log::warn!("disabling control");
|
|
crate::config::write_to_config()
|
|
.await
|
|
.enable_secondary_control = false;
|
|
state
|
|
.tx_to_controllers
|
|
.send_to_all(crate::controller::VoltageCommand::Set(-1.0));
|
|
}
|
|
enum ServerError {
|
|
Prometheus,
|
|
NotFound,
|
|
InvalidPrimaryName,
|
|
NoData,
|
|
}
|
|
|
|
impl From<prometheus::Error> for ServerError {
|
|
fn from(_: prometheus::Error) -> Self {
|
|
Self::Prometheus
|
|
}
|
|
}
|
|
|
|
impl<'a> rocket::response::Responder<'a, 'a> for ServerError {
|
|
fn respond_to(self, _: &'a rocket::Request<'_>) -> rocket::response::Result<'a> {
|
|
Err(match self {
|
|
Self::NotFound => rocket::http::Status::NotFound,
|
|
Self::InvalidPrimaryName => rocket::http::Status::ServiceUnavailable,
|
|
Self::NoData | Self::Prometheus => rocket::http::Status::InternalServerError,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct Cors;
|
|
|
|
#[rocket::async_trait]
|
|
impl rocket::fairing::Fairing for Cors {
|
|
fn info(&self) -> rocket::fairing::Info {
|
|
rocket::fairing::Info {
|
|
name: "Add CORS headers to responses",
|
|
kind: rocket::fairing::Kind::Response,
|
|
}
|
|
}
|
|
|
|
async fn on_response<'r>(
|
|
&self,
|
|
_request: &'r rocket::Request<'_>,
|
|
response: &mut rocket::Response<'r>,
|
|
) {
|
|
response.set_header(rocket::http::Header::new(
|
|
"Access-Control-Allow-Origin",
|
|
"*",
|
|
));
|
|
response.set_header(rocket::http::Header::new(
|
|
"Access-Control-Allow-Methods",
|
|
"POST, GET, PATCH, OPTIONS",
|
|
));
|
|
response.set_header(rocket::http::Header::new(
|
|
"Access-Control-Allow-Headers",
|
|
"*",
|
|
));
|
|
response.set_header(rocket::http::Header::new(
|
|
"Access-Control-Allow-Credentials",
|
|
"true",
|
|
));
|
|
}
|
|
}
|