Compare commits

...

26 Commits

Author SHA1 Message Date
93fbb31273 Update dependency @emotion/react to v11.13.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-22 00:19:37 +00:00
b4eb6f7ea4 Update dependency @mui/icons-material to v6.1.8
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-21 00:18:34 +00:00
00ff6f0b50 Update Rust crate serde_json to v1.0.133
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-18 00:08:43 +00:00
324042f956 Update dependency @mui/icons-material to v6.1.7
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-14 00:52:55 +00:00
e466d03ec5 Update Rust crate clap to v4.5.21
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-14 00:08:48 +00:00
89ba09f872 Update dependency vite to v5.4.11
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-12 00:08:32 +00:00
a322c46ca4 Update dependency uuid to v11.0.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-11 00:27:57 +00:00
0915a3e2d9 Update dependency @mui/x-data-grid to v7.22.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-09 00:16:38 +00:00
07eceaf72f Update Rust crate thiserror to v2
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-11-08 00:16:43 +00:00
0e1396e177 Update dependency react-router-dom to v6.28.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-07 00:17:41 +00:00
e59f21984f Update Rust crate image to v0.25.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-06 00:16:16 +00:00
8c508acd32 Update Rust crate url to v2.5.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-05 00:16:35 +00:00
26e7af7675 Update Rust crate thiserror to v1.0.67
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-04 00:16:05 +00:00
2fadf53dea Sort VM groups
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 18:40:06 +01:00
2b58ce4d5e Hide default group if no VM is in this group
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 18:35:41 +01:00
9755bacc55 Divide per group only when there is at least one group defined
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 18:25:47 +01:00
8b16ce0c5d Sort VM by groups in VM list
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 18:18:39 +01:00
20e6d7931e Can change VM groups
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 18:02:03 +01:00
c908d00c62 Can get the list of groups
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 17:44:10 +01:00
55b49699eb Can assign a group to VMs
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 17:31:05 +01:00
91fe291341 Display allocated ressources to running VMs
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 16:36:24 +01:00
eec6bbb598 Update project dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 16:09:16 +01:00
d2243fa1c2 Update dependency @mui/x-charts to v7.22.1
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-02 01:06:03 +00:00
6e7dd7c1c4 Update Rust crate anyhow to v1.0.92
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-02 00:23:07 +00:00
e40e15287b Update Rust crate thiserror to v1.0.66
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-01 00:36:08 +00:00
800969b9cc Update node Docker tag to v23
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-31 00:36:42 +00:00
18 changed files with 747 additions and 241 deletions

View File

@ -5,7 +5,7 @@ name: default
steps:
- name: web_build
image: node:22
image: node:23
volumes:
- name: web_app
path: /tmp/web_build

View File

@ -368,12 +368,6 @@ dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler2"
version = "2.0.0"
@ -475,9 +469,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
@ -509,9 +503,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.91"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
[[package]]
name = "arbitrary"
@ -580,7 +574,7 @@ dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide 0.8.0",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
@ -750,9 +744,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.31"
version = "1.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9"
dependencies = [
"jobserver",
"libc",
@ -793,9 +787,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.20"
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [
"clap_builder",
"clap_derive",
@ -803,9 +797,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.20"
version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [
"anstream",
"anstyle",
@ -1080,6 +1074,17 @@ dependencies = [
"subtle",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -1177,15 +1182,14 @@ dependencies = [
[[package]]
name = "exr"
version = "1.72.0"
version = "1.73.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
dependencies = [
"bit_field",
"flume",
"half",
"lebe",
"miniz_oxide 0.7.4",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
@ -1223,16 +1227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
dependencies = [
"crc32fast",
"miniz_oxide 0.8.0",
]
[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"spin",
"miniz_oxide",
]
[[package]]
@ -1640,6 +1635,124 @@ dependencies = [
"tracing",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@ -1648,19 +1761,30 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "image"
version = "0.25.4"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [
"bytemuck",
"byteorder-lite",
@ -1887,6 +2011,12 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "local-channel"
version = "0.1.5"
@ -1936,6 +2066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
dependencies = [
"cfg-if",
"rayon",
]
[[package]]
@ -1975,15 +2106,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@ -2333,7 +2455,7 @@ dependencies = [
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.8.0",
"miniz_oxide",
]
[[package]]
@ -2494,7 +2616,7 @@ dependencies = [
"rand_chacha",
"simd_helpers",
"system-deps",
"thiserror",
"thiserror 1.0.67",
"v_frame",
"wasm-bindgen",
]
@ -2510,6 +2632,7 @@ dependencies = [
"loop9",
"quick-error",
"rav1e",
"rayon",
"rgb",
]
@ -2849,9 +2972,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@ -2959,7 +3082,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"thiserror 1.0.67",
"time",
]
@ -2993,9 +3116,6 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
@ -3007,6 +3127,12 @@ dependencies = [
"der",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"
@ -3021,9 +3147,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.85"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@ -3039,6 +3165,17 @@ dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sysinfo"
version = "0.32.0"
@ -3109,18 +3246,38 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.65"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.67",
]
[[package]]
name = "thiserror"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl 2.0.3",
]
[[package]]
name = "thiserror-impl"
version = "1.0.65"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
@ -3170,20 +3327,15 @@ dependencies = [
]
[[package]]
name = "tinyvec"
version = "1.8.0"
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"tinyvec_macros",
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.41.0"
@ -3313,27 +3465,12 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-bidi"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.6"
@ -3358,9 +3495,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.2"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
dependencies = [
"form_urlencoded",
"idna",
@ -3373,6 +3510,18 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
@ -3490,7 +3639,7 @@ dependencies = [
"serde_json",
"sysinfo",
"tempfile",
"thiserror",
"thiserror 2.0.3",
"tokio",
"url",
"uuid",
@ -3640,7 +3789,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -3823,6 +3972,42 @@ dependencies = [
"memchr",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
@ -3844,12 +4029,55 @@ dependencies = [
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zstd"
version = "0.13.2"

View File

@ -33,7 +33,7 @@ virt = "0.4.1"
sysinfo = { version = "0.32.0", features = ["serde"] }
uuid = { version = "1.11.0", features = ["v4", "serde"] }
lazy-regex = "3.3.0"
thiserror = "1.0.65"
thiserror = "2.0.0"
image = "0.25.4"
rand = "0.8.5"
bytes = "1.8.0"

View File

@ -0,0 +1,16 @@
use crate::controllers::{HttpResult, LibVirtReq};
use actix_web::HttpResponse;
/// Get the list of groups
pub async fn list(client: LibVirtReq) -> HttpResult {
let groups = match client.get_full_groups_list().await {
Err(e) => {
log::error!("Failed to get the list of groups! {e}");
return Ok(HttpResponse::InternalServerError()
.json(format!("Failed to get the list of groups! {e}")));
}
Ok(l) => l,
};
Ok(HttpResponse::Ok().json(groups))
}

View File

@ -8,6 +8,7 @@ use std::io::ErrorKind;
pub mod api_tokens_controller;
pub mod auth_controller;
pub mod groups_controller;
pub mod iso_controller;
pub mod network_controller;
pub mod nwfilter_controller;

View File

@ -40,6 +40,7 @@ struct ServerConstraints {
vnc_token_duration: u64,
vm_name_size: LenConstraints,
vm_title_size: LenConstraints,
group_id_size: LenConstraints,
memory_size: LenConstraints,
disk_name_size: LenConstraints,
disk_size: LenConstraints,
@ -72,6 +73,7 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
vm_name_size: LenConstraints { min: 2, max: 50 },
vm_title_size: LenConstraints { min: 0, max: 50 },
group_id_size: LenConstraints { min: 3, max: 50 },
memory_size: LenConstraints {
min: constants::MIN_VM_MEMORY,
max: constants::MAX_VM_MEMORY,

View File

@ -21,7 +21,7 @@ struct VMUuid {
/// Create a new VM
pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
let domain = match req.0.as_tomain() {
let domain = match req.0.as_domain() {
Ok(d) => d,
Err(e) => {
log::error!("Failed to extract domain info! {e}");
@ -83,6 +83,8 @@ pub async fn get_single(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> H
}
};
log::debug!("INFO={info:#?}");
let state = client.get_domain_state(id.uid).await?;
Ok(HttpResponse::Ok().json(VMInfoAndState {
@ -112,7 +114,7 @@ pub async fn update(
id: web::Path<SingleVMUUidReq>,
req: web::Json<VMInfo>,
) -> HttpResult {
let mut domain = match req.0.as_tomain() {
let mut domain = match req.0.as_domain() {
Ok(d) => d,
Err(e) => {
log::error!("Failed to extract domain info! {e}");

View File

@ -7,8 +7,9 @@ use crate::libvirt_lib_structures::XMLUuid;
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::libvirt_rest_structures::vm::{VMGroupId, VMInfo};
use actix::Addr;
use std::collections::HashSet;
#[derive(Clone)]
pub struct LibVirtClient(pub Addr<LibVirtActor>);
@ -107,6 +108,20 @@ impl LibVirtClient {
.await?
}
/// Get the full list of groups
pub async fn get_full_groups_list(&self) -> anyhow::Result<Vec<VMGroupId>> {
let domains = self.get_full_domains_list().await?;
let mut out = HashSet::new();
for d in domains {
if let Some(g) = VMInfo::from_domain(d)?.group {
out.insert(g);
}
}
let mut out: Vec<_> = out.into_iter().collect();
out.sort();
Ok(out)
}
/// Update a network configuration
pub async fn update_network(
&self,

View File

@ -1,7 +1,25 @@
use crate::libvirt_lib_structures::XMLUuid;
/// VirtWeb specific metadata
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
#[serde(rename = "virtweb", default)]
pub struct DomainMetadataVirtWebXML {
#[serde(rename = "@xmlns:virtweb", default)]
pub ns: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
}
/// Domain metadata
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
#[serde(rename = "metadata")]
pub struct DomainMetadataXML {
#[serde(rename = "virtweb:metadata", default)]
pub virtweb: DomainMetadataVirtWebXML,
}
/// OS information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")]
pub struct OSXML {
#[serde(rename = "@firmware", default)]
@ -11,7 +29,7 @@ pub struct OSXML {
}
/// OS Type information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")]
pub struct OSTypeXML {
#[serde(rename = "@arch")]
@ -23,7 +41,7 @@ pub struct OSTypeXML {
}
/// OS Loader information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "loader")]
pub struct OSLoaderXML {
#[serde(rename = "@secure")]
@ -31,39 +49,39 @@ pub struct OSLoaderXML {
}
/// Hypervisor features
#[derive(serde::Serialize, serde::Deserialize, Default)]
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
#[serde(rename = "features")]
pub struct FeaturesXML {
pub acpi: ACPIXML,
}
/// ACPI feature
#[derive(serde::Serialize, serde::Deserialize, Default)]
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
#[serde(rename = "acpi")]
pub struct ACPIXML {}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "mac")]
pub struct NetMacAddress {
#[serde(rename = "@address")]
pub address: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")]
pub struct NetIntSourceXML {
#[serde(rename = "@network")]
pub network: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")]
pub struct NetIntModelXML {
#[serde(rename = "@type")]
pub r#type: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")]
pub struct NetIntFilterParameterXML {
#[serde(rename = "@name")]
@ -72,7 +90,7 @@ pub struct NetIntFilterParameterXML {
pub value: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")]
pub struct NetIntfilterRefXML {
#[serde(rename = "@filter")]
@ -81,7 +99,7 @@ pub struct NetIntfilterRefXML {
pub parameters: Vec<NetIntFilterParameterXML>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "interface")]
pub struct DomainNetInterfaceXML {
#[serde(rename = "@type")]
@ -95,14 +113,14 @@ pub struct DomainNetInterfaceXML {
pub filterref: Option<NetIntfilterRefXML>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "input")]
pub struct DomainInputXML {
#[serde(rename = "@type")]
pub r#type: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "backend")]
pub struct TPMBackendXML {
#[serde(rename = "@type")]
@ -112,7 +130,7 @@ pub struct TPMBackendXML {
pub r#version: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "tpm")]
pub struct TPMDeviceXML {
#[serde(rename = "@model")]
@ -121,7 +139,7 @@ pub struct TPMDeviceXML {
}
/// Devices information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "devices")]
pub struct DevicesXML {
/// Graphics (used for VNC)
@ -150,7 +168,7 @@ pub struct DevicesXML {
}
/// Graphics information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "graphics")]
pub struct GraphicsXML {
#[serde(rename = "@type")]
@ -160,14 +178,14 @@ pub struct GraphicsXML {
}
/// Video device information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "video")]
pub struct VideoXML {
pub model: VideoModelXML,
}
/// Video model device information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")]
pub struct VideoModelXML {
#[serde(rename = "@type")]
@ -175,7 +193,7 @@ pub struct VideoModelXML {
}
/// Disk information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "disk")]
pub struct DiskXML {
#[serde(rename = "@type")]
@ -193,7 +211,7 @@ pub struct DiskXML {
pub address: Option<DiskAddressXML>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "driver")]
pub struct DiskDriverXML {
#[serde(rename = "@name")]
@ -204,14 +222,14 @@ pub struct DiskDriverXML {
pub r#cache: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")]
pub struct DiskSourceXML {
#[serde(rename = "@file")]
pub file: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "target")]
pub struct DiskTargetXML {
#[serde(rename = "@dev")]
@ -220,18 +238,18 @@ pub struct DiskTargetXML {
pub bus: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "readonly")]
pub struct DiskReadOnlyXML {}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "boot")]
pub struct DiskBootXML {
#[serde(rename = "@order")]
pub order: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "address")]
pub struct DiskAddressXML {
#[serde(rename = "@type")]
@ -251,7 +269,7 @@ pub struct DiskAddressXML {
}
/// Domain RAM information
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "memory")]
pub struct DomainMemoryXML {
#[serde(rename = "@unit")]
@ -261,7 +279,7 @@ pub struct DomainMemoryXML {
pub memory: usize,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "topology")]
pub struct DomainCPUTopology {
#[serde(rename = "@sockets")]
@ -272,14 +290,14 @@ pub struct DomainCPUTopology {
pub threads: usize,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")]
pub struct DomainVCPUXML {
#[serde(rename = "$value")]
pub body: usize,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")]
pub struct DomainCPUXML {
#[serde(rename = "@mode")]
@ -288,7 +306,7 @@ pub struct DomainCPUXML {
}
/// Domain information, see https://libvirt.org/formatdomain.html
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "domain")]
pub struct DomainXML {
/// Domain type (kvm)
@ -300,6 +318,9 @@ pub struct DomainXML {
pub genid: Option<uuid::Uuid>,
pub title: Option<String>,
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<DomainMetadataXML>,
pub os: OSXML,
#[serde(default)]
pub features: FeaturesXML,
@ -319,10 +340,32 @@ pub struct DomainXML {
pub on_crash: String,
}
const METADATA_START_MARKER: &str =
"<virtweb:metadata xmlns:virtweb=\"https://virtweb.communiquons.org\">";
const METADATA_END_MARKER: &str = "</virtweb:metadata>";
impl DomainXML {
/// Decode Domain structure from XML definition
pub fn parse_xml(xml: &str) -> anyhow::Result<Self> {
Ok(quick_xml::de::from_str(xml)?)
let mut res: Self = quick_xml::de::from_str(xml)?;
// Handle custom metadata parsing issue
//
// https://github.com/tafia/quick-xml/pull/797
if xml.contains(METADATA_START_MARKER) && xml.contains(METADATA_END_MARKER) {
let s = xml
.split_once(METADATA_START_MARKER)
.unwrap()
.1
.split_once(METADATA_END_MARKER)
.unwrap()
.0;
let s = format!("<virtweb>{s}</virtweb>");
let metadata: DomainMetadataVirtWebXML = quick_xml::de::from_str(&s)?;
res.metadata = Some(DomainMetadataXML { virtweb: metadata });
}
Ok(res)
}
/// Turn this domain into its XML definition

View File

@ -10,6 +10,11 @@ use crate::utils::files_utils::convert_size_unit_to_mb;
use lazy_regex::regex;
use num::Integer;
#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Ord, PartialOrd,
)]
pub struct VMGroupId(pub String);
#[derive(serde::Serialize, serde::Deserialize)]
pub enum BootType {
UEFI,
@ -59,6 +64,9 @@ pub struct VMInfo {
pub genid: Option<XMLUuid>,
pub title: Option<String>,
pub description: Option<String>,
/// Group associated with the VM (VirtWeb specific field)
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<VMGroupId>,
pub boot_type: BootType,
pub architecture: VMArchitecture,
/// VM allocated memory, in megabytes
@ -79,7 +87,7 @@ pub struct VMInfo {
impl VMInfo {
/// Turn this VM into a domain
pub fn as_tomain(&self) -> anyhow::Result<DomainXML> {
pub fn as_domain(&self) -> anyhow::Result<DomainXML> {
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
return Err(StructureExtraction("VM name is invalid!").into());
}
@ -105,6 +113,12 @@ impl VMInfo {
}
}
if let Some(group) = &self.group {
if !regex!("^[a-zA-Z0-9]+$").is_match(&group.0) {
return Err(StructureExtraction("VM group name is invalid!").into());
}
}
if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
return Err(StructureExtraction("VM memory is invalid!").into());
}
@ -282,6 +296,12 @@ impl VMInfo {
title: self.title.clone(),
description: self.description.clone(),
metadata: Some(DomainMetadataXML {
virtweb: DomainMetadataVirtWebXML {
ns: "https://virtweb.communiquons.org".to_string(),
group: self.group.clone().map(|g| g.0),
},
}),
os: OSXML {
r#type: OSTypeXML {
arch: match self.architecture {
@ -369,6 +389,13 @@ impl VMInfo {
genid: domain.genid.map(XMLUuid),
title: domain.title,
description: domain.description,
group: domain
.metadata
.clone()
.unwrap_or_default()
.virtweb
.group
.map(VMGroupId),
boot_type: match domain.os.loader {
None => BootType::UEFI,
Some(l) => match l.secure.as_str() {

View File

@ -22,7 +22,7 @@ use virtweb_backend::constants::{
MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
};
use virtweb_backend::controllers::{
api_tokens_controller, auth_controller, iso_controller, network_controller,
api_tokens_controller, auth_controller, groups_controller, iso_controller, network_controller,
nwfilter_controller, server_controller, static_controller, vm_controller,
};
use virtweb_backend::libvirt_client::LibVirtClient;
@ -210,6 +210,8 @@ async fn main() -> std::io::Result<()> {
web::get().to(vm_controller::vnc_token),
)
.route("/api/vnc", web::get().to(vm_controller::vnc))
// Groups controller
.route("/api/group/list", web::get().to(groups_controller::list))
// Network controller
.route(
"/api/network/create",

View File

@ -15,9 +15,9 @@
"@mdi/react": "^1.6.1",
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.1.6",
"@mui/x-charts": "^7.22.0",
"@mui/x-data-grid": "^7.22.0",
"@testing-library/jest-dom": "^6.6.2",
"@mui/x-charts": "^7.22.1",
"@mui/x-data-grid": "^7.22.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/humanize-duration": "^3.27.1",
@ -2509,16 +2509,16 @@
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
"integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.16.7",
"@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/serialize": "^1.2.0",
"@emotion/serialize": "^1.3.3",
"babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
@ -2528,14 +2528,14 @@
}
},
"node_modules/@emotion/cache": {
"version": "11.13.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
"integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.5.tgz",
"integrity": "sha512-Z3xbtJ+UcK76eWkagZ1onvn/wAVb1GOMuR15s30Fm2wrMgC7jzpnO2JZXr4eujTTqoQFUrZIw/rT0c6Zzjca1g==",
"license": "MIT",
"dependencies": {
"@emotion/memoize": "^0.9.0",
"@emotion/sheet": "^1.4.0",
"@emotion/utils": "^1.4.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"stylis": "4.2.0"
}
@ -2562,17 +2562,17 @@
"license": "MIT"
},
"node_modules/@emotion/react": {
"version": "11.13.3",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz",
"integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==",
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.5.tgz",
"integrity": "sha512-6zeCUxUH+EPF1s+YF/2hPVODeV/7V07YU5x+2tfuRL8MdW6rv5vb2+CBEGTGwBdux0OIERcOS+RzxeK80k2DsQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.12.0",
"@emotion/cache": "^11.13.0",
"@emotion/serialize": "^1.3.1",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/cache": "^11.13.5",
"@emotion/serialize": "^1.3.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
"@emotion/utils": "^1.4.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"hoist-non-react-statics": "^3.3.1"
},
@ -2586,15 +2586,15 @@
}
},
"node_modules/@emotion/serialize": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
"integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
"license": "MIT",
"dependencies": {
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/unitless": "^0.10.0",
"@emotion/utils": "^1.4.1",
"@emotion/utils": "^1.4.2",
"csstype": "^3.0.2"
}
},
@ -2643,9 +2643,9 @@
}
},
"node_modules/@emotion/utils": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
"integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
"license": "MIT"
},
"node_modules/@emotion/weak-memoize": {
@ -4166,9 +4166,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz",
"integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.8.tgz",
"integrity": "sha512-TGAvzwUg9hybDacwfIGFjI2bXYXrIqky+vMfaeay8rvT56/PNAlvIDUJ54kpT5KRc9AWAihOvtDI7/LJOThOmQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
@ -4176,9 +4176,9 @@
}
},
"node_modules/@mui/icons-material": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz",
"integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.8.tgz",
"integrity": "sha512-6frsXcf1TcJKWevWwRup6V4L8lzI33cbHcAjT83YLgKw0vYRZKY0kjMI9fhrJZdRWXgFFgKKvEv3GjoxbqFF7A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
@ -4191,7 +4191,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^6.1.6",
"@mui/material": "^6.1.8",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@ -4202,16 +4202,16 @@
}
},
"node_modules/@mui/material": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz",
"integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.8.tgz",
"integrity": "sha512-QZdQFnXct+7NXIzHgT3qt+sQiO7HYGZU2vymP9Xl9tUMXEOA/S1mZMMb7+WGZrk5TzNlU/kP/85K0da5V1jXoQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.1.6",
"@mui/system": "^6.1.6",
"@mui/core-downloads-tracker": "^6.1.8",
"@mui/system": "^6.1.8",
"@mui/types": "^7.2.19",
"@mui/utils": "^6.1.6",
"@mui/utils": "^6.1.8",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.11",
"clsx": "^2.1.1",
@ -4230,7 +4230,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^6.1.6",
"@mui/material-pigment-css": "^6.1.8",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@ -4251,13 +4251,13 @@
}
},
"node_modules/@mui/private-theming": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz",
"integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.8.tgz",
"integrity": "sha512-TuKl7msynCNCVvhX3c0ef1sF0Qb3VHcPs8XOGB/8bdOGBr/ynmIG1yTMjZeiFQXk8yN9fzK/FDEKMFxILNn3wg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/utils": "^6.1.6",
"@mui/utils": "^6.1.8",
"prop-types": "^15.8.1"
},
"engines": {
@ -4278,9 +4278,9 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz",
"integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.8.tgz",
"integrity": "sha512-ZvEoT0U2nPLSLI+B4by4cVjaZnPT2f20f4JUPkyHdwLv65ZzuoHiTlwyhqX1Ch63p8bcJzKTHQVGisEoMK6PGA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
@ -4312,16 +4312,16 @@
}
},
"node_modules/@mui/system": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz",
"integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.8.tgz",
"integrity": "sha512-i1kLfQoWxzFpXTBQIuPoA3xKnAnP3en4I2T8xIolovSolGQX5k8vGjw1JaydQS40td++cFsgCdEU458HDNTGUA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/private-theming": "^6.1.6",
"@mui/styled-engine": "^6.1.6",
"@mui/private-theming": "^6.1.8",
"@mui/styled-engine": "^6.1.8",
"@mui/types": "^7.2.19",
"@mui/utils": "^6.1.6",
"@mui/utils": "^6.1.8",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@ -4366,9 +4366,9 @@
}
},
"node_modules/@mui/utils": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz",
"integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==",
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.8.tgz",
"integrity": "sha512-O2DWb1kz8hiANVcR7Z4gOB3SvPPsSQGUmStpyBDzde6dJIfBzgV9PbEQOBZd3EBsd1pB+Uv1z5LAJAbymmawrA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
@ -4396,9 +4396,9 @@
}
},
"node_modules/@mui/x-charts": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.22.0.tgz",
"integrity": "sha512-B70ix8keyww9CpfdwbsHygQGsgEySCXuHhGrDRiVyFgK+Be4edBWNswbL3ngIp37CHBbWegaYkPp/Q9GDas0AA==",
"version": "7.22.1",
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.22.1.tgz",
"integrity": "sha512-zgr8CN4yLen5puqaX7Haj5+AoVG7E13HHsIiDoEAuQvuFDF0gKTxTTdLSKXqhd1qJUIIzJaztZtrr3YCVrENqw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.7",
@ -4454,9 +4454,9 @@
}
},
"node_modules/@mui/x-data-grid": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.22.0.tgz",
"integrity": "sha512-gXl7+hG0YRNU3YODlPvz6Q/9+EeUsPAWn/u2YMQmYTgwAxeY5QE3lY224VRnwM5v9SfTFheo1kzAKmXPdjb9tQ==",
"version": "7.22.2",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.22.2.tgz",
"integrity": "sha512-yfy2s5A6tbajQZiEdsba49T4FYb9F0WPrzbbG30dl1+sIiX4ZRX7ma44UIDGPZrsZv8xkkE+p8qeJxZ7OaMteA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.7",
@ -4735,9 +4735,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz",
"integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==",
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz",
"integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@ -5346,9 +5346,9 @@
}
},
"node_modules/@testing-library/jest-dom": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz",
"integrity": "sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
"integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
"license": "MIT",
"dependencies": {
"@adobe/css-tools": "^4.4.0",
@ -18203,12 +18203,12 @@
}
},
"node_modules/react-router": {
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz",
"integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz",
"integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.20.0"
"@remix-run/router": "1.21.0"
},
"engines": {
"node": ">=14.0.0"
@ -18218,13 +18218,13 @@
}
},
"node_modules/react-router-dom": {
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz",
"integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz",
"integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.20.0",
"react-router": "6.27.0"
"@remix-run/router": "1.21.0",
"react-router": "6.28.0"
},
"engines": {
"node": ">=14.0.0"
@ -21062,9 +21062,9 @@
}
},
"node_modules/uuid": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz",
"integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
@ -21110,9 +21110,9 @@
}
},
"node_modules/vite": {
"version": "5.4.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",

View File

@ -11,9 +11,9 @@
"@mdi/react": "^1.6.1",
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.1.6",
"@mui/x-charts": "^7.22.0",
"@mui/x-data-grid": "^7.22.0",
"@testing-library/jest-dom": "^6.6.2",
"@mui/x-charts": "^7.22.1",
"@mui/x-data-grid": "^7.22.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/humanize-duration": "^3.27.1",

View File

@ -0,0 +1,15 @@
import { APIClient } from "./ApiClient";
export class GroupApi {
/**
* Get the entire list of networks
*/
static async GetList(): Promise<string[]> {
return (
await APIClient.exec({
method: "GET",
uri: "/group/list",
})
).data;
}
}

View File

@ -16,6 +16,7 @@ export interface ServerConstraints {
vnc_token_duration: number;
vm_name_size: LenConstraint;
vm_title_size: LenConstraint;
group_id_size: LenConstraint;
memory_size: LenConstraint;
disk_name_size: LenConstraint;
disk_size: LenConstraint;

View File

@ -63,6 +63,7 @@ interface VMInfoInterface {
genid?: string;
title?: string;
description?: string;
group?: string;
boot_type: "UEFI" | "UEFISecureBoot";
architecture: "i686" | "x86_64";
memory: number;
@ -80,6 +81,7 @@ export class VMInfo implements VMInfoInterface {
genid?: string;
title?: string;
description?: string;
group?: string;
boot_type: "UEFI" | "UEFISecureBoot";
architecture: "i686" | "x86_64";
number_vcpu: number;
@ -96,6 +98,7 @@ export class VMInfo implements VMInfoInterface {
this.genid = int.genid;
this.title = int.title;
this.description = int.description;
this.group = int.group;
this.boot_type = int.boot_type;
this.architecture = int.architecture;
this.number_vcpu = int.number_vcpu;

View File

@ -1,3 +1,5 @@
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import VisibilityIcon from "@mui/icons-material/Visibility";
import {
Button,
@ -7,6 +9,7 @@ import {
TableBody,
TableCell,
TableContainer,
TableFooter,
TableHead,
TableRow,
Tooltip,
@ -14,19 +17,27 @@ import {
import { filesize } from "filesize";
import React from "react";
import { useNavigate } from "react-router-dom";
import { VMApi, VMInfo } from "../api/VMApi";
import { GroupApi } from "../api/GroupApi";
import { VMApi, VMInfo, VMState } from "../api/VMApi";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { RouterLink } from "../widgets/RouterLink";
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
import { VMStatusWidget } from "../widgets/vms/VMStatusWidget";
export function VMListRoute(): React.ReactElement {
const [groups, setGroups] = React.useState<Array<string | undefined>>();
const [list, setList] = React.useState<VMInfo[] | undefined>();
const loadKey = React.useRef(1);
const load = async () => {
setList(await VMApi.GetList());
const groups: Array<string | undefined> = await GroupApi.GetList();
const list = await VMApi.GetList();
if (list.find((v) => !v.group) !== undefined) groups.push(undefined);
setGroups(groups);
setList(list);
};
const reload = () => {
@ -51,7 +62,7 @@ export function VMListRoute(): React.ReactElement {
</>
}
>
<VMListWidget list={list!} onReload={reload} />
<VMListWidget list={list!} groups={groups!} onReload={reload} />
</VirtWebRouteContainer>
)}
/>
@ -59,11 +70,37 @@ export function VMListRoute(): React.ReactElement {
}
function VMListWidget(p: {
groups: Array<string | undefined>;
list: VMInfo[];
onReload: () => void;
}): React.ReactElement {
const navigate = useNavigate();
const [hiddenGroups, setHiddenGroups] = React.useState<
Set<string | undefined>
>(new Set());
const [runningVMs, setRunningVMs] = React.useState<Set<string>>(new Set());
const toggleHiddenGroup = (g: string | undefined) => {
if (hiddenGroups.has(g)) hiddenGroups.delete(g);
else hiddenGroups.add(g);
setHiddenGroups(new Set([...hiddenGroups]));
};
const updateVMState = (v: VMInfo, s: VMState) => {
const running = s !== "Shutoff";
if (runningVMs.has(v.name) === running) {
return;
}
if (running) runningVMs.add(v.name);
else runningVMs.delete(v.name);
setRunningVMs(new Set([...runningVMs]));
};
return (
<TableContainer component={Paper}>
<Table>
@ -72,39 +109,100 @@ function VMListWidget(p: {
<TableCell>Name</TableCell>
<TableCell>Description</TableCell>
<TableCell>Memory</TableCell>
<TableCell>vCPU</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{p.list.map((row) => (
<TableRow
hover
key={row.name}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
onDoubleClick={() => navigate(row.ViewURL)}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell>{row.description ?? ""}</TableCell>
<TableCell>{filesize(row.memory * 1000 * 1000)}</TableCell>
<TableCell>
<VMStatusWidget vm={row} />
</TableCell>
<TableCell>
<Tooltip title="View this VM">
<RouterLink to={row.ViewURL}>
<IconButton>
<VisibilityIcon />
{p.groups.map((g, num) => (
<React.Fragment key={num}>
{p.groups.length > 1 && (
<TableRow>
<TableCell
style={{ paddingBottom: 2, paddingTop: 2 }}
colSpan={6}
>
<IconButton
size="small"
onClick={() => toggleHiddenGroup(g)}
>
{!hiddenGroups?.has(g) ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</RouterLink>
</Tooltip>
</TableCell>
</TableRow>
{g ?? "default"}
</TableCell>
</TableRow>
)}
{!hiddenGroups.has(g) &&
p.list
.filter((row) => row.group === g)
.map((row) => (
<TableRow
hover
key={row.name}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
onDoubleClick={() => navigate(row.ViewURL)}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell>{row.description ?? ""}</TableCell>
<TableCell>{vmMemoryToHuman(row.memory)}</TableCell>
<TableCell>{row.number_vcpu}</TableCell>
<TableCell>
<VMStatusWidget
vm={row}
onChange={(s) => updateVMState(row, s)}
/>
</TableCell>
<TableCell>
<Tooltip title="View this VM">
<RouterLink to={row.ViewURL}>
<IconButton>
<VisibilityIcon />
</IconButton>
</RouterLink>
</Tooltip>
</TableCell>
</TableRow>
))}
</React.Fragment>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell>
{vmMemoryToHuman(
p.list
.filter((v) => runningVMs.has(v.name))
.reduce((s, v) => s + v.memory, 0)
)}
{" / "}
{vmMemoryToHuman(p.list.reduce((s, v) => s + v.memory, 0))}
</TableCell>
<TableCell>
{p.list
.filter((v) => runningVMs.has(v.name))
.reduce((s, v) => s + v.number_vcpu, 0)}
{" / "}
{p.list.reduce((s, v) => s + v.number_vcpu, 0)}
</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
);
}
function vmMemoryToHuman(size: number): string {
return filesize(size * 1000 * 1000);
}

View File

@ -1,8 +1,11 @@
import { Button } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import ListIcon from "@mui/icons-material/List";
import { Button, IconButton, Tooltip } from "@mui/material";
import Grid from "@mui/material/Grid2";
import React from "react";
import { useNavigate } from "react-router-dom";
import { validate as validateUUID } from "uuid";
import { GroupApi } from "../../api/GroupApi";
import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi";
import { NWFilter, NWFilterApi } from "../../api/NWFilterApi";
import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
@ -32,6 +35,7 @@ interface DetailsProps {
}
export function VMDetails(p: DetailsProps): React.ReactElement {
const [groupsList, setGroupsList] = React.useState<string[] | any>();
const [isoList, setIsoList] = React.useState<IsoFile[] | any>();
const [vcpuCombinations, setVCPUCombinations] = React.useState<
number[] | any
@ -42,6 +46,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
>();
const load = async () => {
setGroupsList(await GroupApi.GetList());
setIsoList(await IsoFilesApi.GetList());
setVCPUCombinations(await ServerApi.NumberVCPUs());
setNetworksList(await NetworkApi.GetList());
@ -55,6 +60,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
errMsg="Failed to load the list of ISO files"
build={() => (
<VMDetailsInner
groupsList={groupsList}
isoList={isoList}
vcpuCombinations={vcpuCombinations}
networksList={networksList}
@ -75,6 +81,7 @@ enum VMTab {
}
type DetailsInnerProps = DetailsProps & {
groupsList: string[];
isoList: IsoFile[];
vcpuCombinations: number[];
networksList: NetworkInfo[];
@ -117,6 +124,8 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement {
}
function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
const [addGroup, setAddGroup] = React.useState(false);
return (
<Grid container spacing={2}>
{
@ -175,6 +184,50 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
}}
multiline={true}
/>
<div style={{ display: "flex" }}>
{addGroup ? (
<TextInput
label="Group"
editable={p.editable}
value={p.vm.group}
onValueChange={(v) => {
p.vm.group = v;
p.onChange?.();
}}
size={ServerApi.Config.constraints.group_id_size}
/>
) : (
<SelectInput
editable={p.editable}
label="Group"
onValueChange={(v) => {
p.vm.group = v! as any;
p.onChange?.();
}}
value={p.vm.group}
options={[
{ label: "None" },
...p.groupsList.map((g) => {
return { value: g, label: g };
}),
]}
/>
)}
{p.editable && (
<Tooltip
title={
addGroup
? "Use an existing group"
: "Add a new group instead of using existing one"
}
>
<IconButton onClick={() => setAddGroup(!addGroup)}>
{addGroup ? <ListIcon /> : <AddIcon />}
</IconButton>
</Tooltip>
)}
</div>
</EditSection>
{/* General section */}