omni_agent/routes/
instances.rs

1use rocket::{delete, get, post, patch, put};
2use rocket::serde::{Serialize, Deserialize, json::Json};
3use rocket::State;
4use rocket::FromForm;
5use std::sync::{Arc, Mutex};
6use std::collections::HashMap;
7use bollard::Docker;
8use bollard::container::{CreateContainerOptions, Config, StartContainerOptions, StopContainerOptions, RemoveContainerOptions, ListContainersOptions};
9use bollard::image::ListImagesOptions;
10use bollard::system::EventsOptions;
11use futures::stream::{StreamExt, TryStreamExt};
12
13// Data structures
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AppInstance {
16    id: String,
17    name: String,
18    image: String,
19    status: String,
20    created_at: String,
21    ports: Vec<PortMapping>,
22    environment: HashMap<String, String>,
23    volumes: Vec<VolumeMapping>,
24    agent_id: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct PortMapping {
29    host_port: u16,
30    container_port: u16,
31    protocol: String,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct VolumeMapping {
36    host_path: String,
37    container_path: String,
38}
39#[derive(Debug, Clone, rocket::serde::Serialize, rocket::serde::Deserialize)]
40#[serde(crate = "rocket::serde")]
41pub struct AppInstanceRequest {
42    name: String,
43    image: String,
44    ports: Option<Vec<PortMapping>>,
45    environment: Option<HashMap<String, String>>,
46    volumes: Option<Vec<VolumeMapping>>,
47}
48
49// Docker client wrapper
50pub struct AppManager {
51    docker: Docker,
52    instances: Arc<Mutex<HashMap<String, AppInstance>>>,
53}
54
55impl AppManager {
56    pub fn new() -> Result<Self, String> {
57        // Connect to Docker with default configuration
58        // Works across platforms without additional config
59        let docker = match Docker::connect_with_local_defaults() {
60            Ok(docker) => docker,
61            Err(e) => return Err(format!("Failed to connect to Docker: {}", e)),
62        };
63        
64        Ok(AppManager {
65            docker,
66            instances: Arc::new(Mutex::new(HashMap::new())),
67        })
68    }
69}
70
71// API Endpoints
72#[get("/instances")]
73pub async fn list_instances(app_manager: &State<AppManager>) -> Json<Vec<AppInstance>> {
74    let mut instances = Vec::new();
75    
76    // List containers via Docker API
77    let options = Some(ListContainersOptions::<String> {
78        all: true,
79        ..Default::default()
80    });
81    
82    match app_manager.docker.list_containers(options).await {
83        Ok(containers) => {
84            for container in containers {
85                if let (Some(id), Some(image), Some(names), Some(created), Some(status)) = 
86                   (container.id, container.image, container.names, container.created, container.status) {
87                    if let Some(name) = names.first() {
88                        let name = name.trim_start_matches('/').to_string();
89                        let app_instance = AppInstance {
90                            id: id.clone(),
91                            name,
92                            image,
93                            status,
94                            created_at: created.to_string(),
95                            ports: Vec::new(), // Would need to parse from container.ports
96                            environment: HashMap::new(), // Would need additional API call
97                            volumes: Vec::new(), // Would need additional API call
98                            agent_id: "current".to_string(), // In a distributed setup, this would be the agent ID
99                        };
100                        instances.push(app_instance);
101                    }
102                }
103            }
104        },
105        Err(e) => {
106            eprintln!("Failed to list containers: {}", e);
107        }
108    }
109    
110    Json(instances)
111}
112
113#[get("/instances/<id>")]
114pub async fn get_instance(id: String, app_manager: &State<AppManager>) -> Option<Json<AppInstance>> {
115    // Get container details via Docker API
116    match app_manager.docker.inspect_container(&id, None).await {
117        Ok(container) => {
118            let config = container.config?;
119            let state = container.state?;
120            
121            let name = container.name?;
122            let name = name.trim_start_matches('/').to_string();
123            
124            let app_instance = AppInstance {
125                id: container.id.unwrap_or(id),
126                name,
127                image: config.image.unwrap_or_default(),
128                status: state.status.map(|s| s.to_string()).unwrap_or_else(|| "unknown".to_string()),
129                created_at: container.created.unwrap_or_default(),
130                ports: Vec::new(), // Would need to parse from container.network_settings
131                environment: HashMap::new(), // Would need to parse from config.env
132                volumes: Vec::new(), // Would need to parse from container.mounts
133                agent_id: "current".to_string(),
134            };
135            
136            Some(Json(app_instance))
137        },
138        Err(_) => None
139    }
140}
141#[post("/instances", format = "json", data = "<app_req>")]
142pub async fn create_instance(app_req: Json<AppInstanceRequest>, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
143    // Prepare container configuration
144    let name = app_req.name.clone();
145    
146    let mut port_bindings = HashMap::new();
147    if let Some(ports) = &app_req.ports {
148        for port in ports {
149            let host_binding = format!("{}:{}", port.host_port, port.container_port);
150            port_bindings.insert(
151                format!("{}/{}", port.container_port, port.protocol), 
152                Some(vec![bollard::models::PortBinding { 
153                    host_ip: Some("0.0.0.0".to_string()), 
154                    host_port: Some(port.host_port.to_string()) 
155                }])
156            );
157        }
158    }
159    
160    let mut env_vars = Vec::new();
161    if let Some(env) = &app_req.environment {
162        for (key, value) in env {
163            env_vars.push(format!("{}={}", key, value));
164        }
165    }
166    
167    let mut volume_bindings = Vec::new();
168    if let Some(volumes) = &app_req.volumes {
169        for volume in volumes {
170            volume_bindings.push(format!("{}:{}", volume.host_path, volume.container_path));
171        }
172    }
173    
174    // Create container
175    let options = Some(CreateContainerOptions {
176        name: &name,
177        platform: None,
178    });
179    
180    let config = Config {
181        image: Some(app_req.image.clone()),
182        env: Some(env_vars),
183        exposed_ports: Some(HashMap::new()), // Would need to populate from app_req.ports
184        host_config: Some(bollard::models::HostConfig {
185            port_bindings: Some(port_bindings),
186            binds: Some(volume_bindings),
187            ..Default::default()
188        }),
189        ..Default::default()
190    };
191    
192    match app_manager.docker.create_container(options, config).await {
193        Ok(response) => {
194            // Start the container
195            let id = response.id;
196            match app_manager.docker.start_container(&id, None::<StartContainerOptions<String>>).await {
197                Ok(_) => {
198                    // Create app instance object
199                    let app_instance = AppInstance {
200                        id: id.clone(),
201                        name: app_req.name.clone(),
202                        image: app_req.image.clone(),
203                        status: "running".to_string(),
204                        created_at: chrono::Utc::now().to_string(),
205                        ports: app_req.ports.clone().unwrap_or_default(),
206                        environment: app_req.environment.clone().unwrap_or_default(),
207                        volumes: app_req.volumes.clone().unwrap_or_default(),
208                        agent_id: "current".to_string(),
209                    };
210                    
211                    // Store the instance in our local state
212                    app_manager.instances.lock().unwrap().insert(id, app_instance.clone());
213                    
214                    Ok(Json(app_instance))
215                },
216                Err(e) => Err(format!("Failed to start instance: {}", e))
217            }
218        },
219        Err(e) => Err(format!("Failed to create instance: {}", e))
220    }
221}
222
223#[put("/instances/<id>/start")]
224pub async fn start_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
225    // Start container
226    match app_manager.docker.start_container(&id, None::<StartContainerOptions<String>>).await {
227        Ok(_) => {
228            // Get updated container info
229            match get_instance(id, app_manager).await {
230                Some(instance) => Ok(instance),
231                None => Err("Failed to get instance after starting".to_string())
232            }
233        },
234        Err(e) => Err(format!("Failed to start instance: {}", e))
235    }
236}
237
238#[put("/instances/<id>/stop")]
239pub async fn stop_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
240    // Stop container
241    let options = Some(StopContainerOptions {
242        t: 30, // Give it 30 seconds to shut down gracefully
243    });
244    
245    match app_manager.docker.stop_container(&id, options).await {
246        Ok(_) => {
247            // Get updated container info
248            match get_instance(id, app_manager).await {
249                Some(instance) => Ok(instance),
250                None => Err("Failed to get instance after stopping".to_string())
251            }
252        },
253        Err(e) => Err(format!("Failed to stop instance: {}", e))
254    }
255}
256
257#[put("/instances/<id>/restart")]
258pub async fn restart_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
259    // Restart container
260    let options = Some(bollard::container::RestartContainerOptions {
261        t: 30, // Give it 30 seconds to shut down gracefully
262    });
263    
264    match app_manager.docker.restart_container(&id, options).await {
265        Ok(_) => {
266            // Get updated container info
267            match get_instance(id, app_manager).await {
268                Some(instance) => Ok(instance),
269                None => Err("Failed to get instance after restarting".to_string())
270            }
271        },
272        Err(e) => Err(format!("Failed to restart instance: {}", e))
273    }
274}
275#[patch("/instances/<id>", format = "json", data = "<update_req>")]
276pub async fn update_instance(id: String, update_req: Json<AppInstanceRequest>, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
277    // For updating, we generally need to:
278    // 1. Stop the existing container
279    // 2. Remove it (but keep volumes if they're managed externally)
280    // 3. Create a new one with the updated config
281    // 4. Start it
282    
283    // This is a simplified implementation
284    // In practice, you'd want to check what actually changed and handle it accordingly
285    
286    // First, stop the container
287    let stop_result = stop_instance(id.clone(), app_manager).await;
288    if stop_result.is_err() {
289        return Err(format!("Failed to stop instance for update: {}", stop_result.err().unwrap()));
290    }
291    
292    // Then remove it
293    let options = Some(RemoveContainerOptions {
294        force: true,
295        ..Default::default()
296    });
297    
298    match app_manager.docker.remove_container(&id, options).await {
299        Ok(_) => {
300            // Now create a new one with the updated config
301            create_instance(update_req, app_manager).await
302        },
303        Err(e) => Err(format!("Failed to remove instance for update: {}", e))
304    }
305}
306
307#[delete("/instances/<id>")]
308pub async fn delete_instance(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
309    // Remove container
310    let options = Some(RemoveContainerOptions {
311        force: true,
312        ..Default::default()
313    });
314    
315    match app_manager.docker.remove_container(&id, options).await {
316        Ok(_) => {
317            // Remove from our local state
318            app_manager.instances.lock().unwrap().remove(&id);
319            Ok(format!("Instance {} deleted successfully", id))
320        },
321        Err(e) => Err(format!("Failed to delete instance: {}", e))
322    }
323}
324
325#[get("/images")]
326pub async fn list_images(app_manager: &State<AppManager>) -> Json<Vec<String>> {
327    let mut images = Vec::new();
328    
329    // List images via Docker API
330    let options = Some(ListImagesOptions::<String> {
331        all: false,
332        ..Default::default()
333    });
334    
335    match app_manager.docker.list_images(options).await {
336        Ok(image_list) => {
337            for image in image_list {
338                for tag in &image.repo_tags {
339                    images.push(tag.clone());
340                }
341            }
342        },
343        Err(e) => {
344            eprintln!("Failed to list images: {}", e);
345        }
346    }
347    
348    Json(images)
349}
350
351#[get("/events")]
352pub async fn stream_events(app_manager: &State<AppManager>) -> String {
353    // This would typically be implemented with Server-Sent Events or WebSockets
354    // For this example, we'll just demonstrate the Docker events API
355    
356    let options = Some(EventsOptions::<String> {
357        ..Default::default()
358    });
359    
360    let mut event_stream = app_manager.docker.events(options);
361    
362    // In a real implementation, you'd stream these to the client
363    // Here we'll just return a message
364    while let Some(event) = event_stream.next().await {
365        match event {
366            Ok(event) => {
367                println!("Event: {:?}", event);
368                // In a real implementation, send this to the client
369            },
370            Err(e) => {
371                eprintln!("Error receiving event: {}", e);
372                break;
373            }
374        }
375    }
376    
377    "Event streaming would happen here".to_string()
378}
379
380#[get("/health")]
381pub fn health_check() -> String {
382    "App Manager is healthy".to_string()
383}
384
385#[get("/instances/<id>/logs")]
386pub async fn get_instance_logs(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
387    let options = Some(bollard::container::LogsOptions::<String> {
388        stdout: true,
389        stderr: true,
390        follow: false,
391        timestamps: true,
392        tail: "100".to_string(),
393        ..Default::default()
394    });
395
396    match app_manager.docker.logs(&id, options).try_collect::<Vec<_>>().await {
397        Ok(logs) => {
398            let log_content = logs.iter()
399                .map(|chunk| {
400                    match chunk {
401                        bollard::container::LogOutput::StdOut { message: bytes } | 
402                        bollard::container::LogOutput::StdErr { message: bytes } => {
403                            String::from_utf8_lossy(bytes).to_string()
404                        },
405                        bollard::container::LogOutput::StdIn { message: bytes } => {
406                            String::from_utf8_lossy(bytes).to_string()
407                        },
408                        bollard::container::LogOutput::Console { message: bytes } => {
409                            String::from_utf8_lossy(bytes).to_string()
410                        }
411                    }
412                })
413                .collect::<Vec<String>>()
414                .join("");
415            Ok(log_content)
416        },
417        Err(e) => Err(format!("Failed to fetch logs: {}", e))
418    }
419}
420
421#[get("/instances/<id>/stats")]
422pub async fn get_instance_stats(id: String, app_manager: &State<AppManager>) -> Result<Json<bollard::container::Stats>, String> {
423    match app_manager.docker.stats(&id, Some(bollard::container::StatsOptions { 
424        stream: false,
425        one_shot: true,
426    })).try_next().await {
427        Ok(Some(stats)) => Ok(Json(stats)),
428        Ok(None) => Err("No stats available".to_string()),
429        Err(e) => Err(format!("Failed to get stats: {}", e))
430    }
431}
432
433#[put("/instances/<id>/pause")]
434pub async fn pause_instance(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
435    match app_manager.docker.pause_container(&id).await {
436        Ok(_) => Ok(format!("Instance {} paused", id)),
437        Err(e) => Err(format!("Failed to pause instance: {}", e))
438    }
439}
440
441#[put("/instances/<id>/unpause")]
442pub async fn unpause_instance(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
443    match app_manager.docker.unpause_container(&id).await {
444        Ok(_) => Ok(format!("Instance {} unpaused", id)),
445        Err(e) => Err(format!("Failed to unpause instance: {}", e))
446    }
447}
448
449#[get("/instances/<id>/inspect")]
450pub async fn inspect_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<bollard::models::ContainerInspectResponse>, String> {
451    match app_manager.docker.inspect_container(&id, None).await {
452        Ok(info) => Ok(Json(info)),
453        Err(e) => Err(format!("Failed to inspect instance: {}", e))
454    }
455}
456
457// Volume Management
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct VolumeInfo {
461    name: String,
462    mountpoint: String,
463    labels: HashMap<String, String>,
464    created_at: String,
465}
466
467#[get("/volumes")]
468pub async fn list_volumes(app_manager: &State<AppManager>) -> Result<Json<Vec<VolumeInfo>>, String> {
469    match app_manager.docker.list_volumes::<String>(None).await {
470        Ok(volumes) => {
471            let volume_list = volumes.volumes.unwrap_or_default().into_iter()
472                .filter_map(|vol| {
473                    let name = vol.name;
474                    let mountpoint = vol.mountpoint;
475                    let labels = vol.labels;
476                    let created_at = vol.created_at.unwrap_or_default();
477                    
478                    Some(VolumeInfo {
479                        name,
480                        mountpoint,
481                        labels,
482                        created_at,
483                    })
484                })
485                .collect();
486            
487            Ok(Json(volume_list))
488        },
489        Err(e) => Err(format!("Failed to list volumes: {}", e))
490    }
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize)]
494pub struct VolumeCreateRequest {
495    name: String,
496    labels: Option<HashMap<String, String>>,
497}
498
499#[post("/volumes", format = "json", data = "<volume_req>")]
500pub async fn create_volume(volume_req: Json<VolumeCreateRequest>, app_manager: &State<AppManager>) -> Result<Json<VolumeInfo>, String> {
501    let options = bollard::volume::CreateVolumeOptions {
502        name: volume_req.name.clone(),
503        labels: volume_req.labels.clone().unwrap_or_default(),
504        ..Default::default()
505    };
506    
507    match app_manager.docker.create_volume(options).await {
508        Ok(volume) => {
509            let volume_info = VolumeInfo {
510                name: volume.name,
511                mountpoint: volume.mountpoint,
512                labels: volume.labels,
513                created_at: volume.created_at.unwrap_or_default(),
514            };
515            
516            Ok(Json(volume_info))
517        },
518        Err(e) => Err(format!("Failed to create volume: {}", e))
519    }
520}
521
522#[delete("/volumes/<name>")]
523pub async fn delete_volume(name: String, app_manager: &State<AppManager>) -> Result<String, String> {
524    match app_manager.docker.remove_volume(&name, None).await {
525        Ok(_) => Ok(format!("Volume {} deleted successfully", name)),
526        Err(e) => Err(format!("Failed to delete volume: {}", e))
527    }
528}
529
530// Network Management
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct NetworkInfo {
534    id: String,
535    name: String,
536    driver: String,
537    scope: String,
538    containers: HashMap<String, NetworkContainerInfo>,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct NetworkContainerInfo {
543    name: String,
544    endpoint_id: String,
545    ipv4_address: String,
546}
547
548#[get("/networks")]
549pub async fn list_networks(app_manager: &State<AppManager>) -> Result<Json<Vec<NetworkInfo>>, String> {
550    match app_manager.docker.list_networks::<String>(None).await {
551        Ok(networks) => {
552            let network_list = networks.into_iter()
553                .filter_map(|net| {
554                    let id = net.id?;
555                    let name = net.name?;
556                    let driver = net.driver?;
557                    let scope = net.scope?;
558                    
559                    let mut containers = HashMap::new();
560                    if let Some(net_containers) = net.containers {
561                        for (container_id, container_info) in net_containers {
562                            if let (Some(name), Some(endpoint_id), Some(ipv4_address)) = 
563                               (container_info.name, container_info.endpoint_id, container_info.ipv4_address) {
564                                containers.insert(container_id, NetworkContainerInfo {
565                                    name,
566                                    endpoint_id,
567                                    ipv4_address,
568                                });
569                            }
570                        }
571                    }
572                    
573                    Some(NetworkInfo {
574                        id,
575                        name,
576                        driver,
577                        scope,
578                        containers,
579                    })
580                })
581                .collect();
582            
583            Ok(Json(network_list))
584        },
585        Err(e) => Err(format!("Failed to list networks: {}", e))
586    }
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct NetworkCreateRequest {
591    name: String,
592    driver: Option<String>,
593    labels: Option<HashMap<String, String>>,
594}
595
596#[post("/networks", format = "json", data = "<network_req>")]
597pub async fn create_network(network_req: Json<NetworkCreateRequest>, app_manager: &State<AppManager>) -> Result<Json<NetworkInfo>, String> {
598    let options = bollard::network::CreateNetworkOptions {
599        name: network_req.name.clone(),
600        driver: network_req.driver.clone().unwrap_or_default(),
601        labels: network_req.labels.clone().unwrap_or_default(),
602        ..Default::default()
603    };
604    
605    match app_manager.docker.create_network(options).await {
606        Ok(response) => {
607            // Inspect network to get full details
608            match app_manager.docker.inspect_network::<String>(response.id.as_str(), None).await {
609                Ok(network) => {
610                    let mut containers = HashMap::new();
611                    if let Some(net_containers) = network.containers {
612                        for (container_id, container_info) in net_containers {
613                            if let (Some(name), Some(endpoint_id), Some(ipv4_address)) = 
614                               (container_info.name, container_info.endpoint_id, container_info.ipv4_address) {
615                                containers.insert(container_id, NetworkContainerInfo {
616                                    name,
617                                    endpoint_id,
618                                    ipv4_address,
619                                });
620                            }
621                        }
622                    }
623                    
624                    let network_info = NetworkInfo {
625                        id: network.id.unwrap_or_default(),
626                        name: network.name.unwrap_or_default(),
627                        driver: network.driver.unwrap_or_default(),
628                        scope: network.scope.unwrap_or_default(),
629                        containers,
630                    };
631                    
632                    Ok(Json(network_info))
633                },
634                Err(e) => Err(format!("Failed to inspect created network: {}", e))
635            }
636        },
637        Err(e) => Err(format!("Failed to create network: {}", e))
638    }
639}
640
641#[delete("/networks/<id>")]
642pub async fn delete_network(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
643    match app_manager.docker.remove_network(&id).await {
644        Ok(_) => Ok(format!("Network {} deleted successfully", id)),
645        Err(e) => Err(format!("Failed to delete network: {}", e))
646    }
647}
648
649#[put("/instances/<id>/connect/<network_id>")]
650pub async fn connect_instance_to_network(id: String, network_id: String, app_manager: &State<AppManager>) -> Result<String, String> {
651    let options = bollard::network::ConnectNetworkOptions {
652        container: id.clone(),
653        ..Default::default()
654    };
655    
656    match app_manager.docker.connect_network(&network_id, options).await {
657        Ok(_) => Ok(format!("Instance {} connected to network {}", id, network_id)),
658        Err(e) => Err(format!("Failed to connect instance to network: {}", e))
659    }
660}
661
662#[put("/instances/<id>/disconnect/<network_id>")]
663pub async fn disconnect_instance_from_network(id: String, network_id: String, app_manager: &State<AppManager>) -> Result<String, String> {
664    let options = bollard::network::DisconnectNetworkOptions {
665        container: id.clone(),
666        force: false,
667    };
668    
669    match app_manager.docker.disconnect_network(&network_id, options).await {
670        Ok(_) => Ok(format!("Instance {} disconnected from network {}", id, network_id)),
671        Err(e) => Err(format!("Failed to disconnect instance from network: {}", e))
672    }
673}
674
675// Agent Management Routes
676
677#[derive(Debug, Clone, Serialize, Deserialize)]
678pub struct AgentInfo {
679    id: String,
680    name: String,
681    version: String,
682    platform: String,
683    instance_count: usize,
684    status: String,
685    resources: SystemResources,
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
689pub struct SystemResources {
690    cpu_count: usize,
691    memory_total: u64,
692    memory_available: u64,
693    disk_total: u64,
694    disk_available: u64,
695}
696
697#[get("/agent/info")]
698pub async fn get_agent_info(app_manager: &State<AppManager>) -> Json<AgentInfo> {
699    // Get Docker engine info
700    let info = match app_manager.docker.info().await {
701        Ok(info) => info,
702        Err(e) => {
703            eprintln!("Failed to get Docker info: {}", e);
704            return Json(AgentInfo {
705                id: uuid::Uuid::new_v4().to_string(),
706                name: hostname::get().unwrap_or_default().to_string_lossy().to_string(),
707                version: "unknown".to_string(),
708                platform: "unknown".to_string(),
709                instance_count: app_manager.instances.lock().unwrap().len(),
710                status: "degraded".to_string(),
711                resources: SystemResources {
712                    cpu_count: num_cpus::get(),
713                    memory_total: 0,
714                    memory_available: 0,
715                    disk_total: 0,
716                    disk_available: 0,
717                },
718            });
719        }
720    };
721    
722    // Get system resources
723    let memory_info = sys_info::mem_info().unwrap_or(sys_info::MemInfo {
724        total: 0,
725        free: 0,
726        avail: 0,
727        buffers: 0,
728        cached: 0,
729        swap_total: 0,
730        swap_free: 0,
731    });
732    
733    let disk_info = sys_info::disk_info().unwrap_or(sys_info::DiskInfo {
734        total: 0,
735        free: 0,
736    });
737    
738    Json(AgentInfo {
739        id: uuid::Uuid::new_v4().to_string(),
740        name: hostname::get().unwrap_or_default().to_string_lossy().to_string(),
741        version: info.server_version.unwrap_or_default(),
742        platform: format!("{} / {}", 
743            info.operating_system.unwrap_or_default(),
744            info.architecture.unwrap_or_default()),
745        instance_count: app_manager.instances.lock().unwrap().len(),
746        status: "healthy".to_string(),
747        resources: SystemResources {
748            cpu_count: num_cpus::get(),
749            memory_total: memory_info.total * 1024,
750            memory_available: memory_info.avail * 1024,
751            disk_total: disk_info.total * 1024,
752            disk_available: disk_info.free * 1024,
753        },
754    })
755}