Skip to main content

kuma_rs/
lib.rs

1use std::collections::HashMap;
2use std::fmt::Display;
3
4pub type Result<T> = core::result::Result<T, anyhow::Error>;
5
6#[derive(Debug, Clone)]
7pub struct Kuma {
8    pub url: String,
9    pub auth: String,
10}
11
12impl Kuma {
13    pub fn new(url: impl Into<String>, auth: impl Into<String>) -> Self {
14        let mut url = url.into();
15
16        if url.contains("https://") {
17            (0..8).for_each(|_| {
18                url.remove(0);
19            });
20        };
21
22        Self {
23            url,
24            auth: format!("https://:{}", auth.into()),
25        }
26    }
27
28    // infallible: returns offline if server cannot be reached
29    pub async fn get_abstract(&self) -> DataHouse {
30        if let Ok(data) = self.get().await {
31            return data;
32        };
33        DataHouse {
34            entries: HashMap::new(),
35            state: HouseState::Offline,
36        }
37    }
38    // gets the status based on authentication and uri
39    pub async fn get(&self) -> Result<DataHouse> {
40        let uri = format!("{}@{}", self.auth, self.url);
41        println!("URI: {uri}");
42        let data: Vec<Data> = reqwest::get(uri)
43            .await?
44            .text()
45            .await?
46            .split('\n')
47            .filter(|a| a.contains("monitor_status{monitor_name="))
48            .map(|a| {
49                let mut a: Vec<String> = a.split("").map(String::from).collect();
50                for _ in 0..15 {
51                    a.remove(0);
52                }
53                a.into_iter().fold(String::new(), |mut a, b| {
54                    a += &b;
55                    a
56                })
57            })
58            .map(|a| {
59                let mut a: Vec<String> = a
60                    .replace(['"', '\\'], "")
61                    .split(',')
62                    .filter_map(|a| a.split('=').map(String::from).last())
63                    .enumerate()
64                    .map(|a| {
65                        if a.0 != 4 {
66                            return a.1;
67                        }
68                        a.1.split("")
69                            .filter_map(|a| a.parse::<u32>().ok())
70                            .sum::<u32>()
71                            .to_string()
72                    })
73                    .collect();
74                a.remove(3);
75                a
76            })
77            .filter_map(|a| {
78                Some(Data {
79                    monitor_name: a.first()?.clone(),
80                    monitor_type: MonitorType::from_str(a.get(1)?),
81                    monitor_url: a.get(2)?.clone(),
82                    status: Status::from_str(a.get(3)?)?,
83                })
84            })
85            .collect();
86
87        Ok(DataHouse::from_vec(data))
88    }
89}
90#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
91pub struct Data {
92    pub monitor_name: String,
93    pub monitor_type: MonitorType,
94    pub monitor_url: String,
95    pub status: Status,
96}
97
98#[derive(Debug, Clone)]
99pub struct DataHouse {
100    pub entries: HashMap<String, Data>,
101    pub state: HouseState,
102}
103impl DataHouse {
104    pub fn offline_services(&self) -> HashMap<String, Data> {
105        self.clone()
106            .entries
107            .into_iter()
108            .filter(|a| a.1.status == Status::Offline)
109            .collect()
110    }
111    fn from_vec(input: Vec<Data>) -> Self {
112        let mut map = HashMap::new();
113        for x in input {
114            map.insert(x.monitor_name.clone(), x);
115        }
116
117        let (offline_count, online_count) = map.values().fold((0, 0), |(mut of, mut on), b| {
118            match b.status {
119                Status::Online => on += 1,
120                Status::Offline => of += 1,
121            };
122            (of, on)
123        });
124        Self {
125            entries: map,
126            state: match (offline_count, online_count) {
127                (0, 0) => HouseState::Offline,
128                (0, _) => HouseState::Online,
129                (_, 0) => HouseState::Offline,
130                (a, _) => HouseState::Degraded(a),
131            },
132        }
133    }
134}
135#[derive(Debug, Clone, PartialEq)]
136pub enum HouseState {
137    // No services can be reached, fully offline
138    Offline,
139    // Partially offline, issues with certain services
140    Degraded(usize),
141    // fully online
142    Online, // Something went wrong with collecting data
143}
144impl Display for HouseState {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        let str = match self {
147            HouseState::Offline => "Offline",
148            HouseState::Degraded(_) => "Degraded",
149            HouseState::Online => "Online",
150        }
151        .to_string();
152        write!(f, "{}", str)
153    }
154}
155impl HouseState {
156    pub fn is_degraded(&self) -> Option<usize> {
157        match self {
158            HouseState::Offline | HouseState::Online => None,
159            HouseState::Degraded(a) => Some(*a),
160        }
161    }
162}
163#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
164pub enum MonitorType {
165    Http,
166    Other,
167}
168impl Display for MonitorType {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        let str = match self {
171            MonitorType::Http => "HTTP/HTTPS",
172            MonitorType::Other => "Other",
173        }
174        .to_string();
175        write!(f, "{}", str)
176    }
177}
178impl MonitorType {
179    fn from_str(i: impl Into<String>) -> Self {
180        match i.into().as_str() {
181            "http" => MonitorType::Http,
182            _ => MonitorType::Other,
183        }
184    }
185}
186#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
187pub enum Status {
188    Online,
189    Offline,
190}
191impl Display for Status {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        let str = match self {
194            Status::Online => "Online",
195            Status::Offline => "Offline",
196        }
197        .to_string();
198        write!(f, "{}", str)
199    }
200}
201impl Status {
202    fn from_str(i: impl Into<String>) -> Option<Self> {
203        match i.into().parse::<u32>().ok()? {
204            0 => Some(Status::Offline),
205            1 => Some(Status::Online),
206            _ => None,
207        }
208    }
209}