Add html page
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
(
|
(
|
||||||
|
update_period: 30,
|
||||||
services: {
|
services: {
|
||||||
"example": (
|
"example": (
|
||||||
url: "https://example.org",
|
url: "https://example.org",
|
||||||
|
|||||||
@ -31,18 +31,24 @@ pub enum HttpHealthCheckMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct HealthState {
|
pub struct HealthState {
|
||||||
|
pub last_update: Mutex<Option<DateTime<Utc>>>,
|
||||||
pub health: HashMap<ServiceId, Mutex<Option<HealthStatus>>>,
|
pub health: HashMap<ServiceId, Mutex<Option<HealthStatus>>>,
|
||||||
pub config: HealthConfig,
|
pub config: HealthConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct HealthConfig {
|
pub struct HealthConfig {
|
||||||
|
/// The time between updates (in seconds)
|
||||||
|
pub update_period: u64,
|
||||||
|
|
||||||
|
/// The list of services
|
||||||
pub services: HashMap<ServiceId, ServiceConfig>,
|
pub services: HashMap<ServiceId, ServiceConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HealthState {
|
impl HealthState {
|
||||||
pub fn new(config: HealthConfig) -> HealthState {
|
pub fn new(config: HealthConfig) -> HealthState {
|
||||||
HealthState {
|
HealthState {
|
||||||
|
last_update: Mutex::new(None),
|
||||||
health: config
|
health: config
|
||||||
.services
|
.services
|
||||||
.iter()
|
.iter()
|
||||||
@ -53,6 +59,8 @@ impl HealthState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&self) {
|
pub async fn update(&self) {
|
||||||
|
*self.last_update.lock().await = Some(Utc::now());
|
||||||
|
|
||||||
for (id, status) in &self.health {
|
for (id, status) in &self.health {
|
||||||
let url = &self.config.services[id].url;
|
let url = &self.config.services[id].url;
|
||||||
info!("service [{}] querying {}", 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::serve::StaticFiles;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use std::{env, io};
|
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]
|
#[rocket::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
@ -19,17 +29,20 @@ async fn main() -> io::Result<()> {
|
|||||||
.await
|
.await
|
||||||
.expect("failed to read config file");
|
.expect("failed to read config file");
|
||||||
let config = ron::from_str(&config).expect("failed to parse 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()
|
let rocket = rocket::ignite()
|
||||||
//.attach(Template::custom(|engines| {
|
//.attach(Template::custom(|engines| {
|
||||||
//handlebars_util::register_helpers(engines)
|
//handlebars_util::register_helpers(engines)
|
||||||
//}))
|
//}))
|
||||||
.manage(Arc::new(state))
|
.attach(Template::fairing())
|
||||||
|
.manage(Arc::clone(&state))
|
||||||
.mount("/static", StaticFiles::from("static"))
|
.mount("/static", StaticFiles::from("static"))
|
||||||
.mount("/", rocket::routes![routes::pages::index,]);
|
.mount("/", rocket::routes![routes::pages::dashboard]);
|
||||||
//.register(rocket::catchers![auth::login_page,]);
|
//.register(rocket::catchers![auth::login_page,]);
|
||||||
|
|
||||||
|
start_poller(state);
|
||||||
|
|
||||||
rocket.launch().await.expect("rocket failed to launch");
|
rocket.launch().await.expect("rocket failed to launch");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -1,22 +1,57 @@
|
|||||||
use crate::health::HealthState;
|
use crate::health::HealthState;
|
||||||
|
use crate::health::HealthStatus;
|
||||||
|
use chrono::Utc;
|
||||||
use rocket::{get, State};
|
use rocket::{get, State};
|
||||||
|
use rocket_contrib::templates::Template;
|
||||||
|
use serde::Serialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::task::spawn;
|
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn index(state: State<'_, Arc<HealthState>>) -> String {
|
pub async fn dashboard(state: State<'_, Arc<HealthState>>) -> Template {
|
||||||
{
|
#[derive(Debug, Serialize)]
|
||||||
let state = Arc::clone(state.inner());
|
struct Service {
|
||||||
spawn(async move {
|
name: String,
|
||||||
state.update().await;
|
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 {
|
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