Compare commits
	
		
			3 Commits
		
	
	
		
			a61a047db2
			...
			20250618
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b633694f74 | |||
| ab16bd7bcf | |||
| 1080ab5cb2 | 
| @@ -46,8 +46,9 @@ steps: | ||||
|   - cd virtweb_backend | ||||
|   - mv /tmp/web_build/dist static | ||||
|   - cargo build --release | ||||
|   - ls -lah target/release/virtweb_backend | ||||
|   - cp target/release/virtweb_backend /tmp/release | ||||
|   - cargo build --release --example api_curl | ||||
|   - ls -lah target/release/virtweb_backend target/release/examples/api_curl | ||||
|   - cp target/release/virtweb_backend target/release/examples/api_curl /tmp/release | ||||
|  | ||||
| - name: gitea_release | ||||
|   image: plugins/gitea-release | ||||
|   | ||||
							
								
								
									
										200
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										200
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -435,6 +435,21 @@ dependencies = [ | ||||
|  "alloc-no-stdlib", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "android-tzdata" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" | ||||
|  | ||||
| [[package]] | ||||
| name = "android_system_properties" | ||||
| version = "0.1.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "anstream" | ||||
| version = "0.6.18" | ||||
| @@ -496,6 +511,9 @@ name = "arbitrary" | ||||
| version = "1.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" | ||||
| dependencies = [ | ||||
|  "derive_arbitrary", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "arg_enum_proc_macro" | ||||
| @@ -715,6 +733,25 @@ dependencies = [ | ||||
|  "bytes", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "bzip2" | ||||
| version = "0.5.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" | ||||
| dependencies = [ | ||||
|  "bzip2-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "bzip2-sys" | ||||
| version = "0.1.13+1.0.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
|  "pkg-config", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "cc" | ||||
| version = "1.2.23" | ||||
| @@ -748,6 +785,20 @@ version = "0.2.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" | ||||
|  | ||||
| [[package]] | ||||
| name = "chrono" | ||||
| version = "0.4.41" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" | ||||
| dependencies = [ | ||||
|  "android-tzdata", | ||||
|  "iana-time-zone", | ||||
|  "js-sys", | ||||
|  "num-traits", | ||||
|  "wasm-bindgen", | ||||
|  "windows-link", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "cipher" | ||||
| version = "0.4.4" | ||||
| @@ -816,6 +867,12 @@ version = "0.9.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" | ||||
|  | ||||
| [[package]] | ||||
| name = "constant_time_eq" | ||||
| version = "0.3.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" | ||||
|  | ||||
| [[package]] | ||||
| name = "convert_case" | ||||
| version = "0.4.0" | ||||
| @@ -981,6 +1038,12 @@ dependencies = [ | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "deflate64" | ||||
| version = "0.1.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" | ||||
|  | ||||
| [[package]] | ||||
| name = "der" | ||||
| version = "0.7.10" | ||||
| @@ -1001,6 +1064,17 @@ dependencies = [ | ||||
|  "powerfmt", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "derive_arbitrary" | ||||
| version = "1.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "derive_more" | ||||
| version = "0.99.20" | ||||
| @@ -1221,6 +1295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" | ||||
| dependencies = [ | ||||
|  "crc32fast", | ||||
|  "libz-rs-sys", | ||||
|  "miniz_oxide", | ||||
| ] | ||||
|  | ||||
| @@ -1380,9 +1455,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "js-sys", | ||||
|  "libc", | ||||
|  "r-efi", | ||||
|  "wasi 0.14.2+wasi-0.2.4", | ||||
|  "wasm-bindgen", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1641,6 +1718,30 @@ dependencies = [ | ||||
|  "windows-registry", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "iana-time-zone" | ||||
| version = "0.1.63" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" | ||||
| dependencies = [ | ||||
|  "android_system_properties", | ||||
|  "core-foundation-sys", | ||||
|  "iana-time-zone-haiku", | ||||
|  "js-sys", | ||||
|  "log", | ||||
|  "wasm-bindgen", | ||||
|  "windows-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "iana-time-zone-haiku" | ||||
| version = "0.1.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "icu_collections" | ||||
| version = "2.0.0" | ||||
| @@ -1997,6 +2098,26 @@ dependencies = [ | ||||
|  "cc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "liblzma" | ||||
| version = "0.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "66352d7a8ac12d4877b6e6ea5a9b7650ee094257dc40889955bea5bc5b08c1d0" | ||||
| dependencies = [ | ||||
|  "liblzma-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "liblzma-sys" | ||||
| version = "0.4.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
|  "libc", | ||||
|  "pkg-config", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "libyml" | ||||
| version = "0.0.5" | ||||
| @@ -2007,6 +2128,15 @@ dependencies = [ | ||||
|  "version_check", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "libz-rs-sys" | ||||
| version = "0.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" | ||||
| dependencies = [ | ||||
|  "zlib-rs", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "light-openid" | ||||
| version = "1.0.4" | ||||
| @@ -2429,6 +2559,16 @@ version = "1.0.15" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" | ||||
|  | ||||
| [[package]] | ||||
| name = "pbkdf2" | ||||
| version = "0.12.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" | ||||
| dependencies = [ | ||||
|  "digest", | ||||
|  "hmac", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pem" | ||||
| version = "3.0.5" | ||||
| @@ -3781,6 +3921,7 @@ dependencies = [ | ||||
|  "actix-ws", | ||||
|  "anyhow", | ||||
|  "basic-jwt", | ||||
|  "chrono", | ||||
|  "clap", | ||||
|  "dotenvy", | ||||
|  "env_logger", | ||||
| @@ -3808,6 +3949,7 @@ dependencies = [ | ||||
|  "url", | ||||
|  "uuid", | ||||
|  "virt", | ||||
|  "zip", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -4346,6 +4488,20 @@ name = "zeroize" | ||||
| version = "1.8.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" | ||||
| dependencies = [ | ||||
|  "zeroize_derive", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "zeroize_derive" | ||||
| version = "1.4.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "zerotrie" | ||||
| @@ -4380,6 +4536,50 @@ dependencies = [ | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "zip" | ||||
| version = "4.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "af7dcdb4229c0e79c2531a24de7726a0e980417a74fb4d030a35f535665439a0" | ||||
| dependencies = [ | ||||
|  "aes", | ||||
|  "arbitrary", | ||||
|  "bzip2", | ||||
|  "constant_time_eq", | ||||
|  "crc32fast", | ||||
|  "deflate64", | ||||
|  "flate2", | ||||
|  "getrandom 0.3.3", | ||||
|  "hmac", | ||||
|  "indexmap", | ||||
|  "liblzma", | ||||
|  "memchr", | ||||
|  "pbkdf2", | ||||
|  "sha1", | ||||
|  "time", | ||||
|  "zeroize", | ||||
|  "zopfli", | ||||
|  "zstd", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "zlib-rs" | ||||
| version = "0.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" | ||||
|  | ||||
| [[package]] | ||||
| name = "zopfli" | ||||
| version = "0.8.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" | ||||
| dependencies = [ | ||||
|  "bumpalo", | ||||
|  "crc32fast", | ||||
|  "log", | ||||
|  "simd-adler32", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "zstd" | ||||
| version = "0.13.3" | ||||
|   | ||||
| @@ -45,3 +45,5 @@ rust-embed = { version = "8.7.2", features = ["mime-guess"] } | ||||
| dotenvy = "0.15.7" | ||||
| nix = { version = "0.30.1", features = ["net"] } | ||||
| basic-jwt = "0.3.0" | ||||
| zip = "4.1.0" | ||||
| chrono = "0.4.41" | ||||
| @@ -4,6 +4,7 @@ use actix_web::body::BoxBody; | ||||
| use actix_web::{HttpResponse, web}; | ||||
| use std::error::Error; | ||||
| use std::fmt::{Display, Formatter}; | ||||
| use zip::result::ZipError; | ||||
|  | ||||
| pub mod api_tokens_controller; | ||||
| pub mod auth_controller; | ||||
| @@ -102,6 +103,12 @@ impl From<actix_web::Error> for HttpErr { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<ZipError> for HttpErr { | ||||
|     fn from(value: ZipError) -> Self { | ||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<HttpResponse> for HttpErr { | ||||
|     fn from(value: HttpResponse) -> Self { | ||||
|         HttpErr::HTTPResponse(value) | ||||
|   | ||||
| @@ -1,14 +1,24 @@ | ||||
| use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME; | ||||
| use crate::app_config::AppConfig; | ||||
| use crate::constants; | ||||
| use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN}; | ||||
| use crate::controllers::{HttpResult, LibVirtReq}; | ||||
| use crate::extractors::local_auth_extractor::LocalAuthEnabled; | ||||
| use crate::libvirt_rest_structures::hypervisor::HypervisorInfo; | ||||
| use crate::libvirt_rest_structures::net::NetworkInfo; | ||||
| use crate::libvirt_rest_structures::nw_filter::NetworkFilter; | ||||
| use crate::libvirt_rest_structures::vm::VMInfo; | ||||
| use crate::nat::nat_hook; | ||||
| use crate::utils::net_utils; | ||||
| use actix_web::{HttpResponse, Responder}; | ||||
| use crate::utils::time_utils::{format_date, time}; | ||||
| use crate::{api_tokens, constants}; | ||||
| use actix_files::NamedFile; | ||||
| use actix_web::{HttpRequest, HttpResponse, Responder}; | ||||
| use serde::Serialize; | ||||
| use std::fs::File; | ||||
| use std::io::Write; | ||||
| use sysinfo::{Components, Disks, Networks, System}; | ||||
| use zip::ZipWriter; | ||||
| use zip::write::SimpleFileOptions; | ||||
|  | ||||
| #[derive(serde::Serialize)] | ||||
| struct StaticConfig { | ||||
| @@ -199,3 +209,85 @@ pub async fn networks_list() -> HttpResult { | ||||
| pub async fn bridges_list() -> HttpResult { | ||||
|     Ok(HttpResponse::Ok().json(net_utils::bridges_list()?)) | ||||
| } | ||||
|  | ||||
| /// Add JSON file to ZIP | ||||
| fn zip_json<E: Serialize, F>( | ||||
|     zip: &mut ZipWriter<File>, | ||||
|     dir: &str, | ||||
|     content: &Vec<E>, | ||||
|     file_name: F, | ||||
| ) -> anyhow::Result<()> | ||||
| where | ||||
|     F: Fn(&E) -> String, | ||||
| { | ||||
|     for entry in content { | ||||
|         let file_encoded = serde_json::to_string(&entry)?; | ||||
|  | ||||
|         let options = SimpleFileOptions::default() | ||||
|             .compression_method(zip::CompressionMethod::Deflated) | ||||
|             .unix_permissions(0o750); | ||||
|  | ||||
|         zip.start_file(format!("{dir}/{}.json", file_name(entry)), options)?; | ||||
|         zip.write_all(file_encoded.as_bytes())?; | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Export all configuration elements at once | ||||
| pub async fn export_all_configs(req: HttpRequest, client: LibVirtReq) -> HttpResult { | ||||
|     // Perform extractions | ||||
|     let vms = client | ||||
|         .get_full_domains_list() | ||||
|         .await? | ||||
|         .into_iter() | ||||
|         .map(VMInfo::from_domain) | ||||
|         .collect::<Result<Vec<_>, _>>()?; | ||||
|     let networks = client | ||||
|         .get_full_networks_list() | ||||
|         .await? | ||||
|         .into_iter() | ||||
|         .map(NetworkInfo::from_xml) | ||||
|         .collect::<Result<Vec<_>, _>>()?; | ||||
|     let nw_filters = client | ||||
|         .get_full_network_filters_list() | ||||
|         .await? | ||||
|         .into_iter() | ||||
|         .map(NetworkFilter::lib2rest) | ||||
|         .collect::<Result<Vec<_>, _>>()?; | ||||
|     let tokens = api_tokens::full_list().await?; | ||||
|  | ||||
|     // Create ZIP file | ||||
|     let dest_dir = tempfile::tempdir_in(&AppConfig::get().temp_dir)?; | ||||
|     let zip_path = dest_dir.path().join("export.zip"); | ||||
|  | ||||
|     let file = File::create(&zip_path)?; | ||||
|     let mut zip = ZipWriter::new(file); | ||||
|  | ||||
|     // Encode entities to JSON | ||||
|     zip_json(&mut zip, "vms", &vms, |v| v.name.to_string())?; | ||||
|     zip_json(&mut zip, "networks", &networks, |v| v.name.0.to_string())?; | ||||
|     zip_json( | ||||
|         &mut zip, | ||||
|         "nw_filters", | ||||
|         &nw_filters, | ||||
|         |v| match constants::BUILTIN_NETWORK_FILTER_RULES.contains(&v.name.0.as_str()) { | ||||
|             true => format!("builtin/{}", v.name.0), | ||||
|             false => v.name.0.to_string(), | ||||
|         }, | ||||
|     )?; | ||||
|     zip_json(&mut zip, "tokens", &tokens, |v| v.id.0.to_string())?; | ||||
|  | ||||
|     // Finalize ZIP and return response | ||||
|     zip.finish()?; | ||||
|     let file = File::open(zip_path)?; | ||||
|  | ||||
|     let file = NamedFile::from_file( | ||||
|         file, | ||||
|         format!( | ||||
|             "export_{}.zip", | ||||
|             format_date(time() as i64).unwrap().replace('/', "-") | ||||
|         ), | ||||
|     )?; | ||||
|  | ||||
|     Ok(file.into_response(&req)) | ||||
| } | ||||
|   | ||||
| @@ -157,6 +157,10 @@ async fn main() -> std::io::Result<()> { | ||||
|                 "/api/server/bridges", | ||||
|                 web::get().to(server_controller::bridges_list), | ||||
|             ) | ||||
|             .route( | ||||
|                 "/api/server/export_configs", | ||||
|                 web::get().to(server_controller::export_all_configs), | ||||
|             ) | ||||
|             // Auth controller | ||||
|             .route( | ||||
|                 "/api/auth/local", | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| use chrono::Datelike; | ||||
| use std::time::{SystemTime, UNIX_EPOCH}; | ||||
|  | ||||
| /// Get the current time since epoch | ||||
| @@ -13,3 +14,15 @@ pub fn time() -> u64 { | ||||
|         .unwrap() | ||||
|         .as_secs() | ||||
| } | ||||
|  | ||||
| /// Format given UNIX time in a simple format | ||||
| pub fn format_date(time: i64) -> anyhow::Result<String> { | ||||
|     let date = chrono::DateTime::from_timestamp(time, 0).ok_or(anyhow::anyhow!("invalid date"))?; | ||||
|  | ||||
|     Ok(format!( | ||||
|         "{:0>2}/{:0>2}/{}", | ||||
|         date.day(), | ||||
|         date.month(), | ||||
|         date.year() | ||||
|     )) | ||||
| } | ||||
|   | ||||
							
								
								
									
										21
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -33,7 +33,7 @@ | ||||
|         "yaml": "^2.8.0" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@eslint/js": "^9.29.0", | ||||
|         "@eslint/js": "^9.27.0", | ||||
|         "@types/humanize-duration": "^3.27.4", | ||||
|         "@types/jest": "^29.5.14", | ||||
|         "@types/react": "^19.1.8", | ||||
| @@ -744,9 +744,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@eslint/js": { | ||||
|       "version": "9.29.0", | ||||
|       "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", | ||||
|       "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", | ||||
|       "version": "9.27.0", | ||||
|       "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", | ||||
|       "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
| @@ -2789,19 +2789,6 @@ | ||||
|         "url": "https://opencollective.com/eslint" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/eslint/node_modules/@eslint/js": { | ||||
|       "version": "9.27.0", | ||||
|       "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", | ||||
|       "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://eslint.org/donate" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/espree": { | ||||
|       "version": "10.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", | ||||
|   | ||||
| @@ -35,7 +35,7 @@ | ||||
|     "yaml": "^2.8.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@eslint/js": "^9.29.0", | ||||
|     "@eslint/js": "^9.27.0", | ||||
|     "@types/humanize-duration": "^3.27.4", | ||||
|     "@types/jest": "^29.5.14", | ||||
|     "@types/react": "^19.1.8", | ||||
|   | ||||
| @@ -232,4 +232,16 @@ export class ServerApi { | ||||
|       }) | ||||
|     ).data; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Export all server configs | ||||
|    */ | ||||
|   static async ExportServerConfigs(): Promise<Blob> { | ||||
|     return ( | ||||
|       await APIClient.exec({ | ||||
|         method: "GET", | ||||
|         uri: "/server/export_configs", | ||||
|       }) | ||||
|     ).data; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -9,18 +9,21 @@ import { | ||||
| import Icon from "@mdi/react"; | ||||
| import { | ||||
|   Box, | ||||
|   IconButton, | ||||
|   LinearProgress, | ||||
|   Table, | ||||
|   TableBody, | ||||
|   TableCell, | ||||
|   TableHead, | ||||
|   TableRow, | ||||
|   Tooltip, | ||||
|   Typography, | ||||
| } from "@mui/material"; | ||||
| import Grid from "@mui/material/Grid"; | ||||
| import { PieChart } from "@mui/x-charts"; | ||||
| import { filesize } from "filesize"; | ||||
| import humanizeDuration from "humanize-duration"; | ||||
| import IosShareIcon from "@mui/icons-material/IosShare"; | ||||
| import React from "react"; | ||||
| import { | ||||
|   DiskInfo, | ||||
| @@ -31,6 +34,8 @@ import { | ||||
| import { AsyncWidget } from "../widgets/AsyncWidget"; | ||||
| import { VirtWebPaper } from "../widgets/VirtWebPaper"; | ||||
| import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; | ||||
| import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider"; | ||||
| import { useAlert } from "../hooks/providers/AlertDialogProvider"; | ||||
|  | ||||
| export function SysInfoRoute(): React.ReactElement { | ||||
|   const [info, setInfo] = React.useState<ServerSystemInfo>(); | ||||
| @@ -52,6 +57,23 @@ export function SysInfoRoute(): React.ReactElement { | ||||
| export function SysInfoRouteInner(p: { | ||||
|   info: ServerSystemInfo; | ||||
| }): React.ReactElement { | ||||
|   const alert = useAlert(); | ||||
|   const loadingMessage = useLoadingMessage(); | ||||
|   const downloadAllConfig = async () => { | ||||
|     try { | ||||
|       loadingMessage.show("Downloading server config..."); | ||||
|       const res = await ServerApi.ExportServerConfigs(); | ||||
|  | ||||
|       const url = URL.createObjectURL(res); | ||||
|       window.location.href = url; | ||||
|     } catch (e) { | ||||
|       console.error("Failed to download server config!", e); | ||||
|       alert(`Failed to download server config! ${e}`); | ||||
|     } finally { | ||||
|       loadingMessage.hide(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const sumDiskUsage = p.info.disks.reduce( | ||||
|     (prev, disk) => { | ||||
|       return { | ||||
| @@ -63,7 +85,16 @@ export function SysInfoRouteInner(p: { | ||||
|   ); | ||||
|  | ||||
|   return ( | ||||
|     <VirtWebRouteContainer label="Sysinfo"> | ||||
|     <VirtWebRouteContainer | ||||
|       label="Sysinfo" | ||||
|       actions={ | ||||
|         <Tooltip title="Export all server configs"> | ||||
|           <IconButton onClick={downloadAllConfig}> | ||||
|             <IosShareIcon /> | ||||
|           </IconButton> | ||||
|         </Tooltip> | ||||
|       } | ||||
|     > | ||||
|       <Grid container spacing={2}> | ||||
|         {/* Memory */} | ||||
|         <Grid size={{ xs: 4 }}> | ||||
|   | ||||
| @@ -333,8 +333,7 @@ function CloudInitBooleanInput(p: { | ||||
|       label={p.name} | ||||
|       checked={p.yaml.getIn(p.attrPath) === true} | ||||
|       onValueChange={(v) => { | ||||
|         if (v) p.yaml.setIn(p.attrPath, v); | ||||
|         else p.yaml.deleteIn(p.attrPath); | ||||
|         p.yaml.setIn(p.attrPath, v); | ||||
|         p.onChange?.(); | ||||
|       }} | ||||
|     /> | ||||
|   | ||||
| @@ -799,6 +799,11 @@ export function TokenRightsEditor(p: { | ||||
|           right={{ verb: "GET", path: "/api/server/bridges" }} | ||||
|           label="Get list of network bridges" | ||||
|         /> | ||||
|         <RouteRight | ||||
|           {...p} | ||||
|           right={{ verb: "GET", path: "/api/server/export_configs" }} | ||||
|           label="Export all configurations" | ||||
|         /> | ||||
|       </RightsSection> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user