changed branding to nws
This commit is contained in:
parent
cec9ceff7b
commit
061e74bc1a
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1935,6 +1935,7 @@ dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"dotenv_codegen",
|
"dotenv_codegen",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
|
|
|
@ -21,3 +21,4 @@ env_logger = "0.11.3"
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
dotenv_codegen = "0.15.0"
|
dotenv_codegen = "0.15.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -15,7 +15,7 @@ table, th, td {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #d2e0ec;
|
background-color: #ccf2b3; /* #ffed8f; */
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
|
|
164
src/main.rs
164
src/main.rs
|
@ -1,7 +1,8 @@
|
||||||
use axum::{routing::get, Router};
|
use axum::{routing::get, Router};
|
||||||
use chrono::DateTime;
|
|
||||||
use chrono::offset::Utc;
|
use chrono::offset::Utc;
|
||||||
|
use chrono::DateTime;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate dotenv_codegen;
|
extern crate dotenv_codegen;
|
||||||
|
@ -9,20 +10,26 @@ extern crate dotenv;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
mod uptime_service;
|
mod uptime_service;
|
||||||
|
|
||||||
use uptime_service::{UptimeService, Uptime, UptimeType, UptimeStatus};
|
use uptime_service::{Uptime, UptimeService, UptimeStatus, UptimeType};
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
#[template(path = "layout.html")]
|
#[template(path = "layout.html")]
|
||||||
struct ContentTemplate <T: askama::Template> {
|
struct ContentTemplate<T: askama::Template> {
|
||||||
content: T
|
content: T,
|
||||||
|
page_title: Option<String>,
|
||||||
|
page_desc: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
#[template(path = "layout.html")]
|
#[template(path = "layout.html")]
|
||||||
struct RawContentTemplate {
|
struct RawContentTemplate {
|
||||||
content: String
|
content: String,
|
||||||
|
page_title: Option<String>,
|
||||||
|
page_desc: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UptimeInfo {
|
struct UptimeInfo {
|
||||||
|
@ -30,14 +37,14 @@ struct UptimeInfo {
|
||||||
uptime: String,
|
uptime: String,
|
||||||
response_time: String,
|
response_time: String,
|
||||||
status: String,
|
status: String,
|
||||||
url: Option<String>
|
url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct IndexTemplate {
|
struct IndexTemplate {
|
||||||
uptime_infos: Vec<UptimeInfo>,
|
uptime_infos: Vec<UptimeInfo>,
|
||||||
last_updated: String
|
last_updated: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
|
@ -45,19 +52,20 @@ struct IndexTemplate {
|
||||||
struct StatusTemplate {
|
struct StatusTemplate {
|
||||||
dctr_uptime_infos: Vec<UptimeInfo>,
|
dctr_uptime_infos: Vec<UptimeInfo>,
|
||||||
svc_uptime_infos: Vec<UptimeInfo>,
|
svc_uptime_infos: Vec<UptimeInfo>,
|
||||||
last_updated: String
|
last_updated: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BlogInfo {
|
#[derive(Clone)]
|
||||||
title: String,
|
struct BlogInfo<'a> {
|
||||||
date: String,
|
title: &'a str,
|
||||||
url: String
|
date: &'a str,
|
||||||
|
url: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
#[template(path = "blog.html")]
|
#[template(path = "blog.html")]
|
||||||
struct BlogTemplate {
|
struct BlogTemplate<'a> {
|
||||||
blogs: Vec<BlogInfo>
|
blogs: Vec<BlogInfo<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
|
@ -66,7 +74,7 @@ struct DashboardTemplate {}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
uptime_service: UptimeService
|
uptime_service: UptimeService,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -81,7 +89,6 @@ async fn main() {
|
||||||
|
|
||||||
let state = AppState { uptime_service };
|
let state = AppState { uptime_service };
|
||||||
|
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index_handler))
|
.route("/", get(index_handler))
|
||||||
.route("/system_status", get(status_handler))
|
.route("/system_status", get(status_handler))
|
||||||
|
@ -91,10 +98,7 @@ async fn main() {
|
||||||
.nest_service("/assets", tower_http::services::ServeDir::new("assets"))
|
.nest_service("/assets", tower_http::services::ServeDir::new("assets"))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
|
let port_num = env::var("EXPOSE_PORT").unwrap_or("3000".to_string());
|
||||||
let port_num = env::var("EXPOSE_PORT")
|
|
||||||
.unwrap_or("3000".to_string());
|
|
||||||
|
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port_num))
|
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port_num))
|
||||||
.await
|
.await
|
||||||
|
@ -105,44 +109,68 @@ async fn main() {
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn blog_handler() -> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
|
||||||
Ok(ContentTemplate { content: BlogTemplate{ blogs: vec![
|
lazy_static! {
|
||||||
|
static ref blogs: HashMap<&'static str, BlogInfo<'static>> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert(
|
||||||
|
"11-08-2023-postmortem",
|
||||||
BlogInfo {
|
BlogInfo {
|
||||||
title: String::from("Goodbye, NWS"),
|
title: "Downtime Incident Postmortem",
|
||||||
date: String::from("May 15th, 2024"),
|
date: "November 11th, 2023",
|
||||||
url: String::from("goodbye-nws"),
|
url: "11-08-2023-postmortem",
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
m.insert(
|
||||||
|
"ssl-on-cds",
|
||||||
BlogInfo {
|
BlogInfo {
|
||||||
title: String::from("Downtime Incident Postmortem"),
|
title: "SSL on Container Deployment Service (at nickorlow.com)",
|
||||||
date: String::from("November 11th, 2023"),
|
date: "July 12th, 2023",
|
||||||
url: String::from("11-08-2023-postmortem"),
|
url: "https://nickorlow.com/blogs/side-project-7-12-23.html",
|
||||||
},
|
},
|
||||||
BlogInfo {
|
);
|
||||||
title: String::from("SSL on Container Deployment Service (at nickorlow.com)"),
|
m
|
||||||
date: String::from("July 12th, 2023"),
|
};
|
||||||
url: String::from("https://nickorlow.com/blogs/side-project-7-12-23.html"),
|
}
|
||||||
|
|
||||||
|
async fn blog_handler(
|
||||||
|
) -> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
||||||
|
Ok(ContentTemplate {
|
||||||
|
page_title: Some("NWS | Blog".to_string()),
|
||||||
|
page_desc: Some("Read about the engineering behind NWS.".to_string()),
|
||||||
|
content: BlogTemplate {
|
||||||
|
blogs: blogs.values().cloned().collect::<Vec<BlogInfo>>()
|
||||||
},
|
},
|
||||||
] } })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn single_blog_handler(
|
async fn single_blog_handler(
|
||||||
axum::extract::Path((blog_name)): axum::extract::Path<(String)>
|
axum::extract::Path((blog_name)): axum::extract::Path<(String)>,
|
||||||
) -> Result<RawContentTemplate, (axum::http::StatusCode, String)> {
|
) -> Result<RawContentTemplate, (axum::http::StatusCode, String)> {
|
||||||
let blog_content = match std::fs::read_to_string(format!("templates/blogs/{}.html", blog_name)) {
|
let blog_content = match std::fs::read_to_string(format!("templates/blogs/{}.html", blog_name))
|
||||||
|
{
|
||||||
Ok(ctn) => ctn,
|
Ok(ctn) => ctn,
|
||||||
_ => String::from("<h1>Not Found!</h1>")
|
_ => String::from("<h1>Not Found!</h1>"),
|
||||||
};
|
};
|
||||||
Ok(RawContentTemplate { content: blog_content })
|
Ok(RawContentTemplate {
|
||||||
|
page_title: Some("NWS | Blog Post".to_string()),
|
||||||
|
page_desc: Some("A Nick Web Services Blog Post.".to_string()),
|
||||||
|
content: blog_content,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dashboard_handler() -> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
async fn dashboard_handler(
|
||||||
Ok(ContentTemplate { content: DashboardTemplate{} })
|
) -> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
||||||
|
Ok(ContentTemplate {
|
||||||
|
page_title: Some("NWS | Dashboard".to_string()),
|
||||||
|
page_desc: Some("Manage the services you have deployed on NWS.".to_string()),
|
||||||
|
content: DashboardTemplate {},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index_handler(
|
async fn index_handler(
|
||||||
axum::extract::State(state): axum::extract::State<AppState>,
|
axum::extract::State(state): axum::extract::State<AppState>,
|
||||||
)
|
) -> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
||||||
-> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
|
||||||
let uptimes: Vec<Uptime> = state.uptime_service.get_data();
|
let uptimes: Vec<Uptime> = state.uptime_service.get_data();
|
||||||
let lu: DateTime<Utc> = state.uptime_service.get_last_updated().into();
|
let lu: DateTime<Utc> = state.uptime_service.get_last_updated().into();
|
||||||
let lu_str = format!("{} UTC", lu.format("%B %e, %Y %T"));
|
let lu_str = format!("{} UTC", lu.format("%B %e, %Y %T"));
|
||||||
|
@ -154,8 +182,7 @@ async fn index_handler(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
uptime_infos.push(
|
uptime_infos.push(UptimeInfo {
|
||||||
UptimeInfo {
|
|
||||||
name: uptime.name,
|
name: uptime.name,
|
||||||
uptime: uptime.uptime,
|
uptime: uptime.uptime,
|
||||||
response_time: uptime.response_time,
|
response_time: uptime.response_time,
|
||||||
|
@ -163,24 +190,26 @@ async fn index_handler(
|
||||||
UptimeStatus::Up => String::from("Up"),
|
UptimeStatus::Up => String::from("Up"),
|
||||||
UptimeStatus::Down => String::from("DOWN"),
|
UptimeStatus::Down => String::from("DOWN"),
|
||||||
UptimeStatus::Maintenance => String::from("Undergoing Maintenance"),
|
UptimeStatus::Maintenance => String::from("Undergoing Maintenance"),
|
||||||
_ => String::from("Unknown")
|
_ => String::from("Unknown"),
|
||||||
},
|
},
|
||||||
url: None
|
url: None,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let index_template = IndexTemplate {
|
let index_template = IndexTemplate {
|
||||||
uptime_infos,
|
uptime_infos,
|
||||||
last_updated: lu_str
|
last_updated: lu_str,
|
||||||
};
|
};
|
||||||
Ok(ContentTemplate { content: index_template })
|
Ok(ContentTemplate {
|
||||||
|
page_title: None,
|
||||||
|
page_desc: None,
|
||||||
|
content: index_template,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn status_handler(
|
async fn status_handler(
|
||||||
axum::extract::State(state): axum::extract::State<AppState>,
|
axum::extract::State(state): axum::extract::State<AppState>,
|
||||||
)
|
) -> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
||||||
-> Result<ContentTemplate<impl askama::Template>, (axum::http::StatusCode, String)> {
|
|
||||||
let uptimes: Vec<Uptime> = state.uptime_service.get_data();
|
let uptimes: Vec<Uptime> = state.uptime_service.get_data();
|
||||||
let lu: DateTime<Utc> = state.uptime_service.get_last_updated().into();
|
let lu: DateTime<Utc> = state.uptime_service.get_last_updated().into();
|
||||||
let lu_str = format!("{} UTC", lu.format("%B %e, %Y %T"));
|
let lu_str = format!("{} UTC", lu.format("%B %e, %Y %T"));
|
||||||
|
@ -189,11 +218,9 @@ async fn status_handler(
|
||||||
let mut sv_uptime_infos: Vec<UptimeInfo> = vec![];
|
let mut sv_uptime_infos: Vec<UptimeInfo> = vec![];
|
||||||
|
|
||||||
for uptime in uptimes {
|
for uptime in uptimes {
|
||||||
|
|
||||||
match uptime.uptime_type {
|
match uptime.uptime_type {
|
||||||
UptimeType::Datacenter => {
|
UptimeType::Datacenter => {
|
||||||
dc_uptime_infos.push(
|
dc_uptime_infos.push(UptimeInfo {
|
||||||
UptimeInfo {
|
|
||||||
name: uptime.name,
|
name: uptime.name,
|
||||||
uptime: uptime.uptime,
|
uptime: uptime.uptime,
|
||||||
response_time: uptime.response_time,
|
response_time: uptime.response_time,
|
||||||
|
@ -201,15 +228,13 @@ async fn status_handler(
|
||||||
UptimeStatus::Up => String::from("Up"),
|
UptimeStatus::Up => String::from("Up"),
|
||||||
UptimeStatus::Down => String::from("DOWN"),
|
UptimeStatus::Down => String::from("DOWN"),
|
||||||
UptimeStatus::Maintenance => String::from("Undergoing Maintenance"),
|
UptimeStatus::Maintenance => String::from("Undergoing Maintenance"),
|
||||||
_ => String::from("Unknown")
|
_ => String::from("Unknown"),
|
||||||
},
|
},
|
||||||
url: None
|
url: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
},
|
|
||||||
UptimeType::Service => {
|
UptimeType::Service => {
|
||||||
sv_uptime_infos.push(
|
sv_uptime_infos.push(UptimeInfo {
|
||||||
UptimeInfo {
|
|
||||||
name: uptime.name,
|
name: uptime.name,
|
||||||
uptime: uptime.uptime,
|
uptime: uptime.uptime,
|
||||||
response_time: uptime.response_time,
|
response_time: uptime.response_time,
|
||||||
|
@ -217,20 +242,23 @@ async fn status_handler(
|
||||||
UptimeStatus::Up => String::from("Up"),
|
UptimeStatus::Up => String::from("Up"),
|
||||||
UptimeStatus::Down => String::from("DOWN"),
|
UptimeStatus::Down => String::from("DOWN"),
|
||||||
UptimeStatus::Maintenance => String::from("Undergoing Maintenance"),
|
UptimeStatus::Maintenance => String::from("Undergoing Maintenance"),
|
||||||
_ => String::from("Unknown")
|
_ => String::from("Unknown"),
|
||||||
},
|
},
|
||||||
url: Some(uptime.url)
|
url: Some(uptime.url),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
_ => continue,
|
||||||
}
|
|
||||||
_ => continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let service_template = StatusTemplate {
|
let service_template = StatusTemplate {
|
||||||
dctr_uptime_infos: dc_uptime_infos,
|
dctr_uptime_infos: dc_uptime_infos,
|
||||||
svc_uptime_infos: sv_uptime_infos,
|
svc_uptime_infos: sv_uptime_infos,
|
||||||
last_updated: lu_str
|
last_updated: lu_str,
|
||||||
};
|
};
|
||||||
Ok(ContentTemplate { content: service_template })
|
Ok(ContentTemplate {
|
||||||
|
page_title: Some("NWS | System Status".to_string()),
|
||||||
|
page_desc: Some("Check the health of NWS datacenters and services hosted on NWS.".to_string()),
|
||||||
|
content: service_template,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::collections::HashMap;
|
use anyhow::anyhow;
|
||||||
use tokio::time::{sleep};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::{Duration};
|
|
||||||
use chrono::{Datelike, NaiveDate};
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::{anyhow};
|
use chrono::{Datelike, NaiveDate};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
|
@ -17,7 +17,7 @@ pub enum UptimeType {
|
||||||
Provider,
|
Provider,
|
||||||
Service,
|
Service,
|
||||||
Datacenter,
|
Datacenter,
|
||||||
Unknown
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
@ -25,7 +25,7 @@ pub enum UptimeStatus {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
Maintenance,
|
Maintenance,
|
||||||
Unknown
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -35,27 +35,28 @@ pub struct Uptime {
|
||||||
pub response_time: String,
|
pub response_time: String,
|
||||||
pub status: UptimeStatus,
|
pub status: UptimeStatus,
|
||||||
pub uptime_type: UptimeType,
|
pub uptime_type: UptimeType,
|
||||||
pub url: String
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UptimeServiceState {
|
pub struct UptimeServiceState {
|
||||||
uptimes: Vec<Uptime>,
|
uptimes: Vec<Uptime>,
|
||||||
last_updated: SystemTime
|
last_updated: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UptimeService {
|
pub struct UptimeService {
|
||||||
state: Arc<Mutex<UptimeServiceState>>
|
state: Arc<Mutex<UptimeServiceState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UptimeService {
|
impl UptimeService {
|
||||||
const UPDATE_SECONDS: u64 = 300;
|
const UPDATE_SECONDS: u64 = 300;
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let init_state = Arc::new(Mutex::new(
|
let init_state = Arc::new(Mutex::new(UptimeServiceState {
|
||||||
UptimeServiceState { uptimes: vec![], last_updated: UNIX_EPOCH }
|
uptimes: vec![],
|
||||||
));
|
last_updated: UNIX_EPOCH,
|
||||||
|
}));
|
||||||
Self { state: init_state }
|
Self { state: init_state }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ impl UptimeService {
|
||||||
match res {
|
match res {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("{}", err);
|
error!("{}", err);
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
sleep(tokio::time::Duration::from_secs(Self::UPDATE_SECONDS)).await;
|
sleep(tokio::time::Duration::from_secs(Self::UPDATE_SECONDS)).await;
|
||||||
|
@ -90,7 +91,6 @@ impl UptimeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_data(arc_state: Arc<Mutex<UptimeServiceState>>) -> ::anyhow::Result<()> {
|
async fn update_data(arc_state: Arc<Mutex<UptimeServiceState>>) -> ::anyhow::Result<()> {
|
||||||
|
|
||||||
debug!("Starting data update for UptimeService");
|
debug!("Starting data update for UptimeService");
|
||||||
|
|
||||||
let mut request_vars = HashMap::new();
|
let mut request_vars = HashMap::new();
|
||||||
|
@ -102,7 +102,8 @@ impl UptimeService {
|
||||||
|
|
||||||
let current_year = chrono::Utc::today().year();
|
let current_year = chrono::Utc::today().year();
|
||||||
let january_1st = NaiveDate::from_ymd(current_year, 1, 1).and_hms(0, 0, 0);
|
let january_1st = NaiveDate::from_ymd(current_year, 1, 1).and_hms(0, 0, 0);
|
||||||
let duration = january_1st.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0));
|
let duration =
|
||||||
|
january_1st.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0));
|
||||||
let year_start = UNIX_EPOCH + Duration::from_secs(duration.num_seconds() as u64);
|
let year_start = UNIX_EPOCH + Duration::from_secs(duration.num_seconds() as u64);
|
||||||
|
|
||||||
//let ranges = &format!(
|
//let ranges = &format!(
|
||||||
|
@ -122,55 +123,68 @@ impl UptimeService {
|
||||||
request_vars.insert("response_times", "1");
|
request_vars.insert("response_times", "1");
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let res = client.post("https://api.uptimerobot.com/v2/getMonitors")
|
let res = client
|
||||||
|
.post("https://api.uptimerobot.com/v2/getMonitors")
|
||||||
.form(&request_vars)
|
.form(&request_vars)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let resp = res.json::<serde_json::Value>()
|
let resp = res.json::<serde_json::Value>().await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
let monitors = resp.get("monitors")
|
let monitors = resp
|
||||||
|
.get("monitors")
|
||||||
.context("Response did not have a monitors subobject")?
|
.context("Response did not have a monitors subobject")?
|
||||||
.as_array()
|
.as_array()
|
||||||
.context("Monitors subobject was not an array")?;
|
.context("Monitors subobject was not an array")?;
|
||||||
|
|
||||||
|
let mut state = match arc_state.lock() {
|
||||||
let mut state = match arc_state.lock(){
|
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(_) => {return Err(anyhow!("Could not lock shared state"));}
|
Err(_) => {
|
||||||
|
return Err(anyhow!("Could not lock shared state"));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
state.uptimes.clear();
|
state.uptimes.clear();
|
||||||
for monitor in monitors {
|
for monitor in monitors {
|
||||||
let monitor_fqn = monitor.get("friendly_name")
|
let monitor_fqn = monitor
|
||||||
|
.get("friendly_name")
|
||||||
.context("Monitor did not have property 'friendly_name'")?;
|
.context("Monitor did not have property 'friendly_name'")?;
|
||||||
|
|
||||||
debug!("Monitor '{}' processing", monitor_fqn);
|
debug!("Monitor '{}' processing", monitor_fqn);
|
||||||
|
|
||||||
let split_str: Vec<&str> = monitor_fqn.as_str()
|
let split_str: Vec<&str> = monitor_fqn
|
||||||
|
.as_str()
|
||||||
.context("Expected 'friendly_name' to be a string")?
|
.context("Expected 'friendly_name' to be a string")?
|
||||||
.split(".").collect();
|
.split(".")
|
||||||
|
.collect();
|
||||||
if split_str.len() != 2 {
|
if split_str.len() != 2 {
|
||||||
debug!("Monitor '{}' excluded due to bad format", monitor_fqn);
|
debug!("Monitor '{}' excluded due to bad format", monitor_fqn);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let monitor_nt = String::from(*split_str.get(0).context("Expected name to have first part")?);
|
let monitor_nt = String::from(
|
||||||
let monitor_name = String::from(*split_str.get(1).context("Expected name to have second part")?);
|
*split_str
|
||||||
|
.get(0)
|
||||||
|
.context("Expected name to have first part")?,
|
||||||
|
);
|
||||||
|
let monitor_name = String::from(
|
||||||
|
*split_str
|
||||||
|
.get(1)
|
||||||
|
.context("Expected name to have second part")?,
|
||||||
|
);
|
||||||
let monitor_type = match monitor_nt.as_str() {
|
let monitor_type = match monitor_nt.as_str() {
|
||||||
"datacenter" => UptimeType::Datacenter,
|
"datacenter" => UptimeType::Datacenter,
|
||||||
"service" => UptimeType::Service,
|
"service" => UptimeType::Service,
|
||||||
"competitor" => UptimeType::Provider,
|
"competitor" => UptimeType::Provider,
|
||||||
_ => UptimeType::Unknown
|
_ => UptimeType::Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if monitor_type == UptimeType::Unknown {
|
if monitor_type == UptimeType::Unknown {
|
||||||
debug!("Monitor '{}' excluded due to unknown type", monitor_fqn);
|
debug!("Monitor '{}' excluded due to unknown type", monitor_fqn);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let monitor_status_num = monitor.get("status")
|
let monitor_status_num = monitor
|
||||||
|
.get("status")
|
||||||
.context("Expected monitor to have 'status' property")?
|
.context("Expected monitor to have 'status' property")?
|
||||||
.as_u64()
|
.as_u64()
|
||||||
.context("Expected 'status' property to be u64")?;
|
.context("Expected 'status' property to be u64")?;
|
||||||
|
@ -179,50 +193,54 @@ impl UptimeService {
|
||||||
0 => UptimeStatus::Maintenance,
|
0 => UptimeStatus::Maintenance,
|
||||||
1 | 8 | 9 => UptimeStatus::Down,
|
1 | 8 | 9 => UptimeStatus::Down,
|
||||||
2 => UptimeStatus::Up,
|
2 => UptimeStatus::Up,
|
||||||
_ => UptimeStatus::Unknown
|
_ => UptimeStatus::Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
if monitor_status == UptimeStatus::Unknown {
|
if monitor_status == UptimeStatus::Unknown {
|
||||||
debug!("Monitor '{}' excluded due to unknown status (status was {})", monitor_fqn, monitor_status_num);
|
debug!(
|
||||||
|
"Monitor '{}' excluded due to unknown status (status was {})",
|
||||||
|
monitor_fqn, monitor_status_num
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let monitor_rt_val = monitor.get("average_response_time")
|
let monitor_rt_val = monitor
|
||||||
|
.get("average_response_time")
|
||||||
.context("Expected monitor to have property 'average_response_time'")?;
|
.context("Expected monitor to have property 'average_response_time'")?;
|
||||||
|
|
||||||
|
|
||||||
// Because UptimeRobot has the world's worst API ever
|
// Because UptimeRobot has the world's worst API ever
|
||||||
// and decided that it's okay to return multiple datatypes
|
// and decided that it's okay to return multiple datatypes
|
||||||
// for one property based on how they're feeling
|
// for one property based on how they're feeling
|
||||||
let monitor_rt = match monitor_rt_val.as_str() {
|
let monitor_rt = match monitor_rt_val.as_str() {
|
||||||
Some(string) => format!("{}ms", string),
|
Some(string) => format!("{}ms", string),
|
||||||
_ => format!("N/A")
|
_ => format!("N/A"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let monitor_uptime = format!("{}%",
|
let monitor_uptime = format!(
|
||||||
monitor.get("custom_uptime_ranges")
|
"{}%",
|
||||||
|
monitor
|
||||||
|
.get("custom_uptime_ranges")
|
||||||
.context("Expected monitor to have property 'custom_uptime_ranges'")?
|
.context("Expected monitor to have property 'custom_uptime_ranges'")?
|
||||||
.as_str()
|
.as_str()
|
||||||
.context("Expected 'custom_uptime_ranges' to be String")?
|
.context("Expected 'custom_uptime_ranges' to be String")?
|
||||||
);
|
);
|
||||||
|
|
||||||
let monitor_url = String::from(monitor.get("url")
|
let monitor_url = String::from(
|
||||||
|
monitor
|
||||||
|
.get("url")
|
||||||
.context("Expected monitor to have property 'url'")?
|
.context("Expected monitor to have property 'url'")?
|
||||||
.as_str()
|
.as_str()
|
||||||
.context("Expected 'url' to be String")?);
|
.context("Expected 'url' to be String")?,
|
||||||
|
);
|
||||||
|
|
||||||
;
|
state.uptimes.push(Uptime {
|
||||||
|
|
||||||
state.uptimes.push(
|
|
||||||
Uptime {
|
|
||||||
name: monitor_name,
|
name: monitor_name,
|
||||||
uptime: monitor_uptime,
|
uptime: monitor_uptime,
|
||||||
response_time: monitor_rt,
|
response_time: monitor_rt,
|
||||||
status: monitor_status,
|
status: monitor_status,
|
||||||
uptime_type: monitor_type,
|
uptime_type: monitor_type,
|
||||||
url: monitor_url
|
url: monitor_url,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.last_updated = SystemTime::now();
|
state.last_updated = SystemTime::now();
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<h1>SMC Incident Postmortem 11/08/2023</h1>
|
<h1>NWS Incident Postmortem 11/08/2023</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
On November 8th, 2023 at approximately 09:47 UTC, SMC suffered
|
On November 8th, 2023 at approximately 09:47 UTC, NWS suffered
|
||||||
a complete outage. This outage resulted in the downtime of all
|
a complete outage. This outage resulted in the downtime of all
|
||||||
services hosted on SMC and the downtime of the SMC Management
|
services hosted on NWS and the downtime of the NWS Management
|
||||||
Engine and the SMC dashboard.
|
Engine and the NWS dashboard.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The incident lasted 38 minutes after which it was automatically
|
The incident lasted 38 minutes after which it was automatically
|
||||||
resolved and all services were restored. This is SMC' first
|
resolved and all services were restored. This is NWS' first
|
||||||
outage event of 2023.
|
outage event of 2023.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Cause</h2>
|
<h2>Cause</h2>
|
||||||
<p>
|
<p>
|
||||||
SMC utilizes several tactics to ensure uptime. A component of
|
NWS utilizes several tactics to ensure uptime. A component of
|
||||||
this is load balancing and failover. This service is currently
|
this is load balancing and failover. This service is currently
|
||||||
provided by Cloudflare at the DNS level. Cloudflare sends
|
provided by Cloudflare at the DNS level. Cloudflare sends
|
||||||
health check requests to SMC servers at specified intervals. If
|
health check requests to NWS servers at specified intervals. If
|
||||||
it detects that one of the servers is down, it will remove the
|
it detects that one of the servers is down, it will remove the
|
||||||
A record from entry.nws.nickorlow.com for that server (this domain
|
A record from entry.nws.nickorlow.com for that server (this domain
|
||||||
is where all services on SMC direct their traffic via a
|
is where all services on NWS direct their traffic via a
|
||||||
CNAME).
|
CNAME).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -31,34 +31,34 @@
|
||||||
error, but rather an HTTP timeout. This is an indication that the
|
error, but rather an HTTP timeout. This is an indication that the
|
||||||
server may have lost network connectivity. When Cloudflare detected that the
|
server may have lost network connectivity. When Cloudflare detected that the
|
||||||
servers were down, it removed their A records from the
|
servers were down, it removed their A records from the
|
||||||
entry.nws.nickorlow.com domain. Since SMC Pennsylvania servers
|
entry.nws.nickorlow.com domain. Since NWS Pennsylvania servers
|
||||||
have been undergoing maintenance since August 2023, this left no
|
have been undergoing maintenance since August 2023, this left no
|
||||||
servers able to serve requests routed to entry.nws.nickorlow.com,
|
servers able to serve requests routed to entry.nws.nickorlow.com,
|
||||||
resulting in the outage.
|
resulting in the outage.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
SMC utilizes UptimeRobot for monitoring the uptime statistics of
|
NWS utilizes UptimeRobot for monitoring the uptime statistics of
|
||||||
services on SMC and SMC servers. This is the source of the
|
services on NWS and NWS servers. This is the source of the
|
||||||
statistics shown on the SMC status page.
|
statistics shown on the NWS status page.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
UptimeRobot did not detect either of the Texas SMC servers as being
|
UptimeRobot did not detect either of the Texas NWS servers as being
|
||||||
offline for the duration of the outage. This is odd, as UptimeRobot
|
offline for the duration of the outage. This is odd, as UptimeRobot
|
||||||
and Cloudflare did not agree on the status of SMC servers. Logs
|
and Cloudflare did not agree on the status of NWS servers. Logs
|
||||||
on SMC servers showed that requests from UptimeRobot were being
|
on NWS servers showed that requests from UptimeRobot were being
|
||||||
served while no requests from Cloudflare were shown in the logs.
|
served while no requests from Cloudflare were shown in the logs.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
No firewall rules existed that could have blocked the healthcheck traffic from Cloudflare
|
No firewall rules existed that could have blocked the healthcheck traffic from Cloudflare
|
||||||
for either of the SMC servers. There was no other configuration
|
for either of the NWS servers. There was no other configuration
|
||||||
found that would have blocked these requests. As these servers
|
found that would have blocked these requests. As these servers
|
||||||
are on different networks inside different buildings in different
|
are on different networks inside different buildings in different
|
||||||
parts of Texas, their networking equipment is entirely separate.
|
parts of Texas, their networking equipment is entirely separate.
|
||||||
This rules out any failure of networking equipment owned
|
This rules out any failure of networking equipment owned
|
||||||
by SMC. This leads us to believe that the issue may have been
|
by NWS. This leads us to believe that the issue may have been
|
||||||
caused due to an internet traffic anomaly, although we are currently
|
caused due to an internet traffic anomaly, although we are currently
|
||||||
unable to confirm that this is the cause of the issue.
|
unable to confirm that this is the cause of the issue.
|
||||||
</p>
|
</p>
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
SMC will also investigate other methods of failover and load
|
NWS will also investigate other methods of failover and load
|
||||||
balancing.
|
balancing.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>
|
<b>
|
||||||
Nick Web Services (NWS) is now Sharpe Mountain Compute (SMC).
|
Nick Web Services (NWS) is now Nick Web Services (NWS).
|
||||||
</b>
|
</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<h1>Under Construction</h1>
|
<h1>Under Construction</h1>
|
||||||
<p>The dashboard isn't ready yet! Use the <a href="https://nws.nickorlow.com/dashboard">old website</a> for now!</p>
|
<p>The new dashboard isn't ready yet! Nobody but me used it anyways!</p>
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
{%- import "uptime_table.html" as scope -%}
|
{%- import "uptime_table.html" as scope -%}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p style="margin-bottom: -9px;"><i>Nick Web Services is now</i></p>
|
<h1 style="margin-bottom: 0px;margin-top: 0px;">Nick Web Services</h1>
|
||||||
<h1 style="margin-bottom: 0px;margin-top: 0px;">Sharpe Mountain Compute</h1>
|
|
||||||
<p style="margin-top: 0px;">Pottsville, PA - Philadelphia, PA - Austin, TX</p>
|
<p style="margin-top: 0px;">Pottsville, PA - Philadelphia, PA - Austin, TX</p>
|
||||||
|
|
||||||
<a href="https://nws.nickorlow.com">[ Old Website (NWS Branded) ]</a>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Sharpe Mountain Compute is a hosting service based out of the Commonwealth of Pennsylvania
|
Nick Web Services is a hosting service based out of the Commonwealth of Pennsylvania
|
||||||
and the State of Texas.
|
and the State of Texas.
|
||||||
We are committed to achieving maximum uptime with better performance and a lower
|
We are committed to achieving maximum uptime with better performance and a lower
|
||||||
cost than any of the major cloud services.
|
cost than any of the major cloud services.
|
||||||
|
@ -23,6 +21,10 @@ We operate four datacenters located across three cities in two states. This infr
|
||||||
all year) for 2023 and 100% uptime for 2024 (YTD).
|
all year) for 2023 and 100% uptime for 2024 (YTD).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In 2024, YTD we have surpassed both Vercel and Github Pages in total uptime
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Compare us to our competitors!</h2>
|
<h2>Compare us to our competitors!</h2>
|
||||||
{% call scope::uptime_table(uptime_infos) %}
|
{% call scope::uptime_table(uptime_infos) %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Sharpe Mountain Compute</title>
|
|
||||||
|
{% if let Some(title) = page_title %}
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
{% else %}
|
||||||
|
<title>Nick Web Services</title>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if let Some(desc) = page_desc %}
|
||||||
|
<meta name="{{ desc }}" />
|
||||||
|
{% else %}
|
||||||
|
<meta name="Nick Web Services" />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<link rel="stylesheet" href="/assets/style.css">
|
||||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
@ -27,16 +39,10 @@
|
||||||
<hr />
|
<hr />
|
||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<div>
|
<div>
|
||||||
<p style="margin-bottom: 0px; margin-top:0px;"><b>Sharpe Mountain Compute</b></p>
|
<p style="margin-bottom: 0px; margin-top:0px;"><b>Nick Web Services</b></p>
|
||||||
<p style="margin-bottom: 0px;margin-top: 0px;">
|
|
||||||
<small><i>formerly Nick Web Services (NWS)</i></small>
|
|
||||||
</p>
|
|
||||||
<p style="margin-bottom: 0px;margin-top: 0px;">
|
<p style="margin-bottom: 0px;margin-top: 0px;">
|
||||||
<small>Copyright © <a href="https://nickorlow.com">Nicholas Orlowsky</a> 2024</small>
|
<small>Copyright © <a href="https://nickorlow.com">Nicholas Orlowsky</a> 2024</small>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin-bottom: 0px;margin-top: 0px;">
|
|
||||||
<small><i>"We're getting there" - SEPTA</i></small>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img class="flag-img" src="/assets/flag-images/us.png" title="The United States of America"/>
|
<img class="flag-img" src="/assets/flag-images/us.png" title="The United States of America"/>
|
||||||
|
|
|
@ -4,17 +4,22 @@
|
||||||
|
|
||||||
<h2>Datacenter Status</h2>
|
<h2>Datacenter Status</h2>
|
||||||
<p>
|
<p>
|
||||||
The status of each of Sharpe Mountain Compute's 4
|
The status of each of Nick Web Services's 4
|
||||||
datacenters.
|
datacenters.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% call scope::uptime_table(dctr_uptime_infos) %}
|
{% call scope::uptime_table(dctr_uptime_infos) %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Notice: Due to leasing issues, the Philadelphia datacenter will be offline until
|
||||||
|
at least May or August 2025 or it may be discontinued as an NWS location.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Service Status</h2>
|
<h2>Service Status</h2>
|
||||||
<p>
|
<p>
|
||||||
The status of services people host on Sharpe Mountain Compute.
|
The status of services people host on Nick Web Services.
|
||||||
Note that the uptime and performance of services hosted on
|
Note that the uptime and performance of services hosted on
|
||||||
Sharpe Mountain Compute may be affected by factors not controlled by us such as
|
Nick Web Services may be affected by factors not controlled by us such as
|
||||||
bad optimization or buggy software.
|
bad optimization or buggy software.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue