Implements VM groups API (#206)
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Reviewed-on: #206
This commit is contained in:
		@@ -1,5 +1,8 @@
 | 
				
			|||||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
					use crate::controllers::{HttpResult, LibVirtReq};
 | 
				
			||||||
 | 
					use crate::extractors::group_vm_id_extractor::GroupVmIdExtractor;
 | 
				
			||||||
 | 
					use crate::libvirt_rest_structures::vm::VMInfo;
 | 
				
			||||||
use actix_web::HttpResponse;
 | 
					use actix_web::HttpResponse;
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get the list of groups
 | 
					/// Get the list of groups
 | 
				
			||||||
pub async fn list(client: LibVirtReq) -> HttpResult {
 | 
					pub async fn list(client: LibVirtReq) -> HttpResult {
 | 
				
			||||||
@@ -14,3 +17,132 @@ pub async fn list(client: LibVirtReq) -> HttpResult {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(groups))
 | 
					    Ok(HttpResponse::Ok().json(groups))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get information about the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_info(vms_xml: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut vms = Vec::new();
 | 
				
			||||||
 | 
					    for vm in vms_xml.0 {
 | 
				
			||||||
 | 
					        vms.push(VMInfo::from_domain(vm)?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(vms))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Default, serde::Serialize)]
 | 
				
			||||||
 | 
					pub struct TreatmentResult {
 | 
				
			||||||
 | 
					    ok: usize,
 | 
				
			||||||
 | 
					    failed: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Start the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_start(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut res = TreatmentResult::default();
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            match client.start_domain(uuid).await {
 | 
				
			||||||
 | 
					                Ok(_) => res.ok += 1,
 | 
				
			||||||
 | 
					                Err(_) => res.failed += 1,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Shutdown the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_shutdown(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut res = TreatmentResult::default();
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            match client.shutdown_domain(uuid).await {
 | 
				
			||||||
 | 
					                Ok(_) => res.ok += 1,
 | 
				
			||||||
 | 
					                Err(_) => res.failed += 1,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Suspend the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_suspend(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut res = TreatmentResult::default();
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            match client.suspend_domain(uuid).await {
 | 
				
			||||||
 | 
					                Ok(_) => res.ok += 1,
 | 
				
			||||||
 | 
					                Err(_) => res.failed += 1,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Resume the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_resume(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut res = TreatmentResult::default();
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            match client.resume_domain(uuid).await {
 | 
				
			||||||
 | 
					                Ok(_) => res.ok += 1,
 | 
				
			||||||
 | 
					                Err(_) => res.failed += 1,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Kill the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_kill(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut res = TreatmentResult::default();
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            match client.kill_domain(uuid).await {
 | 
				
			||||||
 | 
					                Ok(_) => res.ok += 1,
 | 
				
			||||||
 | 
					                Err(_) => res.failed += 1,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reset the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_reset(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut res = TreatmentResult::default();
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            match client.reset_domain(uuid).await {
 | 
				
			||||||
 | 
					                Ok(_) => res.ok += 1,
 | 
				
			||||||
 | 
					                Err(_) => res.failed += 1,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get the screenshot of the VMs of a group
 | 
				
			||||||
 | 
					pub async fn vm_screenshot(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    if vms.0.is_empty() {
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::NoContent().finish());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let image = if vms.0.len() == 1 {
 | 
				
			||||||
 | 
					        client.screenshot_domain(vms.0[0].uuid.unwrap()).await?
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return Ok(
 | 
				
			||||||
 | 
					            HttpResponse::UnprocessableEntity().json("Cannot return multiple VM screenshots!!")
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().content_type("image/png").body(image))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get the state of the VMs
 | 
				
			||||||
 | 
					pub async fn vm_state(client: LibVirtReq, vms: GroupVmIdExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut states = HashMap::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for vm in vms.0 {
 | 
				
			||||||
 | 
					        if let Some(uuid) = vm.uuid {
 | 
				
			||||||
 | 
					            states.insert(uuid, client.get_domain_state(uuid).await?);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(states))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								virtweb_backend/src/extractors/group_vm_id_extractor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								virtweb_backend/src/extractors/group_vm_id_extractor.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					use crate::controllers::LibVirtReq;
 | 
				
			||||||
 | 
					use crate::libvirt_lib_structures::domain::DomainXML;
 | 
				
			||||||
 | 
					use crate::libvirt_lib_structures::XMLUuid;
 | 
				
			||||||
 | 
					use crate::libvirt_rest_structures::vm::VMGroupId;
 | 
				
			||||||
 | 
					use actix_http::Payload;
 | 
				
			||||||
 | 
					use actix_web::error::ErrorBadRequest;
 | 
				
			||||||
 | 
					use actix_web::web::Query;
 | 
				
			||||||
 | 
					use actix_web::{web, Error, FromRequest, HttpRequest};
 | 
				
			||||||
 | 
					use std::future::Future;
 | 
				
			||||||
 | 
					use std::pin::Pin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct GroupVmIdExtractor(pub Vec<DomainXML>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					struct GroupIDInPath {
 | 
				
			||||||
 | 
					    gid: VMGroupId,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					struct FilterVM {
 | 
				
			||||||
 | 
					    vm_id: Option<XMLUuid>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromRequest for GroupVmIdExtractor {
 | 
				
			||||||
 | 
					    type Error = Error;
 | 
				
			||||||
 | 
					    type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
 | 
				
			||||||
 | 
					        let req = req.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Box::pin(async move {
 | 
				
			||||||
 | 
					            let Ok(group_id) =
 | 
				
			||||||
 | 
					                web::Path::<GroupIDInPath>::from_request(&req, &mut Payload::None).await
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                return Err(ErrorBadRequest("Group ID not specified in path!"));
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let group_id = group_id.into_inner().gid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let filter_vm = match Query::<FilterVM>::from_request(&req, &mut Payload::None).await {
 | 
				
			||||||
 | 
					                Ok(v) => v,
 | 
				
			||||||
 | 
					                Err(e) => {
 | 
				
			||||||
 | 
					                    log::error!("Failed to extract VM id from request! {e}");
 | 
				
			||||||
 | 
					                    return Err(ErrorBadRequest("Failed to extract VM id from request!"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let Ok(client) = LibVirtReq::from_request(&req, &mut Payload::None).await else {
 | 
				
			||||||
 | 
					                return Err(ErrorBadRequest("Failed to extract client handle!"));
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let vms = match client.get_full_group_vm_list(&group_id).await {
 | 
				
			||||||
 | 
					                Ok(vms) => vms,
 | 
				
			||||||
 | 
					                Err(e) => {
 | 
				
			||||||
 | 
					                    log::error!("Failed to get the VMs of the group {group_id:?}: {e}");
 | 
				
			||||||
 | 
					                    return Err(ErrorBadRequest("Failed to get the VMs of the group!"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Filter (if requested by the user)
 | 
				
			||||||
 | 
					            Ok(GroupVmIdExtractor(match filter_vm.vm_id {
 | 
				
			||||||
 | 
					                None => vms,
 | 
				
			||||||
 | 
					                Some(id) => vms.into_iter().filter(|vms| vms.uuid == Some(id)).collect(),
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
pub mod api_auth_extractor;
 | 
					pub mod api_auth_extractor;
 | 
				
			||||||
pub mod auth_extractor;
 | 
					pub mod auth_extractor;
 | 
				
			||||||
 | 
					pub mod group_vm_id_extractor;
 | 
				
			||||||
pub mod local_auth_extractor;
 | 
					pub mod local_auth_extractor;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -122,6 +122,21 @@ impl LibVirtClient {
 | 
				
			|||||||
        Ok(out)
 | 
					        Ok(out)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get the full list of VMs of a given group
 | 
				
			||||||
 | 
					    pub async fn get_full_group_vm_list(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        group: &VMGroupId,
 | 
				
			||||||
 | 
					    ) -> anyhow::Result<Vec<DomainXML>> {
 | 
				
			||||||
 | 
					        let vms = self.get_full_domains_list().await?;
 | 
				
			||||||
 | 
					        let mut out = Vec::new();
 | 
				
			||||||
 | 
					        for vm in vms {
 | 
				
			||||||
 | 
					            if VMInfo::from_domain(vm.clone())?.group == Some(group.clone()) {
 | 
				
			||||||
 | 
					                out.push(vm);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(out)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Update a network configuration
 | 
					    /// Update a network configuration
 | 
				
			||||||
    pub async fn update_network(
 | 
					    pub async fn update_network(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ pub struct DomainMetadataXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// OS information
 | 
					/// OS information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "os")]
 | 
					#[serde(rename = "os")]
 | 
				
			||||||
pub struct OSXML {
 | 
					pub struct OSXML {
 | 
				
			||||||
    #[serde(rename = "@firmware", default)]
 | 
					    #[serde(rename = "@firmware", default)]
 | 
				
			||||||
@@ -29,7 +29,7 @@ pub struct OSXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// OS Type information
 | 
					/// OS Type information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "os")]
 | 
					#[serde(rename = "os")]
 | 
				
			||||||
pub struct OSTypeXML {
 | 
					pub struct OSTypeXML {
 | 
				
			||||||
    #[serde(rename = "@arch")]
 | 
					    #[serde(rename = "@arch")]
 | 
				
			||||||
@@ -41,7 +41,7 @@ pub struct OSTypeXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// OS Loader information
 | 
					/// OS Loader information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "loader")]
 | 
					#[serde(rename = "loader")]
 | 
				
			||||||
pub struct OSLoaderXML {
 | 
					pub struct OSLoaderXML {
 | 
				
			||||||
    #[serde(rename = "@secure")]
 | 
					    #[serde(rename = "@secure")]
 | 
				
			||||||
@@ -49,39 +49,39 @@ pub struct OSLoaderXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Hypervisor features
 | 
					/// Hypervisor features
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
 | 
				
			||||||
#[serde(rename = "features")]
 | 
					#[serde(rename = "features")]
 | 
				
			||||||
pub struct FeaturesXML {
 | 
					pub struct FeaturesXML {
 | 
				
			||||||
    pub acpi: ACPIXML,
 | 
					    pub acpi: ACPIXML,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// ACPI feature
 | 
					/// ACPI feature
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
 | 
				
			||||||
#[serde(rename = "acpi")]
 | 
					#[serde(rename = "acpi")]
 | 
				
			||||||
pub struct ACPIXML {}
 | 
					pub struct ACPIXML {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "mac")]
 | 
					#[serde(rename = "mac")]
 | 
				
			||||||
pub struct NetMacAddress {
 | 
					pub struct NetMacAddress {
 | 
				
			||||||
    #[serde(rename = "@address")]
 | 
					    #[serde(rename = "@address")]
 | 
				
			||||||
    pub address: String,
 | 
					    pub address: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "source")]
 | 
					#[serde(rename = "source")]
 | 
				
			||||||
pub struct NetIntSourceXML {
 | 
					pub struct NetIntSourceXML {
 | 
				
			||||||
    #[serde(rename = "@network")]
 | 
					    #[serde(rename = "@network")]
 | 
				
			||||||
    pub network: String,
 | 
					    pub network: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "model")]
 | 
					#[serde(rename = "model")]
 | 
				
			||||||
pub struct NetIntModelXML {
 | 
					pub struct NetIntModelXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
    pub r#type: String,
 | 
					    pub r#type: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "filterref")]
 | 
					#[serde(rename = "filterref")]
 | 
				
			||||||
pub struct NetIntFilterParameterXML {
 | 
					pub struct NetIntFilterParameterXML {
 | 
				
			||||||
    #[serde(rename = "@name")]
 | 
					    #[serde(rename = "@name")]
 | 
				
			||||||
@@ -90,7 +90,7 @@ pub struct NetIntFilterParameterXML {
 | 
				
			|||||||
    pub value: String,
 | 
					    pub value: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "filterref")]
 | 
					#[serde(rename = "filterref")]
 | 
				
			||||||
pub struct NetIntfilterRefXML {
 | 
					pub struct NetIntfilterRefXML {
 | 
				
			||||||
    #[serde(rename = "@filter")]
 | 
					    #[serde(rename = "@filter")]
 | 
				
			||||||
@@ -99,7 +99,7 @@ pub struct NetIntfilterRefXML {
 | 
				
			|||||||
    pub parameters: Vec<NetIntFilterParameterXML>,
 | 
					    pub parameters: Vec<NetIntFilterParameterXML>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "interface")]
 | 
					#[serde(rename = "interface")]
 | 
				
			||||||
pub struct DomainNetInterfaceXML {
 | 
					pub struct DomainNetInterfaceXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
@@ -113,14 +113,14 @@ pub struct DomainNetInterfaceXML {
 | 
				
			|||||||
    pub filterref: Option<NetIntfilterRefXML>,
 | 
					    pub filterref: Option<NetIntfilterRefXML>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "input")]
 | 
					#[serde(rename = "input")]
 | 
				
			||||||
pub struct DomainInputXML {
 | 
					pub struct DomainInputXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
    pub r#type: String,
 | 
					    pub r#type: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "backend")]
 | 
					#[serde(rename = "backend")]
 | 
				
			||||||
pub struct TPMBackendXML {
 | 
					pub struct TPMBackendXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
@@ -130,7 +130,7 @@ pub struct TPMBackendXML {
 | 
				
			|||||||
    pub r#version: String,
 | 
					    pub r#version: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "tpm")]
 | 
					#[serde(rename = "tpm")]
 | 
				
			||||||
pub struct TPMDeviceXML {
 | 
					pub struct TPMDeviceXML {
 | 
				
			||||||
    #[serde(rename = "@model")]
 | 
					    #[serde(rename = "@model")]
 | 
				
			||||||
@@ -139,7 +139,7 @@ pub struct TPMDeviceXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Devices information
 | 
					/// Devices information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "devices")]
 | 
					#[serde(rename = "devices")]
 | 
				
			||||||
pub struct DevicesXML {
 | 
					pub struct DevicesXML {
 | 
				
			||||||
    /// Graphics (used for VNC)
 | 
					    /// Graphics (used for VNC)
 | 
				
			||||||
@@ -168,7 +168,7 @@ pub struct DevicesXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Graphics information
 | 
					/// Graphics information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "graphics")]
 | 
					#[serde(rename = "graphics")]
 | 
				
			||||||
pub struct GraphicsXML {
 | 
					pub struct GraphicsXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
@@ -178,14 +178,14 @@ pub struct GraphicsXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Video device information
 | 
					/// Video device information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "video")]
 | 
					#[serde(rename = "video")]
 | 
				
			||||||
pub struct VideoXML {
 | 
					pub struct VideoXML {
 | 
				
			||||||
    pub model: VideoModelXML,
 | 
					    pub model: VideoModelXML,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Video model device information
 | 
					/// Video model device information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "model")]
 | 
					#[serde(rename = "model")]
 | 
				
			||||||
pub struct VideoModelXML {
 | 
					pub struct VideoModelXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
@@ -193,7 +193,7 @@ pub struct VideoModelXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Disk information
 | 
					/// Disk information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "disk")]
 | 
					#[serde(rename = "disk")]
 | 
				
			||||||
pub struct DiskXML {
 | 
					pub struct DiskXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
@@ -211,7 +211,7 @@ pub struct DiskXML {
 | 
				
			|||||||
    pub address: Option<DiskAddressXML>,
 | 
					    pub address: Option<DiskAddressXML>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "driver")]
 | 
					#[serde(rename = "driver")]
 | 
				
			||||||
pub struct DiskDriverXML {
 | 
					pub struct DiskDriverXML {
 | 
				
			||||||
    #[serde(rename = "@name")]
 | 
					    #[serde(rename = "@name")]
 | 
				
			||||||
@@ -222,14 +222,14 @@ pub struct DiskDriverXML {
 | 
				
			|||||||
    pub r#cache: String,
 | 
					    pub r#cache: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "source")]
 | 
					#[serde(rename = "source")]
 | 
				
			||||||
pub struct DiskSourceXML {
 | 
					pub struct DiskSourceXML {
 | 
				
			||||||
    #[serde(rename = "@file")]
 | 
					    #[serde(rename = "@file")]
 | 
				
			||||||
    pub file: String,
 | 
					    pub file: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "target")]
 | 
					#[serde(rename = "target")]
 | 
				
			||||||
pub struct DiskTargetXML {
 | 
					pub struct DiskTargetXML {
 | 
				
			||||||
    #[serde(rename = "@dev")]
 | 
					    #[serde(rename = "@dev")]
 | 
				
			||||||
@@ -238,18 +238,18 @@ pub struct DiskTargetXML {
 | 
				
			|||||||
    pub bus: String,
 | 
					    pub bus: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "readonly")]
 | 
					#[serde(rename = "readonly")]
 | 
				
			||||||
pub struct DiskReadOnlyXML {}
 | 
					pub struct DiskReadOnlyXML {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "boot")]
 | 
					#[serde(rename = "boot")]
 | 
				
			||||||
pub struct DiskBootXML {
 | 
					pub struct DiskBootXML {
 | 
				
			||||||
    #[serde(rename = "@order")]
 | 
					    #[serde(rename = "@order")]
 | 
				
			||||||
    pub order: String,
 | 
					    pub order: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "address")]
 | 
					#[serde(rename = "address")]
 | 
				
			||||||
pub struct DiskAddressXML {
 | 
					pub struct DiskAddressXML {
 | 
				
			||||||
    #[serde(rename = "@type")]
 | 
					    #[serde(rename = "@type")]
 | 
				
			||||||
@@ -269,7 +269,7 @@ pub struct DiskAddressXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Domain RAM information
 | 
					/// Domain RAM information
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "memory")]
 | 
					#[serde(rename = "memory")]
 | 
				
			||||||
pub struct DomainMemoryXML {
 | 
					pub struct DomainMemoryXML {
 | 
				
			||||||
    #[serde(rename = "@unit")]
 | 
					    #[serde(rename = "@unit")]
 | 
				
			||||||
@@ -279,7 +279,7 @@ pub struct DomainMemoryXML {
 | 
				
			|||||||
    pub memory: usize,
 | 
					    pub memory: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "topology")]
 | 
					#[serde(rename = "topology")]
 | 
				
			||||||
pub struct DomainCPUTopology {
 | 
					pub struct DomainCPUTopology {
 | 
				
			||||||
    #[serde(rename = "@sockets")]
 | 
					    #[serde(rename = "@sockets")]
 | 
				
			||||||
@@ -290,14 +290,14 @@ pub struct DomainCPUTopology {
 | 
				
			|||||||
    pub threads: usize,
 | 
					    pub threads: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "cpu")]
 | 
					#[serde(rename = "cpu")]
 | 
				
			||||||
pub struct DomainVCPUXML {
 | 
					pub struct DomainVCPUXML {
 | 
				
			||||||
    #[serde(rename = "$value")]
 | 
					    #[serde(rename = "$value")]
 | 
				
			||||||
    pub body: usize,
 | 
					    pub body: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "cpu")]
 | 
					#[serde(rename = "cpu")]
 | 
				
			||||||
pub struct DomainCPUXML {
 | 
					pub struct DomainCPUXML {
 | 
				
			||||||
    #[serde(rename = "@mode")]
 | 
					    #[serde(rename = "@mode")]
 | 
				
			||||||
@@ -306,7 +306,7 @@ pub struct DomainCPUXML {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Domain information, see https://libvirt.org/formatdomain.html
 | 
					/// Domain information, see https://libvirt.org/formatdomain.html
 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "domain")]
 | 
					#[serde(rename = "domain")]
 | 
				
			||||||
pub struct DomainXML {
 | 
					pub struct DomainXML {
 | 
				
			||||||
    /// Domain type (kvm)
 | 
					    /// Domain type (kvm)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
 | 
				
			||||||
pub struct XMLUuid(pub uuid::Uuid);
 | 
					pub struct XMLUuid(pub uuid::Uuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl XMLUuid {
 | 
					impl XMLUuid {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -212,6 +212,42 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
            .route("/api/vnc", web::get().to(vm_controller::vnc))
 | 
					            .route("/api/vnc", web::get().to(vm_controller::vnc))
 | 
				
			||||||
            // Groups controller
 | 
					            // Groups controller
 | 
				
			||||||
            .route("/api/group/list", web::get().to(groups_controller::list))
 | 
					            .route("/api/group/list", web::get().to(groups_controller::list))
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/info",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_info),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/start",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_start),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/shutdown",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_shutdown),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/suspend",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_suspend),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/resume",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_resume),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/kill",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_kill),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/reset",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_reset),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/screenshot",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_screenshot),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/group/{gid}/vm/state",
 | 
				
			||||||
 | 
					                web::get().to(groups_controller::vm_state),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // Network controller
 | 
					            // Network controller
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/api/network/create",
 | 
					                "/api/network/create",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ import { Button } from "@mui/material";
 | 
				
			|||||||
import Grid from "@mui/material/Grid2";
 | 
					import Grid from "@mui/material/Grid2";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { GroupApi } from "../../api/GroupApi";
 | 
				
			||||||
import { NWFilter, NWFilterApi } from "../../api/NWFilterApi";
 | 
					import { NWFilter, NWFilterApi } from "../../api/NWFilterApi";
 | 
				
			||||||
import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
 | 
					import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
 | 
				
			||||||
import { ServerApi } from "../../api/ServerApi";
 | 
					import { ServerApi } from "../../api/ServerApi";
 | 
				
			||||||
@@ -35,12 +36,14 @@ interface DetailsProps {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function APITokenDetails(p: DetailsProps): React.ReactElement {
 | 
					export function APITokenDetails(p: DetailsProps): React.ReactElement {
 | 
				
			||||||
  const [vms, setVMs] = React.useState<VMInfo[]>();
 | 
					  const [vms, setVMs] = React.useState<VMInfo[]>();
 | 
				
			||||||
 | 
					  const [groups, setGroups] = React.useState<string[]>();
 | 
				
			||||||
  const [networks, setNetworks] = React.useState<NetworkInfo[]>();
 | 
					  const [networks, setNetworks] = React.useState<NetworkInfo[]>();
 | 
				
			||||||
  const [nwFilters, setNetworkFilters] = React.useState<NWFilter[]>();
 | 
					  const [nwFilters, setNetworkFilters] = React.useState<NWFilter[]>();
 | 
				
			||||||
  const [tokens, setTokens] = React.useState<APIToken[]>();
 | 
					  const [tokens, setTokens] = React.useState<APIToken[]>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const load = async () => {
 | 
					  const load = async () => {
 | 
				
			||||||
    setVMs(await VMApi.GetList());
 | 
					    setVMs(await VMApi.GetList());
 | 
				
			||||||
 | 
					    setGroups(await GroupApi.GetList());
 | 
				
			||||||
    setNetworks(await NetworkApi.GetList());
 | 
					    setNetworks(await NetworkApi.GetList());
 | 
				
			||||||
    setNetworkFilters(await NWFilterApi.GetList());
 | 
					    setNetworkFilters(await NWFilterApi.GetList());
 | 
				
			||||||
    setTokens(await TokensApi.GetList());
 | 
					    setTokens(await TokensApi.GetList());
 | 
				
			||||||
@@ -54,6 +57,7 @@ export function APITokenDetails(p: DetailsProps): React.ReactElement {
 | 
				
			|||||||
      build={() => (
 | 
					      build={() => (
 | 
				
			||||||
        <APITokenDetailsInner
 | 
					        <APITokenDetailsInner
 | 
				
			||||||
          vms={vms!}
 | 
					          vms={vms!}
 | 
				
			||||||
 | 
					          groups={groups!}
 | 
				
			||||||
          networks={networks!}
 | 
					          networks={networks!}
 | 
				
			||||||
          nwFilters={nwFilters!}
 | 
					          nwFilters={nwFilters!}
 | 
				
			||||||
          tokens={tokens!}
 | 
					          tokens={tokens!}
 | 
				
			||||||
@@ -73,6 +77,7 @@ enum TokenTab {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type DetailsInnerProps = DetailsProps & {
 | 
					type DetailsInnerProps = DetailsProps & {
 | 
				
			||||||
  vms: VMInfo[];
 | 
					  vms: VMInfo[];
 | 
				
			||||||
 | 
					  groups: string[];
 | 
				
			||||||
  networks: NetworkInfo[];
 | 
					  networks: NetworkInfo[];
 | 
				
			||||||
  nwFilters: NWFilter[];
 | 
					  nwFilters: NWFilter[];
 | 
				
			||||||
  tokens: APIToken[];
 | 
					  tokens: APIToken[];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ export function TokenRightsEditor(p: {
 | 
				
			|||||||
  editable: boolean;
 | 
					  editable: boolean;
 | 
				
			||||||
  onChange?: () => void;
 | 
					  onChange?: () => void;
 | 
				
			||||||
  vms: VMInfo[];
 | 
					  vms: VMInfo[];
 | 
				
			||||||
 | 
					  groups: string[];
 | 
				
			||||||
  networks: NetworkInfo[];
 | 
					  networks: NetworkInfo[];
 | 
				
			||||||
  nwFilters: NWFilter[];
 | 
					  nwFilters: NWFilter[];
 | 
				
			||||||
  tokens: APIToken[];
 | 
					  tokens: APIToken[];
 | 
				
			||||||
@@ -238,6 +239,139 @@ export function TokenRightsEditor(p: {
 | 
				
			|||||||
        </Table>
 | 
					        </Table>
 | 
				
			||||||
      </RightsSection>
 | 
					      </RightsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <RightsSection label="VM groups">
 | 
				
			||||||
 | 
					        <RouteRight
 | 
				
			||||||
 | 
					          {...p}
 | 
				
			||||||
 | 
					          right={{ verb: "POST", path: "/api/group/list" }}
 | 
				
			||||||
 | 
					          label="Get the list of groups"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Table size="small">
 | 
				
			||||||
 | 
					          <TableHead>
 | 
				
			||||||
 | 
					            <TableRow>
 | 
				
			||||||
 | 
					              <TableCell>Group name</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Get VM info</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Start VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Shutdown VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Suspend VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Resume VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Kill VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Reset VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Screenshot VM</TableCell>
 | 
				
			||||||
 | 
					              <TableCell align="center">Get VM State</TableCell>
 | 
				
			||||||
 | 
					            </TableRow>
 | 
				
			||||||
 | 
					          </TableHead>
 | 
				
			||||||
 | 
					          <TableBody>
 | 
				
			||||||
 | 
					            {/* All Group operations */}
 | 
				
			||||||
 | 
					            <TableRow hover>
 | 
				
			||||||
 | 
					              <TableCell>
 | 
				
			||||||
 | 
					                <i>All</i>
 | 
				
			||||||
 | 
					              </TableCell>
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/info" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/start" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/shutdown" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/suspend" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/resume" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/kill" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/reset" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/screenshot" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <CellRight
 | 
				
			||||||
 | 
					                {...p}
 | 
				
			||||||
 | 
					                right={{ verb: "GET", path: "/api/group/*/vm/state" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </TableRow>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {/* Per VM operations */}
 | 
				
			||||||
 | 
					            {p.groups.map((v, n) => (
 | 
				
			||||||
 | 
					              <TableRow hover key={n}>
 | 
				
			||||||
 | 
					                <TableCell>{v}</TableCell>
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{ verb: "GET", path: `/api/group/${v}/vm/info` }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/info" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{ verb: "GET", path: `/api/group/${v}/vm/start` }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/start" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{
 | 
				
			||||||
 | 
					                    verb: "GET",
 | 
				
			||||||
 | 
					                    path: `/api/group/${v}/vm/shutdown`,
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/shutdown" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{
 | 
				
			||||||
 | 
					                    verb: "GET",
 | 
				
			||||||
 | 
					                    path: `/api/group/${v}/vm/suspend`,
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/suspend" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{
 | 
				
			||||||
 | 
					                    verb: "GET",
 | 
				
			||||||
 | 
					                    path: `/api/group/${v}/vm/resume`,
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/resume" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{ verb: "GET", path: `/api/group/${v}/vm/kill` }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/kill" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{ verb: "GET", path: `/api/group/${v}/vm/reset` }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/reset" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{
 | 
				
			||||||
 | 
					                    verb: "GET",
 | 
				
			||||||
 | 
					                    path: `/api/group/${v}/vm/screenshot`,
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/screenshot" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <CellRight
 | 
				
			||||||
 | 
					                  {...p}
 | 
				
			||||||
 | 
					                  right={{ verb: "GET", path: `/api/group/${v}/vm/state` }}
 | 
				
			||||||
 | 
					                  parent={{ verb: "GET", path: "/api/group/*/vm/state" }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </TableRow>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					          </TableBody>
 | 
				
			||||||
 | 
					        </Table>
 | 
				
			||||||
 | 
					      </RightsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Networks */}
 | 
					      {/* Networks */}
 | 
				
			||||||
      <RightsSection label="Networks">
 | 
					      <RightsSection label="Networks">
 | 
				
			||||||
        <RouteRight
 | 
					        <RouteRight
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user