Can edit more network settings
This commit is contained in:
		@@ -5,7 +5,7 @@ use crate::controllers::{HttpResult, LibVirtReq};
 | 
				
			|||||||
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
 | 
					use crate::extractors::local_auth_extractor::LocalAuthEnabled;
 | 
				
			||||||
use crate::libvirt_rest_structures::HypervisorInfo;
 | 
					use crate::libvirt_rest_structures::HypervisorInfo;
 | 
				
			||||||
use actix_web::{HttpResponse, Responder};
 | 
					use actix_web::{HttpResponse, Responder};
 | 
				
			||||||
use sysinfo::{System, SystemExt};
 | 
					use sysinfo::{NetworksExt, System, SystemExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn root_index() -> impl Responder {
 | 
					pub async fn root_index() -> impl Responder {
 | 
				
			||||||
    HttpResponse::Ok().body("Hello world!")
 | 
					    HttpResponse::Ok().body("Hello world!")
 | 
				
			||||||
@@ -86,3 +86,16 @@ pub async fn server_info(client: LibVirtReq) -> HttpResult {
 | 
				
			|||||||
        system,
 | 
					        system,
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn networks_list() -> HttpResult {
 | 
				
			||||||
 | 
					    let mut system = System::new();
 | 
				
			||||||
 | 
					    system.refresh_networks_list();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(
 | 
				
			||||||
 | 
					        system
 | 
				
			||||||
 | 
					            .networks()
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .map(|n| n.0.to_string())
 | 
				
			||||||
 | 
					            .collect::<Vec<_>>(),
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -229,10 +229,10 @@ pub struct NetworkForwardXML {
 | 
				
			|||||||
    pub mode: String,
 | 
					    pub mode: String,
 | 
				
			||||||
    #[serde(
 | 
					    #[serde(
 | 
				
			||||||
        default,
 | 
					        default,
 | 
				
			||||||
        rename(serialize = "@mode"),
 | 
					        rename(serialize = "@dev"),
 | 
				
			||||||
        skip_serializing_if = "Option::is_none"
 | 
					        skip_serializing_if = "String::is_empty"
 | 
				
			||||||
    )]
 | 
					    )]
 | 
				
			||||||
    pub dev: Option<String>,
 | 
					    pub dev: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Network DNS information
 | 
					/// Network DNS information
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -421,7 +421,7 @@ impl NetworkInfo {
 | 
				
			|||||||
            forward: match self.forward_mode {
 | 
					            forward: match self.forward_mode {
 | 
				
			||||||
                NetworkForwardMode::NAT => Some(NetworkForwardXML {
 | 
					                NetworkForwardMode::NAT => Some(NetworkForwardXML {
 | 
				
			||||||
                    mode: "nat".to_string(),
 | 
					                    mode: "nat".to_string(),
 | 
				
			||||||
                    dev: self.device,
 | 
					                    dev: self.device.unwrap_or_default(),
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                NetworkForwardMode::Isolated => None,
 | 
					                NetworkForwardMode::Isolated => None,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@@ -443,7 +443,13 @@ impl NetworkInfo {
 | 
				
			|||||||
                None => NetworkForwardMode::Isolated,
 | 
					                None => NetworkForwardMode::Isolated,
 | 
				
			||||||
                Some(_) => NetworkForwardMode::NAT,
 | 
					                Some(_) => NetworkForwardMode::NAT,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            device: xml.forward.map(|f| f.dev).unwrap_or(None),
 | 
					            device: xml
 | 
				
			||||||
 | 
					                .forward
 | 
				
			||||||
 | 
					                .map(|f| match f.dev.is_empty() {
 | 
				
			||||||
 | 
					                    true => None,
 | 
				
			||||||
 | 
					                    false => Some(f.dev),
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .unwrap_or(None),
 | 
				
			||||||
            dns_server: xml.dns.map(|d| d.forwarder.addr),
 | 
					            dns_server: xml.dns.map(|d| d.forwarder.addr),
 | 
				
			||||||
            domain: xml.domain.map(|d| d.name),
 | 
					            domain: xml.domain.map(|d| d.name),
 | 
				
			||||||
            ip_v4: xml
 | 
					            ip_v4: xml
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,6 +101,10 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
                "/api/server/info",
 | 
					                "/api/server/info",
 | 
				
			||||||
                web::get().to(server_controller::server_info),
 | 
					                web::get().to(server_controller::server_info),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/server/networks",
 | 
				
			||||||
 | 
					                web::get().to(server_controller::networks_list),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // Auth controller
 | 
					            // Auth controller
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/api/auth/local",
 | 
					                "/api/auth/local",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										68
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										68
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -31,6 +31,7 @@
 | 
				
			|||||||
        "mui-file-input": "^3.0.1",
 | 
					        "mui-file-input": "^3.0.1",
 | 
				
			||||||
        "react": "^18.2.0",
 | 
					        "react": "^18.2.0",
 | 
				
			||||||
        "react-dom": "^18.2.0",
 | 
					        "react-dom": "^18.2.0",
 | 
				
			||||||
 | 
					        "react-imask": "^7.1.3",
 | 
				
			||||||
        "react-router-dom": "^6.15.0",
 | 
					        "react-router-dom": "^6.15.0",
 | 
				
			||||||
        "react-scripts": "5.0.1",
 | 
					        "react-scripts": "5.0.1",
 | 
				
			||||||
        "react-vnc": "^1.0.0",
 | 
					        "react-vnc": "^1.0.0",
 | 
				
			||||||
@@ -1971,6 +1972,19 @@
 | 
				
			|||||||
        "node": ">=6.9.0"
 | 
					        "node": ">=6.9.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@babel/runtime-corejs3": {
 | 
				
			||||||
 | 
					      "version": "7.23.5",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.5.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-7+ziVclejQTLYhXl+Oi1f6gTGD1XDCeLa4R472TNGQxb08zbEJ0OdNoh5Piz+57Ltmui6xR88BXR4gS3/Toslw==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "core-js-pure": "^3.30.2",
 | 
				
			||||||
 | 
					        "regenerator-runtime": "^0.14.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.9.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@babel/template": {
 | 
					    "node_modules/@babel/template": {
 | 
				
			||||||
      "version": "7.22.5",
 | 
					      "version": "7.22.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
 | 
				
			||||||
@@ -9641,6 +9655,18 @@
 | 
				
			|||||||
        "node": ">= 4"
 | 
					        "node": ">= 4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/imask": {
 | 
				
			||||||
 | 
					      "version": "7.1.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/imask/-/imask-7.1.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jZCqTI5Jgukhl2ff+znBQd8BiHOTlnFYCIgggzHYDdoJsHmSSWr1BaejcYBxsjy4ZIs8Rm0HhbOxQcobcdESRQ==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@babel/runtime-corejs3": "^7.22.6"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "npm": ">=4.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/immer": {
 | 
					    "node_modules/immer": {
 | 
				
			||||||
      "version": "9.0.21",
 | 
					      "version": "9.0.21",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
				
			||||||
@@ -15154,6 +15180,22 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
 | 
				
			||||||
      "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
 | 
					      "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/react-imask": {
 | 
				
			||||||
 | 
					      "version": "7.1.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.1.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-anCnzdkqpDzNwe7ot76kQSvmnz4Sw7AW/QFjjLh3B87HVNv9e2oHC+1m9hQKSIui2Tqm7w68ooMgDFsCQlDMyg==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "imask": "^7.1.3",
 | 
				
			||||||
 | 
					        "prop-types": "^15.8.1"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "npm": ">=4.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "react": ">=0.14.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/react-is": {
 | 
					    "node_modules/react-is": {
 | 
				
			||||||
      "version": "17.0.2",
 | 
					      "version": "17.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
				
			||||||
@@ -19550,6 +19592,15 @@
 | 
				
			|||||||
        "regenerator-runtime": "^0.14.0"
 | 
					        "regenerator-runtime": "^0.14.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@babel/runtime-corejs3": {
 | 
				
			||||||
 | 
					      "version": "7.23.5",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.5.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-7+ziVclejQTLYhXl+Oi1f6gTGD1XDCeLa4R472TNGQxb08zbEJ0OdNoh5Piz+57Ltmui6xR88BXR4gS3/Toslw==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "core-js-pure": "^3.30.2",
 | 
				
			||||||
 | 
					        "regenerator-runtime": "^0.14.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@babel/template": {
 | 
					    "@babel/template": {
 | 
				
			||||||
      "version": "7.22.5",
 | 
					      "version": "7.22.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
 | 
				
			||||||
@@ -25051,6 +25102,14 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
 | 
					      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "imask": {
 | 
				
			||||||
 | 
					      "version": "7.1.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/imask/-/imask-7.1.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jZCqTI5Jgukhl2ff+znBQd8BiHOTlnFYCIgggzHYDdoJsHmSSWr1BaejcYBxsjy4ZIs8Rm0HhbOxQcobcdESRQ==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@babel/runtime-corejs3": "^7.22.6"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "immer": {
 | 
					    "immer": {
 | 
				
			||||||
      "version": "9.0.21",
 | 
					      "version": "9.0.21",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
				
			||||||
@@ -28839,6 +28898,15 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
 | 
				
			||||||
      "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
 | 
					      "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "react-imask": {
 | 
				
			||||||
 | 
					      "version": "7.1.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.1.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-anCnzdkqpDzNwe7ot76kQSvmnz4Sw7AW/QFjjLh3B87HVNv9e2oHC+1m9hQKSIui2Tqm7w68ooMgDFsCQlDMyg==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "imask": "^7.1.3",
 | 
				
			||||||
 | 
					        "prop-types": "^15.8.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "react-is": {
 | 
					    "react-is": {
 | 
				
			||||||
      "version": "17.0.2",
 | 
					      "version": "17.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@
 | 
				
			|||||||
    "mui-file-input": "^3.0.1",
 | 
					    "mui-file-input": "^3.0.1",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
 | 
					    "react-imask": "^7.1.3",
 | 
				
			||||||
    "react-router-dom": "^6.15.0",
 | 
					    "react-router-dom": "^6.15.0",
 | 
				
			||||||
    "react-scripts": "5.0.1",
 | 
					    "react-scripts": "5.0.1",
 | 
				
			||||||
    "react-vnc": "^1.0.0",
 | 
					    "react-vnc": "^1.0.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,8 @@ export interface NetworkInfo {
 | 
				
			|||||||
  ip_v6?: IpConfig;
 | 
					  ip_v6?: IpConfig;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type NetworkStatus = "Started" | "Stopped";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function NetworkURL(n: NetworkInfo, edit: boolean = false): string {
 | 
					export function NetworkURL(n: NetworkInfo, edit: boolean = false): string {
 | 
				
			||||||
  return `/net/${n.uuid}${edit ? "/edit" : ""}`;
 | 
					  return `/net/${n.uuid}${edit ? "/edit" : ""}`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -61,6 +63,38 @@ export class NetworkApi {
 | 
				
			|||||||
    ).data;
 | 
					    ).data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get the status of network
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async GetState(net: NetworkInfo): Promise<NetworkStatus> {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      await APIClient.exec({
 | 
				
			||||||
 | 
					        method: "GET",
 | 
				
			||||||
 | 
					        uri: `/network/${net.uuid}/status`,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).data.status;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start the network
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async Start(net: NetworkInfo): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      method: "GET",
 | 
				
			||||||
 | 
					      uri: `/network/${net.uuid}/start`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Stop the network
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async Stop(net: NetworkInfo): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      method: "GET",
 | 
				
			||||||
 | 
					      uri: `/network/${net.uuid}/stop`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Update an existing network
 | 
					   * Update an existing network
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -170,4 +170,16 @@ export class ServerApi {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
    ).data;
 | 
					    ).data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get host networks card list
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async GetNetworksList(): Promise<string[]> {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      await APIClient.exec({
 | 
				
			||||||
 | 
					        method: "GET",
 | 
				
			||||||
 | 
					        uri: "/server/networks",
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { useNavigate, useParams } from "react-router-dom";
 | 
					import { useNavigate, useParams } from "react-router-dom";
 | 
				
			||||||
import { NetworkApi, NetworkInfo } from "../api/NetworksApi";
 | 
					import { NetworkApi, NetworkInfo, NetworkURL } from "../api/NetworksApi";
 | 
				
			||||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
				
			||||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
@@ -56,7 +56,7 @@ export function EditNetworkRoute(): React.ReactElement {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await NetworkApi.Update(n);
 | 
					      await NetworkApi.Update(n);
 | 
				
			||||||
      snackbar("The network was successfully updated!");
 | 
					      snackbar("The network was successfully updated!");
 | 
				
			||||||
      navigate(`/net/${network!.uuid}`);
 | 
					      navigate(NetworkURL(network!));
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      console.error(e);
 | 
					      console.error(e);
 | 
				
			||||||
      alert("Failed to update network!");
 | 
					      alert("Failed to update network!");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
				
			|||||||
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
				
			||||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					import { NetworkStatusWidget } from "../widgets/net/NetworkStatusWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function NetworksListRoute(): React.ReactElement {
 | 
					export function NetworksListRoute(): React.ReactElement {
 | 
				
			||||||
  const confirm = useConfirm();
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
@@ -93,6 +94,7 @@ function NetworksListRouteInner(p: {
 | 
				
			|||||||
              <TableCell>Description</TableCell>
 | 
					              <TableCell>Description</TableCell>
 | 
				
			||||||
              <TableCell>Network type</TableCell>
 | 
					              <TableCell>Network type</TableCell>
 | 
				
			||||||
              <TableCell>IP</TableCell>
 | 
					              <TableCell>IP</TableCell>
 | 
				
			||||||
 | 
					              <TableCell>State</TableCell>
 | 
				
			||||||
              <TableCell>Actions</TableCell>
 | 
					              <TableCell>Actions</TableCell>
 | 
				
			||||||
            </TableRow>
 | 
					            </TableRow>
 | 
				
			||||||
          </TableHead>
 | 
					          </TableHead>
 | 
				
			||||||
@@ -112,6 +114,9 @@ function NetworksListRouteInner(p: {
 | 
				
			|||||||
                  <TableCell>
 | 
					                  <TableCell>
 | 
				
			||||||
                    {t.ip_v4 && "IPv4"} {t.ip_v6 && "IPv6"}
 | 
					                    {t.ip_v4 && "IPv4"} {t.ip_v6 && "IPv6"}
 | 
				
			||||||
                  </TableCell>
 | 
					                  </TableCell>
 | 
				
			||||||
 | 
					                  <TableCell>
 | 
				
			||||||
 | 
					                    <NetworkStatusWidget net={t} />
 | 
				
			||||||
 | 
					                  </TableCell>
 | 
				
			||||||
                  <TableCell>
 | 
					                  <TableCell>
 | 
				
			||||||
                    <RouterLink to={NetworkURL(t)}>
 | 
					                    <RouterLink to={NetworkURL(t)}>
 | 
				
			||||||
                      <IconButton>
 | 
					                      <IconButton>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,16 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { NetworkApi, NetworkInfo } from "../api/NetworksApi";
 | 
					import {
 | 
				
			||||||
 | 
					  NetworkApi,
 | 
				
			||||||
 | 
					  NetworkInfo,
 | 
				
			||||||
 | 
					  NetworkStatus,
 | 
				
			||||||
 | 
					  NetworkURL,
 | 
				
			||||||
 | 
					} from "../api/NetworksApi";
 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { useNavigate, useParams } from "react-router-dom";
 | 
					import { useNavigate, useParams } from "react-router-dom";
 | 
				
			||||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
					import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
				
			||||||
import { Button } from "@mui/material";
 | 
					import { Button } from "@mui/material";
 | 
				
			||||||
import { NetworkDetails } from "../widgets/net/NetworkDetails";
 | 
					import { NetworkDetails } from "../widgets/net/NetworkDetails";
 | 
				
			||||||
 | 
					import { NetworkStatusWidget } from "../widgets/net/NetworkStatusWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ViewNetworkRoute() {
 | 
					export function ViewNetworkRoute() {
 | 
				
			||||||
  const { uuid } = useParams();
 | 
					  const { uuid } = useParams();
 | 
				
			||||||
@@ -31,18 +37,25 @@ function ViewNetworkRouteInner(p: {
 | 
				
			|||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [netStatus, setNetStatus] = React.useState<NetworkStatus | undefined>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <VirtWebRouteContainer
 | 
					    <VirtWebRouteContainer
 | 
				
			||||||
      label={`Network ${p.network.name}`}
 | 
					      label={`Network ${p.network.name}`}
 | 
				
			||||||
      actions={
 | 
					      actions={
 | 
				
			||||||
        /* TODO: show only if network is stopped */
 | 
					        <span>
 | 
				
			||||||
        <Button
 | 
					          <NetworkStatusWidget net={p.network} onChange={setNetStatus} />
 | 
				
			||||||
          variant="contained"
 | 
					
 | 
				
			||||||
          style={{ marginLeft: "15px" }}
 | 
					          {netStatus === "Stopped" && (
 | 
				
			||||||
          onClick={() => navigate(`/net/${p.network.uuid}/edit`)}
 | 
					            <Button
 | 
				
			||||||
        >
 | 
					              variant="contained"
 | 
				
			||||||
          Edit
 | 
					              style={{ marginLeft: "15px" }}
 | 
				
			||||||
        </Button>
 | 
					              onClick={() => navigate(NetworkURL(p.network, true))}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              Edit
 | 
				
			||||||
 | 
					            </Button>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <NetworkDetails net={p.network} editable={false} />
 | 
					      <NetworkDetails net={p.network} editable={false} />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								virtweb_frontend/src/widgets/StateActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								virtweb_frontend/src/widgets/StateActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					import { IconButton, Tooltip } from "@mui/material";
 | 
				
			||||||
 | 
					import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function StateActionButton<S>(p: {
 | 
				
			||||||
 | 
					  currState: S;
 | 
				
			||||||
 | 
					  cond: S[];
 | 
				
			||||||
 | 
					  icon: React.ReactElement;
 | 
				
			||||||
 | 
					  tooltip: string;
 | 
				
			||||||
 | 
					  confirmMessage?: string;
 | 
				
			||||||
 | 
					  performAction: () => Promise<void>;
 | 
				
			||||||
 | 
					  onExecuted: () => void;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
 | 
					  const alert = useAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!p.cond.includes(p.currState)) return <></>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const performAction = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (p.confirmMessage && !(await confirm(p.confirmMessage))) return;
 | 
				
			||||||
 | 
					      await p.performAction();
 | 
				
			||||||
 | 
					      p.onExecuted();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error(e);
 | 
				
			||||||
 | 
					      alert("Failed to perform action! " + e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Tooltip title={p.tooltip}>
 | 
				
			||||||
 | 
					      <IconButton
 | 
				
			||||||
 | 
					        size="small"
 | 
				
			||||||
 | 
					        onClick={performAction}
 | 
				
			||||||
 | 
					        style={{ paddingBottom: "0px", paddingTop: "0px" }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {p.icon}
 | 
				
			||||||
 | 
					      </IconButton>
 | 
				
			||||||
 | 
					    </Tooltip>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -28,7 +28,11 @@ export function SelectInput(p: {
 | 
				
			|||||||
        onChange={(e) => p.onValueChange(e.target.value)}
 | 
					        onChange={(e) => p.onValueChange(e.target.value)}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {p.options.map((e) => (
 | 
					        {p.options.map((e) => (
 | 
				
			||||||
          <MenuItem key={e.value} value={e.value}>
 | 
					          <MenuItem
 | 
				
			||||||
 | 
					            key={e.value}
 | 
				
			||||||
 | 
					            value={e.value}
 | 
				
			||||||
 | 
					            style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
            {e.label}
 | 
					            {e.label}
 | 
				
			||||||
          </MenuItem>
 | 
					          </MenuItem>
 | 
				
			||||||
        ))}
 | 
					        ))}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ export function TextInput(p: {
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <TextField
 | 
					    <TextField
 | 
				
			||||||
      label={p.label}
 | 
					      label={p.label}
 | 
				
			||||||
      value={p.value}
 | 
					      value={p.value ?? ""}
 | 
				
			||||||
      onChange={(e) =>
 | 
					      onChange={(e) =>
 | 
				
			||||||
        p.onValueChange?.(
 | 
					        p.onValueChange?.(
 | 
				
			||||||
          e.target.value.length === 0 ? undefined : e.target.value
 | 
					          e.target.value.length === 0 ? undefined : e.target.value
 | 
				
			||||||
@@ -47,7 +47,7 @@ export function TextInput(p: {
 | 
				
			|||||||
        readOnly: !p.editable,
 | 
					        readOnly: !p.editable,
 | 
				
			||||||
        type: p.type,
 | 
					        type: p.type,
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      variant={p.editable ? "standard" : "standard"}
 | 
					      variant={"standard"}
 | 
				
			||||||
      style={{ width: "100%", marginBottom: "15px" }}
 | 
					      style={{ width: "100%", marginBottom: "15px" }}
 | 
				
			||||||
      multiline={p.multiline}
 | 
					      multiline={p.multiline}
 | 
				
			||||||
      minRows={p.minRows}
 | 
					      minRows={p.minRows}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,19 +3,44 @@ import { NetworkInfo } from "../../api/NetworksApi";
 | 
				
			|||||||
import { ServerApi } from "../../api/ServerApi";
 | 
					import { ServerApi } from "../../api/ServerApi";
 | 
				
			||||||
import { EditSection } from "../forms/EditSection";
 | 
					import { EditSection } from "../forms/EditSection";
 | 
				
			||||||
import { TextInput } from "../forms/TextInput";
 | 
					import { TextInput } from "../forms/TextInput";
 | 
				
			||||||
 | 
					import { SelectInput } from "../forms/SelectInput";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { AsyncWidget } from "../AsyncWidget";
 | 
				
			||||||
 | 
					import { IPv4Input } from "../forms/IPv4Input";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function NetworkDetails(p: {
 | 
					interface DetailsProps {
 | 
				
			||||||
  net: NetworkInfo;
 | 
					  net: NetworkInfo;
 | 
				
			||||||
  editable: boolean;
 | 
					  editable: boolean;
 | 
				
			||||||
  onChange?: () => void;
 | 
					  onChange?: () => void;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function NetworkDetails(p: DetailsProps): React.ReactElement {
 | 
				
			||||||
 | 
					  const [cardsList, setCardsList] = React.useState<string[] | any>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const load = async () => {
 | 
				
			||||||
 | 
					    setCardsList(await ServerApi.GetNetworksList());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <AsyncWidget
 | 
				
			||||||
 | 
					      loadKey={"1"}
 | 
				
			||||||
 | 
					      load={load}
 | 
				
			||||||
 | 
					      errMsg="Failed to load the list of host network cards!"
 | 
				
			||||||
 | 
					      build={() => <NetworkDetailsInner cardsList={cardsList} {...p} />}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function NetworkDetailsInner(
 | 
				
			||||||
 | 
					  p: DetailsProps & { cardsList: string[] }
 | 
				
			||||||
 | 
					): React.ReactElement {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Grid container spacing={2}>
 | 
					    <Grid container spacing={2}>
 | 
				
			||||||
      {/* Metadata section */}
 | 
					      {/* Metadata section */}
 | 
				
			||||||
      <EditSection title="Metadata">
 | 
					      <EditSection title="Metadata">
 | 
				
			||||||
        <TextInput
 | 
					        <TextInput
 | 
				
			||||||
          label="Name"
 | 
					          label="Name"
 | 
				
			||||||
          editable={p.editable}
 | 
					          editable={p.editable && !p.net.uuid}
 | 
				
			||||||
          value={p.net.name}
 | 
					          value={p.net.name}
 | 
				
			||||||
          onValueChange={(v) => {
 | 
					          onValueChange={(v) => {
 | 
				
			||||||
            p.net.name = v ?? "";
 | 
					            p.net.name = v ?? "";
 | 
				
			||||||
@@ -49,7 +74,58 @@ export function NetworkDetails(p: {
 | 
				
			|||||||
          multiline={true}
 | 
					          multiline={true}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </EditSection>
 | 
					      </EditSection>
 | 
				
			||||||
      TODO:continue
 | 
					      {/* TODO :  autostart */}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <EditSection title="General settings">
 | 
				
			||||||
 | 
					        <SelectInput
 | 
				
			||||||
 | 
					          editable={p.editable}
 | 
				
			||||||
 | 
					          label="Forward mode"
 | 
				
			||||||
 | 
					          onValueChange={(v) => {
 | 
				
			||||||
 | 
					            p.net.forward_mode = v as any;
 | 
				
			||||||
 | 
					            p.onChange?.();
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          value={p.net.forward_mode}
 | 
				
			||||||
 | 
					          options={[
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              label: "NAT",
 | 
				
			||||||
 | 
					              value: "NAT",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              label: "Isolated network",
 | 
				
			||||||
 | 
					              value: "Isolated",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ]}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {p.net.forward_mode === "NAT" && (
 | 
				
			||||||
 | 
					          <SelectInput
 | 
				
			||||||
 | 
					            editable={p.editable}
 | 
				
			||||||
 | 
					            label="Network output device"
 | 
				
			||||||
 | 
					            onValueChange={(v) => {
 | 
				
			||||||
 | 
					              p.net.device = v;
 | 
				
			||||||
 | 
					              p.onChange?.();
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					            value={p.net.device}
 | 
				
			||||||
 | 
					            options={[
 | 
				
			||||||
 | 
					              { label: "Default interface", value: undefined },
 | 
				
			||||||
 | 
					              ...p.cardsList.map((d) => {
 | 
				
			||||||
 | 
					                return { label: d, value: d };
 | 
				
			||||||
 | 
					              }),
 | 
				
			||||||
 | 
					            ]}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <IPv4Input
 | 
				
			||||||
 | 
					          editable={p.editable}
 | 
				
			||||||
 | 
					          label="DNS server to use"
 | 
				
			||||||
 | 
					          value={p.net.dns_server}
 | 
				
			||||||
 | 
					          onValueChange={(v) => {
 | 
				
			||||||
 | 
					            p.net.dns_server = v;
 | 
				
			||||||
 | 
					            p.onChange?.();
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </EditSection>
 | 
				
			||||||
    </Grid>
 | 
					    </Grid>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								virtweb_frontend/src/widgets/net/NetworkStatusWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								virtweb_frontend/src/widgets/net/NetworkStatusWidget.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					import PlayArrowIcon from "@mui/icons-material/PlayArrow";
 | 
				
			||||||
 | 
					import StopIcon from "@mui/icons-material/Stop";
 | 
				
			||||||
 | 
					import { CircularProgress, Typography } from "@mui/material";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  NetworkApi,
 | 
				
			||||||
 | 
					  NetworkInfo,
 | 
				
			||||||
 | 
					  NetworkStatus as NetworkState,
 | 
				
			||||||
 | 
					} from "../../api/NetworksApi";
 | 
				
			||||||
 | 
					import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { StateActionButton } from "../StateActionButton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function NetworkStatusWidget(p: {
 | 
				
			||||||
 | 
					  net: NetworkInfo;
 | 
				
			||||||
 | 
					  onChange?: (s: NetworkState) => void;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const snackbar = useSnackbar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [state, setState] = React.useState<undefined | NetworkState>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const refresh = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const s = await NetworkApi.GetState(p.net);
 | 
				
			||||||
 | 
					      if (s !== state) p.onChange?.(s);
 | 
				
			||||||
 | 
					      setState(s);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error(e);
 | 
				
			||||||
 | 
					      snackbar("Failed to refresh network status!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const changedAction = () => setState(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    refresh();
 | 
				
			||||||
 | 
					    const i = setInterval(() => refresh(), 3000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => clearInterval(i);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (state === undefined)
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        <CircularProgress size={"1rem"} />
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div style={{ display: "inline-flex" }}>
 | 
				
			||||||
 | 
					      <Typography>{state}</Typography>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Start Network */}
 | 
				
			||||||
 | 
					      <StateActionButton
 | 
				
			||||||
 | 
					        currState={state}
 | 
				
			||||||
 | 
					        cond={["Stopped"]}
 | 
				
			||||||
 | 
					        icon={<PlayArrowIcon />}
 | 
				
			||||||
 | 
					        tooltip="Start the Network"
 | 
				
			||||||
 | 
					        performAction={() => NetworkApi.Start(p.net)}
 | 
				
			||||||
 | 
					        onExecuted={changedAction}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Stop network */}
 | 
				
			||||||
 | 
					      <StateActionButton
 | 
				
			||||||
 | 
					        currState={state}
 | 
				
			||||||
 | 
					        cond={["Started"]}
 | 
				
			||||||
 | 
					        icon={<StopIcon />}
 | 
				
			||||||
 | 
					        tooltip="Stop the network"
 | 
				
			||||||
 | 
					        confirmMessage="Do you really want to kill stop this network?"
 | 
				
			||||||
 | 
					        performAction={() => NetworkApi.Stop(p.net)}
 | 
				
			||||||
 | 
					        onExecuted={changedAction}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,18 +4,12 @@ import PlayArrowIcon from "@mui/icons-material/PlayArrow";
 | 
				
			|||||||
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
 | 
					import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
 | 
				
			||||||
import ReplayIcon from "@mui/icons-material/Replay";
 | 
					import ReplayIcon from "@mui/icons-material/Replay";
 | 
				
			||||||
import StopIcon from "@mui/icons-material/Stop";
 | 
					import StopIcon from "@mui/icons-material/Stop";
 | 
				
			||||||
import {
 | 
					import { CircularProgress, Typography } from "@mui/material";
 | 
				
			||||||
  CircularProgress,
 | 
					 | 
				
			||||||
  IconButton,
 | 
					 | 
				
			||||||
  Tooltip,
 | 
					 | 
				
			||||||
  Typography,
 | 
					 | 
				
			||||||
} from "@mui/material";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
import { VMApi, VMInfo, VMState } from "../../api/VMApi";
 | 
					import { VMApi, VMInfo, VMState } from "../../api/VMApi";
 | 
				
			||||||
import { useAlert } from "../../hooks/providers/AlertDialogProvider";
 | 
					 | 
				
			||||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
					 | 
				
			||||||
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { StateActionButton } from "../StateActionButton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function VMStatusWidget(p: {
 | 
					export function VMStatusWidget(p: {
 | 
				
			||||||
  vm: VMInfo;
 | 
					  vm: VMInfo;
 | 
				
			||||||
@@ -59,7 +53,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        /* VNC console */ p.vm.vnc_access && (
 | 
					        /* VNC console */ p.vm.vnc_access && (
 | 
				
			||||||
          <ActionButton
 | 
					          <StateActionButton
 | 
				
			||||||
            currState={state}
 | 
					            currState={state}
 | 
				
			||||||
            cond={["Running"]}
 | 
					            cond={["Running"]}
 | 
				
			||||||
            icon={<PersonalVideoIcon />}
 | 
					            icon={<PersonalVideoIcon />}
 | 
				
			||||||
@@ -71,7 +65,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Start VM */}
 | 
					      {/* Start VM */}
 | 
				
			||||||
      <ActionButton
 | 
					      <StateActionButton
 | 
				
			||||||
        currState={state}
 | 
					        currState={state}
 | 
				
			||||||
        cond={["Shutdown", "Shutoff", "Crashed"]}
 | 
					        cond={["Shutdown", "Shutoff", "Crashed"]}
 | 
				
			||||||
        icon={<PlayArrowIcon />}
 | 
					        icon={<PlayArrowIcon />}
 | 
				
			||||||
@@ -81,7 +75,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Resume VM */}
 | 
					      {/* Resume VM */}
 | 
				
			||||||
      <ActionButton
 | 
					      <StateActionButton
 | 
				
			||||||
        currState={state}
 | 
					        currState={state}
 | 
				
			||||||
        cond={["Paused", "PowerManagementSuspended"]}
 | 
					        cond={["Paused", "PowerManagementSuspended"]}
 | 
				
			||||||
        icon={<PlayArrowIcon />}
 | 
					        icon={<PlayArrowIcon />}
 | 
				
			||||||
@@ -91,7 +85,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Suspend VM */}
 | 
					      {/* Suspend VM */}
 | 
				
			||||||
      <ActionButton
 | 
					      <StateActionButton
 | 
				
			||||||
        currState={state}
 | 
					        currState={state}
 | 
				
			||||||
        cond={["Running"]}
 | 
					        cond={["Running"]}
 | 
				
			||||||
        icon={<PauseIcon />}
 | 
					        icon={<PauseIcon />}
 | 
				
			||||||
@@ -102,7 +96,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Shutdown VM */}
 | 
					      {/* Shutdown VM */}
 | 
				
			||||||
      <ActionButton
 | 
					      <StateActionButton
 | 
				
			||||||
        currState={state}
 | 
					        currState={state}
 | 
				
			||||||
        cond={["Running"]}
 | 
					        cond={["Running"]}
 | 
				
			||||||
        icon={<PowerSettingsNewIcon />}
 | 
					        icon={<PowerSettingsNewIcon />}
 | 
				
			||||||
@@ -113,7 +107,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Kill VM */}
 | 
					      {/* Kill VM */}
 | 
				
			||||||
      <ActionButton
 | 
					      <StateActionButton
 | 
				
			||||||
        currState={state}
 | 
					        currState={state}
 | 
				
			||||||
        cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
 | 
					        cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
 | 
				
			||||||
        icon={<StopIcon />}
 | 
					        icon={<StopIcon />}
 | 
				
			||||||
@@ -124,7 +118,7 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Reset VM */}
 | 
					      {/* Reset VM */}
 | 
				
			||||||
      <ActionButton
 | 
					      <StateActionButton
 | 
				
			||||||
        currState={state}
 | 
					        currState={state}
 | 
				
			||||||
        cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
 | 
					        cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
 | 
				
			||||||
        icon={<ReplayIcon />}
 | 
					        icon={<ReplayIcon />}
 | 
				
			||||||
@@ -136,41 +130,3 @@ export function VMStatusWidget(p: {
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
function ActionButton(p: {
 | 
					 | 
				
			||||||
  currState: VMState;
 | 
					 | 
				
			||||||
  cond: VMState[];
 | 
					 | 
				
			||||||
  icon: React.ReactElement;
 | 
					 | 
				
			||||||
  tooltip: string;
 | 
					 | 
				
			||||||
  confirmMessage?: string;
 | 
					 | 
				
			||||||
  performAction: () => Promise<void>;
 | 
					 | 
				
			||||||
  onExecuted: () => void;
 | 
					 | 
				
			||||||
}): React.ReactElement {
 | 
					 | 
				
			||||||
  const confirm = useConfirm();
 | 
					 | 
				
			||||||
  const alert = useAlert();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!p.cond.includes(p.currState)) return <></>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const performAction = async () => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      if (p.confirmMessage && !(await confirm(p.confirmMessage))) return;
 | 
					 | 
				
			||||||
      await p.performAction();
 | 
					 | 
				
			||||||
      p.onExecuted();
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      console.error(e);
 | 
					 | 
				
			||||||
      alert("Failed to perform action! " + e);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Tooltip title={p.tooltip}>
 | 
					 | 
				
			||||||
      <IconButton
 | 
					 | 
				
			||||||
        size="small"
 | 
					 | 
				
			||||||
        onClick={performAction}
 | 
					 | 
				
			||||||
        style={{ paddingBottom: "0px", paddingTop: "0px" }}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        {p.icon}
 | 
					 | 
				
			||||||
      </IconButton>
 | 
					 | 
				
			||||||
    </Tooltip>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user