Compare commits
12 Commits
092119f089
...
renovate/s
Author | SHA1 | Date | |
---|---|---|---|
1ad4262086 | |||
b633694f74 | |||
ab16bd7bcf | |||
1080ab5cb2 | |||
a2845ddafe | |||
c968b64b51 | |||
12833dc6da | |||
8c4f2a9f2d | |||
9a6b6cfb2d | |||
b28ca5f27d | |||
92f187bf91 | |||
9f1f4b44ca |
@ -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
|
||||||
|
204
virtweb_backend/Cargo.lock
generated
204
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"
|
||||||
@ -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]]
|
||||||
@ -1641,6 +1718,30 @@ dependencies = [
|
|||||||
"windows-registry",
|
"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]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -1997,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"
|
||||||
@ -2007,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"
|
||||||
@ -2429,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"
|
||||||
@ -3281,9 +3421,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.35.1"
|
version = "0.35.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a"
|
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -3781,6 +3921,7 @@ dependencies = [
|
|||||||
"actix-ws",
|
"actix-ws",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"basic-jwt",
|
"basic-jwt",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -3808,6 +3949,7 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
"virt",
|
"virt",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4346,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"
|
||||||
@ -4380,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"
|
||||||
|
@ -31,7 +31,7 @@ tempfile = "3.20.0"
|
|||||||
reqwest = { version = "0.12.20", 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.2", features = ["serde"] }
|
||||||
uuid = { version = "1.16.0", features = ["v4", "serde"] }
|
uuid = { version = "1.16.0", features = ["v4", "serde"] }
|
||||||
lazy-regex = "3.4.1"
|
lazy-regex = "3.4.1"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
@ -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()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
24
virtweb_frontend/package-lock.json
generated
24
virtweb_frontend/package-lock.json
generated
@ -29,10 +29,11 @@
|
|||||||
"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.29.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.8",
|
"@types/react": "^19.1.8",
|
||||||
@ -743,9 +744,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.29.0",
|
"version": "9.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
|
||||||
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
|
"integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2788,19 +2789,6 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/@eslint/js": {
|
|
||||||
"version": "9.27.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
|
|
||||||
"integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://eslint.org/donate"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/espree": {
|
"node_modules/espree": {
|
||||||
"version": "10.3.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
||||||
|
@ -31,10 +31,11 @@
|
|||||||
"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.29.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.8",
|
"@types/react": "^19.1.8",
|
||||||
|
@ -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