Compare commits
	
		
			16 Commits
		
	
	
		
			81ad70b0fb
			...
			20250618
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b633694f74 | |||
| ab16bd7bcf | |||
| 1080ab5cb2 | |||
| a2845ddafe | |||
| c968b64b51 | |||
| 12833dc6da | |||
| 8c4f2a9f2d | |||
| 9a6b6cfb2d | |||
| b28ca5f27d | |||
| 92f187bf91 | |||
| 9f1f4b44ca | |||
| b00d46105b | |||
| 7d6ccd4ce6 | |||
| b5371421e1 | |||
| 07d305c54e | |||
| 78c525f47a | 
| @@ -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 | ||||
|   | ||||
							
								
								
									
										269
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										269
									
								
								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" | ||||
| @@ -760,9 +811,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "clap" | ||||
| version = "4.5.38" | ||||
| version = "4.5.40" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" | ||||
| checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" | ||||
| dependencies = [ | ||||
|  "clap_builder", | ||||
|  "clap_derive", | ||||
| @@ -770,9 +821,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "clap_builder" | ||||
| version = "4.5.38" | ||||
| version = "4.5.40" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" | ||||
| checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" | ||||
| dependencies = [ | ||||
|  "anstream", | ||||
|  "anstyle", | ||||
| @@ -782,9 +833,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "clap_derive" | ||||
| version = "4.5.32" | ||||
| version = "4.5.40" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" | ||||
| checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" | ||||
| dependencies = [ | ||||
|  "heck", | ||||
|  "proc-macro2", | ||||
| @@ -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]] | ||||
| @@ -1622,18 +1699,47 @@ version = "0.1.12" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" | ||||
| dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "bytes", | ||||
|  "futures-channel", | ||||
|  "futures-util", | ||||
|  "http 1.3.1", | ||||
|  "http-body", | ||||
|  "hyper", | ||||
|  "ipnet", | ||||
|  "libc", | ||||
|  "percent-encoding", | ||||
|  "pin-project-lite", | ||||
|  "socket2", | ||||
|  "system-configuration", | ||||
|  "tokio", | ||||
|  "tower-service", | ||||
|  "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]] | ||||
| @@ -1839,6 +1945,16 @@ dependencies = [ | ||||
|  "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]] | ||||
| name = "is_terminal_polyfill" | ||||
| version = "1.70.1" | ||||
| @@ -1982,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" | ||||
| @@ -1992,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" | ||||
| @@ -2414,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" | ||||
| @@ -2783,9 +2938,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" | ||||
|  | ||||
| [[package]] | ||||
| name = "reqwest" | ||||
| version = "0.12.15" | ||||
| version = "0.12.20" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" | ||||
| checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" | ||||
| dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "bytes", | ||||
| @@ -2800,31 +2955,28 @@ dependencies = [ | ||||
|  "hyper-rustls", | ||||
|  "hyper-tls", | ||||
|  "hyper-util", | ||||
|  "ipnet", | ||||
|  "js-sys", | ||||
|  "log", | ||||
|  "mime", | ||||
|  "native-tls", | ||||
|  "once_cell", | ||||
|  "percent-encoding", | ||||
|  "pin-project-lite", | ||||
|  "rustls-pemfile", | ||||
|  "rustls-pki-types", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "serde_urlencoded", | ||||
|  "sync_wrapper", | ||||
|  "system-configuration", | ||||
|  "tokio", | ||||
|  "tokio-native-tls", | ||||
|  "tokio-util", | ||||
|  "tower", | ||||
|  "tower-http", | ||||
|  "tower-service", | ||||
|  "url", | ||||
|  "wasm-bindgen", | ||||
|  "wasm-bindgen-futures", | ||||
|  "wasm-streams", | ||||
|  "web-sys", | ||||
|  "windows-registry", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -2933,15 +3085,6 @@ dependencies = [ | ||||
|  "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]] | ||||
| name = "rustls-pki-types" | ||||
| version = "1.12.0" | ||||
| @@ -3438,9 +3581,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "tokio" | ||||
| version = "1.45.1" | ||||
| version = "1.45.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" | ||||
| checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" | ||||
| dependencies = [ | ||||
|  "backtrace", | ||||
|  "bytes", | ||||
| @@ -3547,6 +3690,24 @@ dependencies = [ | ||||
|  "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]] | ||||
| name = "tower-layer" | ||||
| version = "0.3.3" | ||||
| @@ -3760,6 +3921,7 @@ dependencies = [ | ||||
|  "actix-ws", | ||||
|  "anyhow", | ||||
|  "basic-jwt", | ||||
|  "chrono", | ||||
|  "clap", | ||||
|  "dotenvy", | ||||
|  "env_logger", | ||||
| @@ -3787,6 +3949,7 @@ dependencies = [ | ||||
|  "url", | ||||
|  "uuid", | ||||
|  "virt", | ||||
|  "zip", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -4325,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" | ||||
| @@ -4359,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" | ||||
|   | ||||
| @@ -8,7 +8,7 @@ edition = "2024" | ||||
| [dependencies] | ||||
| log = "0.4.27" | ||||
| 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"] } | ||||
| lazy_static = "1.5.0" | ||||
| actix = "0.13.5" | ||||
| @@ -19,7 +19,7 @@ actix-identity = "0.8.0" | ||||
| actix-cors = "0.7.1" | ||||
| actix-files = "0.6.6" | ||||
| actix-ws = "0.3.0" | ||||
| actix-http = "3.10.0" | ||||
| actix-http = "3.11.0" | ||||
| serde = { version = "1.0.219", features = ["derive"] } | ||||
| serde_json = "1.0.140" | ||||
| serde_yml = "0.0.12" | ||||
| @@ -28,7 +28,7 @@ futures-util = "0.3.31" | ||||
| anyhow = "1.0.98" | ||||
| actix-multipart = "0.7.2" | ||||
| tempfile = "3.20.0" | ||||
| reqwest = { version = "0.12.15", features = ["stream"] } | ||||
| reqwest = { version = "0.12.20", features = ["stream"] } | ||||
| url = "2.5.4" | ||||
| virt = "0.4.2" | ||||
| sysinfo = { version = "0.35.1", features = ["serde"] } | ||||
| @@ -37,7 +37,7 @@ lazy-regex = "3.4.1" | ||||
| thiserror = "2.0.12" | ||||
| image = "0.25.6" | ||||
| 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" | ||||
| ipnetwork = { version = "0.21.1", features = ["serde"] } | ||||
| num = "0.4.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() | ||||
|     )) | ||||
| } | ||||
|   | ||||
							
								
								
									
										19
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								virtweb_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -10,7 +10,7 @@ | ||||
|       "dependencies": { | ||||
|         "@emotion/react": "^11.14.0", | ||||
|         "@emotion/styled": "^11.14.0", | ||||
|         "@fontsource/roboto": "^5.2.5", | ||||
|         "@fontsource/roboto": "^5.2.6", | ||||
|         "@mdi/js": "^7.4.47", | ||||
|         "@mdi/react": "^1.6.1", | ||||
|         "@monaco-editor/react": "^4.7.0", | ||||
| @@ -29,13 +29,14 @@ | ||||
|         "react-syntax-highlighter": "^15.6.1", | ||||
|         "react-vnc": "^3.1.0", | ||||
|         "uuid": "^11.1.0", | ||||
|         "xml-formatter": "^3.6.6" | ||||
|         "xml-formatter": "^3.6.6", | ||||
|         "yaml": "^2.8.0" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@eslint/js": "^9.27.0", | ||||
|         "@types/humanize-duration": "^3.27.4", | ||||
|         "@types/jest": "^29.5.14", | ||||
|         "@types/react": "^19.1.6", | ||||
|         "@types/react": "^19.1.8", | ||||
|         "@types/react-dom": "^19.1.6", | ||||
|         "@types/react-syntax-highlighter": "^15.5.13", | ||||
|         "@types/uuid": "^10.0.0", | ||||
| @@ -780,9 +781,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@fontsource/roboto": { | ||||
|       "version": "5.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz", | ||||
|       "integrity": "sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==", | ||||
|       "version": "5.2.6", | ||||
|       "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.6.tgz", | ||||
|       "integrity": "sha512-hzarG7yAhMoP418smNgfY4fO7UmuUEm5JUtbxCoCcFHT0hOJB+d/qAEyoNjz7YkPU5OjM2LM8rJnW8hfm0JLaA==", | ||||
|       "license": "OFL-1.1", | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ayuhito" | ||||
| @@ -1624,9 +1625,9 @@ | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/react": { | ||||
|       "version": "19.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", | ||||
|       "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", | ||||
|       "version": "19.1.8", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", | ||||
|       "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "csstype": "^3.0.2" | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
|   "dependencies": { | ||||
|     "@emotion/react": "^11.14.0", | ||||
|     "@emotion/styled": "^11.14.0", | ||||
|     "@fontsource/roboto": "^5.2.5", | ||||
|     "@fontsource/roboto": "^5.2.6", | ||||
|     "@mdi/js": "^7.4.47", | ||||
|     "@mdi/react": "^1.6.1", | ||||
|     "@monaco-editor/react": "^4.7.0", | ||||
| @@ -31,13 +31,14 @@ | ||||
|     "react-syntax-highlighter": "^15.6.1", | ||||
|     "react-vnc": "^3.1.0", | ||||
|     "uuid": "^11.1.0", | ||||
|     "xml-formatter": "^3.6.6" | ||||
|     "xml-formatter": "^3.6.6", | ||||
|     "yaml": "^2.8.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@eslint/js": "^9.27.0", | ||||
|     "@types/humanize-duration": "^3.27.4", | ||||
|     "@types/jest": "^29.5.14", | ||||
|     "@types/react": "^19.1.6", | ||||
|     "@types/react": "^19.1.8", | ||||
|     "@types/react-dom": "^19.1.6", | ||||
|     "@types/react-syntax-highlighter": "^15.5.13", | ||||
|     "@types/uuid": "^10.0.0", | ||||
|   | ||||
| @@ -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 }}> | ||||
|   | ||||
| @@ -17,7 +17,9 @@ export function CheckboxInput(p: { | ||||
|         <Checkbox | ||||
|           disabled={!p.editable} | ||||
|           checked={p.checked} | ||||
|           onChange={(e) => { p.onValueChange(e.target.checked); }} | ||||
|           onChange={(e) => { | ||||
|             p.onValueChange(e.target.checked); | ||||
|           }} | ||||
|         /> | ||||
|       } | ||||
|       label={p.label} | ||||
|   | ||||
| @@ -1,8 +1,14 @@ | ||||
| /* eslint-disable @typescript-eslint/no-base-to-string */ | ||||
|  | ||||
| import Editor from "@monaco-editor/react"; | ||||
| import BookIcon from "@mui/icons-material/Book"; | ||||
| import RefreshIcon from "@mui/icons-material/Refresh"; | ||||
| import { Grid, IconButton, InputAdornment, Tooltip } from "@mui/material"; | ||||
| import React from "react"; | ||||
| import { v4 as uuidv4 } from "uuid"; | ||||
| import YAML from "yaml"; | ||||
| import { VMInfo } from "../../api/VMApi"; | ||||
| import { RouterLink } from "../RouterLink"; | ||||
| import { CheckboxInput } from "./CheckboxInput"; | ||||
| import { EditSection } from "./EditSection"; | ||||
| import { SelectInput } from "./SelectInput"; | ||||
| @@ -38,6 +44,14 @@ export function CloudInitEditor(p: CloudInitProps): React.ReactElement { | ||||
|           {...p} | ||||
|           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> | ||||
|     </> | ||||
|   ); | ||||
| @@ -108,12 +122,27 @@ function CloudInitMetadata(p: CloudInitProps): React.ReactElement { | ||||
|  | ||||
| function CloudInitRawUserData(p: CloudInitProps): React.ReactElement { | ||||
|   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 | ||||
|         theme="vs-dark" | ||||
|         options={{ | ||||
|           readOnly: !p.editable, | ||||
|           quickSuggestions: { other: true, comments: true, strings: true }, | ||||
|           wordWrap: "on", | ||||
|         }} | ||||
|         language="yaml" | ||||
|         height={"30vh"} | ||||
| @@ -126,3 +155,187 @@ function CloudInitRawUserData(p: CloudInitProps): React.ReactElement { | ||||
|     </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", | ||||
|               justifyContent: "space-between", | ||||
|               alignItems: "center", | ||||
|               marginBottom: "15px", | ||||
|             }} | ||||
|           > | ||||
|             {p.title && ( | ||||
|               <Typography variant="h5" style={{ marginBottom: "15px" }}> | ||||
|                 {p.title} | ||||
|               </Typography> | ||||
|             )} | ||||
|             {p.title && <Typography variant="h5">{p.title}</Typography>} | ||||
|             {p.actions} | ||||
|           </span> | ||||
|         )} | ||||
|   | ||||
| @@ -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