Compare commits
	
		
			15 Commits
		
	
	
		
			270bead4f5
			...
			20250618
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b633694f74 | |||
| ab16bd7bcf | |||
| 1080ab5cb2 | |||
| a2845ddafe | |||
| c968b64b51 | |||
| 12833dc6da | |||
| 8c4f2a9f2d | |||
| 9a6b6cfb2d | |||
| b28ca5f27d | |||
| 92f187bf91 | |||
| 9f1f4b44ca | |||
| b00d46105b | |||
| 7d6ccd4ce6 | |||
| b5371421e1 | |||
| 07d305c54e | 
| @@ -46,8 +46,9 @@ steps: | |||||||
|   - cd virtweb_backend |   - cd virtweb_backend | ||||||
|   - mv /tmp/web_build/dist static |   - mv /tmp/web_build/dist static | ||||||
|   - cargo build --release |   - cargo build --release | ||||||
|   - ls -lah target/release/virtweb_backend |   - cargo build --release --example api_curl | ||||||
|   - cp target/release/virtweb_backend /tmp/release |   - 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 | - name: gitea_release | ||||||
|   image: plugins/gitea-release |   image: plugins/gitea-release | ||||||
|   | |||||||
							
								
								
									
										269
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										269
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -435,6 +435,21 @@ dependencies = [ | |||||||
|  "alloc-no-stdlib", |  "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]] | [[package]] | ||||||
| name = "anstream" | name = "anstream" | ||||||
| version = "0.6.18" | version = "0.6.18" | ||||||
| @@ -496,6 +511,9 @@ name = "arbitrary" | |||||||
| version = "1.4.1" | version = "1.4.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" | checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" | ||||||
|  | dependencies = [ | ||||||
|  |  "derive_arbitrary", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "arg_enum_proc_macro" | name = "arg_enum_proc_macro" | ||||||
| @@ -715,6 +733,25 @@ dependencies = [ | |||||||
|  "bytes", |  "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]] | [[package]] | ||||||
| name = "cc" | name = "cc" | ||||||
| version = "1.2.23" | version = "1.2.23" | ||||||
| @@ -748,6 +785,20 @@ version = "0.2.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" | 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]] | [[package]] | ||||||
| name = "cipher" | name = "cipher" | ||||||
| version = "0.4.4" | version = "0.4.4" | ||||||
| @@ -760,9 +811,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap" | name = "clap" | ||||||
| version = "4.5.38" | version = "4.5.40" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" | checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "clap_builder", |  "clap_builder", | ||||||
|  "clap_derive", |  "clap_derive", | ||||||
| @@ -770,9 +821,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_builder" | name = "clap_builder" | ||||||
| version = "4.5.38" | version = "4.5.40" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" | checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "anstream", |  "anstream", | ||||||
|  "anstyle", |  "anstyle", | ||||||
| @@ -782,9 +833,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_derive" | name = "clap_derive" | ||||||
| version = "4.5.32" | version = "4.5.40" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" | checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "heck", |  "heck", | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
| @@ -816,6 +867,12 @@ version = "0.9.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "constant_time_eq" | ||||||
|  | version = "0.3.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "convert_case" | name = "convert_case" | ||||||
| version = "0.4.0" | version = "0.4.0" | ||||||
| @@ -981,6 +1038,12 @@ dependencies = [ | |||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "deflate64" | ||||||
|  | version = "0.1.9" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "der" | name = "der" | ||||||
| version = "0.7.10" | version = "0.7.10" | ||||||
| @@ -1001,6 +1064,17 @@ dependencies = [ | |||||||
|  "powerfmt", |  "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]] | [[package]] | ||||||
| name = "derive_more" | name = "derive_more" | ||||||
| version = "0.99.20" | version = "0.99.20" | ||||||
| @@ -1221,6 +1295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "crc32fast", |  "crc32fast", | ||||||
|  |  "libz-rs-sys", | ||||||
|  "miniz_oxide", |  "miniz_oxide", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| @@ -1380,9 +1455,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  |  "js-sys", | ||||||
|  "libc", |  "libc", | ||||||
|  "r-efi", |  "r-efi", | ||||||
|  "wasi 0.14.2+wasi-0.2.4", |  "wasi 0.14.2+wasi-0.2.4", | ||||||
|  |  "wasm-bindgen", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -1622,18 +1699,47 @@ version = "0.1.12" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" | checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "base64 0.22.1", | ||||||
|  "bytes", |  "bytes", | ||||||
|  "futures-channel", |  "futures-channel", | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  "http 1.3.1", |  "http 1.3.1", | ||||||
|  "http-body", |  "http-body", | ||||||
|  "hyper", |  "hyper", | ||||||
|  |  "ipnet", | ||||||
|  "libc", |  "libc", | ||||||
|  |  "percent-encoding", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
|  "socket2", |  "socket2", | ||||||
|  |  "system-configuration", | ||||||
|  "tokio", |  "tokio", | ||||||
|  "tower-service", |  "tower-service", | ||||||
|  "tracing", |  "tracing", | ||||||
|  |  "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]] | [[package]] | ||||||
| @@ -1839,6 +1945,16 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "iri-string" | ||||||
|  | version = "0.7.8" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" | ||||||
|  | dependencies = [ | ||||||
|  |  "memchr", | ||||||
|  |  "serde", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "is_terminal_polyfill" | name = "is_terminal_polyfill" | ||||||
| version = "1.70.1" | version = "1.70.1" | ||||||
| @@ -1982,6 +2098,26 @@ dependencies = [ | |||||||
|  "cc", |  "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]] | [[package]] | ||||||
| name = "libyml" | name = "libyml" | ||||||
| version = "0.0.5" | version = "0.0.5" | ||||||
| @@ -1992,6 +2128,15 @@ dependencies = [ | |||||||
|  "version_check", |  "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]] | [[package]] | ||||||
| name = "light-openid" | name = "light-openid" | ||||||
| version = "1.0.4" | version = "1.0.4" | ||||||
| @@ -2414,6 +2559,16 @@ version = "1.0.15" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" | 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]] | [[package]] | ||||||
| name = "pem" | name = "pem" | ||||||
| version = "3.0.5" | version = "3.0.5" | ||||||
| @@ -2783,9 +2938,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "reqwest" | name = "reqwest" | ||||||
| version = "0.12.15" | version = "0.12.20" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" | checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "base64 0.22.1", |  "base64 0.22.1", | ||||||
|  "bytes", |  "bytes", | ||||||
| @@ -2800,31 +2955,28 @@ dependencies = [ | |||||||
|  "hyper-rustls", |  "hyper-rustls", | ||||||
|  "hyper-tls", |  "hyper-tls", | ||||||
|  "hyper-util", |  "hyper-util", | ||||||
|  "ipnet", |  | ||||||
|  "js-sys", |  "js-sys", | ||||||
|  "log", |  "log", | ||||||
|  "mime", |  "mime", | ||||||
|  "native-tls", |  "native-tls", | ||||||
|  "once_cell", |  | ||||||
|  "percent-encoding", |  "percent-encoding", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
|  "rustls-pemfile", |  "rustls-pki-types", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "serde_urlencoded", |  "serde_urlencoded", | ||||||
|  "sync_wrapper", |  "sync_wrapper", | ||||||
|  "system-configuration", |  | ||||||
|  "tokio", |  "tokio", | ||||||
|  "tokio-native-tls", |  "tokio-native-tls", | ||||||
|  "tokio-util", |  "tokio-util", | ||||||
|  "tower", |  "tower", | ||||||
|  |  "tower-http", | ||||||
|  "tower-service", |  "tower-service", | ||||||
|  "url", |  "url", | ||||||
|  "wasm-bindgen", |  "wasm-bindgen", | ||||||
|  "wasm-bindgen-futures", |  "wasm-bindgen-futures", | ||||||
|  "wasm-streams", |  "wasm-streams", | ||||||
|  "web-sys", |  "web-sys", | ||||||
|  "windows-registry", |  | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -2933,15 +3085,6 @@ dependencies = [ | |||||||
|  "zeroize", |  "zeroize", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "rustls-pemfile" |  | ||||||
| version = "2.2.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" |  | ||||||
| dependencies = [ |  | ||||||
|  "rustls-pki-types", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "rustls-pki-types" | name = "rustls-pki-types" | ||||||
| version = "1.12.0" | version = "1.12.0" | ||||||
| @@ -3438,9 +3581,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio" | name = "tokio" | ||||||
| version = "1.45.1" | version = "1.45.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" | checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "backtrace", |  "backtrace", | ||||||
|  "bytes", |  "bytes", | ||||||
| @@ -3547,6 +3690,24 @@ dependencies = [ | |||||||
|  "tower-service", |  "tower-service", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "tower-http" | ||||||
|  | version = "0.6.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" | ||||||
|  | dependencies = [ | ||||||
|  |  "bitflags 2.9.1", | ||||||
|  |  "bytes", | ||||||
|  |  "futures-util", | ||||||
|  |  "http 1.3.1", | ||||||
|  |  "http-body", | ||||||
|  |  "iri-string", | ||||||
|  |  "pin-project-lite", | ||||||
|  |  "tower", | ||||||
|  |  "tower-layer", | ||||||
|  |  "tower-service", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tower-layer" | name = "tower-layer" | ||||||
| version = "0.3.3" | version = "0.3.3" | ||||||
| @@ -3760,6 +3921,7 @@ dependencies = [ | |||||||
|  "actix-ws", |  "actix-ws", | ||||||
|  "anyhow", |  "anyhow", | ||||||
|  "basic-jwt", |  "basic-jwt", | ||||||
|  |  "chrono", | ||||||
|  "clap", |  "clap", | ||||||
|  "dotenvy", |  "dotenvy", | ||||||
|  "env_logger", |  "env_logger", | ||||||
| @@ -3787,6 +3949,7 @@ dependencies = [ | |||||||
|  "url", |  "url", | ||||||
|  "uuid", |  "uuid", | ||||||
|  "virt", |  "virt", | ||||||
|  |  "zip", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -4325,6 +4488,20 @@ name = "zeroize" | |||||||
| version = "1.8.1" | version = "1.8.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" | 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]] | [[package]] | ||||||
| name = "zerotrie" | name = "zerotrie" | ||||||
| @@ -4359,6 +4536,50 @@ dependencies = [ | |||||||
|  "syn", |  "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]] | [[package]] | ||||||
| name = "zstd" | name = "zstd" | ||||||
| version = "0.13.3" | version = "0.13.3" | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ edition = "2024" | |||||||
| [dependencies] | [dependencies] | ||||||
| log = "0.4.27" | log = "0.4.27" | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.8" | ||||||
| clap = { version = "4.5.38", features = ["derive", "env"] } | clap = { version = "4.5.40", features = ["derive", "env"] } | ||||||
| light-openid = { version = "1.0.4", features = ["crypto-wrapper"] } | light-openid = { version = "1.0.4", features = ["crypto-wrapper"] } | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| actix = "0.13.5" | actix = "0.13.5" | ||||||
| @@ -28,7 +28,7 @@ futures-util = "0.3.31" | |||||||
| anyhow = "1.0.98" | anyhow = "1.0.98" | ||||||
| actix-multipart = "0.7.2" | actix-multipart = "0.7.2" | ||||||
| tempfile = "3.20.0" | tempfile = "3.20.0" | ||||||
| reqwest = { version = "0.12.15", features = ["stream"] } | reqwest = { version = "0.12.20", features = ["stream"] } | ||||||
| url = "2.5.4" | url = "2.5.4" | ||||||
| virt = "0.4.2" | virt = "0.4.2" | ||||||
| sysinfo = { version = "0.35.1", features = ["serde"] } | sysinfo = { version = "0.35.1", features = ["serde"] } | ||||||
| @@ -37,7 +37,7 @@ lazy-regex = "3.4.1" | |||||||
| thiserror = "2.0.12" | thiserror = "2.0.12" | ||||||
| image = "0.25.6" | image = "0.25.6" | ||||||
| rand = "0.9.1" | rand = "0.9.1" | ||||||
| tokio = { version = "1.45.1", features = ["rt", "time", "macros"] } | tokio = { version = "1.45.0", features = ["rt", "time", "macros"] } | ||||||
| futures = "0.3.31" | futures = "0.3.31" | ||||||
| ipnetwork = { version = "0.21.1", features = ["serde"] } | ipnetwork = { version = "0.21.1", features = ["serde"] } | ||||||
| num = "0.4.3" | num = "0.4.3" | ||||||
| @@ -45,3 +45,5 @@ rust-embed = { version = "8.7.2", features = ["mime-guess"] } | |||||||
| dotenvy = "0.15.7" | dotenvy = "0.15.7" | ||||||
| nix = { version = "0.30.1", features = ["net"] } | nix = { version = "0.30.1", features = ["net"] } | ||||||
| basic-jwt = "0.3.0" | 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 actix_web::{HttpResponse, web}; | ||||||
| use std::error::Error; | use std::error::Error; | ||||||
| use std::fmt::{Display, Formatter}; | use std::fmt::{Display, Formatter}; | ||||||
|  | use zip::result::ZipError; | ||||||
|  |  | ||||||
| pub mod api_tokens_controller; | pub mod api_tokens_controller; | ||||||
| pub mod auth_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 { | impl From<HttpResponse> for HttpErr { | ||||||
|     fn from(value: HttpResponse) -> Self { |     fn from(value: HttpResponse) -> Self { | ||||||
|         HttpErr::HTTPResponse(value) |         HttpErr::HTTPResponse(value) | ||||||
|   | |||||||
| @@ -1,14 +1,24 @@ | |||||||
| use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME; | use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME; | ||||||
| use crate::app_config::AppConfig; | 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::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN}; | ||||||
| use crate::controllers::{HttpResult, LibVirtReq}; | use crate::controllers::{HttpResult, LibVirtReq}; | ||||||
| use crate::extractors::local_auth_extractor::LocalAuthEnabled; | use crate::extractors::local_auth_extractor::LocalAuthEnabled; | ||||||
| use crate::libvirt_rest_structures::hypervisor::HypervisorInfo; | 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::nat::nat_hook; | ||||||
| use crate::utils::net_utils; | 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 sysinfo::{Components, Disks, Networks, System}; | ||||||
|  | use zip::ZipWriter; | ||||||
|  | use zip::write::SimpleFileOptions; | ||||||
|  |  | ||||||
| #[derive(serde::Serialize)] | #[derive(serde::Serialize)] | ||||||
| struct StaticConfig { | struct StaticConfig { | ||||||
| @@ -199,3 +209,85 @@ pub async fn networks_list() -> HttpResult { | |||||||
| pub async fn bridges_list() -> HttpResult { | pub async fn bridges_list() -> HttpResult { | ||||||
|     Ok(HttpResponse::Ok().json(net_utils::bridges_list()?)) |     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", |                 "/api/server/bridges", | ||||||
|                 web::get().to(server_controller::bridges_list), |                 web::get().to(server_controller::bridges_list), | ||||||
|             ) |             ) | ||||||
|  |             .route( | ||||||
|  |                 "/api/server/export_configs", | ||||||
|  |                 web::get().to(server_controller::export_all_configs), | ||||||
|  |             ) | ||||||
|             // Auth controller |             // Auth controller | ||||||
|             .route( |             .route( | ||||||
|                 "/api/auth/local", |                 "/api/auth/local", | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | use chrono::Datelike; | ||||||
| use std::time::{SystemTime, UNIX_EPOCH}; | use std::time::{SystemTime, UNIX_EPOCH}; | ||||||
|  |  | ||||||
| /// Get the current time since epoch | /// Get the current time since epoch | ||||||
| @@ -13,3 +14,15 @@ pub fn time() -> u64 { | |||||||
|         .unwrap() |         .unwrap() | ||||||
|         .as_secs() |         .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() | ||||||
|  |     )) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -10,7 +10,7 @@ | |||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@emotion/react": "^11.14.0", |         "@emotion/react": "^11.14.0", | ||||||
|         "@emotion/styled": "^11.14.0", |         "@emotion/styled": "^11.14.0", | ||||||
|         "@fontsource/roboto": "^5.2.5", |         "@fontsource/roboto": "^5.2.6", | ||||||
|         "@mdi/js": "^7.4.47", |         "@mdi/js": "^7.4.47", | ||||||
|         "@mdi/react": "^1.6.1", |         "@mdi/react": "^1.6.1", | ||||||
|         "@monaco-editor/react": "^4.7.0", |         "@monaco-editor/react": "^4.7.0", | ||||||
| @@ -29,13 +29,14 @@ | |||||||
|         "react-syntax-highlighter": "^15.6.1", |         "react-syntax-highlighter": "^15.6.1", | ||||||
|         "react-vnc": "^3.1.0", |         "react-vnc": "^3.1.0", | ||||||
|         "uuid": "^11.1.0", |         "uuid": "^11.1.0", | ||||||
|         "xml-formatter": "^3.6.6" |         "xml-formatter": "^3.6.6", | ||||||
|  |         "yaml": "^2.8.0" | ||||||
|       }, |       }, | ||||||
|       "devDependencies": { |       "devDependencies": { | ||||||
|         "@eslint/js": "^9.27.0", |         "@eslint/js": "^9.27.0", | ||||||
|         "@types/humanize-duration": "^3.27.4", |         "@types/humanize-duration": "^3.27.4", | ||||||
|         "@types/jest": "^29.5.14", |         "@types/jest": "^29.5.14", | ||||||
|         "@types/react": "^19.1.6", |         "@types/react": "^19.1.8", | ||||||
|         "@types/react-dom": "^19.1.6", |         "@types/react-dom": "^19.1.6", | ||||||
|         "@types/react-syntax-highlighter": "^15.5.13", |         "@types/react-syntax-highlighter": "^15.5.13", | ||||||
|         "@types/uuid": "^10.0.0", |         "@types/uuid": "^10.0.0", | ||||||
| @@ -780,9 +781,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@fontsource/roboto": { |     "node_modules/@fontsource/roboto": { | ||||||
|       "version": "5.2.5", |       "version": "5.2.6", | ||||||
|       "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz", |       "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.6.tgz", | ||||||
|       "integrity": "sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==", |       "integrity": "sha512-hzarG7yAhMoP418smNgfY4fO7UmuUEm5JUtbxCoCcFHT0hOJB+d/qAEyoNjz7YkPU5OjM2LM8rJnW8hfm0JLaA==", | ||||||
|       "license": "OFL-1.1", |       "license": "OFL-1.1", | ||||||
|       "funding": { |       "funding": { | ||||||
|         "url": "https://github.com/sponsors/ayuhito" |         "url": "https://github.com/sponsors/ayuhito" | ||||||
| @@ -1624,9 +1625,9 @@ | |||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/react": { |     "node_modules/@types/react": { | ||||||
|       "version": "19.1.6", |       "version": "19.1.8", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", |       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", | ||||||
|       "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", |       "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "csstype": "^3.0.2" |         "csstype": "^3.0.2" | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@emotion/react": "^11.14.0", |     "@emotion/react": "^11.14.0", | ||||||
|     "@emotion/styled": "^11.14.0", |     "@emotion/styled": "^11.14.0", | ||||||
|     "@fontsource/roboto": "^5.2.5", |     "@fontsource/roboto": "^5.2.6", | ||||||
|     "@mdi/js": "^7.4.47", |     "@mdi/js": "^7.4.47", | ||||||
|     "@mdi/react": "^1.6.1", |     "@mdi/react": "^1.6.1", | ||||||
|     "@monaco-editor/react": "^4.7.0", |     "@monaco-editor/react": "^4.7.0", | ||||||
| @@ -31,13 +31,14 @@ | |||||||
|     "react-syntax-highlighter": "^15.6.1", |     "react-syntax-highlighter": "^15.6.1", | ||||||
|     "react-vnc": "^3.1.0", |     "react-vnc": "^3.1.0", | ||||||
|     "uuid": "^11.1.0", |     "uuid": "^11.1.0", | ||||||
|     "xml-formatter": "^3.6.6" |     "xml-formatter": "^3.6.6", | ||||||
|  |     "yaml": "^2.8.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@eslint/js": "^9.27.0", |     "@eslint/js": "^9.27.0", | ||||||
|     "@types/humanize-duration": "^3.27.4", |     "@types/humanize-duration": "^3.27.4", | ||||||
|     "@types/jest": "^29.5.14", |     "@types/jest": "^29.5.14", | ||||||
|     "@types/react": "^19.1.6", |     "@types/react": "^19.1.8", | ||||||
|     "@types/react-dom": "^19.1.6", |     "@types/react-dom": "^19.1.6", | ||||||
|     "@types/react-syntax-highlighter": "^15.5.13", |     "@types/react-syntax-highlighter": "^15.5.13", | ||||||
|     "@types/uuid": "^10.0.0", |     "@types/uuid": "^10.0.0", | ||||||
|   | |||||||
| @@ -232,4 +232,16 @@ export class ServerApi { | |||||||
|       }) |       }) | ||||||
|     ).data; |     ).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 Icon from "@mdi/react"; | ||||||
| import { | import { | ||||||
|   Box, |   Box, | ||||||
|  |   IconButton, | ||||||
|   LinearProgress, |   LinearProgress, | ||||||
|   Table, |   Table, | ||||||
|   TableBody, |   TableBody, | ||||||
|   TableCell, |   TableCell, | ||||||
|   TableHead, |   TableHead, | ||||||
|   TableRow, |   TableRow, | ||||||
|  |   Tooltip, | ||||||
|   Typography, |   Typography, | ||||||
| } from "@mui/material"; | } from "@mui/material"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid"; | ||||||
| import { PieChart } from "@mui/x-charts"; | import { PieChart } from "@mui/x-charts"; | ||||||
| import { filesize } from "filesize"; | import { filesize } from "filesize"; | ||||||
| import humanizeDuration from "humanize-duration"; | import humanizeDuration from "humanize-duration"; | ||||||
|  | import IosShareIcon from "@mui/icons-material/IosShare"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { | import { | ||||||
|   DiskInfo, |   DiskInfo, | ||||||
| @@ -31,6 +34,8 @@ import { | |||||||
| import { AsyncWidget } from "../widgets/AsyncWidget"; | import { AsyncWidget } from "../widgets/AsyncWidget"; | ||||||
| import { VirtWebPaper } from "../widgets/VirtWebPaper"; | import { VirtWebPaper } from "../widgets/VirtWebPaper"; | ||||||
| import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; | import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; | ||||||
|  | import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider"; | ||||||
|  | import { useAlert } from "../hooks/providers/AlertDialogProvider"; | ||||||
|  |  | ||||||
| export function SysInfoRoute(): React.ReactElement { | export function SysInfoRoute(): React.ReactElement { | ||||||
|   const [info, setInfo] = React.useState<ServerSystemInfo>(); |   const [info, setInfo] = React.useState<ServerSystemInfo>(); | ||||||
| @@ -52,6 +57,23 @@ export function SysInfoRoute(): React.ReactElement { | |||||||
| export function SysInfoRouteInner(p: { | export function SysInfoRouteInner(p: { | ||||||
|   info: ServerSystemInfo; |   info: ServerSystemInfo; | ||||||
| }): React.ReactElement { | }): 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( |   const sumDiskUsage = p.info.disks.reduce( | ||||||
|     (prev, disk) => { |     (prev, disk) => { | ||||||
|       return { |       return { | ||||||
| @@ -63,7 +85,16 @@ export function SysInfoRouteInner(p: { | |||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <VirtWebRouteContainer label="Sysinfo"> |     <VirtWebRouteContainer | ||||||
|  |       label="Sysinfo" | ||||||
|  |       actions={ | ||||||
|  |         <Tooltip title="Export all server configs"> | ||||||
|  |           <IconButton onClick={downloadAllConfig}> | ||||||
|  |             <IosShareIcon /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|       <Grid container spacing={2}> |       <Grid container spacing={2}> | ||||||
|         {/* Memory */} |         {/* Memory */} | ||||||
|         <Grid size={{ xs: 4 }}> |         <Grid size={{ xs: 4 }}> | ||||||
|   | |||||||
| @@ -17,7 +17,9 @@ export function CheckboxInput(p: { | |||||||
|         <Checkbox |         <Checkbox | ||||||
|           disabled={!p.editable} |           disabled={!p.editable} | ||||||
|           checked={p.checked} |           checked={p.checked} | ||||||
|           onChange={(e) => { p.onValueChange(e.target.checked); }} |           onChange={(e) => { | ||||||
|  |             p.onValueChange(e.target.checked); | ||||||
|  |           }} | ||||||
|         /> |         /> | ||||||
|       } |       } | ||||||
|       label={p.label} |       label={p.label} | ||||||
|   | |||||||
| @@ -1,8 +1,14 @@ | |||||||
|  | /* eslint-disable @typescript-eslint/no-base-to-string */ | ||||||
|  |  | ||||||
| import Editor from "@monaco-editor/react"; | import Editor from "@monaco-editor/react"; | ||||||
|  | import BookIcon from "@mui/icons-material/Book"; | ||||||
| import RefreshIcon from "@mui/icons-material/Refresh"; | import RefreshIcon from "@mui/icons-material/Refresh"; | ||||||
| import { Grid, IconButton, InputAdornment, Tooltip } from "@mui/material"; | import { Grid, IconButton, InputAdornment, Tooltip } from "@mui/material"; | ||||||
|  | import React from "react"; | ||||||
| import { v4 as uuidv4 } from "uuid"; | import { v4 as uuidv4 } from "uuid"; | ||||||
|  | import YAML from "yaml"; | ||||||
| import { VMInfo } from "../../api/VMApi"; | import { VMInfo } from "../../api/VMApi"; | ||||||
|  | import { RouterLink } from "../RouterLink"; | ||||||
| import { CheckboxInput } from "./CheckboxInput"; | import { CheckboxInput } from "./CheckboxInput"; | ||||||
| import { EditSection } from "./EditSection"; | import { EditSection } from "./EditSection"; | ||||||
| import { SelectInput } from "./SelectInput"; | import { SelectInput } from "./SelectInput"; | ||||||
| @@ -38,6 +44,14 @@ export function CloudInitEditor(p: CloudInitProps): React.ReactElement { | |||||||
|           {...p} |           {...p} | ||||||
|           editable={p.editable && p.vm.cloud_init.attach_config} |           editable={p.editable && p.vm.cloud_init.attach_config} | ||||||
|         /> |         /> | ||||||
|  |         <CloudInitNetworkConfig | ||||||
|  |           {...p} | ||||||
|  |           editable={p.editable && p.vm.cloud_init.attach_config} | ||||||
|  |         /> | ||||||
|  |         <CloudInitUserDataAssistant | ||||||
|  |           {...p} | ||||||
|  |           editable={p.editable && p.vm.cloud_init.attach_config} | ||||||
|  |         /> | ||||||
|       </Grid> |       </Grid> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
| @@ -108,12 +122,27 @@ function CloudInitMetadata(p: CloudInitProps): React.ReactElement { | |||||||
|  |  | ||||||
| function CloudInitRawUserData(p: CloudInitProps): React.ReactElement { | function CloudInitRawUserData(p: CloudInitProps): React.ReactElement { | ||||||
|   return ( |   return ( | ||||||
|     <EditSection title="User data"> |     <EditSection | ||||||
|  |       title="User data" | ||||||
|  |       actions={ | ||||||
|  |         <RouterLink | ||||||
|  |           target="_blank" | ||||||
|  |           to="https://cloudinit.readthedocs.io/en/latest/reference/index.html" | ||||||
|  |         > | ||||||
|  |           <Tooltip title="Official reference"> | ||||||
|  |             <IconButton size="small"> | ||||||
|  |               <BookIcon /> | ||||||
|  |             </IconButton> | ||||||
|  |           </Tooltip> | ||||||
|  |         </RouterLink> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|       <Editor |       <Editor | ||||||
|         theme="vs-dark" |         theme="vs-dark" | ||||||
|         options={{ |         options={{ | ||||||
|           readOnly: !p.editable, |           readOnly: !p.editable, | ||||||
|           quickSuggestions: { other: true, comments: true, strings: true }, |           quickSuggestions: { other: true, comments: true, strings: true }, | ||||||
|  |           wordWrap: "on", | ||||||
|         }} |         }} | ||||||
|         language="yaml" |         language="yaml" | ||||||
|         height={"30vh"} |         height={"30vh"} | ||||||
| @@ -126,3 +155,187 @@ function CloudInitRawUserData(p: CloudInitProps): React.ReactElement { | |||||||
|     </EditSection> |     </EditSection> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function CloudInitNetworkConfig(p: CloudInitProps): React.ReactElement { | ||||||
|  |   if (!p.editable && !p.vm.cloud_init.network_configuration) return <></>; | ||||||
|  |   return ( | ||||||
|  |     <EditSection | ||||||
|  |       title="Network configuration" | ||||||
|  |       actions={ | ||||||
|  |         <RouterLink | ||||||
|  |           target="_blank" | ||||||
|  |           to="https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v2.html" | ||||||
|  |         > | ||||||
|  |           <Tooltip title="Official network configuration reference"> | ||||||
|  |             <IconButton size="small"> | ||||||
|  |               <BookIcon /> | ||||||
|  |             </IconButton> | ||||||
|  |           </Tooltip> | ||||||
|  |         </RouterLink> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|  |       <Editor | ||||||
|  |         theme="vs-dark" | ||||||
|  |         options={{ | ||||||
|  |           readOnly: !p.editable, | ||||||
|  |           quickSuggestions: { other: true, comments: true, strings: true }, | ||||||
|  |           wordWrap: "on", | ||||||
|  |         }} | ||||||
|  |         language="yaml" | ||||||
|  |         height={"30vh"} | ||||||
|  |         value={p.vm.cloud_init.network_configuration ?? ""} | ||||||
|  |         onChange={(v) => { | ||||||
|  |           if (v && v !== "") p.vm.cloud_init.network_configuration = v; | ||||||
|  |           else p.vm.cloud_init.network_configuration = undefined; | ||||||
|  |           p.onChange?.(); | ||||||
|  |         }} | ||||||
|  |       /> | ||||||
|  |     </EditSection> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function CloudInitUserDataAssistant(p: CloudInitProps): React.ReactElement { | ||||||
|  |   const user_data = React.useMemo(() => { | ||||||
|  |     return YAML.parseDocument(p.vm.cloud_init.user_data); | ||||||
|  |   }, [p.vm.cloud_init.user_data]); | ||||||
|  |  | ||||||
|  |   const onChange = () => { | ||||||
|  |     p.vm.cloud_init.user_data = user_data.toString(); | ||||||
|  |  | ||||||
|  |     if (!p.vm.cloud_init.user_data.startsWith("#cloud-config")) | ||||||
|  |       p.vm.cloud_init.user_data = `#cloud-config\n${p.vm.cloud_init.user_data}`; | ||||||
|  |  | ||||||
|  |     p.onChange?.(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const SYSTEMD_NOT_SERIAL = `/bin/sh -c "rm -f /etc/default/grub.d/50-cloudimg-settings.cfg && sed -i 's/quiet splash//g' /etc/default/grub && update-grub"`; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <EditSection title="User data assistant"> | ||||||
|  |       <CloudInitTextInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         name="Default user name" | ||||||
|  |         refUrl="https://cloudinit.readthedocs.io/en/latest/reference/modules.html#set-passwords" | ||||||
|  |         attrPath={["user", "name"]} | ||||||
|  |         onChange={onChange} | ||||||
|  |         yaml={user_data} | ||||||
|  |       /> | ||||||
|  |       <CloudInitTextInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         name="Default user password" | ||||||
|  |         refUrl="https://cloudinit.readthedocs.io/en/latest/reference/modules.html#set-passwords" | ||||||
|  |         attrPath={["password"]} | ||||||
|  |         onChange={onChange} | ||||||
|  |         yaml={user_data} | ||||||
|  |       /> | ||||||
|  |       <CloudInitBooleanInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         name="Expire password to require new password on next login" | ||||||
|  |         yaml={user_data} | ||||||
|  |         attrPath={["chpasswd", "expire"]} | ||||||
|  |         onChange={onChange} | ||||||
|  |         refUrl="https://cloudinit.readthedocs.io/en/latest/reference/modules.html#set-passwords" | ||||||
|  |       /> | ||||||
|  |       <br /> | ||||||
|  |       <CloudInitBooleanInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         name="Enable SSH password auth" | ||||||
|  |         yaml={user_data} | ||||||
|  |         attrPath={["ssh_pwauth"]} | ||||||
|  |         onChange={onChange} | ||||||
|  |         refUrl="https://cloudinit.readthedocs.io/en/latest/reference/modules.html#set-passwords" | ||||||
|  |       /> | ||||||
|  |       <CloudInitTextInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         name="Keyboard layout" | ||||||
|  |         refUrl="https://cloudinit.readthedocs.io/en/latest/reference/modules.html#keyboard" | ||||||
|  |         attrPath={["keyboard", "layout"]} | ||||||
|  |         onChange={onChange} | ||||||
|  |         yaml={user_data} | ||||||
|  |       /> | ||||||
|  |       <CloudInitTextInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         name="Final message" | ||||||
|  |         refUrl="https://cloudinit.readthedocs.io/en/latest/reference/modules.html#final-message" | ||||||
|  |         attrPath={["final_message"]} | ||||||
|  |         onChange={onChange} | ||||||
|  |         yaml={user_data} | ||||||
|  |       /> | ||||||
|  |       {/* /bin/sh -c "rm -f /etc/default/grub.d/50-cloudimg-settings.cfg && update-grub" */} | ||||||
|  |       <CheckboxInput | ||||||
|  |         editable={p.editable} | ||||||
|  |         label="Show all startup messages on tty1, not serial" | ||||||
|  |         checked={ | ||||||
|  |           !!(user_data.get("runcmd") as any)?.items.find( | ||||||
|  |             (a: any) => a.value === SYSTEMD_NOT_SERIAL | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |         onValueChange={(c) => { | ||||||
|  |           if (!user_data.getIn(["runcmd"])) user_data.addIn(["runcmd"], []); | ||||||
|  |  | ||||||
|  |           const runcmd = user_data.getIn(["runcmd"]) as any; | ||||||
|  |  | ||||||
|  |           if (c) { | ||||||
|  |             runcmd.addIn([], SYSTEMD_NOT_SERIAL); | ||||||
|  |           } else { | ||||||
|  |             const idx = runcmd.items.findIndex( | ||||||
|  |               (o: any) => o.value === SYSTEMD_NOT_SERIAL | ||||||
|  |             ); | ||||||
|  |             runcmd.items.splice(idx, 1); | ||||||
|  |           } | ||||||
|  |           onChange(); | ||||||
|  |         }} | ||||||
|  |       /> | ||||||
|  |     </EditSection> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function CloudInitTextInput(p: { | ||||||
|  |   editable: boolean; | ||||||
|  |   name: string; | ||||||
|  |   refUrl: string; | ||||||
|  |   attrPath: Iterable<unknown>; | ||||||
|  |   yaml: YAML.Document; | ||||||
|  |   onChange?: () => void; | ||||||
|  | }): React.ReactElement { | ||||||
|  |   return ( | ||||||
|  |     <TextInput | ||||||
|  |       editable={p.editable} | ||||||
|  |       label={p.name} | ||||||
|  |       value={String(p.yaml.getIn(p.attrPath) ?? "")} | ||||||
|  |       onValueChange={(v) => { | ||||||
|  |         if (v !== undefined) p.yaml.setIn(p.attrPath, v); | ||||||
|  |         else p.yaml.deleteIn(p.attrPath); | ||||||
|  |         p.onChange?.(); | ||||||
|  |       }} | ||||||
|  |       endAdornment={ | ||||||
|  |         <RouterLink to={p.refUrl} target="_blank"> | ||||||
|  |           <IconButton size="small"> | ||||||
|  |             <BookIcon /> | ||||||
|  |           </IconButton> | ||||||
|  |         </RouterLink> | ||||||
|  |       } | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function CloudInitBooleanInput(p: { | ||||||
|  |   editable: boolean; | ||||||
|  |   name: string; | ||||||
|  |   refUrl: string; | ||||||
|  |   attrPath: Iterable<unknown>; | ||||||
|  |   yaml: YAML.Document; | ||||||
|  |   onChange?: () => void; | ||||||
|  | }): React.ReactElement { | ||||||
|  |   return ( | ||||||
|  |     <CheckboxInput | ||||||
|  |       editable={p.editable} | ||||||
|  |       label={p.name} | ||||||
|  |       checked={p.yaml.getIn(p.attrPath) === true} | ||||||
|  |       onValueChange={(v) => { | ||||||
|  |         p.yaml.setIn(p.attrPath, v); | ||||||
|  |         p.onChange?.(); | ||||||
|  |       }} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -19,13 +19,10 @@ export function EditSection( | |||||||
|               display: "flex", |               display: "flex", | ||||||
|               justifyContent: "space-between", |               justifyContent: "space-between", | ||||||
|               alignItems: "center", |               alignItems: "center", | ||||||
|  |               marginBottom: "15px", | ||||||
|             }} |             }} | ||||||
|           > |           > | ||||||
|             {p.title && ( |             {p.title && <Typography variant="h5">{p.title}</Typography>} | ||||||
|               <Typography variant="h5" style={{ marginBottom: "15px" }}> |  | ||||||
|                 {p.title} |  | ||||||
|               </Typography> |  | ||||||
|             )} |  | ||||||
|             {p.actions} |             {p.actions} | ||||||
|           </span> |           </span> | ||||||
|         )} |         )} | ||||||
|   | |||||||
| @@ -799,6 +799,11 @@ export function TokenRightsEditor(p: { | |||||||
|           right={{ verb: "GET", path: "/api/server/bridges" }} |           right={{ verb: "GET", path: "/api/server/bridges" }} | ||||||
|           label="Get list of network bridges" |           label="Get list of network bridges" | ||||||
|         /> |         /> | ||||||
|  |         <RouteRight | ||||||
|  |           {...p} | ||||||
|  |           right={{ verb: "GET", path: "/api/server/export_configs" }} | ||||||
|  |           label="Export all configurations" | ||||||
|  |         /> | ||||||
|       </RightsSection> |       </RightsSection> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user