Compare commits

..

No commits in common. "cb9e09458257b69f6b66edcde97745b1db2867e3" and "88e9c4df82c6d9d0ed02fd7ce31ea60ae88a04d3" have entirely different histories.

6 changed files with 240 additions and 750 deletions

3
.gitignore vendored
View file

@ -1,4 +1,3 @@
target /target
**/*.rs.bk **/*.rs.bk
*.ron *.ron
.vscode/

806
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@ edition = "2018"
reqwest = "0.9.15" reqwest = "0.9.15"
serde = { version = "1.0.90", features = ["derive"] } serde = { version = "1.0.90", features = ["derive"] }
ron = "0.5" ron = "0.5"
actix-web = "1.0.0-beta.2" actix-web = "0.7"
tokio = "0.1" tokio = "0.1"
tera = "0.11" actix = "0.7"
env_logger = "0.6.1"

View file

@ -1,19 +1,13 @@
extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger;
extern crate reqwest; extern crate reqwest;
extern crate ron; extern crate ron;
extern crate serde; extern crate serde;
#[macro_use]
extern crate tera;
mod utils; use actix::System;
use self::utils::EpochTimestamp;
use actix_web::{ use actix_web::{
error::ErrorInternalServerError, http::{Method, StatusCode},
middleware::Logger, server, App, HttpResponse, Json, Result as WebResult, State,
web::{resource, Data},
App, Error as WebError, HttpResponse, HttpServer, Result as WebResult,
}; };
use reqwest::{Client, Url, UrlError}; use reqwest::{Client, Url, UrlError};
use ron::de::from_str; use ron::de::from_str;
@ -21,11 +15,11 @@ use serde::{Deserialize, Serialize};
use std::{ use std::{
error::Error, error::Error,
fs::read_to_string, fs::read_to_string,
sync::{Arc, Mutex, MutexGuard}, sync::{Arc, Mutex},
time::{Duration, Instant},
}; };
use tera::{Context, Tera};
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
struct EndpointConf { struct EndpointConf {
label: Option<String>, label: Option<String>,
endpoint: Option<String>, endpoint: Option<String>,
@ -34,22 +28,21 @@ struct EndpointConf {
body: Option<String>, body: Option<String>,
} }
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
struct WebsiteConf { struct WebsiteConf {
label: String, label: String,
base: String, base: String,
endpoints: Vec<EndpointConf>, endpoints: Vec<EndpointConf>,
} }
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
struct Config { struct Config {
refresh_time: u64, refresh_time: u64,
bind_address: String,
websites: Vec<WebsiteConf>, websites: Vec<WebsiteConf>,
} }
#[derive(Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Status { struct Status {
status: u8, status: u8,
location: String, location: String,
domain: String, domain: String,
@ -57,9 +50,8 @@ pub struct Status {
error: Option<String>, error: Option<String>,
} }
#[derive(Serialize)] struct FetchResults {
pub struct FetchResults { last_update: Instant,
last_update: EpochTimestamp,
refresh_time: u64, refresh_time: u64,
config: Config, config: Config,
statuses: Vec<Status>, statuses: Vec<Status>,
@ -67,55 +59,54 @@ pub struct FetchResults {
type StatusState = Arc<Mutex<FetchResults>>; type StatusState = Arc<Mutex<FetchResults>>;
fn index(tmpl: Data<Tera>, state: Data<StatusState>) -> WebResult<HttpResponse, WebError> { fn index(state: State<StatusState>) -> HttpResponse {
let state = update_state(state.lock().unwrap()); let mut state = state.lock().unwrap();
let mut ctx = Context::new(); let mut result = String::new();
ctx.insert("results", &*state); if Instant::now().duration_since(state.last_update) > Duration::from_secs(state.refresh_time) {
let s = tmpl result = format!(
.render("index.html", &tera::Context::new()) "it has been {:} seconds since last update, updating",
.map_err(|_| ErrorInternalServerError("Template error"))?; Instant::now().duration_since(state.last_update).as_secs()
Ok(HttpResponse::Ok().content_type("text/html").body(s)) );
} state.last_update = Instant::now();
fn json_endpoint(state: Data<StatusState>) -> HttpResponse {
let state = update_state(state.lock().unwrap());
HttpResponse::Ok().json(&state.statuses)
}
fn update_state(mut state: MutexGuard<FetchResults>) -> MutexGuard<FetchResults> {
if EpochTimestamp::now() - state.last_update >= state.refresh_time {
state.last_update = EpochTimestamp::now();
state.statuses = update_status(&state.config); state.statuses = update_status(&state.config);
} }
state let result = format!("{}\n{:?}", result, state.statuses);
HttpResponse::with_body(StatusCode::OK, result)
}
fn json_endpoint(state: State<StatusState>) -> WebResult<Json<Vec<Status>>> {
let mut state = state.lock().unwrap();
if Instant::now().duration_since(state.last_update) > Duration::from_secs(state.refresh_time) {
state.last_update = Instant::now();
state.statuses = update_status(&state.config);
}
Ok(Json(state.statuses.clone()))
} }
fn main() -> Result<(), Box<Error>> { fn main() -> Result<(), Box<Error>> {
let config = from_str::<Config>(&read_to_string("./endstat_conf.ron")?)?; let config = from_str::<Config>(&read_to_string("./endstat_conf.ron")?)?;
let bind_addr = config.bind_address.clone();
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
HttpServer::new(move || { println!("running server");
let state = Arc::from(Mutex::from(FetchResults { let sys = System::new("status");
last_update: EpochTimestamp::now(),
refresh_time: config.refresh_time.clone(), let a: Arc<Mutex<FetchResults>> = Arc::from(Mutex::from(FetchResults {
last_update: Instant::now(),
refresh_time: config.refresh_time,
config: config.clone(), config: config.clone(),
statuses: update_status(&config), statuses: update_status(&config),
})); }));
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
App::new() server::new(move || {
.data(state) App::with_state(a.clone())
.data(tera) .resource("/", |r| r.get().with(index))
.wrap(Logger::default()) .resource("/api", |r| r.get().with(json_endpoint))
.service(resource("/").to(index))
.service(resource("/api").to(json_endpoint))
}) })
.bind(&bind_addr)? .bind("0.0.0.0:8080")?
.run()?; .start();
sys.run();
Ok(()) Ok(())
} }

View file

@ -1,49 +0,0 @@
use serde::{Serialize, Serializer};
use std::{
fmt::{Display, Formatter, Result as FmtResult},
ops::Sub,
time::{SystemTime, UNIX_EPOCH},
};
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub struct EpochTimestamp(SystemTime);
impl EpochTimestamp {
pub fn now() -> Self {
EpochTimestamp(SystemTime::now())
}
}
impl Sub for EpochTimestamp {
type Output = u64;
fn sub(self, other: EpochTimestamp) -> u64 {
self.0.duration_since(other.0).unwrap_or_default().as_secs()
}
}
impl Display for EpochTimestamp {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"{}",
self.0
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
)
}
}
impl Serialize for EpochTimestamp {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_u64(
self.0
.duration_since(SystemTime::from(UNIX_EPOCH))
.unwrap_or_default()
.as_secs(),
)
}
}

View file

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Actix web</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
<h3>What is your name?</h3>
<form>
<input type="text" name="name" /><br/>
<p><input type="submit"></p>
</form>
</p>
</body>
</html>