1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::Result;
11use crate::DakeraClient;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ClusterStatus {
20 pub cluster_id: String,
21 pub state: String,
22 pub node_count: u32,
23 pub total_vectors: u64,
24 pub namespace_count: u64,
25 pub version: String,
26 pub timestamp: u64,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct NodeInfo {
32 pub node_id: String,
33 pub address: String,
34 pub role: String,
35 pub status: String,
36 pub version: String,
37 pub uptime_seconds: u64,
38 pub vector_count: u64,
39 pub memory_bytes: u64,
40 #[serde(default)]
41 pub cpu_percent: f32,
42 #[serde(default)]
43 pub memory_percent: f32,
44 pub last_heartbeat: u64,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct NodeListResponse {
50 pub nodes: Vec<NodeInfo>,
51 pub total: u32,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct IndexStats {
61 pub index_type: String,
62 pub is_built: bool,
63 pub size_bytes: u64,
64 pub indexed_vectors: u64,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub last_rebuild: Option<u64>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct NamespaceAdminInfo {
72 pub name: String,
73 pub vector_count: u64,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub dimension: Option<usize>,
76 pub index_type: String,
77 pub storage_bytes: u64,
78 pub document_count: u64,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub created_at: Option<u64>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub updated_at: Option<u64>,
83 pub index_stats: IndexStats,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct NamespaceListResponse {
89 pub namespaces: Vec<NamespaceAdminInfo>,
90 pub total: u64,
91 pub total_vectors: u64,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct OptimizeRequest {
97 #[serde(default)]
98 pub force: bool,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub target_index_type: Option<String>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct OptimizeResponse {
106 pub success: bool,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub job_id: Option<String>,
109 pub message: String,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct IndexStatsResponse {
119 pub namespaces: HashMap<String, IndexStats>,
120 pub total_indexed_vectors: u64,
121 pub total_size_bytes: u64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct RebuildIndexRequest {
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub namespace: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub index_type: Option<String>,
131 #[serde(default)]
132 pub force: bool,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct RebuildIndexResponse {
138 pub success: bool,
139 pub job_id: String,
140 pub message: String,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct CacheStats {
150 pub enabled: bool,
151 pub cache_type: String,
152 pub entries: u64,
153 pub size_bytes: u64,
154 pub hits: u64,
155 pub misses: u64,
156 pub hit_rate: f64,
157 pub evictions: u64,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ClearCacheRequest {
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub namespace: Option<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct ClearCacheResponse {
170 pub success: bool,
171 pub entries_cleared: u64,
172 pub message: String,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct RuntimeConfig {
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub max_vectors_per_namespace: Option<u64>,
184 pub default_index_type: String,
185 pub cache_enabled: bool,
186 pub cache_max_size_bytes: u64,
187 pub rate_limit_enabled: bool,
188 pub rate_limit_rps: u32,
189 pub query_timeout_ms: u64,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct UpdateConfigResponse {
195 pub success: bool,
196 pub config: RuntimeConfig,
197 pub message: String,
198 #[serde(default, skip_serializing_if = "Vec::is_empty")]
199 pub warnings: Vec<String>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct QuotaConfig {
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub max_vectors: Option<u64>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub max_storage_bytes: Option<u64>,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub max_queries_per_minute: Option<u64>,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub max_writes_per_minute: Option<u64>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct QuotaUsage {
222 #[serde(default)]
223 pub current_vectors: u64,
224 #[serde(default)]
225 pub current_storage_bytes: u64,
226 #[serde(default)]
227 pub queries_this_minute: u64,
228 #[serde(default)]
229 pub writes_this_minute: u64,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct QuotaStatus {
235 pub namespace: String,
236 pub config: QuotaConfig,
237 pub usage: QuotaUsage,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct QuotaListResponse {
243 pub quotas: Vec<QuotaStatus>,
244 pub total: u64,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub default_config: Option<QuotaConfig>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct SlowQueryEntry {
256 pub id: String,
257 pub timestamp: u64,
258 pub namespace: String,
259 pub query_type: String,
260 pub duration_ms: f64,
261 #[serde(default)]
262 pub parameters: Option<serde_json::Value>,
263 #[serde(default)]
264 pub results_count: u64,
265 #[serde(default)]
266 pub vectors_scanned: u64,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct SlowQueryListResponse {
272 pub queries: Vec<SlowQueryEntry>,
273 pub total: u64,
274 pub threshold_ms: f64,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct BackupInfo {
284 pub backup_id: String,
285 pub name: String,
286 pub backup_type: String,
287 pub status: String,
288 pub namespaces: Vec<String>,
289 pub vector_count: u64,
290 pub size_bytes: u64,
291 pub created_at: u64,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub completed_at: Option<u64>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub duration_seconds: Option<u64>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub storage_path: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub error: Option<String>,
300 pub encrypted: bool,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 pub compression: Option<String>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct BackupListResponse {
308 pub backups: Vec<BackupInfo>,
309 pub total: u64,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct CreateBackupRequest {
315 pub name: String,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub backup_type: Option<String>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub namespaces: Option<Vec<String>>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub encrypt: Option<bool>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pub compression: Option<String>,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct CreateBackupResponse {
329 pub backup: BackupInfo,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub estimated_completion: Option<u64>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct RestoreBackupRequest {
337 pub backup_id: String,
338 #[serde(skip_serializing_if = "Option::is_none")]
339 pub target_namespaces: Option<Vec<String>>,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub overwrite: Option<bool>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub point_in_time: Option<u64>,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct RestoreBackupResponse {
349 pub restore_id: String,
350 pub status: String,
351 pub backup_id: String,
352 pub namespaces: Vec<String>,
353 pub started_at: u64,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub estimated_completion: Option<u64>,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub progress_percent: Option<u8>,
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub vectors_restored: Option<u64>,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub completed_at: Option<u64>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub duration_seconds: Option<u64>,
364 #[serde(skip_serializing_if = "Option::is_none")]
365 pub error: Option<String>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct TtlCleanupRequest {
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub namespace: Option<String>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct TtlCleanupResponse {
382 pub success: bool,
383 pub vectors_removed: u64,
384 pub namespaces_cleaned: Vec<String>,
385 pub message: String,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct TtlStats {
391 pub namespace: String,
392 pub vectors_with_ttl: u64,
393 pub expiring_within_hour: u64,
394 pub expiring_within_day: u64,
395 pub expired_pending_cleanup: u64,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct TtlStatsResponse {
401 pub namespaces: Vec<TtlStats>,
402 pub total_with_ttl: u64,
403 pub total_expired: u64,
404}
405
406impl DakeraClient {
411 pub async fn cluster_status(&self) -> Result<ClusterStatus> {
417 let url = format!("{}/admin/cluster/status", self.base_url);
418 let response = self.client.get(&url).send().await?;
419 self.handle_response(response).await
420 }
421
422 pub async fn cluster_nodes(&self) -> Result<NodeListResponse> {
424 let url = format!("{}/admin/cluster/nodes", self.base_url);
425 let response = self.client.get(&url).send().await?;
426 self.handle_response(response).await
427 }
428
429 pub async fn list_namespaces_admin(&self) -> Result<NamespaceListResponse> {
435 let url = format!("{}/admin/namespaces", self.base_url);
436 let response = self.client.get(&url).send().await?;
437 self.handle_response(response).await
438 }
439
440 pub async fn delete_namespace_admin(&self, namespace: &str) -> Result<serde_json::Value> {
442 let url = format!("{}/admin/namespaces/{}", self.base_url, namespace);
443 let response = self.client.delete(&url).send().await?;
444 self.handle_response(response).await
445 }
446
447 pub async fn optimize_namespace(
449 &self,
450 namespace: &str,
451 request: OptimizeRequest,
452 ) -> Result<OptimizeResponse> {
453 let url = format!("{}/admin/namespaces/{}/optimize", self.base_url, namespace);
454 let response = self.client.post(&url).json(&request).send().await?;
455 self.handle_response(response).await
456 }
457
458 pub async fn index_stats(&self) -> Result<IndexStatsResponse> {
464 let url = format!("{}/admin/indexes/stats", self.base_url);
465 let response = self.client.get(&url).send().await?;
466 self.handle_response(response).await
467 }
468
469 pub async fn rebuild_indexes(
471 &self,
472 request: RebuildIndexRequest,
473 ) -> Result<RebuildIndexResponse> {
474 let url = format!("{}/admin/indexes/rebuild", self.base_url);
475 let response = self.client.post(&url).json(&request).send().await?;
476 self.handle_response(response).await
477 }
478
479 pub async fn cache_stats(&self) -> Result<CacheStats> {
485 let url = format!("{}/admin/cache/stats", self.base_url);
486 let response = self.client.get(&url).send().await?;
487 self.handle_response(response).await
488 }
489
490 pub async fn cache_clear(&self, namespace: Option<&str>) -> Result<ClearCacheResponse> {
492 let url = format!("{}/admin/cache/clear", self.base_url);
493 let request = ClearCacheRequest {
494 namespace: namespace.map(|s| s.to_string()),
495 };
496 let response = self.client.post(&url).json(&request).send().await?;
497 self.handle_response(response).await
498 }
499
500 pub async fn get_config(&self) -> Result<RuntimeConfig> {
506 let url = format!("{}/admin/config", self.base_url);
507 let response = self.client.get(&url).send().await?;
508 self.handle_response(response).await
509 }
510
511 pub async fn update_config(
513 &self,
514 updates: HashMap<String, serde_json::Value>,
515 ) -> Result<UpdateConfigResponse> {
516 let url = format!("{}/admin/config", self.base_url);
517 let response = self.client.put(&url).json(&updates).send().await?;
518 self.handle_response(response).await
519 }
520
521 pub async fn get_quotas(&self) -> Result<QuotaListResponse> {
527 let url = format!("{}/admin/quotas", self.base_url);
528 let response = self.client.get(&url).send().await?;
529 self.handle_response(response).await
530 }
531
532 pub async fn get_quota(&self, namespace: &str) -> Result<QuotaStatus> {
534 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
535 let response = self.client.get(&url).send().await?;
536 self.handle_response(response).await
537 }
538
539 pub async fn set_quota(
541 &self,
542 namespace: &str,
543 config: QuotaConfig,
544 ) -> Result<serde_json::Value> {
545 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
546 let request = serde_json::json!({ "config": config });
547 let response = self.client.put(&url).json(&request).send().await?;
548 self.handle_response(response).await
549 }
550
551 pub async fn delete_quota(&self, namespace: &str) -> Result<serde_json::Value> {
553 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
554 let response = self.client.delete(&url).send().await?;
555 self.handle_response(response).await
556 }
557
558 pub async fn update_quotas(&self, config: Option<QuotaConfig>) -> Result<serde_json::Value> {
560 let url = format!("{}/admin/quotas/default", self.base_url);
561 let request = serde_json::json!({ "config": config });
562 let response = self.client.put(&url).json(&request).send().await?;
563 self.handle_response(response).await
564 }
565
566 pub async fn slow_queries(
572 &self,
573 limit: Option<usize>,
574 namespace: Option<&str>,
575 query_type: Option<&str>,
576 ) -> Result<SlowQueryListResponse> {
577 let mut url = format!("{}/admin/slow-queries", self.base_url);
578 let mut params = Vec::new();
579 if let Some(l) = limit {
580 params.push(format!("limit={}", l));
581 }
582 if let Some(ns) = namespace {
583 params.push(format!("namespace={}", ns));
584 }
585 if let Some(qt) = query_type {
586 params.push(format!("query_type={}", qt));
587 }
588 if !params.is_empty() {
589 url.push('?');
590 url.push_str(¶ms.join("&"));
591 }
592 let response = self.client.get(&url).send().await?;
593 self.handle_response(response).await
594 }
595
596 pub async fn slow_query_summary(&self) -> Result<serde_json::Value> {
598 let url = format!("{}/admin/slow-queries/summary", self.base_url);
599 let response = self.client.get(&url).send().await?;
600 self.handle_response(response).await
601 }
602
603 pub async fn clear_slow_queries(&self) -> Result<serde_json::Value> {
605 let url = format!("{}/admin/slow-queries", self.base_url);
606 let response = self.client.delete(&url).send().await?;
607 self.handle_response(response).await
608 }
609
610 pub async fn create_backup(
616 &self,
617 request: CreateBackupRequest,
618 ) -> Result<CreateBackupResponse> {
619 let url = format!("{}/admin/backups", self.base_url);
620 let response = self.client.post(&url).json(&request).send().await?;
621 self.handle_response(response).await
622 }
623
624 pub async fn list_backups(&self) -> Result<BackupListResponse> {
626 let url = format!("{}/admin/backups", self.base_url);
627 let response = self.client.get(&url).send().await?;
628 self.handle_response(response).await
629 }
630
631 pub async fn get_backup(&self, backup_id: &str) -> Result<BackupInfo> {
633 let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
634 let response = self.client.get(&url).send().await?;
635 self.handle_response(response).await
636 }
637
638 pub async fn restore_backup(
640 &self,
641 request: RestoreBackupRequest,
642 ) -> Result<RestoreBackupResponse> {
643 let url = format!("{}/admin/backups/restore", self.base_url);
644 let response = self.client.post(&url).json(&request).send().await?;
645 self.handle_response(response).await
646 }
647
648 pub async fn delete_backup(&self, backup_id: &str) -> Result<serde_json::Value> {
650 let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
651 let response = self.client.delete(&url).send().await?;
652 self.handle_response(response).await
653 }
654
655 pub async fn ttl_cleanup(&self, namespace: Option<&str>) -> Result<TtlCleanupResponse> {
661 let url = format!("{}/admin/ttl/cleanup", self.base_url);
662 let request = TtlCleanupRequest {
663 namespace: namespace.map(|s| s.to_string()),
664 };
665 let response = self.client.post(&url).json(&request).send().await?;
666 self.handle_response(response).await
667 }
668
669 pub async fn ttl_stats(&self) -> Result<TtlStatsResponse> {
671 let url = format!("{}/admin/ttl/stats", self.base_url);
672 let response = self.client.get(&url).send().await?;
673 self.handle_response(response).await
674 }
675}