summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock73
-rw-r--r--Cargo.toml9
-rw-r--r--assets/index.html8
-rw-r--r--assets/index.js44
-rw-r--r--config.toml2
-rw-r--r--src/api.rs8
-rw-r--r--src/lib.rs17
-rw-r--r--src/main.rs14
-rw-r--r--src/service.rs4
-rw-r--r--src/service/http.rs12
-rw-r--r--src/service/systemd.rs17
-rw-r--r--src/service/tcp.rs10
-rw-r--r--src/sse.rs11
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 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>status.tobyvin.dev</title>
<link rel="stylesheet" type="text/css" href="index.css" />
- <script src="index.js"></script>
+ <script src="index.js"></script>
</head>
<body>
<main>
<header>
<hgroup>
- <h1>tobyvin.dev Status</h1>
+ <h1>SrvStat</h1>
</hgroup>
</header>
-
<table>
<tr id="status">
<td><b>Status</b></td>
<td id="issues">No issues detected</td>
</tr>
</table>
-
<hr></hr>
-
<table id="services"></table>
</main>
</body>
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<ServiceHandles>) -> 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<ServiceHandles> {
.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<String>),
- Fail(Option<String>),
- #[default]
- Unknown,
+ Ok,
+ Error(Option<String>),
+}
+
+impl Default for Status {
+ fn default() -> Self {
+ Status::Error(Some("Unknown".to_string()))
+ }
}
impl Status {
@@ -36,7 +39,7 @@ impl Status {
impl<T: std::error::Error> From<T> 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<dyn std::error::Error>> {
- 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<ServiceHandles> {
- 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<String>,
State(services): State<ServiceHandles>,
) -> Result<Sse<impl Stream<Item = Result<Event, axum::Error>>>, 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()))
}