Add html page
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
(
|
||||
update_period: 30,
|
||||
services: {
|
||||
"example": (
|
||||
url: "https://example.org",
|
||||
|
||||
@ -31,18 +31,24 @@ pub enum HttpHealthCheckMode {
|
||||
}
|
||||
|
||||
pub struct HealthState {
|
||||
pub last_update: Mutex<Option<DateTime<Utc>>>,
|
||||
pub health: HashMap<ServiceId, Mutex<Option<HealthStatus>>>,
|
||||
pub config: HealthConfig,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct HealthConfig {
|
||||
/// The time between updates (in seconds)
|
||||
pub update_period: u64,
|
||||
|
||||
/// The list of services
|
||||
pub services: HashMap<ServiceId, ServiceConfig>,
|
||||
}
|
||||
|
||||
impl HealthState {
|
||||
pub fn new(config: HealthConfig) -> HealthState {
|
||||
HealthState {
|
||||
last_update: Mutex::new(None),
|
||||
health: config
|
||||
.services
|
||||
.iter()
|
||||
@ -53,6 +59,8 @@ impl HealthState {
|
||||
}
|
||||
|
||||
pub async fn update(&self) {
|
||||
*self.last_update.lock().await = Some(Utc::now());
|
||||
|
||||
for (id, status) in &self.health {
|
||||
let url = &self.config.services[id].url;
|
||||
info!("service [{}] querying {}", id, url);
|
||||
|
||||
21
src/main.rs
21
src/main.rs
@ -6,8 +6,18 @@ use health::HealthState;
|
||||
use rocket_contrib::serve::StaticFiles;
|
||||
use rocket_contrib::templates::Template;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{env, io};
|
||||
use tokio::fs;
|
||||
use tokio::{fs, task, time::sleep};
|
||||
|
||||
fn start_poller(state: Arc<HealthState>) {
|
||||
task::spawn(async move {
|
||||
loop {
|
||||
state.update().await;
|
||||
sleep(Duration::from_secs(state.config.update_period)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
@ -19,17 +29,20 @@ async fn main() -> io::Result<()> {
|
||||
.await
|
||||
.expect("failed to read config file");
|
||||
let config = ron::from_str(&config).expect("failed to parse config file");
|
||||
let state = HealthState::new(config);
|
||||
let state = Arc::new(HealthState::new(config));
|
||||
|
||||
let rocket = rocket::ignite()
|
||||
//.attach(Template::custom(|engines| {
|
||||
//handlebars_util::register_helpers(engines)
|
||||
//}))
|
||||
.manage(Arc::new(state))
|
||||
.attach(Template::fairing())
|
||||
.manage(Arc::clone(&state))
|
||||
.mount("/static", StaticFiles::from("static"))
|
||||
.mount("/", rocket::routes![routes::pages::index,]);
|
||||
.mount("/", rocket::routes![routes::pages::dashboard]);
|
||||
//.register(rocket::catchers![auth::login_page,]);
|
||||
|
||||
start_poller(state);
|
||||
|
||||
rocket.launch().await.expect("rocket failed to launch");
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,22 +1,57 @@
|
||||
use crate::health::HealthState;
|
||||
use crate::health::HealthStatus;
|
||||
use chrono::Utc;
|
||||
use rocket::{get, State};
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::spawn;
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(state: State<'_, Arc<HealthState>>) -> String {
|
||||
{
|
||||
let state = Arc::clone(state.inner());
|
||||
spawn(async move {
|
||||
state.update().await;
|
||||
});
|
||||
pub async fn dashboard(state: State<'_, Arc<HealthState>>) -> Template {
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Service {
|
||||
name: String,
|
||||
status_text: &'static str,
|
||||
status_color: &'static str,
|
||||
}
|
||||
|
||||
let mut out = String::new();
|
||||
#[derive(Debug, Serialize)]
|
||||
struct TemplateContext {
|
||||
last_update: String,
|
||||
services: Vec<Service>,
|
||||
}
|
||||
|
||||
let last_update = *state.last_update.lock().await;
|
||||
|
||||
let mut context = TemplateContext {
|
||||
last_update: last_update
|
||||
.map(|last_update| {
|
||||
let now = Utc::now();
|
||||
if now.date() == last_update.date() {
|
||||
format!("UTC {}", last_update.format("%H:%M:%S"))
|
||||
} else {
|
||||
format!("UTC {}", last_update.format("%Y-%m-%d %H:%M:%S"))
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| "never".to_string()),
|
||||
services: vec![],
|
||||
};
|
||||
|
||||
for (id, status) in &state.health {
|
||||
out.push_str(&format!("{}: {:?}\n", id, &*status.lock().await));
|
||||
let (status_color, status_text) = match *status.lock().await {
|
||||
Some(HealthStatus::Up) => ("green", "UP"),
|
||||
Some(HealthStatus::Down) => ("red", "DOWN"),
|
||||
Some(HealthStatus::Errored) => ("orange", "ERRORED"),
|
||||
None => ("#5b5b5b", "UNKNOWN"),
|
||||
}
|
||||
.into();
|
||||
|
||||
context.services.push(Service {
|
||||
name: id.clone(),
|
||||
status_color,
|
||||
status_text,
|
||||
})
|
||||
}
|
||||
|
||||
out
|
||||
Template::render("dashboard", &context)
|
||||
}
|
||||
|
||||
37
static/styles/common.css
Normal file
37
static/styles/common.css
Normal file
@ -0,0 +1,37 @@
|
||||
body {
|
||||
font-family: Ubuntu;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: gray;
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.4em;
|
||||
font-style: italic;
|
||||
letter-spacing: 0.2em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.service_list {
|
||||
}
|
||||
|
||||
.service_entry {
|
||||
margin-top: 0.6em;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.service_name {
|
||||
color: #19345e;
|
||||
}
|
||||
|
||||
.service_status {
|
||||
padding: 0.2em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.last_update {
|
||||
}
|
||||
|
||||
.last_update_time {
|
||||
color: purple;
|
||||
}
|
||||
36
templates/dashboard.hbs
Normal file
36
templates/dashboard.hbs
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/static/icon.svg">
|
||||
<link rel="stylesheet" href="/static/styles/common.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono&display=swap">
|
||||
|
||||
<title>healthpot</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 class="title">healthpot</h1>
|
||||
|
||||
<div class="last_update">
|
||||
<span>last update:</span>
|
||||
<span class="last_update_time">{{last_update}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="service_list">
|
||||
{{#each services}}
|
||||
<li class="service_entry">
|
||||
<span class="service_name">{{this.name}}</span>
|
||||
<span> is </span>
|
||||
<span class="service_status"
|
||||
style="background-color: {{this.status_color}}">
|
||||
{{this.status_text}}
|
||||
</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user