aegis_server/
admin.rs

1//! Aegis Admin API
2//!
3//! Administrative endpoints for the web dashboard and management operations.
4//!
5//! @version 0.1.0
6//! @author AutomataNexus Development Team
7
8use serde::{Deserialize, Serialize};
9
10// =============================================================================
11// Cluster Info
12// =============================================================================
13
14/// Information about the cluster.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ClusterInfo {
17    pub name: String,
18    pub version: String,
19    pub node_count: usize,
20    pub leader_id: Option<String>,
21    pub state: ClusterState,
22    pub uptime_seconds: u64,
23}
24
25/// State of the cluster.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum ClusterState {
28    Healthy,
29    Degraded,
30    Unavailable,
31    Initializing,
32}
33
34impl Default for ClusterInfo {
35    fn default() -> Self {
36        Self {
37            name: "aegis-cluster".to_string(),
38            version: env!("CARGO_PKG_VERSION").to_string(),
39            node_count: 0,
40            leader_id: None,
41            state: ClusterState::Initializing,
42            uptime_seconds: 0,
43        }
44    }
45}
46
47// =============================================================================
48// Node Info
49// =============================================================================
50
51/// Information about a cluster node.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct NodeInfo {
54    pub id: String,
55    pub address: String,
56    pub role: NodeRole,
57    pub status: NodeStatus,
58    pub version: String,
59    pub uptime_seconds: u64,
60    pub last_heartbeat: u64,
61    pub metrics: NodeMetrics,
62}
63
64/// Role of a node.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum NodeRole {
67    Leader,
68    Follower,
69    Candidate,
70    Learner,
71}
72
73/// Status of a node.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75pub enum NodeStatus {
76    Online,
77    Offline,
78    Joining,
79    Leaving,
80    Unknown,
81}
82
83/// Metrics for a node.
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct NodeMetrics {
86    pub cpu_usage_percent: f64,
87    pub memory_usage_bytes: u64,
88    pub memory_total_bytes: u64,
89    pub disk_usage_bytes: u64,
90    pub disk_total_bytes: u64,
91    pub connections_active: u64,
92    pub queries_per_second: f64,
93    // Network I/O metrics
94    pub network_bytes_in: u64,
95    pub network_bytes_out: u64,
96    pub network_packets_in: u64,
97    pub network_packets_out: u64,
98    // Latency histogram metrics
99    pub latency_p50_ms: f64,
100    pub latency_p90_ms: f64,
101    pub latency_p95_ms: f64,
102    pub latency_p99_ms: f64,
103    pub latency_max_ms: f64,
104}
105
106// =============================================================================
107// Database Info
108// =============================================================================
109
110/// Information about a database.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct DatabaseInfo {
113    pub name: String,
114    pub size_bytes: u64,
115    pub table_count: usize,
116    pub index_count: usize,
117    pub created_at: u64,
118    pub last_modified: u64,
119}
120
121/// Information about a table.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct TableInfo {
124    pub name: String,
125    pub database: String,
126    pub row_count: u64,
127    pub size_bytes: u64,
128    pub columns: Vec<ColumnInfo>,
129    pub indexes: Vec<IndexInfo>,
130    pub created_at: u64,
131}
132
133/// Information about a column.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ColumnInfo {
136    pub name: String,
137    pub data_type: String,
138    pub nullable: bool,
139    pub primary_key: bool,
140    pub default_value: Option<String>,
141}
142
143/// Information about an index.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct IndexInfo {
146    pub name: String,
147    pub columns: Vec<String>,
148    pub index_type: String,
149    pub unique: bool,
150    pub size_bytes: u64,
151}
152
153// =============================================================================
154// Query Info
155// =============================================================================
156
157/// Information about a running query.
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct QueryInfo {
160    pub id: String,
161    pub sql: String,
162    pub database: String,
163    pub user: String,
164    pub state: QueryState,
165    pub started_at: u64,
166    pub duration_ms: u64,
167    pub rows_examined: u64,
168    pub rows_returned: u64,
169}
170
171/// State of a query.
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
173pub enum QueryState {
174    Running,
175    Finished,
176    Cancelled,
177    Failed,
178}
179
180/// Query statistics.
181#[derive(Debug, Clone, Default, Serialize, Deserialize)]
182pub struct QueryStats {
183    pub total_queries: u64,
184    pub queries_per_second: f64,
185    pub avg_duration_ms: f64,
186    pub p50_duration_ms: f64,
187    pub p95_duration_ms: f64,
188    pub p99_duration_ms: f64,
189    pub slow_queries: u64,
190    pub failed_queries: u64,
191}
192
193// =============================================================================
194// Replication Info
195// =============================================================================
196
197/// Information about replication status.
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct ReplicationInfo {
200    pub enabled: bool,
201    pub mode: ReplicationMode,
202    pub lag_ms: u64,
203    pub last_applied_index: u64,
204    pub commit_index: u64,
205    pub replicas: Vec<ReplicaInfo>,
206}
207
208/// Replication mode.
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
210pub enum ReplicationMode {
211    Synchronous,
212    Asynchronous,
213    SemiSynchronous,
214}
215
216/// Information about a replica.
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct ReplicaInfo {
219    pub node_id: String,
220    pub status: ReplicaStatus,
221    pub lag_ms: u64,
222    pub last_applied_index: u64,
223}
224
225/// Status of a replica.
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
227pub enum ReplicaStatus {
228    InSync,
229    Lagging,
230    CatchingUp,
231    Offline,
232}
233
234// =============================================================================
235// Shard Info
236// =============================================================================
237
238/// Information about sharding.
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct ShardingInfo {
241    pub enabled: bool,
242    pub shard_count: usize,
243    pub replication_factor: usize,
244    pub shards: Vec<ShardInfo>,
245}
246
247/// Information about a shard.
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct ShardInfo {
250    pub id: String,
251    pub state: ShardState,
252    pub key_range_start: String,
253    pub key_range_end: String,
254    pub primary_node: String,
255    pub replica_nodes: Vec<String>,
256    pub size_bytes: u64,
257    pub row_count: u64,
258}
259
260/// State of a shard.
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
262pub enum ShardState {
263    Active,
264    Migrating,
265    Splitting,
266    Merging,
267    Inactive,
268}
269
270// =============================================================================
271// Connection Info
272// =============================================================================
273
274/// Information about connections.
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct ConnectionInfo {
277    pub active: u64,
278    pub idle: u64,
279    pub total: u64,
280    pub max: u64,
281    pub connections: Vec<ConnectionDetails>,
282}
283
284/// Details about a connection.
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct ConnectionDetails {
287    pub id: String,
288    pub user: String,
289    pub database: String,
290    pub client_address: String,
291    pub state: ConnectionState,
292    pub connected_at: u64,
293    pub last_activity: u64,
294    pub current_query: Option<String>,
295}
296
297/// State of a connection.
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
299pub enum ConnectionState {
300    Active,
301    Idle,
302    IdleInTransaction,
303    Waiting,
304}
305
306// =============================================================================
307// Storage Info
308// =============================================================================
309
310/// Information about storage.
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct StorageInfo {
313    pub total_bytes: u64,
314    pub used_bytes: u64,
315    pub available_bytes: u64,
316    pub data_bytes: u64,
317    pub index_bytes: u64,
318    pub wal_bytes: u64,
319    pub temp_bytes: u64,
320}
321
322// =============================================================================
323// Alert Info
324// =============================================================================
325
326/// Information about an alert.
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct AlertInfo {
329    pub id: String,
330    pub severity: AlertSeverity,
331    pub source: String,
332    pub message: String,
333    pub timestamp: u64,
334    pub acknowledged: bool,
335    pub resolved: bool,
336}
337
338/// Severity of an alert.
339#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
340pub enum AlertSeverity {
341    Info,
342    Warning,
343    Error,
344    Critical,
345}
346
347// =============================================================================
348// User Info
349// =============================================================================
350
351/// Information about a user.
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct UserInfo {
354    pub username: String,
355    pub roles: Vec<String>,
356    pub created_at: u64,
357    pub last_login: Option<u64>,
358    pub enabled: bool,
359}
360
361// =============================================================================
362// Backup Info
363// =============================================================================
364
365/// Information about backups.
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct BackupInfo {
368    pub id: String,
369    pub backup_type: BackupType,
370    pub state: BackupState,
371    pub size_bytes: u64,
372    pub started_at: u64,
373    pub completed_at: Option<u64>,
374    pub duration_seconds: Option<u64>,
375    pub database: Option<String>,
376}
377
378/// Type of backup.
379#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
380pub enum BackupType {
381    Full,
382    Incremental,
383    Snapshot,
384}
385
386/// State of a backup.
387#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
388pub enum BackupState {
389    InProgress,
390    Completed,
391    Failed,
392    Cancelled,
393}
394
395// =============================================================================
396// Dashboard Summary
397// =============================================================================
398
399/// Summary for the dashboard home page.
400#[derive(Debug, Clone, Default, Serialize, Deserialize)]
401pub struct DashboardSummary {
402    pub cluster: ClusterSummary,
403    pub performance: PerformanceSummary,
404    pub storage: StorageSummary,
405    pub alerts: AlertSummary,
406}
407
408/// Cluster summary.
409#[derive(Debug, Clone, Default, Serialize, Deserialize)]
410pub struct ClusterSummary {
411    pub state: String,
412    pub node_count: usize,
413    pub healthy_nodes: usize,
414    pub leader_id: Option<String>,
415    pub version: String,
416}
417
418/// Performance summary.
419#[derive(Debug, Clone, Default, Serialize, Deserialize)]
420pub struct PerformanceSummary {
421    pub queries_per_second: f64,
422    pub avg_latency_ms: f64,
423    pub active_connections: u64,
424    pub cpu_usage_percent: f64,
425    pub memory_usage_percent: f64,
426}
427
428/// Storage summary.
429#[derive(Debug, Clone, Default, Serialize, Deserialize)]
430pub struct StorageSummary {
431    pub total_bytes: u64,
432    pub used_bytes: u64,
433    pub usage_percent: f64,
434    pub database_count: usize,
435    pub table_count: usize,
436}
437
438/// Alert summary.
439#[derive(Debug, Clone, Default, Serialize, Deserialize)]
440pub struct AlertSummary {
441    pub total: usize,
442    pub critical: usize,
443    pub warning: usize,
444    pub unacknowledged: usize,
445}
446
447// =============================================================================
448// Admin API Service
449// =============================================================================
450
451/// Admin API service for dashboard operations.
452pub struct AdminService {
453    start_time: std::time::Instant,
454}
455
456impl AdminService {
457    /// Create a new admin service.
458    pub fn new() -> Self {
459        Self {
460            start_time: std::time::Instant::now(),
461        }
462    }
463
464    /// Get cluster information.
465    pub fn get_cluster_info(&self) -> ClusterInfo {
466        ClusterInfo {
467            name: "aegis-cluster".to_string(),
468            version: env!("CARGO_PKG_VERSION").to_string(),
469            node_count: 3,
470            leader_id: Some("node-0".to_string()),
471            state: ClusterState::Healthy,
472            uptime_seconds: self.start_time.elapsed().as_secs(),
473        }
474    }
475
476    /// Get dashboard summary.
477    pub fn get_dashboard_summary(&self) -> DashboardSummary {
478        DashboardSummary {
479            cluster: ClusterSummary {
480                state: "Healthy".to_string(),
481                node_count: 3,
482                healthy_nodes: 3,
483                leader_id: Some("node-0".to_string()),
484                version: env!("CARGO_PKG_VERSION").to_string(),
485            },
486            performance: PerformanceSummary {
487                queries_per_second: 1250.0,
488                avg_latency_ms: 2.5,
489                active_connections: 45,
490                cpu_usage_percent: 35.0,
491                memory_usage_percent: 62.0,
492            },
493            storage: StorageSummary {
494                total_bytes: 100_000_000_000,
495                used_bytes: 45_000_000_000,
496                usage_percent: 45.0,
497                database_count: 5,
498                table_count: 42,
499            },
500            alerts: AlertSummary {
501                total: 3,
502                critical: 0,
503                warning: 2,
504                unacknowledged: 1,
505            },
506        }
507    }
508
509    /// Get list of nodes.
510    pub fn get_nodes(&self) -> Vec<NodeInfo> {
511        let uptime = self.start_time.elapsed().as_secs();
512        vec![
513            NodeInfo {
514                id: "node-0".to_string(),
515                address: "10.0.0.1:9090".to_string(),
516                role: NodeRole::Leader,
517                status: NodeStatus::Online,
518                version: env!("CARGO_PKG_VERSION").to_string(),
519                uptime_seconds: uptime,
520                last_heartbeat: Self::now(),
521                metrics: NodeMetrics {
522                    cpu_usage_percent: 35.0,
523                    memory_usage_bytes: 2_000_000_000,
524                    memory_total_bytes: 8_000_000_000,
525                    disk_usage_bytes: 50_000_000_000,
526                    disk_total_bytes: 100_000_000_000,
527                    connections_active: 15,
528                    queries_per_second: 450.0,
529                    network_bytes_in: 125_000_000 * uptime,
530                    network_bytes_out: 98_000_000 * uptime,
531                    network_packets_in: 850_000 * uptime,
532                    network_packets_out: 720_000 * uptime,
533                    latency_p50_ms: 0.8,
534                    latency_p90_ms: 2.1,
535                    latency_p95_ms: 3.5,
536                    latency_p99_ms: 8.2,
537                    latency_max_ms: 45.0,
538                },
539            },
540            NodeInfo {
541                id: "node-1".to_string(),
542                address: "10.0.0.2:9090".to_string(),
543                role: NodeRole::Follower,
544                status: NodeStatus::Online,
545                version: env!("CARGO_PKG_VERSION").to_string(),
546                uptime_seconds: uptime.saturating_sub(100),
547                last_heartbeat: Self::now(),
548                metrics: NodeMetrics {
549                    cpu_usage_percent: 28.0,
550                    memory_usage_bytes: 1_800_000_000,
551                    memory_total_bytes: 8_000_000_000,
552                    disk_usage_bytes: 48_000_000_000,
553                    disk_total_bytes: 100_000_000_000,
554                    connections_active: 12,
555                    queries_per_second: 380.0,
556                    network_bytes_in: 98_000_000 * uptime.saturating_sub(100),
557                    network_bytes_out: 76_000_000 * uptime.saturating_sub(100),
558                    network_packets_in: 720_000 * uptime.saturating_sub(100),
559                    network_packets_out: 580_000 * uptime.saturating_sub(100),
560                    latency_p50_ms: 0.9,
561                    latency_p90_ms: 2.3,
562                    latency_p95_ms: 3.8,
563                    latency_p99_ms: 9.1,
564                    latency_max_ms: 52.0,
565                },
566            },
567            NodeInfo {
568                id: "node-2".to_string(),
569                address: "10.0.0.3:9090".to_string(),
570                role: NodeRole::Follower,
571                status: NodeStatus::Online,
572                version: env!("CARGO_PKG_VERSION").to_string(),
573                uptime_seconds: uptime.saturating_sub(50),
574                last_heartbeat: Self::now(),
575                metrics: NodeMetrics {
576                    cpu_usage_percent: 32.0,
577                    memory_usage_bytes: 1_900_000_000,
578                    memory_total_bytes: 8_000_000_000,
579                    disk_usage_bytes: 49_000_000_000,
580                    disk_total_bytes: 100_000_000_000,
581                    connections_active: 18,
582                    queries_per_second: 420.0,
583                    network_bytes_in: 105_000_000 * uptime.saturating_sub(50),
584                    network_bytes_out: 82_000_000 * uptime.saturating_sub(50),
585                    network_packets_in: 780_000 * uptime.saturating_sub(50),
586                    network_packets_out: 620_000 * uptime.saturating_sub(50),
587                    latency_p50_ms: 1.1,
588                    latency_p90_ms: 2.8,
589                    latency_p95_ms: 4.2,
590                    latency_p99_ms: 10.5,
591                    latency_max_ms: 58.0,
592                },
593            },
594        ]
595    }
596
597    /// Get storage information.
598    pub fn get_storage_info(&self) -> StorageInfo {
599        StorageInfo {
600            total_bytes: 100_000_000_000,
601            used_bytes: 45_000_000_000,
602            available_bytes: 55_000_000_000,
603            data_bytes: 35_000_000_000,
604            index_bytes: 8_000_000_000,
605            wal_bytes: 1_500_000_000,
606            temp_bytes: 500_000_000,
607        }
608    }
609
610    /// Get query statistics.
611    pub fn get_query_stats(&self) -> QueryStats {
612        QueryStats {
613            total_queries: 1_250_000,
614            queries_per_second: 1250.0,
615            avg_duration_ms: 2.5,
616            p50_duration_ms: 1.2,
617            p95_duration_ms: 8.5,
618            p99_duration_ms: 25.0,
619            slow_queries: 150,
620            failed_queries: 12,
621        }
622    }
623
624    /// Get current timestamp.
625    fn now() -> u64 {
626        std::time::SystemTime::now()
627            .duration_since(std::time::UNIX_EPOCH)
628            .unwrap_or_default()
629            .as_millis() as u64
630    }
631}
632
633impl Default for AdminService {
634    fn default() -> Self {
635        Self::new()
636    }
637}
638
639// =============================================================================
640// Tests
641// =============================================================================
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646
647    #[test]
648    fn test_cluster_info() {
649        let service = AdminService::new();
650        let info = service.get_cluster_info();
651
652        assert_eq!(info.name, "aegis-cluster");
653        assert_eq!(info.state, ClusterState::Healthy);
654    }
655
656    #[test]
657    fn test_dashboard_summary() {
658        let service = AdminService::new();
659        let summary = service.get_dashboard_summary();
660
661        assert_eq!(summary.cluster.node_count, 3);
662        assert!(summary.performance.queries_per_second > 0.0);
663    }
664
665    #[test]
666    fn test_get_nodes() {
667        let service = AdminService::new();
668        let nodes = service.get_nodes();
669
670        assert_eq!(nodes.len(), 3);
671        assert_eq!(nodes[0].role, NodeRole::Leader);
672        assert_eq!(nodes[1].role, NodeRole::Follower);
673    }
674
675    #[test]
676    fn test_storage_info() {
677        let service = AdminService::new();
678        let storage = service.get_storage_info();
679
680        assert!(storage.total_bytes > storage.used_bytes);
681        assert_eq!(
682            storage.available_bytes,
683            storage.total_bytes - storage.used_bytes
684        );
685    }
686
687    #[test]
688    fn test_query_stats() {
689        let service = AdminService::new();
690        let stats = service.get_query_stats();
691
692        assert!(stats.total_queries > 0);
693        assert!(stats.p50_duration_ms < stats.p95_duration_ms);
694        assert!(stats.p95_duration_ms < stats.p99_duration_ms);
695    }
696
697    #[test]
698    fn test_node_metrics() {
699        let service = AdminService::new();
700        let nodes = service.get_nodes();
701
702        for node in nodes {
703            assert!(node.metrics.cpu_usage_percent <= 100.0);
704            assert!(node.metrics.memory_usage_bytes <= node.metrics.memory_total_bytes);
705        }
706    }
707}