Compare commits
4 Commits
ef049e5171
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
e9ac30898b
|
|||
|
cff7dddf4e
|
|||
|
f40922030c
|
|||
|
38186e4371
|
2385
Cargo.lock
generated
2385
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
@ -19,24 +19,10 @@ duplicate = "0.2"
|
|||||||
handlebars = "3"
|
handlebars = "3"
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
ron = "0.6.4"
|
ron = "0.6.4"
|
||||||
|
rocket = { version = "0.5", features = ["secrets", "msgpack", "json"] }
|
||||||
[dependencies.rocket]
|
rocket_dyn_templates = { version = "0.1.0", features = ["handlebars"] }
|
||||||
#version = "0.4"
|
tokio = { version = "1", features = ["fs"] }
|
||||||
git = "https://github.com/SergioBenitez/Rocket"
|
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] }
|
||||||
branch = "master"
|
clap = { version = "4.4.18", features = ["derive", "env"] }
|
||||||
features = ["secrets"]
|
eyre = "0.6.11"
|
||||||
|
color-eyre = "0.6.2"
|
||||||
[dependencies.rocket_contrib]
|
|
||||||
#version = "0.4"
|
|
||||||
git = "https://github.com/SergioBenitez/Rocket"
|
|
||||||
branch = "master"
|
|
||||||
features = ["handlebars_templates", "uuid"]
|
|
||||||
|
|
||||||
[dependencies.tokio]
|
|
||||||
version = "1"
|
|
||||||
features = ["fs"]
|
|
||||||
|
|
||||||
[dependencies.reqwest]
|
|
||||||
version = "0.11"
|
|
||||||
default-features = false
|
|
||||||
features = ["rustls-tls"]
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
pub type ServiceId = String;
|
pub type ServiceId = String;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, Serialize)]
|
||||||
pub enum HealthStatus {
|
pub enum HealthStatus {
|
||||||
/// The service is up and running
|
/// The service is up and running
|
||||||
Up,
|
Up,
|
||||||
@ -69,8 +69,8 @@ impl HealthState {
|
|||||||
last_update: Mutex::new(None),
|
last_update: Mutex::new(None),
|
||||||
health: config
|
health: config
|
||||||
.services
|
.services
|
||||||
.iter()
|
.keys()
|
||||||
.map(|(id, _config)| (id.clone(), Mutex::new(None)))
|
.map(|id| (id.clone(), Mutex::new(None)))
|
||||||
.collect(),
|
.collect(),
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ impl HttpHealthCheckMode {
|
|||||||
.map_err(|e| error!("invalid status code: {}", e))
|
.map_err(|e| error!("invalid status code: {}", e))
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|status| status == &actual)
|
.filter(|status| status == &actual)
|
||||||
.and_then(|_| if_valid),
|
.and(if_valid),
|
||||||
PartialStatusCode::Status5XX if actual.is_server_error() => if_valid,
|
PartialStatusCode::Status5XX if actual.is_server_error() => if_valid,
|
||||||
PartialStatusCode::Status4XX if actual.is_server_error() => if_valid,
|
PartialStatusCode::Status4XX if actual.is_server_error() => if_valid,
|
||||||
PartialStatusCode::Status3XX if actual.is_redirection() => if_valid,
|
PartialStatusCode::Status3XX if actual.is_redirection() => if_valid,
|
||||||
@ -123,13 +123,13 @@ impl HttpHealthCheckMode {
|
|||||||
let check_up = self
|
let check_up = self
|
||||||
.up_status_codes
|
.up_status_codes
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|up_code| validate_status(&up_code, response_status, HealthStatus::Up));
|
.flat_map(|up_code| validate_status(up_code, response_status, HealthStatus::Up));
|
||||||
|
|
||||||
// Check if response status matches expected status codes for Down
|
// Check if response status matches expected status codes for Down
|
||||||
let check_down = self
|
let check_down = self
|
||||||
.down_status_codes
|
.down_status_codes
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|down_code| validate_status(&down_code, response_status, HealthStatus::Down));
|
.flat_map(|down_code| validate_status(down_code, response_status, HealthStatus::Down));
|
||||||
|
|
||||||
// Compute status, defaulting to Errored if neigher Up nor Down matched
|
// Compute status, defaulting to Errored if neigher Up nor Down matched
|
||||||
check_up
|
check_up
|
||||||
|
|||||||
73
src/main.rs
73
src/main.rs
@ -1,15 +1,53 @@
|
|||||||
mod health;
|
mod health;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
|
use eyre::Context;
|
||||||
use health::HealthState;
|
use health::HealthState;
|
||||||
use rocket_contrib::serve::StaticFiles;
|
use rocket::fs::FileServer;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{env, io};
|
|
||||||
use tokio::{fs, task, time::sleep};
|
use tokio::{fs, task, time::sleep};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Opt {
|
||||||
|
/// Path to the config file.
|
||||||
|
#[clap(short, long, env = "CONFIG_PATH", default_value = "config.ron")]
|
||||||
|
config: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::main]
|
||||||
|
async fn main() -> eyre::Result<()> {
|
||||||
|
dotenv().ok();
|
||||||
|
let opt = Opt::parse();
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
let config = fs::read_to_string(&opt.config)
|
||||||
|
.await
|
||||||
|
.wrap_err_with(|| format!("failed to read config file: {:?}", opt.config))?;
|
||||||
|
let config = ron::from_str(&config)
|
||||||
|
.wrap_err_with(|| format!("failed to parse config file: {:?}", opt.config))?;
|
||||||
|
let state = Arc::new(HealthState::new(config));
|
||||||
|
|
||||||
|
let rocket = rocket::build()
|
||||||
|
.attach(Template::fairing())
|
||||||
|
.manage(Arc::clone(&state))
|
||||||
|
.mount("/static", FileServer::from("static"))
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
rocket::routes![routes::pages::dashboard, routes::api::status],
|
||||||
|
);
|
||||||
|
|
||||||
|
start_poller(state);
|
||||||
|
|
||||||
|
rocket.launch().await.wrap_err("rocket failed to launch")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn start_poller(state: Arc<HealthState>) {
|
fn start_poller(state: Arc<HealthState>) {
|
||||||
task::spawn(async move {
|
task::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
@ -18,32 +56,3 @@ fn start_poller(state: Arc<HealthState>) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::main]
|
|
||||||
async fn main() -> io::Result<()> {
|
|
||||||
dotenv().ok();
|
|
||||||
|
|
||||||
let config_path = env::var("CONFIG_PATH").unwrap_or("config.ron".into());
|
|
||||||
|
|
||||||
let config = fs::read_to_string(&config_path)
|
|
||||||
.await
|
|
||||||
.expect("failed to read config file");
|
|
||||||
let config = ron::from_str(&config).expect("failed to parse config file");
|
|
||||||
let state = Arc::new(HealthState::new(config));
|
|
||||||
|
|
||||||
let rocket = rocket::ignite()
|
|
||||||
//.attach(Template::custom(|engines| {
|
|
||||||
//handlebars_util::register_helpers(engines)
|
|
||||||
//}))
|
|
||||||
.attach(Template::fairing())
|
|
||||||
.manage(Arc::clone(&state))
|
|
||||||
.mount("/static", StaticFiles::from("static"))
|
|
||||||
.mount("/", rocket::routes![routes::pages::dashboard]);
|
|
||||||
//.register(rocket::catchers![auth::login_page,]);
|
|
||||||
|
|
||||||
start_poller(state);
|
|
||||||
|
|
||||||
rocket.launch().await.expect("rocket failed to launch");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
use crate::health::HealthState;
|
||||||
|
use crate::health::HealthStatus;
|
||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::Utc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::{get, State};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Status {
|
||||||
|
last_update: Option<DateTime<Utc>>,
|
||||||
|
services: BTreeMap<String, HealthStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/status")]
|
||||||
|
pub async fn status(state: &State<Arc<HealthState>>) -> Json<Status> {
|
||||||
|
let last_update = *state.last_update.lock().await;
|
||||||
|
let mut services = BTreeMap::new();
|
||||||
|
|
||||||
|
for (id, status) in &state.health {
|
||||||
|
let Some(status) = *status.lock().await else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
services.insert(id.clone(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(Status {
|
||||||
|
last_update,
|
||||||
|
services,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -2,12 +2,12 @@ use crate::health::HealthState;
|
|||||||
use crate::health::HealthStatus;
|
use crate::health::HealthStatus;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use rocket::{get, State};
|
use rocket::{get, State};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn dashboard(state: State<'_, Arc<HealthState>>) -> Template {
|
pub async fn dashboard(state: &State<Arc<HealthState>>) -> Template {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct Service {
|
struct Service {
|
||||||
name: String,
|
name: String,
|
||||||
@ -27,7 +27,7 @@ pub async fn dashboard(state: State<'_, Arc<HealthState>>) -> Template {
|
|||||||
last_update: last_update
|
last_update: last_update
|
||||||
.map(|last_update| {
|
.map(|last_update| {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
if now.date() == last_update.date() {
|
if now.date_naive() == last_update.date_naive() {
|
||||||
format!("UTC {}", last_update.format("%H:%M:%S"))
|
format!("UTC {}", last_update.format("%H:%M:%S"))
|
||||||
} else {
|
} else {
|
||||||
format!("UTC {}", last_update.format("%Y-%m-%d %H:%M:%S"))
|
format!("UTC {}", last_update.format("%Y-%m-%d %H:%M:%S"))
|
||||||
@ -43,8 +43,7 @@ pub async fn dashboard(state: State<'_, Arc<HealthState>>) -> Template {
|
|||||||
Some(HealthStatus::Down) => ("red", "DOWN"),
|
Some(HealthStatus::Down) => ("red", "DOWN"),
|
||||||
Some(HealthStatus::Errored) => ("orange", "ERRORED"),
|
Some(HealthStatus::Errored) => ("orange", "ERRORED"),
|
||||||
None => ("#5b5b5b", "UNKNOWN"),
|
None => ("#5b5b5b", "UNKNOWN"),
|
||||||
}
|
};
|
||||||
.into();
|
|
||||||
|
|
||||||
context.services.push(Service {
|
context.services.push(Service {
|
||||||
name: id.clone(),
|
name: id.clone(),
|
||||||
|
|||||||
Reference in New Issue
Block a user