unifly_api/session/
clients.rs1use serde_json::json;
7use tracing::debug;
8
9use crate::error::Error;
10use crate::session::client::SessionClient;
11use crate::session::models::{SessionClientEntry, SessionUserEntry};
12
13impl SessionClient {
14 pub async fn list_clients(&self) -> Result<Vec<SessionClientEntry>, Error> {
18 let url = self.site_url("stat/sta");
19 debug!("listing connected clients");
20 self.get(url).await
21 }
22
23 pub async fn block_client(&self, mac: &str) -> Result<(), Error> {
27 let url = self.site_url("cmd/stamgr");
28 debug!(mac, "blocking client");
29 let _: Vec<serde_json::Value> = self
30 .post(
31 url,
32 &json!({
33 "cmd": "block-sta",
34 "mac": mac,
35 }),
36 )
37 .await?;
38 Ok(())
39 }
40
41 pub async fn unblock_client(&self, mac: &str) -> Result<(), Error> {
45 let url = self.site_url("cmd/stamgr");
46 debug!(mac, "unblocking client");
47 let _: Vec<serde_json::Value> = self
48 .post(
49 url,
50 &json!({
51 "cmd": "unblock-sta",
52 "mac": mac,
53 }),
54 )
55 .await?;
56 Ok(())
57 }
58
59 pub async fn kick_client(&self, mac: &str) -> Result<(), Error> {
63 let url = self.site_url("cmd/stamgr");
64 debug!(mac, "kicking client");
65 let _: Vec<serde_json::Value> = self
66 .post(
67 url,
68 &json!({
69 "cmd": "kick-sta",
70 "mac": mac,
71 }),
72 )
73 .await?;
74 Ok(())
75 }
76
77 pub async fn forget_client(&self, mac: &str) -> Result<(), Error> {
81 let url = self.site_url("cmd/stamgr");
82 debug!(mac, "forgetting client");
83 let _: Vec<serde_json::Value> = self
84 .post(
85 url,
86 &json!({
87 "cmd": "forget-sta",
88 "macs": [mac],
89 }),
90 )
91 .await?;
92 Ok(())
93 }
94
95 pub async fn authorize_guest(
105 &self,
106 mac: &str,
107 minutes: u32,
108 up_kbps: Option<u32>,
109 down_kbps: Option<u32>,
110 quota_mb: Option<u32>,
111 ) -> Result<(), Error> {
112 let url = self.site_url("cmd/stamgr");
113 debug!(mac, minutes, "authorizing guest");
114
115 let mut body = json!({
116 "cmd": "authorize-guest",
117 "mac": mac,
118 "minutes": minutes,
119 });
120
121 let obj = body
122 .as_object_mut()
123 .expect("json! macro always produces an object");
124 if let Some(up) = up_kbps {
125 obj.insert("up".into(), json!(up));
126 }
127 if let Some(down) = down_kbps {
128 obj.insert("down".into(), json!(down));
129 }
130 if let Some(quota) = quota_mb {
131 obj.insert("bytes".into(), json!(quota));
132 }
133
134 let _: Vec<serde_json::Value> = self.post(url, &body).await?;
135 Ok(())
136 }
137
138 pub async fn unauthorize_guest(&self, mac: &str) -> Result<(), Error> {
142 let url = self.site_url("cmd/stamgr");
143 debug!(mac, "revoking guest authorization");
144 let _: Vec<serde_json::Value> = self
145 .post(
146 url,
147 &json!({
148 "cmd": "unauthorize-guest",
149 "mac": mac,
150 }),
151 )
152 .await?;
153 Ok(())
154 }
155
156 pub async fn list_users(&self) -> Result<Vec<SessionUserEntry>, Error> {
162 let url = self.site_url("rest/user");
163 debug!("listing known users");
164 self.get(url).await
165 }
166
167 pub async fn set_client_fixed_ip(
172 &self,
173 mac: &str,
174 ip: &str,
175 network_id: &str,
176 ) -> Result<(), Error> {
177 debug!(mac, ip, network_id, "setting client fixed IP");
178
179 let users = self.list_users().await?;
180 let normalized_mac = mac.to_lowercase();
181 let existing = users
182 .iter()
183 .find(|u| u.mac.to_lowercase() == normalized_mac);
184
185 if let Some(user) = existing {
186 let url = self.site_url(&format!("rest/user/{}", user.id));
188 let _: Vec<serde_json::Value> = self
189 .put(
190 url,
191 &json!({
192 "use_fixedip": true,
193 "fixed_ip": ip,
194 "network_id": network_id,
195 }),
196 )
197 .await?;
198 } else {
199 let url = self.site_url("rest/user");
201 let _: Vec<serde_json::Value> = self
202 .post(
203 url,
204 &json!({
205 "mac": normalized_mac,
206 "use_fixedip": true,
207 "fixed_ip": ip,
208 "network_id": network_id,
209 }),
210 )
211 .await?;
212 }
213 Ok(())
214 }
215
216 pub async fn remove_client_fixed_ip(
221 &self,
222 mac: &str,
223 network_id: Option<&str>,
224 ) -> Result<(), Error> {
225 debug!(mac, ?network_id, "removing client fixed IP");
226
227 let users = self.list_users().await?;
228 let normalized_mac = mac.to_lowercase();
229 let matches: Vec<&SessionUserEntry> = users
230 .iter()
231 .filter(|u| {
232 u.mac.to_lowercase() == normalized_mac
233 && network_id.is_none_or(|nid| u.network_id.as_deref() == Some(nid))
234 })
235 .collect();
236
237 if matches.is_empty() {
238 return Err(Error::SessionApi {
239 message: if let Some(nid) = network_id {
240 format!("no reservation for MAC {mac} on network {nid}")
241 } else {
242 format!("no known user with MAC {mac}")
243 },
244 });
245 }
246
247 for user in matches {
248 let url = self.site_url(&format!("rest/user/{}", user.id));
249 let _: Vec<serde_json::Value> = self
250 .put(
251 url,
252 &json!({
253 "use_fixedip": false,
254 }),
255 )
256 .await?;
257 }
258 Ok(())
259 }
260}