From fe16a923190243dfde5db6ceff2ef0bcf9158926 Mon Sep 17 00:00:00 2001 From: Toby Vincent Date: Tue, 1 Oct 2024 13:15:24 -0500 Subject: feat: simplify service status type --- Cargo.lock | 73 +++++++++++++++++++++++++------------------------- Cargo.toml | 9 +++++-- assets/index.html | 8 ++---- assets/index.js | 44 +++++++++++++++++------------- config.toml | 2 +- src/api.rs | 8 +++--- src/lib.rs | 17 +++++++----- src/main.rs | 14 ++++------ src/service.rs | 4 +-- src/service/http.rs | 12 +++------ src/service/systemd.rs | 17 +++++------- src/service/tcp.rs | 10 +++---- src/sse.rs | 11 +++++--- 13 files changed, 117 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b072987..9b36084 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,15 +67,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -183,15 +183,15 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.19" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" dependencies = [ "shlex", ] @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" @@ -590,9 +590,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "linux-raw-sys" @@ -782,9 +782,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "proc-macro2" @@ -857,7 +857,6 @@ dependencies = [ "base64", "bytes", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2", @@ -951,9 +950,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -1002,9 +1001,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -1054,9 +1053,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -1152,9 +1151,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1199,9 +1198,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -1212,18 +1211,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -1351,9 +1350,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -1527,9 +1526,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -1798,9 +1797,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 1cb7655..b70af54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,16 @@ futures = "0.3.30" futures-util = "0.3.30" hyper-util = { version = "0.1.9", features = ["tokio"] } main_error = "0.1.2" -reqwest = { version = "0.12.7", features = ["blocking"] } +reqwest = "0.12.7" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" thiserror = "1.0.63" -tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread", "net", "time"] } +tokio = { version = "1.40.0", features = [ + "macros", + "rt-multi-thread", + "net", + "time", +] } tokio-stream = { version = "0.1.16", features = ["sync"] } toml = "0.8.19" tower-http = { version = "0.6.1", features = ["fs", "trace"] } diff --git a/assets/index.html b/assets/index.html index c168318..b0a6c18 100644 --- a/assets/index.html +++ b/assets/index.html @@ -3,27 +3,23 @@ - status.tobyvin.dev - +
-

tobyvin.dev Status

+

SrvStat

-
Status No issues detected
-
-
diff --git a/assets/index.js b/assets/index.js index e65369c..bd0f308 100644 --- a/assets/index.js +++ b/assets/index.js @@ -29,41 +29,49 @@ function updateStatus() { function updateService(name, node, status) { switch (status.status) { - case "pass": - node.textContent = "Operational"; + case "ok": + node.textContent = "Ok"; node.setAttribute("class", "ok"); + serviceMap.set(name, true); break; - case "fail": - node.textContent = "Down"; + case "error": + node.textContent = "Error"; node.title = status.output; node.setAttribute("class", "error"); + serviceMap.set(name, false); break; - case "warn": - node.textContent = "Warning"; - node.title = status.output; - node.setAttribute("class", "warning"); - break; - case "unknown": - node.textContent = "Unknown"; - node.setAttribute("class", "warning"); } - serviceMap.set(name, status.status === "pass"); updateStatus(); } getServices().then((services) => { - for (const [service] of Object.entries(services)) { + const evtSource = new EventSource("sse"); + evtSource.onmessage = (event) => { + console.log(event) + }; + + for (const [service, status] of Object.entries(services)) { const table = document.getElementById("services"); const row = table.insertRow(); const nameNode = row.insertCell(); nameNode.textContent = service; const node = row.insertCell(); + updateService(service, node, status); const evtSource = new EventSource(`sse/${service}`); - evtSource.onmessage = (event) => { - const status = JSON.parse(event.data); - updateService(service, node, status); - }; + + evtSource.addEventListener("ok", (event) => { + node.textContent = "Ok"; + node.setAttribute("class", "ok"); + serviceMap.set(name, true); + }); + + evtSource.addEventListener("error", (event) => { + node.textContent = "Error"; + node.title = event.data; + node.setAttribute("class", "error"); + serviceMap.set(name, false); + }); } }); diff --git a/config.toml b/config.toml index 815ae94..306c16b 100644 --- a/config.toml +++ b/config.toml @@ -1,5 +1,5 @@ root = "assets" -address = "127.0.0.1:8080" +address = "127.0.0.1:8084" [services] "tobyvin.dev" = { url = "https://tobyvin.dev" } diff --git a/src/api.rs b/src/api.rs index 1489c21..5a8deb6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -45,12 +45,12 @@ pub async fn healthcheck(State(services): State) -> Health { let status = match checks .values() - .filter(|s| !matches!(s, Status::Pass)) + .filter(|s| !matches!(s, Status::Ok)) .count() { - 0 => Status::Pass, - 1 => Status::Fail(Some("1 issue detected".to_string())), - n => Status::Fail(Some(format!("{n} issues detected"))), + 0 => Status::Ok, + 1 => Status::Error(Some("1 issue detected".to_string())), + n => Status::Error(Some(format!("{n} issues detected"))), }; Health { status, checks } diff --git a/src/lib.rs b/src/lib.rs index d24f635..dc0efe7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,14 +14,17 @@ pub fn router() -> axum::Router { .nest("/sse", sse::router()) } -#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "lowercase", tag = "status", content = "output")] pub enum Status { - Pass, - Warn(Option), - Fail(Option), - #[default] - Unknown, + Ok, + Error(Option), +} + +impl Default for Status { + fn default() -> Self { + Status::Error(Some("Unknown".to_string())) + } } impl Status { @@ -36,7 +39,7 @@ impl Status { impl From for Status { fn from(value: T) -> Self { - Status::Fail(Some(value.to_string())) + Status::Error(Some(value.to_string())) } } diff --git a/src/main.rs b/src/main.rs index 99af338..fbf27cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fs::File, path::PathBuf, sync::Arc}; use tower_http::services::ServeDir; use tracing::level_filters::LevelFilter; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; +use tracing_subscriber::EnvFilter; use statsrv::service::Service; @@ -12,14 +12,10 @@ const DEFAULT_CONFIG: &str = "./config.toml"; #[tokio::main] async fn main() -> Result<(), Box> { - tracing_subscriber::registry() - .with( - EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env_lossy(), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(); + tracing_subscriber::fmt().with_env_filter(filter).init(); let config = match Config::parse() { Ok(c) => c, diff --git a/src/service.rs b/src/service.rs index c45fcb1..8e0863c 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use systemd::Systemd; use tcp::Tcp; use tokio::{ - sync::watch::{Receiver, Sender}, + sync::watch::{channel, Receiver, Sender}, task::JoinHandle, }; use tokio_stream::wrappers::WatchStream; @@ -34,7 +34,7 @@ pub struct ServiceHandle { impl ServiceHandle { pub fn new(service: impl ServiceSpawner) -> Self { - let (tx, rx) = tokio::sync::watch::channel(Status::default()); + let (tx, rx) = channel(Status::Error(None)); let handle = tokio::spawn(service.spawn(tx)); Self { handle, rx } } diff --git a/src/service/http.rs b/src/service/http.rs index 7c875b9..8950096 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -32,14 +32,10 @@ impl ServiceSpawner for Http { .try_clone() .expect("Clone with no body should never fail"); let resp = client.execute(req).await; - let status = match resp.map(|r| r.status().as_u16()) { - Ok(code) if code == self.status_code => Status::Pass, - Ok(code) => Status::Fail(Some(format!("Status code: {code}"))), - Err(err) => { - tracing::error!("HTTP request error: {err}"); - Status::Unknown - } - }; + let status = resp.map_or_else(Into::into, |r| match r.status().as_u16() { + c if c == self.status_code => Status::Ok, + c => Status::Error(Some(format!("Status code: {c}"))), + }); tx.send_if_modified(|s| s.update(status)); } diff --git a/src/service/systemd.rs b/src/service/systemd.rs index 90213a0..ee220b8 100644 --- a/src/service/systemd.rs +++ b/src/service/systemd.rs @@ -21,17 +21,14 @@ impl ServiceSpawner for Systemd { loop { interval.tick().await; - let status = match command.output() { - Ok(output) if output.status.success() => Status::Pass, - Ok(output) => { - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - Status::Fail(Some(format!("Service state: {}", stdout))) + let status = command.output().map_or_else(Into::into, |o| { + if o.status.success() { + Status::Ok + } else { + let stdout = String::from_utf8_lossy(&o.stdout).trim().to_string(); + Status::Error(Some(format!("Service state: {}", stdout))) } - Err(err) => { - tracing::error!("Failed to spawn process: {err}"); - Status::Unknown - } - }; + }); tx.send_if_modified(|s| s.update(status)); } diff --git a/src/service/tcp.rs b/src/service/tcp.rs index 42791bc..7b79afd 100644 --- a/src/service/tcp.rs +++ b/src/service/tcp.rs @@ -32,7 +32,7 @@ impl ServiceSpawner for Tcp { Ok(conn) => { // TODO: figure out how to wait for connection to close conn.ready(Interest::READABLE).await?; - tx.send_if_modified(|s| s.update(Status::Pass)); + tx.send_if_modified(|s| s.update(Status::Ok)); } Err(err) => { tx.send_if_modified(|s| s.update(err.into())); @@ -50,18 +50,18 @@ mod tests { #[tracing_test::traced_test] #[ignore] async fn test_tcp_watch() { - let (tx, mut rx) = tokio::sync::watch::channel(Status::default()); + let (tx, mut rx) = tokio::sync::watch::channel(Status::Error(None)); let tests = tokio::spawn(async move { - assert!(matches!(*rx.borrow_and_update(), Status::Unknown)); + assert!(matches!(*rx.borrow_and_update(), Status::Error(None))); rx.changed().await.unwrap(); - assert!(matches!(*rx.borrow_and_update(), Status::Pass)); + assert!(matches!(*rx.borrow_and_update(), Status::Ok)); rx.changed().await.unwrap(); assert_eq!( *rx.borrow_and_update(), - Status::Fail(Some(String::from("Disconnected"))) + Status::Error(Some(String::from("Disconnected"))) ); }); diff --git a/src/sse.rs b/src/sse.rs index b4a8840..88befd1 100644 --- a/src/sse.rs +++ b/src/sse.rs @@ -13,10 +13,10 @@ use tokio_stream::StreamExt; use crate::{service::ServiceHandles, Error}; pub fn router() -> Router { - axum::Router::new().route("/:name", get(sse_handler)) + axum::Router::new().route("/:name", get(service_events)) } -pub async fn sse_handler( +pub async fn service_events( Path(name): Path, State(services): State, ) -> Result>>, Error> { @@ -24,7 +24,12 @@ pub async fn sse_handler( .get(&name) .ok_or_else(|| Error::ServiceNotFound(name))? .into_stream() - .map(|s| Event::default().json_data(s)); + .map(|s| match s { + crate::Status::Ok => Event::default().event("ok"), + crate::Status::Error(None) => Event::default().event("error"), + crate::Status::Error(Some(msg)) => Event::default().event("error").data(msg), + }) + .map(Ok); Ok(Sse::new(stream).keep_alive(KeepAlive::default())) } -- cgit v1.2.3-70-g09d2