now renders in groups

master
Edward Shen 2019-05-01 16:59:55 -04:00
parent 211a3f9477
commit c83190ada6
Signed by: edward
GPG Key ID: F350507060ED6C90
4 changed files with 57 additions and 36 deletions

View File

@ -8,21 +8,26 @@ use serde::Serialize;
use tera::{Context, Tera}; use tera::{Context, Tera};
#[derive(Clone, Serialize, Default, Debug)] #[derive(Clone, Serialize, Default, Debug)]
pub struct Status { pub struct EndpointStatus {
pub status: u8, pub status: u8,
pub location: String, pub location: String,
pub domain: String,
pub endpoint: String, pub endpoint: String,
pub error: Option<String>, pub error: Option<String>,
} }
#[derive(Serialize, Debug)]
pub struct StatusGroup {
pub label: String,
pub endpoints: Vec<EndpointStatus>,
}
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct QueryResults { pub struct QueryResults {
pub timestamp: DateTime<Utc>, pub timestamp: DateTime<Utc>,
pub timestamp_str: String, pub timestamp_str: String,
pub refresh_time: u64, pub refresh_time: u64,
pub config: Config, pub config: Config,
pub statuses: Vec<Status>, pub groups: Vec<StatusGroup>,
} }
pub fn index(tmpl: Data<Tera>, state: Data<State>) -> WebResult<HttpResponse, WebError> { pub fn index(tmpl: Data<Tera>, state: Data<State>) -> WebResult<HttpResponse, WebError> {
@ -38,5 +43,5 @@ pub fn index(tmpl: Data<Tera>, state: Data<State>) -> WebResult<HttpResponse, We
pub fn json_endpoint(state: Data<State>) -> HttpResponse { pub fn json_endpoint(state: Data<State>) -> HttpResponse {
let state = state.read().unwrap(); let state = state.read().unwrap();
HttpResponse::Ok().json(&state.statuses) HttpResponse::Ok().json(&*state.groups)
} }

View File

@ -45,7 +45,7 @@ fn main() {
timestamp_str: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), timestamp_str: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
refresh_time: config.refresh_time.clone(), refresh_time: config.refresh_time.clone(),
config: config.clone(), config: config.clone(),
statuses: update_status(&config), groups: update_status(&config),
})); }));
let clone_state = Arc::clone(&state); let clone_state = Arc::clone(&state);

View File

@ -1,4 +1,8 @@
use crate::{config::*, handlers::Status, State}; use crate::{
config::*,
handlers::{EndpointStatus, StatusGroup},
State,
};
use chrono::prelude::*; use chrono::prelude::*;
use reqwest::{Client, Url, UrlError}; use reqwest::{Client, Url, UrlError};
@ -7,23 +11,33 @@ pub fn update_state(state: State) {
let mut write_state = state.try_write().expect("Could not unlock"); let mut write_state = state.try_write().expect("Could not unlock");
write_state.timestamp = Utc::now(); write_state.timestamp = Utc::now();
write_state.timestamp_str = Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); write_state.timestamp_str = Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
write_state.statuses = new_statuses.unwrap(); write_state.groups = new_statuses.unwrap();
} }
pub fn update_status(config: &Config) -> Vec<Status> { pub fn update_status(config: &Config) -> Vec<StatusGroup> {
let client = Client::new(); let client = Client::new();
let mut results: Vec<Status> = vec![]; let mut results: Vec<StatusGroup> = Vec::with_capacity(config.websites.len());
for website_conf in &config.websites { for website_conf in &config.websites {
let mut group = Vec::with_capacity(website_conf.endpoints.len());
for endpoint in &website_conf.endpoints { for endpoint in &website_conf.endpoints {
results.push(get_result(website_conf, &client, endpoint)); group.push(get_result(website_conf, &client, endpoint));
} }
results.push(StatusGroup {
label: website_conf.label.clone(),
endpoints: group,
});
} }
results results
} }
fn get_result(website_conf: &WebsiteConfig, client: &Client, endpoint: &EndpointConfig) -> Status { fn get_result(
website_conf: &WebsiteConfig,
client: &Client,
endpoint: &EndpointConfig,
) -> EndpointStatus {
let (label, path, port, code, body) = get_endpoint_info(endpoint.clone()); let (label, path, port, code, body) = get_endpoint_info(endpoint.clone());
let url = get_url(&website_conf.base, &path, port).expect("reading config"); let url = get_url(&website_conf.base, &path, port).expect("reading config");
let ping_result = client.get(&url).send(); let ping_result = client.get(&url).send();
@ -56,18 +70,16 @@ fn get_result(website_conf: &WebsiteConfig, client: &Client, endpoint: &Endpoint
}); });
} }
Status { EndpointStatus {
status: if error.is_some() { 1 } else { 0 }, status: if error.is_some() { 1 } else { 0 },
location: url, location: url,
domain: website_conf.label.clone(),
endpoint: label, endpoint: label,
error, error,
} }
} }
Err(e) => Status { Err(e) => EndpointStatus {
status: 2, status: 2,
location: url, location: url,
domain: website_conf.label.clone(),
endpoint: label, endpoint: label,
error: Some(format!("{}", e)), error: Some(format!("{}", e)),
}, },

View File

@ -8,17 +8,17 @@
<style> <style>
body { body {
background-color: #212121; background-color: #212121;
margin: 0;
color: #fff; color: #fff;
font-family: 'Montserrat', sans-serif; font-family: 'Montserrat', sans-serif;
}
main {
width: 700px; width: 700px;
margin: 0 auto; margin: 0 auto;
margin-top: 5rem; margin-top: 5rem;
} }
main {
padding: 1rem 0;
}
header { header {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
@ -28,45 +28,49 @@
section { section {
display: flex; display: flex;
align-items: stretch; align-items: stretch;
justify-content: space-between;
background-color: #424242; background-color: #424242;
margin: 1rem 0; margin: 1rem 0;
padding: 1rem;
border-radius: 1rem; border-radius: 1rem;
} }
p { margin: 0; } p { margin: 0; }
a {
color: #fff;
text-decoration: none;
}
h1 { display: block; margin: 0; } h1 { display: block; margin: 0; }
h3 { margin-top: 0; } h3 { margin-top: 0; }
.indicator { .indicator {
width: 1rem; width: 1rem;
height: 1rem; min-height: 100%;
border-radius: 1rem; border-radius: 1rem 0 0 1rem;
margin-right: 1rem;
} }
.ok { background-color: green; }
.warn { background-color: yellow; } .ok { background-color: #4ed34e; }
.error { background-color: red; } .warn { background-color: #fcfc64; }
.error { background-color: #ff392e; }
.error-msg { font-family: 'Source Code Pro', monospace;} .error-msg { font-family: 'Source Code Pro', monospace;}
</style> </style>
</head> </head>
<body> <body>
<main>
<header> <header>
<h1>Status</h1> <h1>Status Overview</h1>
<p>{{ results.timestamp_str }}</p> <p>{{ results.timestamp_str }}</p>
</header> </header>
{% for status in results.statuses -%} {% for group in results.groups -%}
<h2>{{ group.label }}</h2>
{% for status in group.endpoints -%}
<section> <section>
<div>
<!-- <p>{{ status.domain }}</p> -->
<h3>{{ status.endpoint }}</h3>
<p>{{ status.location }}</p>
{% if status.error %}<p class="error-msg">{{ status.error }}</p>{% endif %}
</div>
<aside class="indicator {% if status.status == 0 %}ok{% elif status.status == 1 %}warn{% else %}error{% endif %}"></aside> <aside class="indicator {% if status.status == 0 %}ok{% elif status.status == 1 %}warn{% else %}error{% endif %}"></aside>
<main>
<h3>{{ status.endpoint }}</h3>
<a href="{{ status.location }}">{{ status.location }}</a>
{% if status.error %}<p class="error-msg">{{ status.error }}</p>{% endif %}
</main>
</section> </section>
{% endfor -%} {% endfor -%}
</main> {% endfor -%}
</body> </body>
</html> </html>