Skip to main content

unifly_api/legacy/
system.rs

1// Legacy API system endpoints
2//
3// Controller-level operations: sysinfo, health dashboard, backup management.
4
5use serde_json::json;
6use tracing::debug;
7
8use crate::error::Error;
9use crate::legacy::client::LegacyClient;
10
11impl LegacyClient {
12    /// Get controller system information.
13    ///
14    /// `GET /api/s/{site}/stat/sysinfo`
15    ///
16    /// Returns loosely-typed JSON because the field set varies by
17    /// platform and firmware version.
18    pub async fn get_sysinfo(&self) -> Result<serde_json::Value, Error> {
19        let url = self.site_url("stat/sysinfo");
20        debug!("fetching sysinfo");
21        let mut data: Vec<serde_json::Value> = self.get(url).await?;
22        // sysinfo typically returns a single-element array
23        Ok(data.pop().unwrap_or(serde_json::Value::Null))
24    }
25
26    /// Get site health dashboard metrics.
27    ///
28    /// `GET /api/s/{site}/stat/health`
29    ///
30    /// Returns subsystem health entries (wan, lan, wlan, vpn, etc.).
31    pub async fn get_health(&self) -> Result<Vec<serde_json::Value>, Error> {
32        let url = self.site_url("stat/health");
33        debug!("fetching site health");
34        self.get(url).await
35    }
36
37    /// List available controller backups.
38    ///
39    /// `POST /api/s/{site}/cmd/backup` with `{"cmd": "list-backups"}`
40    pub async fn list_backups(&self) -> Result<Vec<serde_json::Value>, Error> {
41        let url = self.site_url("cmd/backup");
42        debug!("listing backups");
43        self.post(url, &json!({ "cmd": "list-backups" })).await
44    }
45
46    /// Delete a backup file from the controller.
47    ///
48    /// `POST /api/s/{site}/cmd/backup` with
49    /// `{"cmd": "delete-backup", "filename": "..."}`
50    pub async fn delete_backup(&self, filename: &str) -> Result<(), Error> {
51        let url = self.site_url("cmd/backup");
52        debug!(filename, "deleting backup");
53        let _: Vec<serde_json::Value> = self
54            .post(
55                url,
56                &json!({
57                    "cmd": "delete-backup",
58                    "filename": filename,
59                }),
60            )
61            .await?;
62        Ok(())
63    }
64
65    /// Download a backup file from the controller.
66    ///
67    /// `GET /dl/autobackup/{filename}`
68    pub async fn download_backup(&self, filename: &str) -> Result<Vec<u8>, Error> {
69        let prefix = self
70            .platform()
71            .legacy_prefix()
72            .unwrap_or("")
73            .trim_end_matches('/');
74        let base = self.base_url().as_str().trim_end_matches('/');
75        let encoded: String = url::form_urlencoded::byte_serialize(filename.as_bytes()).collect();
76        let url = format!("{base}{prefix}/dl/autobackup/{encoded}");
77        debug!(filename, url, "downloading backup");
78        let resp = self
79            .http()
80            .get(url)
81            .send()
82            .await
83            .map_err(Error::Transport)?;
84        if !resp.status().is_success() {
85            return Err(Error::LegacyApi {
86                message: format!("backup download failed: HTTP {}", resp.status()),
87            });
88        }
89        let bytes = resp.bytes().await.map_err(Error::Transport)?;
90        Ok(bytes.to_vec())
91    }
92
93    /// List controller admins.
94    ///
95    /// `GET /api/stat/admin` — controller-level (not site-scoped).
96    pub async fn list_admins(&self) -> Result<Vec<serde_json::Value>, Error> {
97        let url = self.api_url("stat/admin");
98        debug!("listing admins");
99        self.get(url).await
100    }
101
102    /// Create a new controller backup.
103    ///
104    /// `POST /api/s/{site}/cmd/backup` with `{"cmd": "backup"}`
105    pub async fn create_backup(&self) -> Result<(), Error> {
106        let url = self.site_url("cmd/backup");
107        debug!("creating backup");
108        let _: Vec<serde_json::Value> = self.post(url, &json!({ "cmd": "backup" })).await?;
109        Ok(())
110    }
111
112    /// Reboot the controller.
113    ///
114    /// `POST /api/system/reboot`
115    pub async fn reboot_controller(&self) -> Result<(), Error> {
116        let url = self.api_url("system/reboot");
117        debug!("rebooting controller");
118        let _: Vec<serde_json::Value> = self.post(url, &json!({})).await?;
119        Ok(())
120    }
121
122    /// Power off the controller.
123    ///
124    /// `POST /api/system/poweroff`
125    pub async fn poweroff_controller(&self) -> Result<(), Error> {
126        let url = self.api_url("system/poweroff");
127        debug!("powering off controller");
128        let _: Vec<serde_json::Value> = self.post(url, &json!({})).await?;
129        Ok(())
130    }
131}