Add html page

This commit is contained in:
2021-01-31 01:42:39 +01:00
parent e491d4aff6
commit 36834dd5fd
6 changed files with 144 additions and 14 deletions

View File

@ -1,4 +1,5 @@
(
update_period: 30,
services: {
"example": (
url: "https://example.org",

View File

@ -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);

View File

@ -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(())

View File

@ -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
View 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
View 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>