Ready to implement network routes contents
This commit is contained in:
		| @@ -367,10 +367,12 @@ impl Handler<DefineNetwork> for LibVirtActor { | |||||||
|         msg.0.ips = vec![]; |         msg.0.ips = vec![]; | ||||||
|  |  | ||||||
|         let mut network_xml = serde_xml_rs::to_string(&msg.0)?; |         let mut network_xml = serde_xml_rs::to_string(&msg.0)?; | ||||||
|  |         log::trace!("Serialize network XML start: {network_xml}"); | ||||||
|  |  | ||||||
|         let ips_xml = ips_xml.join("\n"); |         let ips_xml = ips_xml.join("\n"); | ||||||
|         network_xml = network_xml.replacen("</network>", &format!("{ips_xml}</network>"), 1); |         network_xml = network_xml.replacen("</network>", &format!("{ips_xml}</network>"), 1); | ||||||
|  |  | ||||||
|  |         log::debug!("Source network structure: {:#?}", msg.0); | ||||||
|         log::debug!("Define network XML: {network_xml}"); |         log::debug!("Define network XML: {network_xml}"); | ||||||
|  |  | ||||||
|         let network = Network::define_xml(&self.m, &network_xml)?; |         let network = Network::define_xml(&self.m, &network_xml)?; | ||||||
| @@ -428,3 +430,83 @@ impl Handler<DeleteNetwork> for LibVirtActor { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Message)] | ||||||
|  | #[rtype(result = "anyhow::Result<bool>")] | ||||||
|  | pub struct IsNetworkAutostart(pub XMLUuid); | ||||||
|  |  | ||||||
|  | impl Handler<IsNetworkAutostart> for LibVirtActor { | ||||||
|  |     type Result = anyhow::Result<bool>; | ||||||
|  |  | ||||||
|  |     fn handle(&mut self, msg: IsNetworkAutostart, _ctx: &mut Self::Context) -> Self::Result { | ||||||
|  |         log::debug!( | ||||||
|  |             "Check if autostart is enabled for a network: {}", | ||||||
|  |             msg.0.as_string() | ||||||
|  |         ); | ||||||
|  |         let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; | ||||||
|  |         Ok(network.get_autostart()?) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Message)] | ||||||
|  | #[rtype(result = "anyhow::Result<()>")] | ||||||
|  | pub struct SetNetworkAutostart(pub XMLUuid, pub bool); | ||||||
|  |  | ||||||
|  | impl Handler<SetNetworkAutostart> for LibVirtActor { | ||||||
|  |     type Result = anyhow::Result<()>; | ||||||
|  |  | ||||||
|  |     fn handle(&mut self, msg: SetNetworkAutostart, _ctx: &mut Self::Context) -> Self::Result { | ||||||
|  |         log::debug!( | ||||||
|  |             "Set autostart enabled={} for a network: {}", | ||||||
|  |             msg.1, | ||||||
|  |             msg.0.as_string() | ||||||
|  |         ); | ||||||
|  |         let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; | ||||||
|  |         network.set_autostart(msg.1)?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Message)] | ||||||
|  | #[rtype(result = "anyhow::Result<bool>")] | ||||||
|  | pub struct IsNetworkStarted(pub XMLUuid); | ||||||
|  |  | ||||||
|  | impl Handler<IsNetworkStarted> for LibVirtActor { | ||||||
|  |     type Result = anyhow::Result<bool>; | ||||||
|  |  | ||||||
|  |     fn handle(&mut self, msg: IsNetworkStarted, _ctx: &mut Self::Context) -> Self::Result { | ||||||
|  |         log::debug!("Check if a network is started: {}", msg.0.as_string()); | ||||||
|  |         let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; | ||||||
|  |         Ok(network.is_active()?) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Message)] | ||||||
|  | #[rtype(result = "anyhow::Result<()>")] | ||||||
|  | pub struct StartNetwork(pub XMLUuid); | ||||||
|  |  | ||||||
|  | impl Handler<StartNetwork> for LibVirtActor { | ||||||
|  |     type Result = anyhow::Result<()>; | ||||||
|  |  | ||||||
|  |     fn handle(&mut self, msg: StartNetwork, _ctx: &mut Self::Context) -> Self::Result { | ||||||
|  |         log::debug!("Start a network: {}", msg.0.as_string()); | ||||||
|  |         let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; | ||||||
|  |         network.create()?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Message)] | ||||||
|  | #[rtype(result = "anyhow::Result<()>")] | ||||||
|  | pub struct StopNetwork(pub XMLUuid); | ||||||
|  |  | ||||||
|  | impl Handler<StopNetwork> for LibVirtActor { | ||||||
|  |     type Result = anyhow::Result<()>; | ||||||
|  |  | ||||||
|  |     fn handle(&mut self, msg: StopNetwork, _ctx: &mut Self::Context) -> Self::Result { | ||||||
|  |         log::debug!("Stop a network: {}", msg.0.as_string()); | ||||||
|  |         let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; | ||||||
|  |         network.destroy()?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -149,7 +149,14 @@ impl AppConfig { | |||||||
|  |  | ||||||
|     /// Get root storage directory |     /// Get root storage directory | ||||||
|     pub fn storage_path(&self) -> PathBuf { |     pub fn storage_path(&self) -> PathBuf { | ||||||
|         Path::new(&self.storage).canonicalize().unwrap() |         let storage_path = Path::new(&self.storage); | ||||||
|  |         if !storage_path.is_dir() { | ||||||
|  |             panic!( | ||||||
|  |                 "Specified storage path ({}) is not a directory!", | ||||||
|  |                 self.storage | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         storage_path.canonicalize().unwrap() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Get iso storage directory |     /// Get iso storage directory | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ pub async fn create(client: LibVirtReq, req: web::Json<NetworkInfo>) -> HttpResu | |||||||
|             return Ok(HttpResponse::BadRequest().body(e.to_string())); |             return Ok(HttpResponse::BadRequest().body(e.to_string())); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let uid = client.update_network(network).await?; |     let uid = client.update_network(network).await?; | ||||||
|  |  | ||||||
|     Ok(HttpResponse::Ok().json(NetworkID { uid })) |     Ok(HttpResponse::Ok().json(NetworkID { uid })) | ||||||
| @@ -60,3 +61,62 @@ pub async fn delete(client: LibVirtReq, path: web::Path<NetworkID>) -> HttpResul | |||||||
|  |  | ||||||
|     Ok(HttpResponse::Ok().json("Network deleted")) |     Ok(HttpResponse::Ok().json("Network deleted")) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(serde::Serialize, serde::Deserialize)] | ||||||
|  | pub struct NetworkAutostart { | ||||||
|  |     autostart: bool, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get autostart value of a network | ||||||
|  | pub async fn get_autostart(client: LibVirtReq, id: web::Path<NetworkID>) -> HttpResult { | ||||||
|  |     Ok(HttpResponse::Ok().json(NetworkAutostart { | ||||||
|  |         autostart: client.is_network_autostart(id.uid).await?, | ||||||
|  |     })) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Configure autostart value for a network | ||||||
|  | pub async fn set_autostart( | ||||||
|  |     client: LibVirtReq, | ||||||
|  |     id: web::Path<NetworkID>, | ||||||
|  |     body: web::Json<NetworkAutostart>, | ||||||
|  | ) -> HttpResult { | ||||||
|  |     client.set_network_autostart(id.uid, body.autostart).await?; | ||||||
|  |     Ok(HttpResponse::Accepted().json("OK")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(serde::Serialize)] | ||||||
|  | enum NetworkStatus { | ||||||
|  |     Started, | ||||||
|  |     Stopped, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(serde::Serialize)] | ||||||
|  | struct NetworkStatusResponse { | ||||||
|  |     status: NetworkStatus, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get network status | ||||||
|  | pub async fn status(client: LibVirtReq, id: web::Path<NetworkID>) -> HttpResult { | ||||||
|  |     let started = client.is_network_started(id.uid).await?; | ||||||
|  |  | ||||||
|  |     Ok(HttpResponse::Ok().json(NetworkStatusResponse { | ||||||
|  |         status: match started { | ||||||
|  |             true => NetworkStatus::Started, | ||||||
|  |             false => NetworkStatus::Stopped, | ||||||
|  |         }, | ||||||
|  |     })) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Start a network | ||||||
|  | pub async fn start(client: LibVirtReq, id: web::Path<NetworkID>) -> HttpResult { | ||||||
|  |     client.start_network(id.uid).await?; | ||||||
|  |  | ||||||
|  |     Ok(HttpResponse::Accepted().json("Network started")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Stop a network | ||||||
|  | pub async fn stop(client: LibVirtReq, id: web::Path<NetworkID>) -> HttpResult { | ||||||
|  |     client.stop_network(id.uid).await?; | ||||||
|  |  | ||||||
|  |     Ok(HttpResponse::Accepted().json("Network stopped")) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -29,11 +29,13 @@ struct LenConstraints { | |||||||
| #[derive(serde::Serialize)] | #[derive(serde::Serialize)] | ||||||
| struct ServerConstraints { | struct ServerConstraints { | ||||||
|     iso_max_size: usize, |     iso_max_size: usize, | ||||||
|     name_size: LenConstraints, |     vm_name_size: LenConstraints, | ||||||
|     title_size: LenConstraints, |     vm_title_size: LenConstraints, | ||||||
|     memory_size: LenConstraints, |     memory_size: LenConstraints, | ||||||
|     disk_name_size: LenConstraints, |     disk_name_size: LenConstraints, | ||||||
|     disk_size: LenConstraints, |     disk_size: LenConstraints, | ||||||
|  |     net_name_size: LenConstraints, | ||||||
|  |     net_title_size: LenConstraints, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { | pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { | ||||||
| @@ -45,8 +47,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { | |||||||
|         constraints: ServerConstraints { |         constraints: ServerConstraints { | ||||||
|             iso_max_size: constants::ISO_MAX_SIZE, |             iso_max_size: constants::ISO_MAX_SIZE, | ||||||
|  |  | ||||||
|             name_size: LenConstraints { min: 2, max: 50 }, |             vm_name_size: LenConstraints { min: 2, max: 50 }, | ||||||
|             title_size: LenConstraints { min: 0, max: 50 }, |             vm_title_size: LenConstraints { min: 0, max: 50 }, | ||||||
|             memory_size: LenConstraints { |             memory_size: LenConstraints { | ||||||
|                 min: constants::MIN_VM_MEMORY, |                 min: constants::MIN_VM_MEMORY, | ||||||
|                 max: constants::MAX_VM_MEMORY, |                 max: constants::MAX_VM_MEMORY, | ||||||
| @@ -59,6 +61,9 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { | |||||||
|                 min: DISK_SIZE_MIN, |                 min: DISK_SIZE_MIN, | ||||||
|                 max: DISK_SIZE_MAX, |                 max: DISK_SIZE_MAX, | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|  |             net_name_size: LenConstraints { min: 2, max: 50 }, | ||||||
|  |             net_title_size: LenConstraints { min: 0, max: 50 }, | ||||||
|         }, |         }, | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -116,4 +116,31 @@ impl LibVirtClient { | |||||||
|     pub async fn delete_network(&self, id: XMLUuid) -> anyhow::Result<()> { |     pub async fn delete_network(&self, id: XMLUuid) -> anyhow::Result<()> { | ||||||
|         self.0.send(libvirt_actor::DeleteNetwork(id)).await? |         self.0.send(libvirt_actor::DeleteNetwork(id)).await? | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Get auto-start status of a network | ||||||
|  |     pub async fn is_network_autostart(&self, id: XMLUuid) -> anyhow::Result<bool> { | ||||||
|  |         self.0.send(libvirt_actor::IsNetworkAutostart(id)).await? | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Update autostart value of a network | ||||||
|  |     pub async fn set_network_autostart(&self, id: XMLUuid, autostart: bool) -> anyhow::Result<()> { | ||||||
|  |         self.0 | ||||||
|  |             .send(libvirt_actor::SetNetworkAutostart(id, autostart)) | ||||||
|  |             .await? | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Check out whether a network is started or not | ||||||
|  |     pub async fn is_network_started(&self, id: XMLUuid) -> anyhow::Result<bool> { | ||||||
|  |         self.0.send(libvirt_actor::IsNetworkStarted(id)).await? | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Start a network | ||||||
|  |     pub async fn start_network(&self, id: XMLUuid) -> anyhow::Result<()> { | ||||||
|  |         self.0.send(libvirt_actor::StartNetwork(id)).await? | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Stop a network | ||||||
|  |     pub async fn stop_network(&self, id: XMLUuid) -> anyhow::Result<()> { | ||||||
|  |         self.0.send(libvirt_actor::StopNetwork(id)).await? | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -194,6 +194,26 @@ async fn main() -> std::io::Result<()> { | |||||||
|                 "/api/network/{uid}", |                 "/api/network/{uid}", | ||||||
|                 web::delete().to(network_controller::delete), |                 web::delete().to(network_controller::delete), | ||||||
|             ) |             ) | ||||||
|  |             .route( | ||||||
|  |                 "/api/network/{uid}/autostart", | ||||||
|  |                 web::get().to(network_controller::get_autostart), | ||||||
|  |             ) | ||||||
|  |             .route( | ||||||
|  |                 "/api/network/{uid}/autostart", | ||||||
|  |                 web::put().to(network_controller::set_autostart), | ||||||
|  |             ) | ||||||
|  |             .route( | ||||||
|  |                 "/api/network/{uid}/status", | ||||||
|  |                 web::get().to(network_controller::status), | ||||||
|  |             ) | ||||||
|  |             .route( | ||||||
|  |                 "/api/network/{uid}/start", | ||||||
|  |                 web::get().to(network_controller::start), | ||||||
|  |             ) | ||||||
|  |             .route( | ||||||
|  |                 "/api/network/{uid}/stop", | ||||||
|  |                 web::get().to(network_controller::stop), | ||||||
|  |             ) | ||||||
|     }) |     }) | ||||||
|     .bind(&AppConfig::get().listen_address)? |     .bind(&AppConfig::get().listen_address)? | ||||||
|     .run() |     .run() | ||||||
|   | |||||||
| @@ -1,25 +1,30 @@ | |||||||
| import React from "react"; | import React from "react"; | ||||||
| import "./App.css"; |  | ||||||
| import { | import { | ||||||
|   Route, |   Route, | ||||||
|   RouterProvider, |   RouterProvider, | ||||||
|   createBrowserRouter, |   createBrowserRouter, | ||||||
|   createRoutesFromElements, |   createRoutesFromElements, | ||||||
| } from "react-router-dom"; | } from "react-router-dom"; | ||||||
| import { NotFoundRoute } from "./routes/NotFound"; | import "./App.css"; | ||||||
| import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; |  | ||||||
| import { BaseLoginPage } from "./widgets/BaseLoginPage"; |  | ||||||
| import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; |  | ||||||
| import { LoginRoute } from "./routes/auth/LoginRoute"; |  | ||||||
| import { AuthApi } from "./api/AuthApi"; | import { AuthApi } from "./api/AuthApi"; | ||||||
| import { IsoFilesRoute } from "./routes/IsoFilesRoute"; |  | ||||||
| import { ServerApi } from "./api/ServerApi"; | import { ServerApi } from "./api/ServerApi"; | ||||||
|  | import { | ||||||
|  |   CreateNetworkRoute, | ||||||
|  |   EditNetworkRoute, | ||||||
|  | } from "./routes/EditNetworkRoute"; | ||||||
|  | import { CreateVMRoute, EditVMRoute } from "./routes/EditVMRoute"; | ||||||
|  | import { IsoFilesRoute } from "./routes/IsoFilesRoute"; | ||||||
|  | import { NetworksListRoute } from "./routes/NetworksListRoute"; | ||||||
|  | import { NotFoundRoute } from "./routes/NotFound"; | ||||||
| import { SysInfoRoute } from "./routes/SysInfoRoute"; | import { SysInfoRoute } from "./routes/SysInfoRoute"; | ||||||
| import { VMListRoute } from "./routes/VMListRoute"; | import { VMListRoute } from "./routes/VMListRoute"; | ||||||
| import { CreateVMRoute, EditVMRoute } from "./routes/EditVMRoute"; |  | ||||||
| import { VMRoute } from "./routes/VMRoute"; | import { VMRoute } from "./routes/VMRoute"; | ||||||
| import { VNCRoute } from "./routes/VNCRoute"; | import { VNCRoute } from "./routes/VNCRoute"; | ||||||
| import { NetworksListRoute } from "./routes/NetworksListRoute"; | import { LoginRoute } from "./routes/auth/LoginRoute"; | ||||||
|  | import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; | ||||||
|  | import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; | ||||||
|  | import { BaseLoginPage } from "./widgets/BaseLoginPage"; | ||||||
|  | import { ViewNetworkRoute } from "./routes/ViewNetworkRoute"; | ||||||
|  |  | ||||||
| interface AuthContext { | interface AuthContext { | ||||||
|   signedIn: boolean; |   signedIn: boolean; | ||||||
| @@ -49,6 +54,9 @@ export function App() { | |||||||
|           <Route path="vm/:uuid/vnc" element={<VNCRoute />} /> |           <Route path="vm/:uuid/vnc" element={<VNCRoute />} /> | ||||||
|  |  | ||||||
|           <Route path="net" element={<NetworksListRoute />} /> |           <Route path="net" element={<NetworksListRoute />} /> | ||||||
|  |           <Route path="net/new" element={<CreateNetworkRoute />} /> | ||||||
|  |           <Route path="net/:uuid" element={<ViewNetworkRoute />} /> | ||||||
|  |           <Route path="net/:uuid/edit" element={<EditNetworkRoute />} /> | ||||||
|  |  | ||||||
|           <Route path="sysinfo" element={<SysInfoRoute />} /> |           <Route path="sysinfo" element={<SysInfoRoute />} /> | ||||||
|           <Route path="*" element={<NotFoundRoute />} /> |           <Route path="*" element={<NotFoundRoute />} /> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ export interface IpConfig { | |||||||
|  |  | ||||||
| export interface NetworkInfo { | export interface NetworkInfo { | ||||||
|   name: string; |   name: string; | ||||||
|   uuid: string; |   uuid?: string; | ||||||
|   title?: string; |   title?: string; | ||||||
|   description?: string; |   description?: string; | ||||||
|   forward_mode: "NAT" | "Isolated"; |   forward_mode: "NAT" | "Isolated"; | ||||||
| @@ -24,6 +24,19 @@ export function NetworkURL(n: NetworkInfo, edit: boolean = false): string { | |||||||
| } | } | ||||||
|  |  | ||||||
| export class NetworkApi { | export class NetworkApi { | ||||||
|  |   /** | ||||||
|  |    * Create a new network | ||||||
|  |    */ | ||||||
|  |   static async Create(n: NetworkInfo): Promise<{ uid: string }> { | ||||||
|  |     return ( | ||||||
|  |       await APIClient.exec({ | ||||||
|  |         method: "POST", | ||||||
|  |         uri: "/network/create", | ||||||
|  |         jsonData: n, | ||||||
|  |       }) | ||||||
|  |     ).data; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Get the entire list of networks |    * Get the entire list of networks | ||||||
|    */ |    */ | ||||||
| @@ -36,6 +49,31 @@ export class NetworkApi { | |||||||
|     ).data; |     ).data; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Get the information about a single network | ||||||
|  |    */ | ||||||
|  |   static async GetSingle(uuid: string): Promise<NetworkInfo> { | ||||||
|  |     return ( | ||||||
|  |       await APIClient.exec({ | ||||||
|  |         method: "GET", | ||||||
|  |         uri: `/network/${uuid}`, | ||||||
|  |       }) | ||||||
|  |     ).data; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Update an existing network | ||||||
|  |    */ | ||||||
|  |   static async Update(n: NetworkInfo): Promise<{ uid: string }> { | ||||||
|  |     return ( | ||||||
|  |       await APIClient.exec({ | ||||||
|  |         method: "PUT", | ||||||
|  |         uri: `/network/${n.uuid}`, | ||||||
|  |         jsonData: n, | ||||||
|  |       }) | ||||||
|  |     ).data; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Delete a network |    * Delete a network | ||||||
|    */ |    */ | ||||||
|   | |||||||
| @@ -10,11 +10,13 @@ export interface ServerConfig { | |||||||
|  |  | ||||||
| export interface ServerConstraints { | export interface ServerConstraints { | ||||||
|   iso_max_size: number; |   iso_max_size: number; | ||||||
|   name_size: LenConstraint; |   vm_name_size: LenConstraint; | ||||||
|   title_size: LenConstraint; |   vm_title_size: LenConstraint; | ||||||
|   memory_size: LenConstraint; |   memory_size: LenConstraint; | ||||||
|   disk_name_size: LenConstraint; |   disk_name_size: LenConstraint; | ||||||
|   disk_size: LenConstraint; |   disk_size: LenConstraint; | ||||||
|  |   net_name_size: LenConstraint; | ||||||
|  |   net_title_size: LenConstraint; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface LenConstraint { | export interface LenConstraint { | ||||||
|   | |||||||
							
								
								
									
										122
									
								
								virtweb_frontend/src/routes/EditNetworkRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								virtweb_frontend/src/routes/EditNetworkRoute.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | import { useNavigate, useParams } from "react-router-dom"; | ||||||
|  | import { NetworkApi, NetworkInfo } from "../api/NetworksApi"; | ||||||
|  | import { useAlert } from "../hooks/providers/AlertDialogProvider"; | ||||||
|  | import { useSnackbar } from "../hooks/providers/SnackbarProvider"; | ||||||
|  | import React from "react"; | ||||||
|  | import { AsyncWidget } from "../widgets/AsyncWidget"; | ||||||
|  | import { NetworkDetails } from "../widgets/net/NetworkDetails"; | ||||||
|  | import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; | ||||||
|  | import { Button } from "@mui/material"; | ||||||
|  |  | ||||||
|  | export function CreateNetworkRoute(): React.ReactElement { | ||||||
|  |   const alert = useAlert(); | ||||||
|  |   const snackbar = useSnackbar(); | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |  | ||||||
|  |   const [network] = React.useState<NetworkInfo>({ | ||||||
|  |     name: "NewNetwork", | ||||||
|  |     forward_mode: "Isolated", | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const createNetwork = async (n: NetworkInfo) => { | ||||||
|  |     try { | ||||||
|  |       const res = await NetworkApi.Create(n); | ||||||
|  |       snackbar("The network was successfully created!"); | ||||||
|  |       navigate(`/net/${res.uid}`); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error(e); | ||||||
|  |       alert("Failed to create network!"); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <EditNetworkRouteInner | ||||||
|  |       network={network} | ||||||
|  |       creating={true} | ||||||
|  |       onCancel={() => navigate("/net")} | ||||||
|  |       onSave={createNetwork} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function EditNetworkRoute(): React.ReactElement { | ||||||
|  |   const alert = useAlert(); | ||||||
|  |   const snackbar = useSnackbar(); | ||||||
|  |  | ||||||
|  |   const { uuid } = useParams(); | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |  | ||||||
|  |   const [network, setNetwork] = React.useState<NetworkInfo | undefined>(); | ||||||
|  |  | ||||||
|  |   const load = async () => { | ||||||
|  |     setNetwork(await NetworkApi.GetSingle(uuid!)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const updateNetwork = async (n: NetworkInfo) => { | ||||||
|  |     try { | ||||||
|  |       await NetworkApi.Update(n); | ||||||
|  |       snackbar("The network was successfully updated!"); | ||||||
|  |       navigate(`/net/${network!.uuid}`); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error(e); | ||||||
|  |       alert("Failed to update network!"); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <AsyncWidget | ||||||
|  |       loadKey={uuid} | ||||||
|  |       ready={network !== undefined} | ||||||
|  |       errMsg="Failed to fetch network information!" | ||||||
|  |       load={load} | ||||||
|  |       build={() => ( | ||||||
|  |         <EditNetworkRouteInner | ||||||
|  |           network={network!} | ||||||
|  |           creating={false} | ||||||
|  |           onCancel={() => navigate(`/net/${uuid}`)} | ||||||
|  |           onSave={updateNetwork} | ||||||
|  |         /> | ||||||
|  |       )} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function EditNetworkRouteInner(p: { | ||||||
|  |   network: NetworkInfo; | ||||||
|  |   creating: boolean; | ||||||
|  |   onCancel: () => void; | ||||||
|  |   onSave: (vm: NetworkInfo) => Promise<void>; | ||||||
|  | }): React.ReactElement { | ||||||
|  |   const [changed, setChanged] = React.useState(false); | ||||||
|  |  | ||||||
|  |   const [, updateState] = React.useState<any>(); | ||||||
|  |   const forceUpdate = React.useCallback(() => updateState({}), []); | ||||||
|  |  | ||||||
|  |   const valueChanged = () => { | ||||||
|  |     setChanged(true); | ||||||
|  |     forceUpdate(); | ||||||
|  |   }; | ||||||
|  |   return ( | ||||||
|  |     <VirtWebRouteContainer | ||||||
|  |       label={p.creating ? "Create a Network" : "Edit Network"} | ||||||
|  |       actions={ | ||||||
|  |         <span> | ||||||
|  |           {changed && ( | ||||||
|  |             <Button | ||||||
|  |               variant="contained" | ||||||
|  |               onClick={() => p.onSave(p.network)} | ||||||
|  |               style={{ marginRight: "10px" }} | ||||||
|  |             > | ||||||
|  |               {p.creating ? "Create" : "Save"} | ||||||
|  |             </Button> | ||||||
|  |           )} | ||||||
|  |           <Button onClick={p.onCancel} variant="outlined"> | ||||||
|  |             Cancel | ||||||
|  |           </Button> | ||||||
|  |         </span> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|  |       <NetworkDetails net={p.network} editable={true} onChange={valueChanged} /> | ||||||
|  |     </VirtWebRouteContainer> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								virtweb_frontend/src/routes/ViewNetworkRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								virtweb_frontend/src/routes/ViewNetworkRoute.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | import React from "react"; | ||||||
|  | import { NetworkApi, NetworkInfo } from "../api/NetworksApi"; | ||||||
|  | import { AsyncWidget } from "../widgets/AsyncWidget"; | ||||||
|  | import { useNavigate, useParams } from "react-router-dom"; | ||||||
|  | import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; | ||||||
|  | import { Button } from "@mui/material"; | ||||||
|  | import { NetworkDetails } from "../widgets/net/NetworkDetails"; | ||||||
|  |  | ||||||
|  | export function ViewNetworkRoute() { | ||||||
|  |   const { uuid } = useParams(); | ||||||
|  |  | ||||||
|  |   const [network, setNetwork] = React.useState<NetworkInfo | undefined>(); | ||||||
|  |  | ||||||
|  |   const load = async () => { | ||||||
|  |     setNetwork(await NetworkApi.GetSingle(uuid!)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <AsyncWidget | ||||||
|  |       loadKey={uuid} | ||||||
|  |       ready={network !== undefined} | ||||||
|  |       errMsg="Failed to fetch network information!" | ||||||
|  |       load={load} | ||||||
|  |       build={() => <ViewNetworkRouteInner network={network!} />} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function ViewNetworkRouteInner(p: { | ||||||
|  |   network: NetworkInfo; | ||||||
|  | }): React.ReactElement { | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <VirtWebRouteContainer | ||||||
|  |       label={`Network ${p.network.name}`} | ||||||
|  |       actions={ | ||||||
|  |         /* TODO: show only if network is stopped */ | ||||||
|  |         <Button | ||||||
|  |           variant="contained" | ||||||
|  |           style={{ marginLeft: "15px" }} | ||||||
|  |           onClick={() => navigate(`/net/${p.network.uuid}/edit`)} | ||||||
|  |         > | ||||||
|  |           Edit | ||||||
|  |         </Button> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|  |       <NetworkDetails net={p.network} editable={false} /> | ||||||
|  |     </VirtWebRouteContainer> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -3,8 +3,7 @@ import { | |||||||
|   mdiDisc, |   mdiDisc, | ||||||
|   mdiHome, |   mdiHome, | ||||||
|   mdiInformation, |   mdiInformation, | ||||||
|   mdiLan, |   mdiLan | ||||||
|   mdiNetwork, |  | ||||||
| } from "@mdi/js"; | } from "@mdi/js"; | ||||||
| import Icon from "@mdi/react"; | import Icon from "@mdi/react"; | ||||||
| import { | import { | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								virtweb_frontend/src/widgets/forms/EditSection.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								virtweb_frontend/src/widgets/forms/EditSection.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | import { Grid, Paper, Typography } from "@mui/material"; | ||||||
|  | import { PropsWithChildren } from "react"; | ||||||
|  |  | ||||||
|  | export function EditSection( | ||||||
|  |   p: { title: string } & PropsWithChildren | ||||||
|  | ): React.ReactElement { | ||||||
|  |   return ( | ||||||
|  |     <Grid item sm={12} md={6}> | ||||||
|  |       <Paper style={{ margin: "10px", padding: "10px" }}> | ||||||
|  |         <Typography variant="h5" style={{ marginBottom: "15px" }}> | ||||||
|  |           {p.title} | ||||||
|  |         </Typography> | ||||||
|  |         {p.children} | ||||||
|  |       </Paper> | ||||||
|  |     </Grid> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -22,7 +22,7 @@ export function VMSelectIsoInput(p: { | |||||||
|   if (!p.value && !p.editable) return <></>; |   if (!p.value && !p.editable) return <></>; | ||||||
|  |  | ||||||
|   if (p.value) { |   if (p.value) { | ||||||
|     const iso = p.isoList.find((d) => d.filename == p.value); |     const iso = p.isoList.find((d) => d.filename === p.value); | ||||||
|     return ( |     return ( | ||||||
|       <ListItem |       <ListItem | ||||||
|         secondaryAction={ |         secondaryAction={ | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								virtweb_frontend/src/widgets/net/NetworkDetails.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								virtweb_frontend/src/widgets/net/NetworkDetails.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | import { Grid } from "@mui/material"; | ||||||
|  | import { NetworkInfo } from "../../api/NetworksApi"; | ||||||
|  | import { ServerApi } from "../../api/ServerApi"; | ||||||
|  | import { EditSection } from "../forms/EditSection"; | ||||||
|  | import { TextInput } from "../forms/TextInput"; | ||||||
|  |  | ||||||
|  | export function NetworkDetails(p: { | ||||||
|  |   net: NetworkInfo; | ||||||
|  |   editable: boolean; | ||||||
|  |   onChange?: () => void; | ||||||
|  | }): React.ReactElement { | ||||||
|  |   return ( | ||||||
|  |     <Grid container spacing={2}> | ||||||
|  |       {/* Metadata section */} | ||||||
|  |       <EditSection title="Metadata"> | ||||||
|  |         <TextInput | ||||||
|  |           label="Name" | ||||||
|  |           editable={p.editable} | ||||||
|  |           value={p.net.name} | ||||||
|  |           onValueChange={(v) => { | ||||||
|  |             p.net.name = v ?? ""; | ||||||
|  |             p.onChange?.(); | ||||||
|  |           }} | ||||||
|  |           checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)} | ||||||
|  |           size={ServerApi.Config.constraints.net_name_size} | ||||||
|  |         /> | ||||||
|  |  | ||||||
|  |         <TextInput label="UUID" editable={false} value={p.net.uuid} /> | ||||||
|  |  | ||||||
|  |         <TextInput | ||||||
|  |           label="Title" | ||||||
|  |           editable={p.editable} | ||||||
|  |           value={p.net.title} | ||||||
|  |           onValueChange={(v) => { | ||||||
|  |             p.net.title = v; | ||||||
|  |             p.onChange?.(); | ||||||
|  |           }} | ||||||
|  |           size={ServerApi.Config.constraints.net_title_size} | ||||||
|  |         /> | ||||||
|  |  | ||||||
|  |         <TextInput | ||||||
|  |           label="Description" | ||||||
|  |           editable={p.editable} | ||||||
|  |           value={p.net.description} | ||||||
|  |           onValueChange={(v) => { | ||||||
|  |             p.net.description = v; | ||||||
|  |             p.onChange?.(); | ||||||
|  |           }} | ||||||
|  |           multiline={true} | ||||||
|  |         /> | ||||||
|  |       </EditSection> | ||||||
|  |       TODO:continue | ||||||
|  |     </Grid> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,19 +1,18 @@ | |||||||
| import { Grid, Paper, Typography } from "@mui/material"; | import { Grid } from "@mui/material"; | ||||||
| import { PropsWithChildren } from "react"; | import React from "react"; | ||||||
| import { validate as validateUUID } from "uuid"; | import { validate as validateUUID } from "uuid"; | ||||||
|  | import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; | ||||||
| import { ServerApi } from "../../api/ServerApi"; | import { ServerApi } from "../../api/ServerApi"; | ||||||
| import { VMInfo } from "../../api/VMApi"; | import { VMInfo } from "../../api/VMApi"; | ||||||
|  | import { AsyncWidget } from "../AsyncWidget"; | ||||||
| import { CheckboxInput } from "../forms/CheckboxInput"; | import { CheckboxInput } from "../forms/CheckboxInput"; | ||||||
|  | import { EditSection } from "../forms/EditSection"; | ||||||
| import { SelectInput } from "../forms/SelectInput"; | import { SelectInput } from "../forms/SelectInput"; | ||||||
| import { TextInput } from "../forms/TextInput"; | import { TextInput } from "../forms/TextInput"; | ||||||
| import { VMScreenshot } from "./VMScreenshot"; | import { VMAutostartInput } from "../forms/VMAutostartInput"; | ||||||
| import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; |  | ||||||
| import { AsyncWidget } from "../AsyncWidget"; |  | ||||||
| import React from "react"; |  | ||||||
| import { filesize } from "filesize"; |  | ||||||
| import { VMDisksList } from "../forms/VMDisksList"; | import { VMDisksList } from "../forms/VMDisksList"; | ||||||
| import { VMSelectIsoInput } from "../forms/VMSelectIsoInput"; | import { VMSelectIsoInput } from "../forms/VMSelectIsoInput"; | ||||||
| import { VMAutostartInput } from "../forms/VMAutostartInput"; | import { VMScreenshot } from "./VMScreenshot"; | ||||||
|  |  | ||||||
| interface DetailsProps { | interface DetailsProps { | ||||||
|   vm: VMInfo; |   vm: VMInfo; | ||||||
| @@ -34,7 +33,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement { | |||||||
|       loadKey={"1"} |       loadKey={"1"} | ||||||
|       load={load} |       load={load} | ||||||
|       errMsg="Failed to load the list of ISO files" |       errMsg="Failed to load the list of ISO files" | ||||||
|       build={() => <VMDetailsInner isoList={list!} {...p} />} |       build={() => <VMDetailsInner isoList={list} {...p} />} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| @@ -63,7 +62,7 @@ function VMDetailsInner( | |||||||
|             p.onChange?.(); |             p.onChange?.(); | ||||||
|           }} |           }} | ||||||
|           checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)} |           checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)} | ||||||
|           size={ServerApi.Config.constraints.name_size} |           size={ServerApi.Config.constraints.vm_name_size} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <TextInput label="UUID" editable={false} value={p.vm.uuid} /> |         <TextInput label="UUID" editable={false} value={p.vm.uuid} /> | ||||||
| @@ -87,7 +86,7 @@ function VMDetailsInner( | |||||||
|             p.vm.title = v; |             p.vm.title = v; | ||||||
|             p.onChange?.(); |             p.onChange?.(); | ||||||
|           }} |           }} | ||||||
|           size={ServerApi.Config.constraints.title_size} |           size={ServerApi.Config.constraints.vm_title_size} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <TextInput |         <TextInput | ||||||
| @@ -176,18 +175,3 @@ function VMDetailsInner( | |||||||
|     </Grid> |     </Grid> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| function EditSection( |  | ||||||
|   p: { title: string } & PropsWithChildren |  | ||||||
| ): React.ReactElement { |  | ||||||
|   return ( |  | ||||||
|     <Grid item sm={12} md={6}> |  | ||||||
|       <Paper style={{ margin: "10px", padding: "10px" }}> |  | ||||||
|         <Typography variant="h5" style={{ marginBottom: "15px" }}> |  | ||||||
|           {p.title} |  | ||||||
|         </Typography> |  | ||||||
|         {p.children} |  | ||||||
|       </Paper> |  | ||||||
|     </Grid> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user