From fb312d21025425a7ef01c0139bae908a1c286608 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Fri, 4 Mar 2022 14:18:15 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.toml | 28 ++++++++++++ LICENSE | 21 +++++++++ README.adoc | 34 ++++++++++++++ checkbar.png | Bin 0 -> 4154 bytes src/main.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.adoc create mode 100644 checkbar.png create mode 100644 src/main.rs 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 0000000000000000000000000000000000000000..2afedde2577af018bc535df867836954caef233e GIT binary patch literal 4154 zcmV-A5XJ9_P)X1^@s6qPpx@0004lX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$i(@I5JI@m$PAwzYtAS&XhRVYG*P%E_RU~=gfG%+M8 zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0YbCJG%GX)Xu54? z(Mc8{Svtpa#g{| zF^>&skX=9cAN=mtDou=gNs$!L^Wr!k!$5c!Xf+(?``B?>CqVESxY9fRjV3VtNqVEB zC60jDHgIv>(Ud*lat9cAGGtSBr65hAR07`5=$i^a|1HqB>h;#%$LRx*qpmVHz`-Ff zQl{)RpLh3m_xA6Zc7H$E5^`yksgz6r000JJOGiWi`~dm@{SVlFga7~l32;bRa{vG? zBLDy{BLR4&KXw2B00(qQO+^Rh0|X5THqxsCI{*Lx8FWQhbVF}#ZDnqB07G(RVRU6= zAa`kWXdp*PO;A^X4i^9b4iia4K~#9!?VNi|RM)=8KWpAIf}`Thz`;8}3szgJi8MCW z6t%6f+7v|$Hb*_Nx6#@$dI1UdLmPi>=l7l&C85tQF**VI{$jHdfQAS2aMs|)52nh)Z zvVK{=jO-j`WMpJy=O`m1BO^P<-;+gMMWGBBC{))%q8MZbgr9|biN?PZ( z-c|X|ah=p;*I>JB<96chhfBYJrq`ONI8{N}g)(aYw-)z!_ptSCc}okQ)z-YXpO4yq4Tqc6~-tYt1q&8XQc}XMUAqVg2?@%#Y}qmj3JUn{yYC(@=Xf*uCRe||N?CCk zH3w^OKjsdKY#Lv0r2K3-wo5iDPF2wI$Cj?IIn)vrXDTQwEu$m4gSwq{*sj>HU9!E+j86kCKuSzWnk_rc9X<9Qi7hY}&Mm z!oot19XrOfY14wvCnhG6mzPITQ4ybg_8Ai=PQ0V~12LP;oIH7wMT-`pR4Q4%d^sO~ z{4s@vg>-gyVzb%Uw{IUwNl5@iM@O@H^JaGJ*nww|XkQ4yx^?R~ckUcN|NQfSc~VnT z$ek4*}8RWzpus~Xy@ph z;^fAkZxHs^FrrhU(N=1y+tC}jxF)!0UfE3emT(?<=@Cr3Of;=-!ej9uJOb;VtVAb8 zqpnk9&M*^`7(>j081(u3fv(>2yP` z-|s*a3N~-v%!LaV_~x5$Fc=I(Mn+OpRD@2aWB2aeoH%iU>C>lUHk(l>6dXQ$m>+)l z;Wsrt{q)n!oH>)MtgJzum)UG4BqRi*(HNAj)9Gjp*beb{Jh)sgwA$WP-0Ib<5khd{ z#0icbJ<6g5d5@@P4Xv?*TPJy~!jka8iwnB@-sgU*G&Hm%6CPKas zL1kABoBpW|2PHNer8XOXzj_r_zyzA^a8RJvb9mG!%CuTCO(w!U9<1FgsCt?Ab$DSQr+I#UEJ} z6cljg%o!RQ8rZvcFQZ3~COSHL$o2QIWXTd9fBbPi{P073KA+zKIh{^s&z?wP}j6$*cIJ;(ZP2U?=jVOOg;T2)1nUQe-3$2UeJ zO{(G3?*;x65dpwUw{Men;|7206XDz~dcB^uwl;r6<#M^$xN&3f(mvq4)9FN#Bvjq{ zbUGa`yzm0W#l`)W;zOetVRhzM*p8!nfNl9CdhefHTQ z=}SpT!DuwHZ{NP(wz1bcV7onBzkZ$k{Cr$4S5W%m#fv#|Fy{E?v4bbN9^_$`}XaA zM|LOm+wFGt@88dxZ@!7aU+U ziz6&73`vrB|NZw_xNsp!Nl7FnC6Sq#IiQX$TefiK%$a@@3FVWx3SBc8Dy zbeD8Qz8#6^?79<%9uLJi*~#^+>v%)Fs2Ww6cAC1Dr~ca>n0A?{|G1u;DK`;bLCniB z!FdK-PArkuhV&aOKJs)~;PkTU#3dIXO9mhK90o, + 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))); + } +}