kuma_client/
client.rs

1use crate::{
2    docker_host::{DockerHost, DockerHostList},
3    error::{Error, InvalidReferenceError, Result, TotpResult},
4    event::Event,
5    maintenance::{Maintenance, MaintenanceList, MaintenanceMonitor, MaintenanceStatusPage},
6    monitor::{Monitor, MonitorList},
7    notification::{Notification, NotificationList},
8    response::LoginResponse,
9    status_page::{PublicGroupList, StatusPage, StatusPageList},
10    tag::{Tag, TagDefinition},
11    util::ResultLogger,
12    Config,
13};
14use futures_util::FutureExt;
15use itertools::Itertools;
16use log::{debug, trace, warn};
17use native_tls::{Certificate, TlsConnector};
18use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
19use rust_socketio::{
20    asynchronous::{Client as SocketIO, ClientBuilder},
21    Event as SocketIOEvent, Payload,
22};
23use serde::de::DeserializeOwned;
24use serde_json::{json, Value};
25use std::{
26    collections::{HashMap, HashSet},
27    fs, mem,
28    str::FromStr,
29    sync::{Arc, Weak},
30    time::Duration,
31};
32use tap::prelude::*;
33use tokio::{runtime::Handle, sync::Mutex};
34use totp_rs::{Rfc6238, TOTP};
35
36struct Ready {
37    pub monitor_list: bool,
38    pub notification_list: bool,
39    pub maintenance_list: bool,
40    pub status_page_list: bool,
41    pub docker_host_list: bool,
42}
43
44impl Ready {
45    pub fn new() -> Self {
46        Self {
47            monitor_list: false,
48            notification_list: false,
49            maintenance_list: false,
50            status_page_list: false,
51            docker_host_list: false,
52        }
53    }
54
55    pub fn reset(&mut self) {
56        *self = Ready::new()
57    }
58
59    pub fn is_ready(&self) -> bool {
60        self.monitor_list
61            && self.notification_list
62            && self.maintenance_list
63            && self.status_page_list
64            && self.docker_host_list
65    }
66}
67
68struct Worker {
69    config: Arc<Config>,
70    #[allow(dead_code)]
71    socket_io: Arc<Mutex<Option<SocketIO>>>,
72    monitors: Arc<Mutex<MonitorList>>,
73    notifications: Arc<Mutex<NotificationList>>,
74    docker_hosts: Arc<Mutex<DockerHostList>>,
75    maintenances: Arc<Mutex<MaintenanceList>>,
76    status_pages: Arc<Mutex<StatusPageList>>,
77    is_connected: Arc<Mutex<bool>>,
78    is_ready: Arc<Mutex<Ready>>,
79    is_logged_in: Arc<Mutex<bool>>,
80    auth_token: Arc<Mutex<Option<String>>>,
81    reqwest: Arc<Mutex<reqwest::Client>>,
82    custom_cert: Option<(String, Certificate)>,
83}
84
85impl Worker {
86    fn new(config: Config) -> Result<Arc<Self>> {
87        let custom_cert = config
88            .tls
89            .cert
90            .as_ref()
91            .map(|file| -> Result<(String, Certificate)> {
92                fs::read(file)
93                    .map_err(|e| Error::InvalidTlsCert(file.clone(), e.to_string()))
94                    .and_then(|content| {
95                        Certificate::from_pem(&content)
96                            .map_err(|e| Error::InvalidTlsCert(file.clone(), e.to_string()))
97                    })
98                    .map(|cert| (file.clone(), cert))
99            })
100            .transpose()?;
101
102        let mut reqwest_builder = reqwest::Client::builder()
103            .danger_accept_invalid_certs(!config.tls.verify)
104            .default_headers(HeaderMap::from_iter(
105                config
106                    .headers
107                    .iter()
108                    .filter_map(|header| header.split_once("="))
109                    .filter_map(|(key, value)| {
110                        match (
111                            HeaderName::from_bytes(key.as_bytes()),
112                            HeaderValue::from_bytes(value.as_bytes()),
113                        ) {
114                            (Ok(key), Ok(value)) => Some((key, value)),
115                            _ => None,
116                        }
117                    }),
118            ));
119
120        if let Some((file, cert)) = &custom_cert {
121            reqwest_builder = reqwest_builder.add_root_certificate(
122                reqwest::Certificate::from_der(
123                    &cert
124                        .to_der()
125                        .map_err(|e| Error::InvalidTlsCert(file.clone(), e.to_string()))?,
126                )
127                .map_err(|e| Error::InvalidTlsCert(file.clone(), e.to_string()))?,
128            );
129        }
130
131        Ok(Arc::new(Worker {
132            config: Arc::new(config.clone()),
133            socket_io: Arc::new(Mutex::new(None)),
134            monitors: Default::default(),
135            notifications: Default::default(),
136            maintenances: Default::default(),
137            status_pages: Default::default(),
138            docker_hosts: Default::default(),
139            is_connected: Arc::new(Mutex::new(false)),
140            is_ready: Arc::new(Mutex::new(Ready::new())),
141            is_logged_in: Arc::new(Mutex::new(false)),
142            auth_token: Arc::new(Mutex::new(config.auth_token)),
143            reqwest: Arc::new(Mutex::new(reqwest_builder.build().unwrap())),
144            custom_cert: custom_cert,
145        }))
146    }
147
148    fn get_mfa_token(self: &Arc<Self>) -> TotpResult<Option<String>> {
149        Ok(match &self.config.mfa_secret {
150            Some(secret) => {
151                let totp = match secret {
152                    url if url.starts_with("otpauth://") => TOTP::from_url(url)?,
153                    secret => TOTP::from_rfc6238(Rfc6238::with_defaults(
154                        totp_rs::Secret::Encoded(secret.clone()).to_bytes()?,
155                    )?)?,
156                };
157                Some(totp.generate_current()?)
158            }
159            None => self.config.mfa_token.clone(),
160        })
161    }
162
163    async fn on_monitor_list(self: &Arc<Self>, monitor_list: MonitorList) -> Result<()> {
164        *self.monitors.lock().await = monitor_list;
165        self.is_ready.lock().await.monitor_list = true;
166
167        Ok(())
168    }
169
170    async fn on_notification_list(
171        self: &Arc<Self>,
172        notification_list: NotificationList,
173    ) -> Result<()> {
174        *self.notifications.lock().await = notification_list;
175        self.is_ready.lock().await.notification_list = true;
176
177        Ok(())
178    }
179
180    async fn on_maintenance_list(
181        self: &Arc<Self>,
182        maintenance_list: MaintenanceList,
183    ) -> Result<()> {
184        *self.maintenances.lock().await = maintenance_list;
185        self.is_ready.lock().await.maintenance_list = true;
186
187        Ok(())
188    }
189
190    async fn on_status_page_list(self: &Arc<Self>, status_page_list: StatusPageList) -> Result<()> {
191        *self.status_pages.lock().await = status_page_list;
192        self.is_ready.lock().await.status_page_list = true;
193
194        Ok(())
195    }
196
197    async fn on_docker_host_list(self: &Arc<Self>, docker_host_list: DockerHostList) -> Result<()> {
198        *self.docker_hosts.lock().await = docker_host_list;
199        self.is_ready.lock().await.docker_host_list = true;
200
201        Ok(())
202    }
203
204    async fn on_info(self: &Arc<Self>) -> Result<()> {
205        *self.is_connected.lock().await = true;
206        let logged_in = *self.is_logged_in.lock().await;
207
208        if !logged_in {
209            let auth_token = self.auth_token.lock().await.clone();
210
211            // Try logging in with a token if available
212            if let Some(auth_token) = auth_token {
213                if self.login_by_token(auth_token).await.is_ok() {
214                    return Ok(());
215                }
216            }
217
218            if let (Some(username), Some(password)) = (&self.config.username, &self.config.password)
219            {
220                let mfa_token = self.get_mfa_token()?;
221                self.login(username, password, mfa_token).await?;
222            }
223        }
224
225        Ok(())
226    }
227
228    async fn on_login_required(self: &Arc<Self>) -> Result<()> {
229        Ok(())
230    }
231
232    async fn on_auto_login(self: &Arc<Self>) -> Result<()> {
233        debug!("Logged in using AutoLogin!");
234        *self.is_logged_in.lock().await = true;
235        Ok(())
236    }
237
238    async fn on_delete_monitor_from_list(self: &Arc<Self>, monitor_id: i32) -> Result<()> {
239        self.monitors.lock().await.remove(&monitor_id.to_string());
240        Ok(())
241    }
242
243    async fn on_update_monitor_into_list(self: &Arc<Self>, monitors: MonitorList) -> Result<()> {
244        self.monitors.lock().await.extend(monitors);
245        Ok(())
246    }
247
248    async fn on_event(self: &Arc<Self>, event: Event, payload: Value) -> Result<()> {
249        match event {
250            Event::MonitorList => {
251                self.on_monitor_list(
252                    serde_json::from_value(payload)
253                        .log_error(module_path!(), |_| "Failed to deserialize MonitorList")
254                        .unwrap(),
255                )
256                .await?
257            }
258            Event::NotificationList => {
259                self.on_notification_list(
260                    serde_json::from_value(payload)
261                        .log_error(module_path!(), |_| "Failed to deserialize NotificationList")
262                        .unwrap(),
263                )
264                .await?
265            }
266            Event::MaintenanceList => {
267                self.on_maintenance_list(
268                    serde_json::from_value(payload)
269                        .log_error(module_path!(), |_| "Failed to deserialize MaintenanceList")
270                        .unwrap(),
271                )
272                .await?
273            }
274            Event::StatusPageList => {
275                self.on_status_page_list(
276                    serde_json::from_value(payload)
277                        .log_error(module_path!(), |_| "Failed to deserialize StatusPageList")
278                        .unwrap(),
279                )
280                .await?
281            }
282            Event::DockerHostList => {
283                self.on_docker_host_list(
284                    serde_json::from_value(payload)
285                        .log_error(module_path!(), |_| "Failed to deserialize DockerHostList")
286                        .unwrap(),
287                )
288                .await?
289            }
290            Event::Info => self.on_info().await?,
291            Event::AutoLogin => self.on_auto_login().await?,
292            Event::LoginRequired => self.on_login_required().await?,
293            Event::UpdateMonitorIntoList => {
294                self.on_update_monitor_into_list(
295                    serde_json::from_value(payload)
296                        .log_error(module_path!(), |_| {
297                            "Failed to deserialize UpdateMonitorIntoList"
298                        })
299                        .unwrap(),
300                )
301                .await?
302            }
303            Event::DeleteMonitorFromList => {
304                self.on_delete_monitor_from_list(payload.as_i64().unwrap().try_into().unwrap())
305                    .await?
306            }
307            _ => {}
308        }
309
310        Ok(())
311    }
312
313    fn extract_response<T: DeserializeOwned>(
314        response: Vec<Value>,
315        result_ptr: impl AsRef<str>,
316        verify: bool,
317    ) -> Result<T> {
318        let json = json!(response);
319
320        if verify
321            && !json
322                .pointer("/0/0/ok")
323                .ok_or_else(|| {
324                    Error::InvalidResponse(response.clone(), result_ptr.as_ref().to_owned())
325                })?
326                .as_bool()
327                .unwrap_or_default()
328        {
329            let error_msg = json
330                .pointer("/0/0/msg")
331                .unwrap_or_else(|| &json!(null))
332                .as_str()
333                .unwrap_or_else(|| "Unknown error");
334
335            return Err(Error::ServerError(error_msg.to_owned()));
336        }
337
338        json.pointer(&format!("/0/0{}", result_ptr.as_ref()))
339            .and_then(|value| serde_json::from_value(value.to_owned()).ok())
340            .ok_or_else(|| Error::InvalidResponse(response, result_ptr.as_ref().to_owned()))
341    }
342
343    async fn call<A, T>(
344        self: &Arc<Self>,
345        method: impl Into<String>,
346        args: A,
347        result_ptr: impl Into<String>,
348        verify: bool,
349    ) -> Result<T>
350    where
351        A: IntoIterator<Item = Value> + Send + Clone,
352        T: DeserializeOwned + Send + 'static,
353    {
354        let method = method.into();
355        let result_ptr: String = result_ptr.into();
356
357        let method_ref = method.clone();
358        let args: A = args.clone();
359        let result_ptr = result_ptr.clone();
360        let (tx, mut rx) = tokio::sync::mpsc::channel::<Result<T>>(1);
361
362        let lock = self.socket_io.lock().await;
363        let socket_io = match &*lock {
364            Some(socket_io) => socket_io,
365            None => Err(Error::Disconnected)?,
366        };
367
368        socket_io
369            .emit_with_ack(
370                method.clone(),
371                Payload::Text(args.into_iter().collect_vec()),
372                Duration::from_secs_f64(self.config.call_timeout),
373                move |message: Payload, _: SocketIO| {
374                    debug!("call {} -> {:?}", method_ref, &message);
375                    let tx = tx.clone();
376                    let result_ptr = result_ptr.clone();
377                    async move {
378                        _ = match message {
379                            Payload::Text(response) => {
380                                tx.send(Self::extract_response(response, result_ptr, verify))
381                                    .await
382                            }
383                            _ => tx.send(Err(Error::UnsupportedResponse)).await,
384                        }
385                    }
386                    .boxed()
387                },
388            )
389            .await
390            .map_err(|e| Error::CommunicationError(e.to_string()))?;
391
392        let result =
393            tokio::time::timeout(Duration::from_secs_f64(self.config.call_timeout), rx.recv())
394                .await
395                .map_err(|_| Error::CallTimeout(method.clone()))?
396                .ok_or_else(|| Error::CallTimeout(method))?;
397
398        result
399    }
400
401    pub async fn login(
402        self: &Arc<Self>,
403        username: impl AsRef<str>,
404        password: impl AsRef<str>,
405        token: Option<String>,
406    ) -> Result<()> {
407        let result: Result<LoginResponse> = self
408            .call(
409                "login",
410                vec![serde_json::to_value(HashMap::from([
411                    ("username", json!(username.as_ref())),
412                    ("password", json!(password.as_ref())),
413                    ("token", json!(token)),
414                ]))
415                .unwrap()],
416                "",
417                false,
418            )
419            .await;
420
421        match result {
422            Ok(LoginResponse::TokenRequired { .. }) => Err(Error::TokenRequired),
423            Ok(LoginResponse::Normal {
424                ok: true,
425                token: Some(auth_token),
426                ..
427            }) => {
428                debug!("Logged in as {}!", username.as_ref());
429                *self.is_logged_in.lock().await = true;
430                *self.auth_token.lock().await = Some(auth_token);
431                Ok(())
432            }
433            Ok(LoginResponse::Normal {
434                ok: false,
435                msg: Some(msg),
436                ..
437            }) => Err(Error::LoginError(msg)),
438            Err(e) => {
439                *self.is_logged_in.lock().await = false;
440                Err(e)
441            }
442            _ => {
443                *self.is_logged_in.lock().await = false;
444                Err(Error::LoginError("Unexpect login response".to_owned()))
445            }
446        }
447        .log_warn(std::module_path!(), |e| e.to_string())
448    }
449
450    pub async fn login_by_token(self: &Arc<Self>, auth_token: impl AsRef<str>) -> Result<()> {
451        let result: Result<LoginResponse> = self
452            .call("loginByToken", vec![json!(auth_token.as_ref())], "", false)
453            .await;
454
455        match result {
456            Ok(LoginResponse::TokenRequired { .. }) => Err(Error::TokenRequired),
457            Ok(LoginResponse::Normal { ok: true, .. }) => {
458                debug!("Logged in using auth_token!");
459                *self.is_logged_in.lock().await = true;
460                Ok(())
461            }
462            Ok(LoginResponse::Normal {
463                ok: false,
464                msg: Some(msg),
465                ..
466            }) => Err(Error::LoginError(msg)),
467            Err(e) => {
468                *self.is_logged_in.lock().await = false;
469                Err(e)
470            }
471            _ => {
472                *self.is_logged_in.lock().await = false;
473                Err(Error::LoginError("Unexpect login response".to_owned()))
474            }
475        }
476        .log_warn(std::module_path!(), |e| e.to_string())
477    }
478
479    async fn get_tags(self: &Arc<Self>) -> Result<Vec<TagDefinition>> {
480        self.call("getTags", vec![], "/tags", true).await
481    }
482
483    pub async fn add_tag(self: &Arc<Self>, tag: &mut TagDefinition) -> Result<()> {
484        *tag = self
485            .call(
486                "addTag",
487                vec![serde_json::to_value(tag.clone()).unwrap()],
488                "/tag",
489                true,
490            )
491            .await?;
492
493        Ok(())
494    }
495
496    pub async fn edit_tag(self: &Arc<Self>, tag: &mut TagDefinition) -> Result<()> {
497        *tag = self
498            .call(
499                "editTag",
500                vec![serde_json::to_value(tag.clone()).unwrap()],
501                "/tag",
502                true,
503            )
504            .await?;
505
506        Ok(())
507    }
508
509    pub async fn delete_tag(self: &Arc<Self>, tag_id: i32) -> Result<()> {
510        let _: bool = self
511            .call("deleteTag", vec![json!(tag_id)], "/ok", true)
512            .await?;
513
514        Ok(())
515    }
516
517    pub async fn add_notification(self: &Arc<Self>, notification: &mut Notification) -> Result<()> {
518        self.edit_notification(notification).await
519    }
520
521    pub async fn edit_notification(
522        self: &Arc<Self>,
523        notification: &mut Notification,
524    ) -> Result<()> {
525        let json = serde_json::to_value(notification.clone()).unwrap();
526        let config_json =
527            serde_json::to_value(notification.config.clone().unwrap_or_else(|| json!({}))).unwrap();
528
529        let merge = serde_merge::omerge(config_json, &json).unwrap();
530
531        notification.id = Some(
532            self.call(
533                "addNotification",
534                vec![merge, notification.id.into()],
535                "/id",
536                true,
537            )
538            .await?,
539        );
540
541        Ok(())
542    }
543
544    pub async fn delete_notification(self: &Arc<Self>, notification_id: i32) -> Result<()> {
545        let _: bool = self
546            .call(
547                "deleteNotification",
548                vec![json!(notification_id)],
549                "/ok",
550                true,
551            )
552            .await?;
553
554        Ok(())
555    }
556
557    pub async fn add_monitor_tag(
558        self: &Arc<Self>,
559        monitor_id: i32,
560        tag_id: i32,
561        value: Option<String>,
562    ) -> Result<()> {
563        let _: bool = self
564            .call(
565                "addMonitorTag",
566                vec![
567                    json!(tag_id),
568                    json!(monitor_id),
569                    json!(value.unwrap_or_default()),
570                ],
571                "/ok",
572                true,
573            )
574            .await?;
575
576        Ok(())
577    }
578
579    pub async fn edit_monitor_tag(
580        self: &Arc<Self>,
581        monitor_id: i32,
582        tag_id: i32,
583        value: Option<String>,
584    ) -> Result<()> {
585        let _: bool = self
586            .call(
587                "editMonitorTag",
588                vec![
589                    json!(tag_id),
590                    json!(monitor_id),
591                    json!(value.unwrap_or_default()),
592                ],
593                "/ok",
594                true,
595            )
596            .await?;
597
598        Ok(())
599    }
600
601    pub async fn delete_monitor_tag(
602        self: &Arc<Self>,
603        monitor_id: i32,
604        tag_id: i32,
605        value: Option<String>,
606    ) -> Result<()> {
607        let _: bool = self
608            .call(
609                "deleteMonitorTag",
610                vec![
611                    json!(tag_id),
612                    json!(monitor_id),
613                    json!(value.unwrap_or_default()),
614                ],
615                "/ok",
616                true,
617            )
618            .await?;
619
620        Ok(())
621    }
622
623    pub async fn delete_monitor(self: &Arc<Self>, monitor_id: i32) -> Result<()> {
624        let _: bool = self
625            .call("deleteMonitor", vec![json!(monitor_id)], "/ok", true)
626            .await?;
627
628        Ok(())
629    }
630
631    async fn update_monitor_tags(self: &Arc<Self>, monitor_id: i32, tags: &Vec<Tag>) -> Result<()> {
632        let new_tags = tags
633            .iter()
634            .filter_map(|tag| tag.tag_id.and_then(|id| Some((id, tag))))
635            .collect::<HashMap<_, _>>();
636
637        if let Some(monitor) = self.monitors.lock().await.get(&monitor_id.to_string()) {
638            let current_tags = monitor
639                .common()
640                .tags()
641                .iter()
642                .filter_map(|tag| tag.tag_id.and_then(|id| Some((id, tag))))
643                .collect::<HashMap<_, _>>();
644
645            let duplicates = monitor
646                .common()
647                .tags()
648                .iter()
649                .duplicates_by(|tag| tag.tag_id)
650                .filter_map(|tag| tag.tag_id.as_ref().map(|id| (id, tag)))
651                .collect::<HashMap<_, _>>();
652
653            let to_delete = current_tags
654                .iter()
655                .filter(|(id, _)| !new_tags.contains_key(*id) && !duplicates.contains_key(*id))
656                .collect_vec();
657
658            let to_create = new_tags
659                .iter()
660                .filter(|(id, _)| !current_tags.contains_key(*id))
661                .collect_vec();
662
663            let to_update = current_tags
664                .keys()
665                .filter_map(|id| match (current_tags.get(id), new_tags.get(id)) {
666                    (Some(current), Some(new)) => Some((id, current, new)),
667                    _ => None,
668                })
669                .collect_vec();
670
671            for (tag_id, tag) in duplicates {
672                self.delete_monitor_tag(monitor_id, *tag_id, tag.value.clone())
673                    .await?;
674            }
675
676            for (tag_id, tag) in to_delete {
677                self.delete_monitor_tag(monitor_id, *tag_id, tag.value.clone())
678                    .await?;
679            }
680
681            for (tag_id, tag) in to_create {
682                self.add_monitor_tag(monitor_id, *tag_id, tag.value.clone())
683                    .await?
684            }
685
686            for (tag_id, current, new) in to_update {
687                if current.value != new.value {
688                    self.edit_monitor_tag(monitor_id, *tag_id, new.value.clone())
689                        .await?;
690                }
691            }
692        } else {
693            for tag in tags {
694                if let Some(tag_id) = tag.tag_id {
695                    self.add_monitor_tag(monitor_id, tag_id, tag.value.clone())
696                        .await?;
697                }
698            }
699        }
700
701        Ok(())
702    }
703
704    async fn verify_monitor(self: &Arc<Self>, monitor: &Monitor) -> Result<()> {
705        if let Some(referenced_parent) = monitor.common().parent() {
706            if !self
707                .monitors
708                .lock()
709                .await
710                .values()
711                .any(|m| m.common().id().is_some_and(|id| &id == referenced_parent))
712            {
713                return Err(Error::InvalidReference(
714                    InvalidReferenceError::InvalidParent(referenced_parent.to_string()),
715                ));
716            }
717        }
718
719        if let Some(referenced_notifications) = monitor.common().notification_id_list() {
720            let available_notifications = self
721                .notifications
722                .lock()
723                .await
724                .iter()
725                .filter_map(|n| n.id)
726                .collect::<HashSet<_>>();
727
728            for (notification_id, _) in referenced_notifications {
729                if let Some(id) = notification_id.parse::<i32>().ok() {
730                    if !available_notifications.contains(&id) {
731                        return Err(Error::InvalidReference(
732                            InvalidReferenceError::InvalidNotification(notification_id.to_owned()),
733                        ));
734                    }
735                } else {
736                    return Err(Error::InvalidReference(
737                        InvalidReferenceError::InvalidNotification(notification_id.to_owned()),
738                    ));
739                }
740            }
741        }
742
743        if let Monitor::Docker {
744            value: docker_monitor,
745        } = monitor
746        {
747            if let Some(referenced_docker_host) = &docker_monitor.docker_host {
748                if !self
749                    .docker_hosts
750                    .lock()
751                    .await
752                    .iter()
753                    .any(|dh| dh.id.is_some_and(|id| &id == referenced_docker_host))
754                {
755                    return Err(Error::InvalidReference(
756                        InvalidReferenceError::InvalidDockerHost(
757                            referenced_docker_host.to_string(),
758                        ),
759                    ));
760                }
761            }
762        }
763
764        Ok(())
765    }
766
767    pub async fn add_monitor(self: &Arc<Self>, monitor: &mut Monitor, verify: bool) -> Result<()> {
768        if verify {
769            self.verify_monitor(monitor).await?;
770        }
771
772        let tags = mem::take(monitor.common_mut().tags_mut());
773        let notifications = mem::take(monitor.common_mut().notification_id_list_mut());
774
775        #[cfg(feature = "private-api")]
776        let parent_name = mem::take(monitor.common_mut().parent_name_mut());
777        #[cfg(feature = "private-api")]
778        let create_paused = mem::take(monitor.common_mut().create_paused_mut());
779        #[cfg(feature = "private-api")]
780        let notification_names = mem::take(monitor.common_mut().notification_names_mut());
781        #[cfg(feature = "private-api")]
782        let docker_host_name = match monitor {
783            Monitor::Docker {
784                value: docker_monitor,
785            } => mem::take(&mut docker_monitor.docker_host_name),
786            _ => None,
787        };
788        #[cfg(feature = "private-api")]
789        let tag_names = mem::take(monitor.common_mut().tag_names_mut());
790
791        let id: i32 = self
792            .clone()
793            .call(
794                "add",
795                vec![serde_json::to_value(&monitor).unwrap()],
796                "/monitorID",
797                true,
798            )
799            .await?;
800
801        *monitor.common_mut().id_mut() = Some(id);
802        *monitor.common_mut().notification_id_list_mut() = notifications;
803        *monitor.common_mut().tags_mut() = tags;
804
805        #[cfg(feature = "private-api")]
806        {
807            *monitor.common_mut().parent_name_mut() = parent_name;
808            *monitor.common_mut().create_paused_mut() = create_paused;
809            *monitor.common_mut().notification_names_mut() = notification_names;
810            if let Monitor::Docker {
811                value: docker_monitor,
812            } = monitor
813            {
814                docker_monitor.docker_host_name = docker_host_name;
815            }
816            *monitor.common_mut().tag_names_mut() = tag_names;
817        }
818
819        self.edit_monitor(monitor, false).await?;
820
821        self.monitors
822            .lock()
823            .await
824            .insert(id.to_string(), monitor.clone());
825
826        #[cfg(feature = "private-api")]
827        if create_paused == Some(true) {
828            self.pause_monitor(id).await?;
829        }
830
831        Ok(())
832    }
833
834    pub async fn get_monitor(self: &Arc<Self>, monitor_id: i32) -> Result<Monitor> {
835        self.call(
836            "getMonitor",
837            vec![serde_json::to_value(monitor_id.clone()).unwrap()],
838            "/monitor",
839            true,
840        )
841        .await
842        .map_err(|e| match e {
843            Error::ServerError(msg) if msg.contains("Cannot read properties of null") => {
844                Error::IdNotFound("Monitor".to_owned(), monitor_id)
845            }
846            _ => e,
847        })
848    }
849
850    pub async fn edit_monitor(self: &Arc<Self>, monitor: &mut Monitor, verify: bool) -> Result<()> {
851        if verify {
852            self.verify_monitor(monitor).await?;
853        }
854
855        let tags = mem::take(monitor.common_mut().tags_mut());
856
857        #[cfg(feature = "private-api")]
858        let create_paused = mem::take(monitor.common_mut().create_paused_mut());
859
860        let mut monitor_json = serde_json::to_value(&monitor).unwrap();
861
862        // Workaround for https://github.com/BigBoot/AutoKuma/issues/72 until fixed in UptimeKuma
863        if let Some(monitor_json) = monitor_json.as_object_mut() {
864            if !monitor_json.contains_key("url") {
865                monitor_json.insert("url".to_owned(), json!("https://"));
866            }
867        }
868
869        let id: i32 = self
870            .call("editMonitor", vec![monitor_json], "/monitorID", true)
871            .await?;
872
873        self.update_monitor_tags(id, &tags).await?;
874
875        *monitor.common_mut().tags_mut() = tags;
876
877        #[cfg(feature = "private-api")]
878        {
879            *monitor.common_mut().create_paused_mut() = create_paused;
880        }
881
882        Ok(())
883    }
884
885    pub async fn pause_monitor(self: &Arc<Self>, monitor_id: i32) -> Result<()> {
886        let _: bool = self
887            .call(
888                "pauseMonitor",
889                vec![serde_json::to_value(monitor_id).unwrap()],
890                "/ok",
891                true,
892            )
893            .await?;
894
895        Ok(())
896    }
897
898    pub async fn resume_monitor(self: &Arc<Self>, monitor_id: i32) -> Result<()> {
899        let _: bool = self
900            .call(
901                "resumeMonitor",
902                vec![serde_json::to_value(monitor_id).unwrap()],
903                "/ok",
904                true,
905            )
906            .await?;
907
908        Ok(())
909    }
910
911    async fn get_maintenance_monitors(
912        self: &Arc<Self>,
913        maintenance_id: i32,
914    ) -> Result<Vec<MaintenanceMonitor>> {
915        self.call(
916            "getMonitorMaintenance",
917            vec![serde_json::to_value(maintenance_id).unwrap()],
918            "/monitors",
919            true,
920        )
921        .await
922    }
923
924    async fn set_maintenance_monitors(
925        self: &Arc<Self>,
926        maintenance_id: i32,
927        monitors: &Vec<MaintenanceMonitor>,
928    ) -> Result<()> {
929        let _: bool = self
930            .call(
931                "addMonitorMaintenance",
932                vec![
933                    serde_json::to_value(maintenance_id).unwrap(),
934                    serde_json::to_value(monitors).unwrap(),
935                ],
936                "/ok",
937                true,
938            )
939            .await?;
940
941        Ok(())
942    }
943
944    async fn get_maintenance_status_pages(
945        self: &Arc<Self>,
946        maintenance_id: i32,
947    ) -> Result<Vec<MaintenanceStatusPage>> {
948        self.call(
949            "getMaintenanceStatusPage",
950            vec![serde_json::to_value(maintenance_id).unwrap()],
951            "/statusPages",
952            true,
953        )
954        .await
955    }
956
957    async fn set_maintenance_status_pages(
958        self: &Arc<Self>,
959        maintenance_id: i32,
960        status_pages: &Vec<MaintenanceStatusPage>,
961    ) -> Result<()> {
962        let _: bool = self
963            .call(
964                "addMaintenanceStatusPage",
965                vec![
966                    serde_json::to_value(maintenance_id).unwrap(),
967                    serde_json::to_value(status_pages).unwrap(),
968                ],
969                "/ok",
970                true,
971            )
972            .await?;
973
974        Ok(())
975    }
976
977    pub async fn delete_maintenance(self: &Arc<Self>, maintenance_id: i32) -> Result<()> {
978        let _: bool = self
979            .call(
980                "deleteMaintenance",
981                vec![json!(maintenance_id)],
982                "/ok",
983                true,
984            )
985            .await?;
986
987        Ok(())
988    }
989
990    pub async fn add_maintenance(self: &Arc<Self>, maintenance: &mut Maintenance) -> Result<()> {
991        let id = self
992            .call(
993                "addMaintenance",
994                vec![serde_json::to_value(maintenance.clone()).unwrap()],
995                "/maintenanceID",
996                true,
997            )
998            .await?;
999
1000        maintenance.common_mut().id = Some(id);
1001        if let Some(monitors) = &maintenance.common().monitors {
1002            self.set_maintenance_monitors(id, monitors).await?;
1003        }
1004        if let Some(status_pages) = &maintenance.common().status_pages {
1005            self.set_maintenance_status_pages(id, status_pages).await?;
1006        }
1007
1008        Ok(())
1009    }
1010
1011    pub async fn get_maintenance(self: &Arc<Self>, maintenance_id: i32) -> Result<Maintenance> {
1012        let mut maintenance: Maintenance = self
1013            .call(
1014                "getMaintenance",
1015                vec![serde_json::to_value(maintenance_id.clone()).unwrap()],
1016                "/maintenance",
1017                true,
1018            )
1019            .await
1020            .map_err(|e| match e {
1021                Error::ServerError(msg) if msg.contains("Cannot read properties of null") => {
1022                    Error::IdNotFound("Maintenance".to_owned(), maintenance_id)
1023                }
1024                _ => e,
1025            })?;
1026
1027        maintenance.common_mut().monitors =
1028            Some(self.get_maintenance_monitors(maintenance_id).await?);
1029        maintenance.common_mut().status_pages =
1030            Some(self.get_maintenance_status_pages(maintenance_id).await?);
1031
1032        Ok(maintenance)
1033    }
1034
1035    pub async fn edit_maintenance(self: &Arc<Self>, maintenance: &mut Maintenance) -> Result<()> {
1036        let id = self
1037            .call(
1038                "addMaintenance",
1039                vec![serde_json::to_value(maintenance.clone()).unwrap()],
1040                "/maintenanceID",
1041                true,
1042            )
1043            .await?;
1044
1045        maintenance.common_mut().id = Some(id);
1046        if let Some(monitors) = &maintenance.common().monitors {
1047            self.set_maintenance_monitors(id, monitors).await?;
1048        }
1049        if let Some(status_pages) = &maintenance.common().status_pages {
1050            self.set_maintenance_status_pages(id, status_pages).await?;
1051        }
1052
1053        Ok(())
1054    }
1055
1056    pub async fn pause_maintenance(self: &Arc<Self>, maintenance_id: i32) -> Result<()> {
1057        let _: bool = self
1058            .call(
1059                "pauseMaintenance",
1060                vec![serde_json::to_value(maintenance_id).unwrap()],
1061                "/ok",
1062                true,
1063            )
1064            .await?;
1065
1066        Ok(())
1067    }
1068
1069    pub async fn resume_maintenance(self: &Arc<Self>, maintenance_id: i32) -> Result<()> {
1070        let _: bool = self
1071            .call(
1072                "resumeMaintenance",
1073                vec![serde_json::to_value(maintenance_id).unwrap()],
1074                "/ok",
1075                true,
1076            )
1077            .await?;
1078
1079        Ok(())
1080    }
1081
1082    async fn get_public_group_list(self: &Arc<Self>, slug: &str) -> Result<PublicGroupList> {
1083        let response: Value = self
1084            .reqwest
1085            .lock()
1086            .await
1087            .get(
1088                self.config
1089                    .url
1090                    .join(&format!("api/status-page/{}", slug))
1091                    .map_err(|e| Error::InvalidUrl(e.to_string()))?,
1092            )
1093            .send()
1094            .await?
1095            .json()
1096            .await?;
1097
1098        let monitor_list = response
1099            .clone()
1100            .pointer("/publicGroupList")
1101            .ok_or_else(|| {
1102                Error::InvalidResponse(vec![response.clone()], "/publicGroupList".to_owned())
1103            })?
1104            .clone();
1105
1106        Ok(serde_json::from_value(monitor_list)
1107            .log_warn(std::module_path!(), |e| e.to_string())
1108            .map_err(|_| Error::UnsupportedResponse)?)
1109    }
1110
1111    pub async fn delete_status_page(self: &Arc<Self>, slug: &str) -> Result<()> {
1112        let _: bool = self
1113            .call("deleteStatusPage", vec![json!(slug)], "/ok", true)
1114            .await?;
1115
1116        Ok(())
1117    }
1118
1119    pub async fn add_status_page(self: &Arc<Self>, status_page: &mut StatusPage) -> Result<()> {
1120        let ok: bool = self
1121            .call(
1122                "addStatusPage",
1123                vec![
1124                    serde_json::to_value(status_page.title.clone()).unwrap(),
1125                    serde_json::to_value(status_page.slug.clone()).unwrap(),
1126                ],
1127                "/ok",
1128                true,
1129            )
1130            .await?;
1131
1132        if !ok {
1133            return Err(Error::ServerError("Unable to add status page".to_owned()));
1134        }
1135
1136        self.edit_status_page(status_page).await?;
1137
1138        Ok(())
1139    }
1140
1141    pub async fn get_status_page(self: &Arc<Self>, slug: &str) -> Result<StatusPage> {
1142        let mut status_page: StatusPage = self
1143            .call(
1144                "getStatusPage",
1145                vec![serde_json::to_value(slug).unwrap()],
1146                "/config",
1147                true,
1148            )
1149            .await
1150            .map_err(|e| match e {
1151                Error::ServerError(msg) if msg.contains("Cannot read properties of null") => {
1152                    Error::SlugNotFound("StatusPage".to_owned(), slug.to_owned())
1153                }
1154                _ => e,
1155            })?;
1156
1157        status_page.public_group_list = Some(
1158            self.get_public_group_list(&status_page.slug.clone().unwrap_or_default())
1159                .await?,
1160        );
1161
1162        Ok(status_page)
1163    }
1164
1165    pub async fn edit_status_page(self: &Arc<Self>, status_page: &mut StatusPage) -> Result<()> {
1166        let mut config = serde_json::to_value(status_page.clone()).unwrap();
1167        config
1168            .as_object_mut()
1169            .unwrap()
1170            .insert("logo".to_owned(), status_page.icon.clone().into());
1171
1172        let _: bool = self
1173            .call(
1174                "saveStatusPage",
1175                vec![
1176                    serde_json::to_value(status_page.slug.clone()).unwrap(),
1177                    serde_json::to_value(config).unwrap(),
1178                    serde_json::to_value(status_page.icon.clone()).unwrap(),
1179                    serde_json::to_value(status_page.public_group_list.clone()).unwrap(),
1180                ],
1181                "/ok",
1182                true,
1183            )
1184            .await?;
1185
1186        Ok(())
1187    }
1188
1189    pub async fn add_docker_host(self: &Arc<Self>, docker_host: &mut DockerHost) -> Result<()> {
1190        self.edit_docker_host(docker_host).await
1191    }
1192
1193    pub async fn edit_docker_host(self: &Arc<Self>, docker_host: &mut DockerHost) -> Result<()> {
1194        docker_host.id = self
1195            .call(
1196                "addDockerHost",
1197                vec![
1198                    serde_json::to_value(docker_host.clone()).unwrap(),
1199                    serde_json::to_value(docker_host.id.clone()).unwrap(),
1200                ],
1201                "/id",
1202                true,
1203            )
1204            .await?;
1205
1206        Ok(())
1207    }
1208
1209    pub async fn delete_docker_host(self: &Arc<Self>, docker_host_id: i32) -> Result<()> {
1210        let _: bool = self
1211            .call(
1212                "deleteDockerHost",
1213                vec![serde_json::to_value(docker_host_id).unwrap()],
1214                "/ok",
1215                true,
1216            )
1217            .await?;
1218
1219        Ok(())
1220    }
1221
1222    pub async fn test_docker_host(self: &Arc<Self>, docker_host: &DockerHost) -> Result<String> {
1223        let msg: String = self
1224            .call(
1225                "testDockerHost",
1226                vec![serde_json::to_value(docker_host).unwrap()],
1227                "/msg",
1228                true,
1229            )
1230            .await?;
1231
1232        Ok(msg)
1233    }
1234
1235    pub async fn get_database_size(self: &Arc<Self>) -> Result<u64> {
1236        let size: u64 = self.call("getDatabaseSize", vec![], "/size", true).await?;
1237        Ok(size)
1238    }
1239
1240    pub async fn shrink_database(self: &Arc<Self>) -> Result<()> {
1241        let _: bool = self.call("shrinkDatabase", vec![], "/ok", true).await?;
1242        Ok(())
1243    }
1244
1245    pub async fn connect(self: &Arc<Self>) -> Result<()> {
1246        let mut tls_config = TlsConnector::builder();
1247
1248        tls_config.danger_accept_invalid_certs(!self.config.tls.verify);
1249
1250        if let Some((_, cert)) = &self.custom_cert {
1251            tls_config.add_root_certificate(cert.clone());
1252        }
1253
1254        self.is_ready.lock().await.reset();
1255        *self.is_logged_in.lock().await = false;
1256        *self.socket_io.lock().await = None;
1257
1258        let mut builder = ClientBuilder::new(
1259            self.config
1260                .url
1261                .join("socket.io/")
1262                .map_err(|e| Error::InvalidUrl(e.to_string()))?,
1263        )
1264        .tls_config(tls_config.build().map_err(|e| {
1265            Error::InvalidTlsCert(
1266                self.custom_cert
1267                    .as_ref()
1268                    .map(|(file, _)| file.to_owned())
1269                    .unwrap_or_default(),
1270                e.to_string(),
1271            )
1272        })?)
1273        .transport_type(rust_socketio::TransportType::Websocket);
1274
1275        for (key, value) in self
1276            .config
1277            .headers
1278            .iter()
1279            .filter_map(|header| header.split_once("="))
1280        {
1281            builder = builder.opening_header(key, value);
1282        }
1283
1284        let handle = Handle::current();
1285        let self_ref = Arc::downgrade(self);
1286        let client = builder
1287            .on_any(move |event, payload, _| {
1288                let handle = handle.clone();
1289                let self_ref: Weak<Worker> = self_ref.clone();
1290                trace!("Client::on_any({:?}, {:?})", &event, &payload);
1291                async move {
1292                    if let Some(arc) = self_ref.upgrade() {
1293                        match (event, payload) {
1294                            (SocketIOEvent::Message, Payload::Text(params)) => {
1295                                if let Ok(e) = Event::from_str(
1296                                    &params[0]
1297                                        .as_str()
1298                                        .log_warn(std::module_path!(), || {
1299                                            "Error while deserializing Event..."
1300                                        })
1301                                        .unwrap_or(""),
1302                                ) {
1303                                    handle.clone().spawn(async move {
1304                                        _ = arc.on_event(e, json!(null)).await.log_warn(
1305                                            std::module_path!(),
1306                                            |e| {
1307                                                format!(
1308                                                    "Error while handling message event: {}",
1309                                                    e.to_string()
1310                                                )
1311                                            },
1312                                        );
1313                                    });
1314                                }
1315                            }
1316                            (event, Payload::Text(params)) => {
1317                                if let Ok(e) = Event::from_str(&String::from(event)) {
1318                                    handle.clone().spawn(async move {
1319                                        _ = arc
1320                                            .on_event(e.clone(), params.into_iter().next().unwrap())
1321                                            .await
1322                                            .log_warn(std::module_path!(), |err| {
1323                                                format!(
1324                                                    "Error while handling '{:?}' event: {}",
1325                                                    e,
1326                                                    err.to_string()
1327                                                )
1328                                            });
1329                                    });
1330                                }
1331                            }
1332                            _ => {}
1333                        }
1334                    }
1335                }
1336                .boxed()
1337            })
1338            .connect()
1339            .await
1340            .log_error(std::module_path!(), |_| "Error during connect")
1341            .ok();
1342
1343        debug!("Waiting for connection");
1344
1345        debug!("Connection opened!");
1346        *self.socket_io.lock().await = client;
1347
1348        for i in 0..10 {
1349            if self.is_ready().await {
1350                debug!("Connected!");
1351                return Ok(());
1352            }
1353
1354            debug!("Waiting for Kuma to get ready...");
1355            tokio::time::sleep(Duration::from_millis(200 * i)).await;
1356        }
1357
1358        warn!("Timeout while waiting for Kuma to get ready...");
1359        match *self.is_connected.lock().await {
1360            true => Err(Error::NotAuthenticated),
1361            false => Err(Error::ConnectionTimeout),
1362        }
1363    }
1364
1365    pub async fn disconnect(self: &Arc<Self>) -> Result<()> {
1366        let self_ref = self.to_owned();
1367        tokio::spawn(async move {
1368            let socket_io = self_ref.socket_io.lock().await;
1369            if let Some(socket_io) = &*socket_io {
1370                _ = socket_io.disconnect().await;
1371            }
1372            drop(socket_io);
1373            *self_ref.socket_io.lock().await = None;
1374            debug!("Connection closed!");
1375        })
1376        .await
1377        .pipe(|result| {
1378            return match result {
1379                Ok(_) => Ok(()),
1380                Err(e) if e.is_cancelled() => Ok(()),
1381                Err(e) => Err(Error::CommunicationError(format!(
1382                    "Error while disconnecting: {}",
1383                    e.to_string()
1384                ))),
1385            };
1386        })
1387        .log_error(std::module_path!(), |e| e.to_string())?;
1388
1389        Ok(())
1390    }
1391
1392    pub async fn is_ready(self: &Arc<Self>) -> bool {
1393        self.is_ready.lock().await.is_ready()
1394    }
1395}
1396
1397/// A client for interacting with Uptime Kuma.
1398///
1399/// Example:
1400/// ```
1401/// // Connect to the server
1402/// let client = Client::connect(Config {
1403///         url: Url::parse("http://localhost:3001").expect("Invalid URL"),
1404///         username: Some("Username".to_owned()),
1405///         password: Some("Password".to_owned()),
1406///         ..Default::default()
1407///     })
1408///     .await
1409///     .expect("Failed to connect to server");
1410///
1411/// // Create a tag
1412/// let tag_definition = client
1413///     .add_tag(TagDefinition {
1414///         name: Some("example_tag".to_owned()),
1415///         color: Some("red".to_owned()),
1416///         ..Default::default()
1417///     })
1418///     .await
1419///     .expect("Failed to add tag");
1420///
1421/// // Create a group
1422/// let group = client
1423///     .add_monitor(MonitorGroup {
1424///         name: Some("Example Group".to_owned()),
1425///         tags: vec![Tag {
1426///             tag_id: tag_definition.tag_id,
1427///             value: Some("example_group".to_owned()),
1428///             ..Default::default()
1429///         }],
1430///         ..Default::default()
1431///     })
1432///     .await
1433///     .expect("Failed to add group");
1434///
1435/// // Createa a notification
1436/// let notification = client
1437///     .add_notification(Notification {
1438///         name: Some("Example Notification".to_owned()),
1439///         config: Some(serde_json::json!({
1440///             "webhookURL": "https://webhook.site/304eeaf2-0248-49be-8985-2c86175520ca",
1441///             "webhookContentType": "json"
1442///         })),
1443///         ..Default::default()
1444///     })
1445///     .await
1446///     .expect("Failed to add notification");
1447///
1448/// // Create a monitor
1449/// client
1450///     .add_monitor(MonitorHttp {
1451///         name: Some("Monitor Name".to_owned()),
1452///         url: Some("https://example.com".to_owned()),
1453///         parent: group.common().id().clone(),
1454///         tags: vec![Tag {
1455///             tag_id: tag_definition.tag_id,
1456///             value: Some("example_monitor".to_owned()),
1457///             ..Default::default()
1458///         }],
1459///         notification_id_list: Some(
1460///             vec![(
1461///                 notification.id.expect("No notification ID").to_string(),
1462///                 true,
1463///             )]
1464///             .into_iter()
1465///             .collect(),
1466///         ),
1467///         ..Default::default()
1468///     })
1469///     .await
1470///     .expect("Failed to add monitor");
1471///
1472/// let monitors = client.get_monitors().await.expect("Failed to get monitors");
1473/// println!("{:?}", monitors);
1474/// ```
1475///
1476pub struct Client {
1477    worker: Arc<Worker>,
1478}
1479
1480impl Client {
1481    pub async fn connect(config: Config) -> Result<Client> {
1482        let worker = Worker::new(config)?;
1483        match worker.connect().await {
1484            Ok(_) => Ok(Self { worker }),
1485            Err(e) => {
1486                _ = worker
1487                    .disconnect()
1488                    .await
1489                    .log_error(std::module_path!(), |e| e.to_string());
1490
1491                Err(e)
1492            }
1493        }
1494    }
1495
1496    /// Retrieves a list of monitors from Uptime Kuma.
1497    pub async fn get_monitors(&self) -> Result<MonitorList> {
1498        match self.worker.is_ready().await {
1499            true => Ok(self.worker.monitors.lock().await.clone()),
1500            false => Err(Error::NotReady),
1501        }
1502    }
1503
1504    /// Retrieves information about a specific monitor identified by its ID.
1505    pub async fn get_monitor(&self, monitor_id: i32) -> Result<Monitor> {
1506        self.worker.get_monitor(monitor_id).await
1507    }
1508
1509    /// Adds a new monitor to Uptime Kuma.
1510    pub async fn add_monitor<T: Into<Monitor>>(&self, monitor: T) -> Result<Monitor> {
1511        let mut monitor = monitor.into();
1512        self.worker.add_monitor(&mut monitor, true).await?;
1513        Ok(monitor)
1514    }
1515
1516    /// Edits an existing monitor in Uptime Kuma.
1517    pub async fn edit_monitor<T: Into<Monitor>>(&self, monitor: T) -> Result<Monitor> {
1518        let mut monitor = monitor.into();
1519        self.worker.edit_monitor(&mut monitor, true).await?;
1520        Ok(monitor)
1521    }
1522
1523    /// Deletes a monitor from Uptime Kuma based on its ID.
1524    pub async fn delete_monitor(&self, monitor_id: i32) -> Result<()> {
1525        self.worker.delete_monitor(monitor_id).await
1526    }
1527
1528    /// Pauses a monitor in Uptime Kuma based on its ID.
1529    pub async fn pause_monitor(&self, monitor_id: i32) -> Result<()> {
1530        self.worker.pause_monitor(monitor_id).await
1531    }
1532
1533    /// Resumes a paused monitor in Uptime Kuma based on its ID.
1534    pub async fn resume_monitor(&self, monitor_id: i32) -> Result<()> {
1535        self.worker.resume_monitor(monitor_id).await
1536    }
1537
1538    /// Retrieves a list of tags from Uptime Kuma.
1539    pub async fn get_tags(&self) -> Result<Vec<TagDefinition>> {
1540        self.worker.get_tags().await
1541    }
1542
1543    /// Retrieves information about a specific tag identified by its ID.
1544    pub async fn get_tag(&self, tag_id: i32) -> Result<TagDefinition> {
1545        self.worker.get_tags().await.and_then(|tags| {
1546            tags.into_iter()
1547                .find(|tag| tag.tag_id == Some(tag_id))
1548                .ok_or_else(|| Error::IdNotFound("Tag".to_owned(), tag_id))
1549        })
1550    }
1551
1552    /// Adds a new tag to Uptime Kuma.
1553    pub async fn add_tag(&self, mut tag: TagDefinition) -> Result<TagDefinition> {
1554        self.worker.add_tag(&mut tag).await?;
1555        Ok(tag)
1556    }
1557
1558    /// Edits an existing tag in Uptime Kuma.
1559    pub async fn edit_tag(&self, mut tag: TagDefinition) -> Result<TagDefinition> {
1560        self.worker.edit_tag(&mut tag).await?;
1561        Ok(tag)
1562    }
1563
1564    /// Deletes a tag from Uptime Kuma based on its ID.
1565    pub async fn delete_tag(&self, tag_id: i32) -> Result<()> {
1566        self.worker.delete_tag(tag_id).await
1567    }
1568
1569    /// Retrieves a list of notifications from Uptime Kuma.
1570    pub async fn get_notifications(&self) -> Result<NotificationList> {
1571        match self.worker.is_ready().await {
1572            true => Ok(self.worker.notifications.lock().await.clone()),
1573            false => Err(Error::NotReady),
1574        }
1575    }
1576
1577    /// Retrieves information about a specific notification identified by its ID.
1578    pub async fn get_notification(&self, notification_id: i32) -> Result<Notification> {
1579        self.get_notifications().await.and_then(|notifications| {
1580            notifications
1581                .into_iter()
1582                .find(|notification| notification.id == Some(notification_id))
1583                .ok_or_else(|| Error::IdNotFound("Notification".to_owned(), notification_id))
1584        })
1585    }
1586
1587    /// Adds a new notification to Uptime Kuma.
1588    pub async fn add_notification(&self, mut notification: Notification) -> Result<Notification> {
1589        self.worker.add_notification(&mut notification).await?;
1590        Ok(notification)
1591    }
1592
1593    /// Edits an existing notification in Uptime Kuma.
1594    pub async fn edit_notification(&self, mut notification: Notification) -> Result<Notification> {
1595        self.worker.edit_notification(&mut notification).await?;
1596        Ok(notification)
1597    }
1598
1599    /// Deletes a notification from Uptime Kuma based on its ID.
1600    pub async fn delete_notification(&self, notification_id: i32) -> Result<()> {
1601        self.worker.delete_notification(notification_id).await
1602    }
1603
1604    /// Retrieves a list of maintenances from Uptime Kuma.
1605    pub async fn get_maintenances(&self) -> Result<MaintenanceList> {
1606        match self.worker.is_ready().await {
1607            true => Ok(self.worker.maintenances.lock().await.clone()),
1608            false => Err(Error::NotReady),
1609        }
1610    }
1611
1612    /// Retrieves information about a specific maintenance identified by its ID.
1613    pub async fn get_maintenance(&self, maintenance_id: i32) -> Result<Maintenance> {
1614        self.worker.get_maintenance(maintenance_id).await
1615    }
1616
1617    /// Adds a new maintenance to Uptime Kuma.
1618    pub async fn add_maintenance(&self, mut maintenance: Maintenance) -> Result<Maintenance> {
1619        self.worker.add_maintenance(&mut maintenance).await?;
1620        Ok(maintenance)
1621    }
1622
1623    /// Edits an existing maintenance in Uptime Kuma.
1624    pub async fn edit_maintenance(&self, mut maintenance: Maintenance) -> Result<Maintenance> {
1625        self.worker.edit_maintenance(&mut maintenance).await?;
1626        Ok(maintenance)
1627    }
1628
1629    /// Deletes a maintenance from Uptime Kuma based on its ID.
1630    pub async fn delete_maintenance(&self, maintenance_id: i32) -> Result<()> {
1631        self.worker.delete_maintenance(maintenance_id).await
1632    }
1633
1634    /// Pauses a maintenance in Uptime Kuma based on its ID.
1635    pub async fn pause_maintenance(&self, maintenance_id: i32) -> Result<()> {
1636        self.worker.pause_maintenance(maintenance_id).await
1637    }
1638
1639    /// Resumes a paused maintenance in Uptime Kuma based on its ID.
1640    pub async fn resume_maintenance(&self, maintenance_id: i32) -> Result<()> {
1641        self.worker.resume_maintenance(maintenance_id).await
1642    }
1643
1644    /// Retrieves a list of status pages from Uptime Kuma.
1645    pub async fn get_status_pages(&self) -> Result<StatusPageList> {
1646        match self.worker.is_ready().await {
1647            true => Ok(self.worker.status_pages.lock().await.clone()),
1648            false => Err(Error::NotReady),
1649        }
1650    }
1651
1652    /// Retrieves information about a specific status page identified by its slug.
1653    pub async fn get_status_page<T: AsRef<str>>(&self, slug: T) -> Result<StatusPage> {
1654        self.worker.get_status_page(slug.as_ref()).await
1655    }
1656
1657    /// Adds a new status page to Uptime Kuma.
1658    pub async fn add_status_page(&self, mut status_page: StatusPage) -> Result<StatusPage> {
1659        self.worker.add_status_page(&mut status_page).await?;
1660        Ok(status_page)
1661    }
1662
1663    /// Edits an existing status page in Uptime Kuma.
1664    pub async fn edit_status_page(&self, mut status_page: StatusPage) -> Result<StatusPage> {
1665        self.worker.edit_status_page(&mut status_page).await?;
1666        Ok(status_page)
1667    }
1668
1669    /// Deletes a status page from Uptime Kuma based on its slug.
1670    pub async fn delete_status_page<T: AsRef<str>>(&self, slug: T) -> Result<()> {
1671        self.worker.delete_status_page(slug.as_ref()).await
1672    }
1673
1674    /// Retrieves a list of status pages from Uptime Kuma.
1675    pub async fn get_docker_hosts(&self) -> Result<DockerHostList> {
1676        match self.worker.is_ready().await {
1677            true => Ok(self.worker.docker_hosts.lock().await.clone()),
1678            false => Err(Error::NotReady),
1679        }
1680    }
1681
1682    /// Retrieves information about a specific docker host identified by its id.
1683    pub async fn get_docker_host(&self, docker_host_id: i32) -> Result<DockerHost> {
1684        self.get_docker_hosts().await.and_then(|docker_host| {
1685            docker_host
1686                .into_iter()
1687                .find(|docker_host| docker_host.id == Some(docker_host_id))
1688                .ok_or_else(|| Error::IdNotFound("Docker Host".to_owned(), docker_host_id))
1689        })
1690    }
1691
1692    /// Adds a new docker host to Uptime Kuma.
1693    pub async fn add_docker_host(&self, mut docker_host: DockerHost) -> Result<DockerHost> {
1694        self.worker.add_docker_host(&mut docker_host).await?;
1695        Ok(docker_host)
1696    }
1697
1698    /// Edits an existing docker host in Uptime Kuma.
1699    pub async fn edit_docker_host(&self, mut docker_host: DockerHost) -> Result<DockerHost> {
1700        self.worker.edit_docker_host(&mut docker_host).await?;
1701        Ok(docker_host)
1702    }
1703
1704    /// Deletes a docker host from Uptime Kuma based on its id.
1705    pub async fn delete_docker_host(&self, docker_host_id: i32) -> Result<()> {
1706        self.worker.delete_docker_host(docker_host_id).await
1707    }
1708
1709    /// Test a docker host in Uptime Kuma.
1710    pub async fn test_docker_host<T: std::borrow::Borrow<DockerHost>>(
1711        &self,
1712        docker_host: T,
1713    ) -> Result<String> {
1714        self.worker.test_docker_host(docker_host.borrow()).await
1715    }
1716
1717    /// Get the size of the monitor database (SQLite only)
1718    pub async fn get_database_size(&self) -> Result<u64> {
1719        self.worker.get_database_size().await
1720    }
1721
1722    /// Trigger database VACUUM for the monitor database (SQLite only)
1723    pub async fn shrink_database(&self) -> Result<()> {
1724        self.worker.shrink_database().await
1725    }
1726
1727    /// Disconnects the client from Uptime Kuma.
1728    pub async fn disconnect(&self) -> Result<()> {
1729        self.worker.disconnect().await
1730    }
1731
1732    /// Get the auth token from this client if available.
1733    pub async fn get_auth_token(&self) -> Option<String> {
1734        self.worker.auth_token.lock().await.clone()
1735    }
1736}
1737
1738impl Drop for Client {
1739    fn drop(&mut self) {
1740        let worker = self.worker.clone();
1741        tokio::spawn(async move {
1742            _ = worker
1743                .disconnect()
1744                .await
1745                .log_error(std::module_path!(), |e| e.to_string());
1746        });
1747    }
1748}