commit fb312d21025425a7ef01c0139bae908a1c286608 Author: Paul-Christian Volkmer Date: Fri Mar 4 14:18:15 2022 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dd42b2f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "checkbar" +version = "0.1.0" +edition = "2021" +author = "Paul-Christian Volkmer " + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dirs = "4" +serde = { version = "1", features = ["derive"] } +chrono = { version = "*", features = ["serde"] } +tokio = { version = "1", features = ["full"] } +toml = "*" + +[dependencies.reqwest] +version = "*" +features = ["json", "rustls-tls"] +default-features = false + +[dev-dependencies] +serde_derive = "1" + +[profile.release] +opt-level = "s" +codegen-units = 1 +lto = "thin" +strip = "debuginfo" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4aa621 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Paul-Christian Volkmer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..7ff3f19 --- /dev/null +++ b/README.adoc @@ -0,0 +1,34 @@ += Checkbar + +This tool shows up/warn/down state of configured hosts or applications using i3bar input protocol. + +image::checkbar.png[] + +== Usage + +You should create a configuration file `.checkbar.toml` in your home directory, e.g. + +---- +# Update interval in seconds. Default value if not set is 60 sec. +interval = 60 + +[[checks]] +name = "Host 1" +url = "https://host1.example.com" + +[[checks]] +name = "Host 2" +url = "https://host2.example.com" + +[[checks]] +name = "App 1" +url = "https://app.example.com/actuator/health" +check_type = "Actuator" +---- + +Each host or application to be checked constists of `name` and `url`. + +You can optionally specify `check_type`: + +* `Html`: Default value, checks if a request is succeessful and returns HTTP OK - 200. +* `Actuator`: Like `Html`, but checks if _Actuator_ shows that the application is up and running. diff --git a/checkbar.png b/checkbar.png new file mode 100644 index 0000000..2afedde Binary files /dev/null and b/checkbar.png differ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..00867c5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,125 @@ +use serde::Deserialize; +use std::fmt::{Display, Formatter, Result}; +use std::time::Duration; + +#[derive(Deserialize)] +struct Config { + interval: Option, + checks: Vec, +} + +#[derive(Deserialize)] +struct CheckConfig { + name: String, + url: String, + check_type: Option, +} + +#[derive(Deserialize)] +enum CheckType { + Http, + Actuator, +} + +#[derive(Deserialize)] +struct ActuatorResponse { + status: String, +} + +struct CheckResult { + name: String, + state: CheckState, +} + +impl Display for CheckResult { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let color = match &self.state { + CheckState::Up => "#00FF00", + CheckState::Warn => "#FFFF00", + CheckState::Down => "#FF0000", + }; + write!( + f, + "{{\"full_text\":\"{}\",\"separator_block_width\":16,\"color\":\"{}\"}}", + self.name, color + ) + } +} + +enum CheckState { + Up, + Warn, + Down, +} + +async fn check_host(check_config: &CheckConfig) -> CheckResult { + let state = match reqwest::get(check_config.url.as_str()).await { + Ok(r) => { + if r.status().is_success() { + match check_config.check_type { + Some(CheckType::Actuator) => match r.json::().await { + Ok(ar) => { + if ar.status == "OK" { + CheckState::Up + } else { + CheckState::Warn + } + } + _ => CheckState::Warn, + }, + // Default: HTTP + _ => CheckState::Up, + } + } else { + CheckState::Warn + } + } + Err(_) => CheckState::Down, + }; + + CheckResult { + name: check_config.name.to_string(), + state, + } +} + +async fn print_states(check_configs: &[CheckConfig]) { + print!("["); + let mut entries = vec![]; + for check_config in check_configs { + entries.push(format!("{}", check_host(check_config).await)); + } + entries.push(format!( + "{{\"full_text\":\"check@{}\"}}", + chrono::Local::now().format("%H:%M") + )); + println!("{}],", entries.join(",")); +} + +#[tokio::main(flavor = "current_thread")] +async fn main() { + println!("{{\"version\":1,\"click_events\":false}}"); + println!("["); + loop { + let home_dir = dirs::home_dir().unwrap(); + let config = match std::fs::read_to_string(format!( + "{}/.checkbar.toml", + home_dir.to_str().unwrap_or("") + )) { + Ok(config) => match toml::from_str(config.as_str()) { + Ok(config) => config, + Err(_e) => Config { + interval: None, + checks: vec![], + }, + }, + Err(_e) => Config { + interval: None, + checks: vec![], + }, + }; + + print_states(&config.checks).await; + std::thread::sleep(Duration::from_secs(config.interval.unwrap_or(60))); + } +}