use crate::{config::*, updater::update_status}; use chrono::prelude::*; #[cfg(feature = "webhooks")] use reqwest::Client; use serde::{Serialize, Serializer}; #[derive(Serialize, Debug)] pub struct QueryResults { pub timestamp: DateTime, pub timestamp_str: String, pub refresh_time: u64, pub config: Config, pub groups: Vec, } impl QueryResults { pub fn new(config: Config) -> Self { let time = Utc::now(); QueryResults { timestamp: time, timestamp_str: Self::format_timestamp(time), refresh_time: config.refresh_time, config: config.clone(), groups: update_status(&config), } } pub fn update(&mut self, updated_groups: Vec) { self.update_timestamp(); #[cfg(feature = "webhooks")] { let client = Client::new(); // Async blocker: we will need to rewrite this part once async has // been stabilized, or make it so that the order of websites is an // invariant after implementing async. Probably use a hashmap? for group in 0..self.groups.len() { for endpoint in 0..self.groups[group].endpoints.len() { let old_endpoint = &self.groups[group].endpoints[endpoint]; let new_endpoint = &updated_groups[group].endpoints[endpoint]; if old_endpoint != new_endpoint { panic!("endpoint order was not maintained"); } if new_endpoint.errors != old_endpoint.errors { if let Some(webhooks) = &self.config.websites[group].endpoints[endpoint].webhooks { macro_rules! gen_hooks { ($(($hook:ident, $cond:expr)),*) => { $(if let Some(url) = &webhooks.$hook { if new_endpoint.status == $cond { if let Err(e) = client.post(url).json(new_endpoint).send() { warn!("{}", e) } } })* }; } gen_hooks!( (on_change, new_endpoint.status), (on_ok, EndpointStatus::OK), (on_warn, EndpointStatus::WARN), (on_error, EndpointStatus::ERROR), (on_not_ok, EndpointStatus::OK) ); } } } } } self.groups = updated_groups; } #[inline] fn update_timestamp(&mut self) { let current_time = Utc::now(); self.timestamp = current_time; self.timestamp_str = Self::format_timestamp(current_time); } fn format_timestamp(timestamp: DateTime) -> String { timestamp.format("%Y-%m-%d %H:%M:%S").to_string() } } #[derive(Serialize, Debug)] pub struct StatusGroup { pub label: String, pub endpoints: Vec, } /// This holds the results of pinging a single endpoint. RTT exists iff there /// aren't any errors. #[derive(Clone, Serialize, Debug)] pub struct Endpoint { pub status: EndpointStatus, pub location: String, pub label: String, pub rtt: Option, pub errors: Vec, } impl PartialEq for Endpoint { fn eq(&self, other: &Endpoint) -> bool { self.location == other.location && self.label == other.label } } /// Various helper functions for generating resulting statuses impl Endpoint { pub fn ok(location: String, label: String, rtt: String) -> Self { Endpoint { status: EndpointStatus::OK, location, label, rtt: Some(rtt), errors: vec![], } } pub fn warn(location: String, label: String, rtt: String, errors: Vec) -> Self { Endpoint { status: EndpointStatus::WARN, location, label, rtt: Some(rtt), errors, } } pub fn error(location: String, label: String, errors: Vec) -> Self { Endpoint { status: EndpointStatus::ERROR, location, label, rtt: None, errors, } } pub fn maintenance(location: String, label: String, errors: Vec) -> Self { Endpoint { status: EndpointStatus::MAINTENANCE, location, label, rtt: None, errors, } } pub fn unknown(location: String, label: String, errors: Vec) -> Self { Endpoint { status: EndpointStatus::UNKNOWN, location, label, rtt: None, errors, } } } #[derive(Clone, Debug, PartialEq)] pub enum EndpointStatus { OK, WARN, ERROR, MAINTENANCE, UNKNOWN, } /// Custom serialization implementation, since its rendered form will be as a /// CSS class. The default serialization keeps things uppercase, which is /// discouraged as CSS class names. impl Serialize for EndpointStatus { fn serialize(&self, s: S) -> Result where S: Serializer, { s.serialize_str(match *self { EndpointStatus::OK => "ok", EndpointStatus::WARN => "warn", EndpointStatus::ERROR => "error", EndpointStatus::MAINTENANCE => "maintenance", EndpointStatus::UNKNOWN => "unknown", }) } } impl Into for EndpointStatus { fn into(self) -> String { match self { EndpointStatus::OK => String::from("ok"), EndpointStatus::WARN => String::from("warn"), EndpointStatus::ERROR => String::from("error"), EndpointStatus::MAINTENANCE => String::from("maintenance"), EndpointStatus::UNKNOWN => String::from("unknown"), } } }