Compare commits

..

140 Commits

Author SHA1 Message Date
b943691d18 Update dependency @mui/x-data-grid to v7.22.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-25 00:18:32 +00:00
bc051ee678 Update dependency @emotion/styled to v11.13.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-24 00:19:12 +00:00
c7a2d1af23 Update Rust crate url to v2.5.4
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-23 00:19:18 +00:00
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
5917068add Fix sysinfo compatibility issue
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-30 22:04:13 +01:00
9b14d62830 Update frontend dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-30 21:47:22 +01:00
25503a688b WIP update frontend dependencies 2024-10-30 21:38:29 +01:00
868adc6cee Update backend dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-30 21:38:06 +01:00
528e30f3dc Update dependency react-router-dom to v6.27.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-12 00:28:44 +00:00
cc42d20e67 Update Rust crate quick-xml to v0.36.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-24 00:13:11 +00:00
d189470539 Update Rust crate image to v0.25.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-23 00:13:20 +00:00
6fdd9f91fa Update Rust crate env_logger to v0.11.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-22 00:34:28 +00:00
69c2d12fcd Update Rust crate clap to v4.5.18
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-21 00:34:43 +00:00
174e4a2c79 Merge pull request 'Update Rust crate anyhow to v1.0.89' (#172) from renovate/anyhow-1.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #172
2024-09-20 20:20:35 +00:00
847ab20a63 Update Rust crate anyhow to v1.0.89
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-18 00:34:00 +00:00
09c32a5555 Merge pull request 'Update Rust crate anyhow to v1.0.88' (#171) from renovate/anyhow-1.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #171
2024-09-17 09:30:08 +00:00
220c943642 Update Rust crate anyhow to v1.0.88
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-14 00:33:40 +00:00
e5d709c34f Update Rust crate actix-session to v0.10.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-13 00:35:05 +00:00
1e359a3b8e Update Rust crate actix-web-actors to v4.3.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-12 00:35:27 +00:00
dbff6358db Update dependency @mui/x-charts to v7.16.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-11 00:33:38 +00:00
d5c05a0cdd Update dependency react-router-dom to v6.26.2
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-10 01:09:06 +00:00
1d24d2a84c Update dependency @fontsource/roboto to v5.0.15
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-10 00:33:27 +00:00
d35dac2de8 Fix compatibility issue with sysinfo 0.31.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-09 09:44:43 +00:00
01141f77e2 Update Rust crate sysinfo to 0.31.0 2024-09-09 09:44:43 +00:00
56f765a15a Merge pull request 'Update dependency @mui/x-data-grid to v7.16.0' (#164) from renovate/mui-x-data-grid-7.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #164
2024-09-09 09:37:52 +00:00
639b7f4b38 Update dependency @mui/x-data-grid to v7.16.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-06 00:34:48 +00:00
babda3acd1 Update dependency @mui/x-data-grid to v7.15.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-05 00:33:27 +00:00
197b72cad0 Update dependency vite to v5.4.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-04 00:33:36 +00:00
1910c7081b Update dependency @mui/x-charts to v7.15.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-03 00:34:49 +00:00
eda0fc80b0 Update dependency @mui/material to v5.16.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-02 00:33:45 +00:00
f6e5356109 Update dependency @mui/icons-material to v5.16.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-01 00:34:23 +00:00
11da25b4c0 Update dependency @types/react to v18.3.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-31 00:34:10 +00:00
2599032581 Update dependency @testing-library/react to v16.0.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-30 00:33:25 +00:00
ed58d60e84 Update dependency @emotion/styled to v11.13.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-29 00:33:52 +00:00
a126e76eef Update dependency @emotion/react to v11.13.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-28 00:33:41 +00:00
c472dfe807 Fix compatibility issue
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-27 14:06:35 +00:00
c883f13bf8 Update Rust crate virt to 0.4.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-08-27 00:33:18 +00:00
b320f0b326 Update Rust crate quick-xml to 0.36.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-26 00:51:38 +00:00
9812120ed6 Update Rust crate actix-session to 0.10.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-26 00:33:15 +00:00
9ebd3b0315 Update dependency vite to v5.4.2
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-25 00:51:31 +00:00
24afa12be2 Update dependency react-router-dom to v6.26.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-25 00:32:42 +00:00
310689312c Update dependency @types/react to v18.3.4
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-24 01:10:27 +00:00
e7f4bc44e7 Update dependency @fontsource/roboto to v5.0.14
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-24 00:33:15 +00:00
165937f88b Merge pull request 'Update Rust crate bytes to v1.6.1' (#133) from renovate/bytes-1.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #133
2024-08-23 07:58:40 +00:00
a5d81de62b Merge pull request 'Update Rust crate actix-multipart to 0.7.0' (#123) from renovate/actix-multipart-0.x into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #123
2024-08-23 07:58:33 +00:00
ba2b3494cf Fix typo
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-15 12:18:30 +02:00
1944415371 Update dependency vite to v5.4.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-08 00:11:08 +00:00
4130fdda1c Update dependency react-router-dom to v6.26.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-02 00:11:23 +00:00
e4ef4c43bd Update dependency vite to v5.3.5
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-27 01:11:14 +00:00
afdf639d9b Update dependency @mui/x-data-grid to v7.11.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-27 00:11:27 +00:00
f2d6b9a5dd Merge pull request 'Update Rust crate clap to v4.5.11' (#141) from renovate/clap-4.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #141
2024-07-26 10:47:05 +00:00
e3b61baf11 Update dependency @mui/x-charts to v7.11.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-26 00:12:58 +00:00
20732860cf Update Rust crate clap to v4.5.11
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-07-26 00:12:55 +00:00
7f14ab8a54 Update Rust crate clap to v4.5.10
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-24 00:11:26 +00:00
87d4c5b0fd Update dependency @mui/x-data-grid to v7.11.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-19 01:05:09 +00:00
0f58f82e52 Update dependency @mui/x-charts to v7.11.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-19 00:10:23 +00:00
16b73a2030 Update dependency react-router-dom to v6.25.1
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-18 01:10:31 +00:00
a32954785d Update Rust crate thiserror to v1.0.63
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-18 00:10:51 +00:00
2789fc299f Update dependency react-router-dom to v6.25.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-17 01:22:08 +00:00
0257ecba0b Update dependency vite to v5.3.4
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-17 00:11:05 +00:00
17fc64b1fe Update Rust crate bytes to v1.6.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-07-15 00:10:23 +00:00
fbc818b5f3 Update dependency mui-file-input to v4.0.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-14 00:10:10 +00:00
a4292795d1 Update Rust crate thiserror to v1.0.62
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-13 00:10:26 +00:00
529e16c0c7 Update dependency @mui/x-data-grid to v7.10.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-12 01:03:51 +00:00
e1adc1456f Update dependency @mui/x-charts to v7.10.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-12 00:10:28 +00:00
f1f4a88ae3 Update dependency xml-formatter to v3.6.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-11 00:09:56 +00:00
8fdbb0f442 Update Rust crate uuid to v1.10.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-10 01:22:00 +00:00
9efb1b29df Update Rust crate clap to v4.5.9
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-10 00:10:55 +00:00
2c07f5f121 Update Rust crate sysinfo to v0.30.13
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-09 00:22:53 +00:00
953f6fdcf2 Update dependency mui-file-input to v4.0.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-08 00:23:02 +00:00
d66e384137 Update Rust crate actix-multipart to 0.7.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-07-07 00:24:06 +00:00
80bf70502f Update Rust crate serde to v1.0.204
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-07 00:24:04 +00:00
7f6cf26617 Update dependency @mui/x-data-grid to v7.9.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-06 01:20:01 +00:00
c9cf39bb76 Update dependency @mui/x-charts to v7.9.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-06 00:23:02 +00:00
93afb646ca Update Rust crate mime_guess to v2.0.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-05 00:23:07 +00:00
4b358acbde Update dependency vite to v5.3.3
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-04 01:34:28 +00:00
b97dbc8149 Update dependency react-router-dom to v6.24.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-04 00:23:37 +00:00
e1292ae922 Merge pull request 'Update Rust crate log to v0.4.22' (#116) from renovate/log-0.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #116
2024-07-03 09:18:45 +00:00
3e812b5530 Update Rust crate log to v0.4.22
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-07-03 00:23:20 +00:00
b1e268bf63 Update Rust crate clap to v4.5.8
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-03 00:23:13 +00:00
887c4608b4 Update Rust crate serde_json to v1.0.120
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-02 00:23:53 +00:00
49e33cfd57 Update dependency @mui/x-data-grid to v7.8.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-01 01:31:20 +00:00
842733caa3 Update Rust crate serde_json to v1.0.119
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-01 00:09:27 +00:00
b6b56fdba8 Update dependency @mui/x-charts to v7.8.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-30 01:17:10 +00:00
8163d5e52f Update Rust crate quick-xml to 0.35.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-30 00:08:33 +00:00
7aca0aee13 Merge pull request 'Update dependency @mui/icons-material to v5.15.21' (#108) from renovate/mui-icons-material-5.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #108
2024-06-29 08:41:12 +00:00
39fc34ef26 Merge pull request 'Update dependency @mui/material to v5.15.21' (#109) from renovate/mui-material-5.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #109
2024-06-29 08:41:01 +00:00
be06339bd7 Update dependency @mui/material to v5.15.21
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-29 00:18:48 +00:00
00c1047734 Update dependency @mui/icons-material to v5.15.21
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-29 00:18:45 +00:00
a6c54ada50 Update dependency vite to v5.3.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-28 00:16:53 +00:00
8803c6755b Update dependency react-router-dom to v6.24.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-26 01:11:34 +00:00
cdab9df5c1 Update Rust crate serde_json to v1.0.118
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-26 00:17:32 +00:00
75b8c1d9e9 Update Rust crate uuid to v1.9.1
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-25 01:11:17 +00:00
557fb7d97b Update Rust crate quick-xml to 0.34.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-25 00:17:11 +00:00
bb85e58008 Merge pull request 'Update Rust crate quick-xml to 0.33.0' (#102) from renovate/quick-xml-0.x into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #102
2024-06-24 06:54:21 +00:00
b8c1375f4f Update Rust crate quick-xml to 0.33.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-23 00:16:21 +00:00
a96f6f33df Update dependency @mui/x-data-grid to v7.7.1
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-22 01:15:11 +00:00
a55061a2cd Update dependency @mui/x-charts to v7.7.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-22 00:17:00 +00:00
e6d3dd926c Merge pull request 'Update dependency @types/uuid to v10' (#99) from renovate/uuid-10.x into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #99
2024-06-21 16:16:49 +00:00
95dc089943 Update dependency @types/uuid to v10
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-21 00:17:18 +00:00
dafef923f0 Update Rust crate actix-web to v4.8.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-21 00:17:14 +00:00
5095a701eb Update Rust crate actix-http to v3.8.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-20 00:15:59 +00:00
a157484105 Update Rust crate url to v2.5.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-19 00:16:23 +00:00
0e4bf4414c Update Rust crate reqwest to v0.12.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-18 00:17:46 +00:00
0a2a9d66e1 Merge pull request 'Update dependency vite to v5.3.1' (#93) from renovate/vite-5.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #93
2024-06-15 08:08:01 +00:00
c4ff5d0621 Update dependency uuid to v10
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-15 00:16:57 +00:00
ff1391694d Update dependency vite to v5.3.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-15 00:16:54 +00:00
368ae4e89d Merge pull request 'Update Rust crate clap to v4.5.7' (#85) from renovate/clap-4.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #85
2024-06-14 11:45:25 +00:00
a539c092f5 Update dependency @mui/x-data-grid to v7.7.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-14 01:08:33 +00:00
dbf44e6204 Update dependency @mui/x-charts to v7.7.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-14 00:16:42 +00:00
448b029c17 Update dependency @mui/material to v5.15.20
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-13 01:12:10 +00:00
f06082ce82 Update dependency @mui/icons-material to v5.15.20
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-13 00:14:17 +00:00
272763bdc3 Update Rust crate quick-xml to 0.32.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-12 00:55:57 +00:00
1dd2dfc684 Update dependency @vitejs/plugin-react to v4.3.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-12 00:13:51 +00:00
4f7161ae9e Update Rust crate clap to v4.5.7
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-11 00:14:46 +00:00
29 changed files with 6770 additions and 7475 deletions

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -8,41 +8,41 @@ edition = "2021"
[dependencies] [dependencies]
log = "0.4.21" log = "0.4.21"
env_logger = "0.11.3" env_logger = "0.11.3"
clap = { version = "4.5.4", features = ["derive", "env"] } clap = { version = "4.5.20", features = ["derive", "env"] }
light-openid = { version = "1.0.2", features = ["crypto-wrapper"] } light-openid = { version = "1.0.2", features = ["crypto-wrapper"] }
lazy_static = "1.4.0" lazy_static = "1.5.0"
actix = "0.13.3" actix = "0.13.3"
actix-web = "4.5.1" actix-web = "4.9.0"
actix-remote-ip = "0.1.0" actix-remote-ip = "0.1.0"
actix-session = { version = "0.9.0", features = ["cookie-session"] } actix-session = { version = "0.10.0", features = ["cookie-session"] }
actix-identity = "0.7.1" actix-identity = "0.8.0"
actix-cors = "0.7.0" actix-cors = "0.7.0"
actix-files = "0.6.5" actix-files = "0.6.5"
actix-web-actors = "4.3.0" actix-web-actors = "4.3.0"
actix-http = "3.6.0" actix-http = "3.9.0"
serde = { version = "1.0.199", features = ["derive"] } serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.116" serde_json = "1.0.132"
quick-xml = { version = "0.31.0", features = ["serialize", "overlapped-lists"] } quick-xml = { version = "0.37.0", features = ["serialize", "overlapped-lists"] }
futures-util = "0.3.30" futures-util = "0.3.31"
anyhow = "1.0.82" anyhow = "1.0.91"
actix-multipart = "0.6.1" actix-multipart = "0.7.0"
tempfile = "3.10.1" tempfile = "3.13.0"
reqwest = { version = "0.12.4", features = ["stream"] } reqwest = { version = "0.12.9", features = ["stream"] }
url = "2.5.0" url = "2.5.0"
virt = "0.3.1" virt = "0.4.1"
sysinfo = { version = "0.30.11", features = ["serde"] } sysinfo = { version = "0.32.0", features = ["serde"] }
uuid = { version = "1.8.0", features = ["v4", "serde"] } uuid = { version = "1.11.0", features = ["v4", "serde"] }
lazy-regex = "3.1.0" lazy-regex = "3.3.0"
thiserror = "1.0.59" thiserror = "2.0.0"
image = "0.25.1" image = "0.25.4"
rand = "0.8.5" rand = "0.8.5"
bytes = "1.6.0" bytes = "1.8.0"
tokio = "1.37.0" tokio = "1.41.0"
futures = "0.3.30" futures = "0.3.31"
ipnetwork = "0.20.0" ipnetwork = "0.20.0"
num = "0.4.2" num = "0.4.2"
rust-embed = { version = "8.3.0" } rust-embed = { version = "8.5.0" }
mime_guess = "2.0.4" mime_guess = "2.0.4"
dotenvy = "0.15.7" dotenvy = "0.15.7"
nix = { version = "0.28.0", features = ["net"] } nix = { version = "0.29.0", features = ["net"] }
basic-jwt = "0.2.0" basic-jwt = "0.2.0"

View File

@ -31,7 +31,7 @@ impl LibVirtActor {
"Will connect to hypvervisor at address '{}'", "Will connect to hypvervisor at address '{}'",
hypervisor_uri hypervisor_uri
); );
let conn = Connect::open(hypervisor_uri)?; let conn = Connect::open(Some(hypervisor_uri))?;
Ok(Self { m: conn }) Ok(Self { m: conn })
} }

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 api_tokens_controller;
pub mod auth_controller; pub mod auth_controller;
pub mod groups_controller;
pub mod iso_controller; pub mod iso_controller;
pub mod network_controller; pub mod network_controller;
pub mod nwfilter_controller; pub mod nwfilter_controller;

View File

@ -40,6 +40,7 @@ struct ServerConstraints {
vnc_token_duration: u64, vnc_token_duration: u64,
vm_name_size: LenConstraints, vm_name_size: LenConstraints,
vm_title_size: LenConstraints, vm_title_size: LenConstraints,
group_id_size: LenConstraints,
memory_size: LenConstraints, memory_size: LenConstraints,
disk_name_size: LenConstraints, disk_name_size: LenConstraints,
disk_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_name_size: LenConstraints { min: 2, max: 50 },
vm_title_size: LenConstraints { min: 0, max: 50 }, vm_title_size: LenConstraints { min: 0, max: 50 },
group_id_size: LenConstraints { min: 3, max: 50 },
memory_size: LenConstraints { memory_size: LenConstraints {
min: constants::MIN_VM_MEMORY, min: constants::MIN_VM_MEMORY,
max: constants::MAX_VM_MEMORY, max: constants::MAX_VM_MEMORY,
@ -171,7 +173,7 @@ pub async fn network_hook_status() -> HttpResult {
pub async fn number_vcpus() -> HttpResult { pub async fn number_vcpus() -> HttpResult {
let mut system = System::new(); let mut system = System::new();
system.refresh_cpu(); system.refresh_cpu_all();
let number_cpus = system.cpus().len(); let number_cpus = system.cpus().len();
assert_ne!(number_cpus, 0, "Got invlid number of CPU!"); assert_ne!(number_cpus, 0, "Got invlid number of CPU!");

View File

@ -21,7 +21,7 @@ struct VMUuid {
/// Create a new VM /// Create a new VM
pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult { 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, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("Failed to extract domain info! {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?; let state = client.get_domain_state(id.uid).await?;
Ok(HttpResponse::Ok().json(VMInfoAndState { Ok(HttpResponse::Ok().json(VMInfoAndState {
@ -112,7 +114,7 @@ pub async fn update(
id: web::Path<SingleVMUUidReq>, id: web::Path<SingleVMUUidReq>,
req: web::Json<VMInfo>, req: web::Json<VMInfo>,
) -> HttpResult { ) -> HttpResult {
let mut domain = match req.0.as_tomain() { let mut domain = match req.0.as_domain() {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("Failed to extract domain info! {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::hypervisor::HypervisorInfo;
use crate::libvirt_rest_structures::net::NetworkInfo; use crate::libvirt_rest_structures::net::NetworkInfo;
use crate::libvirt_rest_structures::nw_filter::NetworkFilter; 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 actix::Addr;
use std::collections::HashSet;
#[derive(Clone)] #[derive(Clone)]
pub struct LibVirtClient(pub Addr<LibVirtActor>); pub struct LibVirtClient(pub Addr<LibVirtActor>);
@ -107,6 +108,20 @@ impl LibVirtClient {
.await? .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 /// Update a network configuration
pub async fn update_network( pub async fn update_network(
&self, &self,

View File

@ -1,7 +1,25 @@
use crate::libvirt_lib_structures::XMLUuid; 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 /// OS information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")] #[serde(rename = "os")]
pub struct OSXML { pub struct OSXML {
#[serde(rename = "@firmware", default)] #[serde(rename = "@firmware", default)]
@ -11,7 +29,7 @@ pub struct OSXML {
} }
/// OS Type information /// OS Type information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")] #[serde(rename = "os")]
pub struct OSTypeXML { pub struct OSTypeXML {
#[serde(rename = "@arch")] #[serde(rename = "@arch")]
@ -23,7 +41,7 @@ pub struct OSTypeXML {
} }
/// OS Loader information /// OS Loader information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "loader")] #[serde(rename = "loader")]
pub struct OSLoaderXML { pub struct OSLoaderXML {
#[serde(rename = "@secure")] #[serde(rename = "@secure")]
@ -31,39 +49,39 @@ pub struct OSLoaderXML {
} }
/// Hypervisor features /// Hypervisor features
#[derive(serde::Serialize, serde::Deserialize, Default)] #[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
#[serde(rename = "features")] #[serde(rename = "features")]
pub struct FeaturesXML { pub struct FeaturesXML {
pub acpi: ACPIXML, pub acpi: ACPIXML,
} }
/// ACPI feature /// ACPI feature
#[derive(serde::Serialize, serde::Deserialize, Default)] #[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
#[serde(rename = "acpi")] #[serde(rename = "acpi")]
pub struct ACPIXML {} pub struct ACPIXML {}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "mac")] #[serde(rename = "mac")]
pub struct NetMacAddress { pub struct NetMacAddress {
#[serde(rename = "@address")] #[serde(rename = "@address")]
pub address: String, pub address: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")] #[serde(rename = "source")]
pub struct NetIntSourceXML { pub struct NetIntSourceXML {
#[serde(rename = "@network")] #[serde(rename = "@network")]
pub network: String, pub network: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")] #[serde(rename = "model")]
pub struct NetIntModelXML { pub struct NetIntModelXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
pub r#type: String, pub r#type: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")] #[serde(rename = "filterref")]
pub struct NetIntFilterParameterXML { pub struct NetIntFilterParameterXML {
#[serde(rename = "@name")] #[serde(rename = "@name")]
@ -72,7 +90,7 @@ pub struct NetIntFilterParameterXML {
pub value: String, pub value: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")] #[serde(rename = "filterref")]
pub struct NetIntfilterRefXML { pub struct NetIntfilterRefXML {
#[serde(rename = "@filter")] #[serde(rename = "@filter")]
@ -81,7 +99,7 @@ pub struct NetIntfilterRefXML {
pub parameters: Vec<NetIntFilterParameterXML>, pub parameters: Vec<NetIntFilterParameterXML>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "interface")] #[serde(rename = "interface")]
pub struct DomainNetInterfaceXML { pub struct DomainNetInterfaceXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -95,14 +113,14 @@ pub struct DomainNetInterfaceXML {
pub filterref: Option<NetIntfilterRefXML>, pub filterref: Option<NetIntfilterRefXML>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "input")] #[serde(rename = "input")]
pub struct DomainInputXML { pub struct DomainInputXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
pub r#type: String, pub r#type: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "backend")] #[serde(rename = "backend")]
pub struct TPMBackendXML { pub struct TPMBackendXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -112,7 +130,7 @@ pub struct TPMBackendXML {
pub r#version: String, pub r#version: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "tpm")] #[serde(rename = "tpm")]
pub struct TPMDeviceXML { pub struct TPMDeviceXML {
#[serde(rename = "@model")] #[serde(rename = "@model")]
@ -121,7 +139,7 @@ pub struct TPMDeviceXML {
} }
/// Devices information /// Devices information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "devices")] #[serde(rename = "devices")]
pub struct DevicesXML { pub struct DevicesXML {
/// Graphics (used for VNC) /// Graphics (used for VNC)
@ -150,7 +168,7 @@ pub struct DevicesXML {
} }
/// Graphics information /// Graphics information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "graphics")] #[serde(rename = "graphics")]
pub struct GraphicsXML { pub struct GraphicsXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -160,14 +178,14 @@ pub struct GraphicsXML {
} }
/// Video device information /// Video device information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "video")] #[serde(rename = "video")]
pub struct VideoXML { pub struct VideoXML {
pub model: VideoModelXML, pub model: VideoModelXML,
} }
/// Video model device information /// Video model device information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")] #[serde(rename = "model")]
pub struct VideoModelXML { pub struct VideoModelXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -175,7 +193,7 @@ pub struct VideoModelXML {
} }
/// Disk information /// Disk information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "disk")] #[serde(rename = "disk")]
pub struct DiskXML { pub struct DiskXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -193,7 +211,7 @@ pub struct DiskXML {
pub address: Option<DiskAddressXML>, pub address: Option<DiskAddressXML>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "driver")] #[serde(rename = "driver")]
pub struct DiskDriverXML { pub struct DiskDriverXML {
#[serde(rename = "@name")] #[serde(rename = "@name")]
@ -204,14 +222,14 @@ pub struct DiskDriverXML {
pub r#cache: String, pub r#cache: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")] #[serde(rename = "source")]
pub struct DiskSourceXML { pub struct DiskSourceXML {
#[serde(rename = "@file")] #[serde(rename = "@file")]
pub file: String, pub file: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "target")] #[serde(rename = "target")]
pub struct DiskTargetXML { pub struct DiskTargetXML {
#[serde(rename = "@dev")] #[serde(rename = "@dev")]
@ -220,18 +238,18 @@ pub struct DiskTargetXML {
pub bus: String, pub bus: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "readonly")] #[serde(rename = "readonly")]
pub struct DiskReadOnlyXML {} pub struct DiskReadOnlyXML {}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "boot")] #[serde(rename = "boot")]
pub struct DiskBootXML { pub struct DiskBootXML {
#[serde(rename = "@order")] #[serde(rename = "@order")]
pub order: String, pub order: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "address")] #[serde(rename = "address")]
pub struct DiskAddressXML { pub struct DiskAddressXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -251,7 +269,7 @@ pub struct DiskAddressXML {
} }
/// Domain RAM information /// Domain RAM information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "memory")] #[serde(rename = "memory")]
pub struct DomainMemoryXML { pub struct DomainMemoryXML {
#[serde(rename = "@unit")] #[serde(rename = "@unit")]
@ -261,7 +279,7 @@ pub struct DomainMemoryXML {
pub memory: usize, pub memory: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "topology")] #[serde(rename = "topology")]
pub struct DomainCPUTopology { pub struct DomainCPUTopology {
#[serde(rename = "@sockets")] #[serde(rename = "@sockets")]
@ -272,14 +290,14 @@ pub struct DomainCPUTopology {
pub threads: usize, pub threads: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")] #[serde(rename = "cpu")]
pub struct DomainVCPUXML { pub struct DomainVCPUXML {
#[serde(rename = "$value")] #[serde(rename = "$value")]
pub body: usize, pub body: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")] #[serde(rename = "cpu")]
pub struct DomainCPUXML { pub struct DomainCPUXML {
#[serde(rename = "@mode")] #[serde(rename = "@mode")]
@ -288,7 +306,7 @@ pub struct DomainCPUXML {
} }
/// Domain information, see https://libvirt.org/formatdomain.html /// Domain information, see https://libvirt.org/formatdomain.html
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "domain")] #[serde(rename = "domain")]
pub struct DomainXML { pub struct DomainXML {
/// Domain type (kvm) /// Domain type (kvm)
@ -300,6 +318,9 @@ pub struct DomainXML {
pub genid: Option<uuid::Uuid>, pub genid: Option<uuid::Uuid>,
pub title: Option<String>, pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<DomainMetadataXML>,
pub os: OSXML, pub os: OSXML,
#[serde(default)] #[serde(default)]
pub features: FeaturesXML, pub features: FeaturesXML,
@ -319,10 +340,32 @@ pub struct DomainXML {
pub on_crash: String, 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 { impl DomainXML {
/// Decode Domain structure from XML definition /// Decode Domain structure from XML definition
pub fn parse_xml(xml: &str) -> anyhow::Result<Self> { 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 /// 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 lazy_regex::regex;
use num::Integer; 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)] #[derive(serde::Serialize, serde::Deserialize)]
pub enum BootType { pub enum BootType {
UEFI, UEFI,
@ -59,6 +64,9 @@ pub struct VMInfo {
pub genid: Option<XMLUuid>, pub genid: Option<XMLUuid>,
pub title: Option<String>, pub title: Option<String>,
pub description: 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 boot_type: BootType,
pub architecture: VMArchitecture, pub architecture: VMArchitecture,
/// VM allocated memory, in megabytes /// VM allocated memory, in megabytes
@ -79,7 +87,7 @@ pub struct VMInfo {
impl VMInfo { impl VMInfo {
/// Turn this VM into a domain /// 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) { if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
return Err(StructureExtraction("VM name is invalid!").into()); 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 { if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
return Err(StructureExtraction("VM memory is invalid!").into()); return Err(StructureExtraction("VM memory is invalid!").into());
} }
@ -282,6 +296,12 @@ impl VMInfo {
title: self.title.clone(), title: self.title.clone(),
description: self.description.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 { os: OSXML {
r#type: OSTypeXML { r#type: OSTypeXML {
arch: match self.architecture { arch: match self.architecture {
@ -369,6 +389,13 @@ impl VMInfo {
genid: domain.genid.map(XMLUuid), genid: domain.genid.map(XMLUuid),
title: domain.title, title: domain.title,
description: domain.description, description: domain.description,
group: domain
.metadata
.clone()
.unwrap_or_default()
.virtweb
.group
.map(VMGroupId),
boot_type: match domain.os.loader { boot_type: match domain.os.loader {
None => BootType::UEFI, None => BootType::UEFI,
Some(l) => match l.secure.as_str() { 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, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
}; };
use virtweb_backend::controllers::{ 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, nwfilter_controller, server_controller, static_controller, vm_controller,
}; };
use virtweb_backend::libvirt_client::LibVirtClient; use virtweb_backend::libvirt_client::LibVirtClient;
@ -210,6 +210,8 @@ async fn main() -> std::io::Result<()> {
web::get().to(vm_controller::vnc_token), web::get().to(vm_controller::vnc_token),
) )
.route("/api/vnc", web::get().to(vm_controller::vnc)) .route("/api/vnc", web::get().to(vm_controller::vnc))
// Groups controller
.route("/api/group/list", web::get().to(groups_controller::list))
// Network controller // Network controller
.route( .route(
"/api/network/create", "/api/network/create",

View File

@ -9,7 +9,7 @@ make
The release file will be available in `virtweb_backend/target/release/virtweb_backend`. The release file will be available in `virtweb_backend/target/release/virtweb_backend`.
This is the only artifcat that must be copied to the server. It is recommended to copy it to the `/usr/local/bin` directory. This is the only artifact that must be copied to the server. It is recommended to copy it to the `/usr/local/bin` directory.
## Install requirements ## Install requirements
In order to work properly, VirtWeb relies on `libvirt`, `qemu` and `kvm`: In order to work properly, VirtWeb relies on `libvirt`, `qemu` and `kvm`:

File diff suppressed because it is too large Load Diff

View File

@ -6,36 +6,36 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.13", "@fontsource/roboto": "^5.1.0",
"@mdi/js": "^7.2.96", "@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mui/icons-material": "^5.14.7", "@mui/icons-material": "^6.1.6",
"@mui/material": "^5.14.7", "@mui/material": "^6.1.6",
"@mui/x-charts": "^7.3.0", "@mui/x-charts": "^7.22.1",
"@mui/x-data-grid": "^7.3.0", "@mui/x-data-grid": "^7.22.1",
"@testing-library/jest-dom": "^6.4.2", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.0", "@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/humanize-duration": "^3.27.1", "@types/humanize-duration": "^3.27.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.14",
"@types/react": "^18.2.79", "@types/react": "^18.3.12",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.3.1",
"@types/react-syntax-highlighter": "^15.5.11", "@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^9.0.5", "@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.3.3",
"date-and-time": "^3.1.1", "date-and-time": "^3.6.0",
"filesize": "^10.0.12", "filesize": "^10.1.6",
"humanize-duration": "^3.29.0", "humanize-duration": "^3.29.0",
"mui-file-input": "^4.0.4", "mui-file-input": "^6.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.23.0", "react-router-dom": "^6.23.0",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.6.1",
"react-vnc": "^2.0.0", "react-vnc": "^2.0.2",
"typescript": "^4.0.0", "typescript": "^4.9.5",
"uuid": "^9.0.1", "uuid": "^11.0.2",
"vite": "^5.2.10", "vite": "^5.4.10",
"vite-tsconfig-paths": "^4.2.2", "vite-tsconfig-paths": "^5.0.1",
"web-vitals": "^3.5.2", "web-vitals": "^3.5.2",
"xml-formatter": "^3.6.0" "xml-formatter": "^3.6.0"
}, },

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; vnc_token_duration: number;
vm_name_size: LenConstraint; vm_name_size: LenConstraint;
vm_title_size: LenConstraint; vm_title_size: LenConstraint;
group_id_size: LenConstraint;
memory_size: LenConstraint; memory_size: LenConstraint;
disk_name_size: LenConstraint; disk_name_size: LenConstraint;
disk_size: LenConstraint; disk_size: LenConstraint;
@ -73,7 +74,7 @@ interface SystemInfo {
secs: number; secs: number;
nanos: number; nanos: number;
}; };
global_cpu_info: GlobalCPUInfo; global_cpu_usage: number;
cpus: CpuCore[]; cpus: CpuCore[];
physical_core_count: number; physical_core_count: number;
total_memory: number; total_memory: number;
@ -94,14 +95,6 @@ interface SystemInfo {
host_name: string; host_name: string;
} }
interface GlobalCPUInfo {
cpu_usage: number;
name: string;
vendor_id: string;
brand: string;
frequency: number;
}
interface CpuCore { interface CpuCore {
cpu_usage: number; cpu_usage: number;
name: string; name: string;

View File

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

View File

@ -8,7 +8,6 @@ import {
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import { import {
Box, Box,
Grid,
LinearProgress, LinearProgress,
Table, Table,
TableBody, TableBody,
@ -17,7 +16,10 @@ import {
TableRow, TableRow,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import Grid from "@mui/material/Grid2";
import { PieChart } from "@mui/x-charts"; import { PieChart } from "@mui/x-charts";
import { filesize } from "filesize";
import humanizeDuration from "humanize-duration";
import React from "react"; import React from "react";
import { import {
DiskInfo, DiskInfo,
@ -28,8 +30,6 @@ 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 humanizeDuration from "humanize-duration";
import { filesize } from "filesize";
export function SysInfoRoute(): React.ReactElement { export function SysInfoRoute(): React.ReactElement {
const [info, setInfo] = React.useState<ServerSystemInfo>(); const [info, setInfo] = React.useState<ServerSystemInfo>();
@ -65,7 +65,7 @@ export function SysInfoRouteInner(p: {
<VirtWebRouteContainer label="Sysinfo"> <VirtWebRouteContainer label="Sysinfo">
<Grid container spacing={2}> <Grid container spacing={2}>
{/* Memory */} {/* Memory */}
<Grid xs={4}> <Grid size={{ xs: 4 }}>
<Box flexGrow={1}> <Box flexGrow={1}>
<Typography style={{ textAlign: "center" }}>Memory</Typography> <Typography style={{ textAlign: "center" }}>Memory</Typography>
<PieChart <PieChart
@ -97,7 +97,7 @@ export function SysInfoRouteInner(p: {
</Grid> </Grid>
{/* Disk usage */} {/* Disk usage */}
<Grid xs={4}> <Grid size={{ xs: 4 }}>
<Box flexGrow={1}> <Box flexGrow={1}>
<Typography style={{ textAlign: "center" }}>Disk usage</Typography> <Typography style={{ textAlign: "center" }}>Disk usage</Typography>
<PieChart <PieChart
@ -125,7 +125,7 @@ export function SysInfoRouteInner(p: {
</Grid> </Grid>
{/* CPU usage */} {/* CPU usage */}
<Grid xs={4}> <Grid size={{ xs: 4 }}>
<Box flexGrow={1}> <Box flexGrow={1}>
<Typography style={{ textAlign: "center" }}>CPU usage</Typography> <Typography style={{ textAlign: "center" }}>CPU usage</Typography>
<PieChart <PieChart
@ -134,13 +134,13 @@ export function SysInfoRouteInner(p: {
data: [ data: [
{ {
id: 1, id: 1,
value: 100 - p.info.system.global_cpu_info.cpu_usage, value: 100 - p.info.system.global_cpu_usage,
label: "Free", label: "Free",
}, },
{ {
id: 2, id: 2,
value: p.info.system.global_cpu_info.cpu_usage, value: p.info.system.global_cpu_usage,
label: "Used", label: "Used",
}, },
], ],
@ -180,18 +180,18 @@ export function SysInfoRouteInner(p: {
label="CPU info" label="CPU info"
icon={<Icon size={"1rem"} path={mdiMemory} />} icon={<Icon size={"1rem"} path={mdiMemory} />}
entries={[ entries={[
{ label: "Brand", value: p.info.system.global_cpu_info.brand }, { label: "Brand", value: p.info.system.cpus[0].brand },
{ {
label: "Vendor ID", label: "Vendor ID",
value: p.info.system.global_cpu_info.vendor_id, value: p.info.system.cpus[0].vendor_id,
}, },
{ {
label: "CPU usage", label: "CPU usage",
value: p.info.system.global_cpu_info.cpu_usage, value: p.info.system.cpus[0].cpu_usage,
}, },
{ {
label: "Name", label: "Name",
value: p.info.system.global_cpu_info.name, value: p.info.system.cpus[0].name,
}, },
{ {
label: "CPU model", label: "CPU model",

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

View File

@ -3,7 +3,7 @@ import Icon from "@mdi/react";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import CssBaseline from "@mui/material/CssBaseline"; import CssBaseline from "@mui/material/CssBaseline";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid2";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { Link, Outlet } from "react-router-dom"; import { Link, Outlet } from "react-router-dom";
@ -38,10 +38,7 @@ export function BaseLoginPage() {
<Grid container component="main" sx={{ height: "100vh" }}> <Grid container component="main" sx={{ height: "100vh" }}>
<CssBaseline /> <CssBaseline />
<Grid <Grid
item size={{ xs: false, sm: 4, md: 7 }}
xs={false}
sm={4}
md={7}
sx={{ sx={{
backgroundImage: "url(/login_splash.jpg)", backgroundImage: "url(/login_splash.jpg)",
backgroundRepeat: "no-repeat", backgroundRepeat: "no-repeat",
@ -53,7 +50,12 @@ export function BaseLoginPage() {
backgroundPosition: "center", backgroundPosition: "center",
}} }}
/> />
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square> <Grid
size={{ xs: 12, sm: 8, md: 5 }}
component={Paper}
elevation={6}
square
>
<Box <Box
sx={{ sx={{
my: 8, my: 8,

View File

@ -1,5 +1,6 @@
import { Grid, Paper, Typography } from "@mui/material"; import { Paper, Typography } from "@mui/material";
import React, { PropsWithChildren } from "react"; import React, { PropsWithChildren } from "react";
import Grid from "@mui/material/Grid2";
export function EditSection( export function EditSection(
p: { p: {
@ -9,7 +10,7 @@ export function EditSection(
} & PropsWithChildren } & PropsWithChildren
): React.ReactElement { ): React.ReactElement {
return ( return (
<Grid item sm={12} md={p.fullWidth ? 12 : 6}> <Grid size={{ sm: 12, md: p.fullWidth ? 12 : 6 }}>
<Paper style={{ margin: "10px", padding: "10px" }}> <Paper style={{ margin: "10px", padding: "10px" }}>
{(p.title || p.actions) && ( {(p.title || p.actions) && (
<span <span

View File

@ -4,7 +4,6 @@ import DeleteIcon from "@mui/icons-material/Delete";
import { import {
Avatar, Avatar,
Button, Button,
Grid,
IconButton, IconButton,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
@ -19,6 +18,7 @@ import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
import { IPInput } from "./IPInput"; import { IPInput } from "./IPInput";
import { MACInput } from "./MACInput"; import { MACInput } from "./MACInput";
import { TextInput } from "./TextInput"; import { TextInput } from "./TextInput";
import Grid from "@mui/material/Grid2";
export function NetDHCPHostReservations(p: { export function NetDHCPHostReservations(p: {
editable: boolean; editable: boolean;
@ -39,7 +39,7 @@ export function NetDHCPHostReservations(p: {
<> <>
<Grid container> <Grid container>
{p.dhcp.hosts.map((h, num) => ( {p.dhcp.hosts.map((h, num) => (
<Grid key={num} sm={12} md={6} item style={{ padding: "10px" }}> <Grid key={num} size={{ sm: 12, md: 6 }} style={{ padding: "10px" }}>
<HostReservationWidget <HostReservationWidget
key={num} key={num}
{...p} {...p}

View File

@ -5,11 +5,11 @@ import {
Card, Card,
CardActions, CardActions,
CardContent, CardContent,
Grid,
IconButton, IconButton,
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import Grid from "@mui/material/Grid2";
import React, { PropsWithChildren } from "react"; import React, { PropsWithChildren } from "react";
import { NatEntry } from "../../api/NetworksApi"; import { NatEntry } from "../../api/NetworksApi";
import { ServerApi } from "../../api/ServerApi"; import { ServerApi } from "../../api/ServerApi";
@ -295,7 +295,7 @@ function NATEntryProp(
p: PropsWithChildren<{ label?: string }> p: PropsWithChildren<{ label?: string }>
): React.ReactElement { ): React.ReactElement {
return ( return (
<Grid item sm={12} md={6} style={{ padding: "20px" }}> <Grid size={{ sm: 12, md: 6 }} style={{ padding: "20px" }}>
{p.label && ( {p.label && (
<Typography variant="h6" style={{ marginBottom: "10px" }}> <Typography variant="h6" style={{ marginBottom: "10px" }}>
{p.label} {p.label}

View File

@ -4,13 +4,13 @@ import DeleteIcon from "@mui/icons-material/Delete";
import { import {
Avatar, Avatar,
Button, Button,
Grid,
IconButton, IconButton,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
ListItemText, ListItemText,
Tooltip, Tooltip,
} from "@mui/material"; } from "@mui/material";
import Grid from "@mui/material/Grid2";
import { NWFilter } from "../../api/NWFilterApi"; import { NWFilter } from "../../api/NWFilterApi";
import { NetworkInfo } from "../../api/NetworksApi"; import { NetworkInfo } from "../../api/NetworksApi";
import { ServerApi } from "../../api/ServerApi"; import { ServerApi } from "../../api/ServerApi";

View File

@ -1,4 +1,5 @@
import { Button, Checkbox, Grid } from "@mui/material"; import { Button, Checkbox } from "@mui/material";
import Grid from "@mui/material/Grid2";
import React from "react"; import React from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { IpConfig, NetworkApi, NetworkInfo } from "../../api/NetworksApi"; import { IpConfig, NetworkApi, NetworkInfo } from "../../api/NetworksApi";

View File

@ -1,4 +1,5 @@
import { Button, Grid } from "@mui/material"; import { Button } from "@mui/material";
import Grid from "@mui/material/Grid2";
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { import {
@ -6,6 +7,7 @@ import {
NWFilterApi, NWFilterApi,
NWFilterIsBuiltin, NWFilterIsBuiltin,
} from "../../api/NWFilterApi"; } from "../../api/NWFilterApi";
import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/providers/AlertDialogProvider"; import { useAlert } from "../../hooks/providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
import { useSnackbar } from "../../hooks/providers/SnackbarProvider"; import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
@ -13,12 +15,11 @@ import { AsyncWidget } from "../AsyncWidget";
import { TabsWidget } from "../TabsWidget"; import { TabsWidget } from "../TabsWidget";
import { XMLAsyncWidget } from "../XMLWidget"; import { XMLAsyncWidget } from "../XMLWidget";
import { EditSection } from "../forms/EditSection"; import { EditSection } from "../forms/EditSection";
import { TextInput } from "../forms/TextInput";
import { ServerApi } from "../../api/ServerApi";
import { SelectInput } from "../forms/SelectInput";
import { NWFSelectReferencedFilters } from "../forms/NWFSelectReferencedFilters"; import { NWFSelectReferencedFilters } from "../forms/NWFSelectReferencedFilters";
import { NWFilterRules } from "../forms/NWFilterRules";
import { NWFilterPriorityInput } from "../forms/NWFilterPriorityInput"; import { NWFilterPriorityInput } from "../forms/NWFilterPriorityInput";
import { NWFilterRules } from "../forms/NWFilterRules";
import { SelectInput } from "../forms/SelectInput";
import { TextInput } from "../forms/TextInput";
interface DetailsProps { interface DetailsProps {
nwfilter: NWFilter; nwfilter: NWFilter;

View File

@ -1,4 +1,5 @@
import { Button, Grid } from "@mui/material"; import { Button } from "@mui/material";
import Grid from "@mui/material/Grid2";
import React from "react"; import React from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { NWFilter, NWFilterApi } from "../../api/NWFilterApi"; import { NWFilter, NWFilterApi } from "../../api/NWFilterApi";

View File

@ -1,7 +1,11 @@
import { Button, Grid } 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 React from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { validate as validateUUID } from "uuid"; import { validate as validateUUID } from "uuid";
import { GroupApi } from "../../api/GroupApi";
import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi";
import { NWFilter, NWFilterApi } from "../../api/NWFilterApi"; import { NWFilter, NWFilterApi } from "../../api/NWFilterApi";
import { NetworkApi, NetworkInfo } from "../../api/NetworksApi"; import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
@ -12,6 +16,7 @@ import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
import { useSnackbar } from "../../hooks/providers/SnackbarProvider"; import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
import { AsyncWidget } from "../AsyncWidget"; import { AsyncWidget } from "../AsyncWidget";
import { TabsWidget } from "../TabsWidget"; import { TabsWidget } from "../TabsWidget";
import { XMLAsyncWidget } from "../XMLWidget";
import { CheckboxInput } from "../forms/CheckboxInput"; import { CheckboxInput } from "../forms/CheckboxInput";
import { EditSection } from "../forms/EditSection"; import { EditSection } from "../forms/EditSection";
import { ResAutostartInput } from "../forms/ResAutostartInput"; import { ResAutostartInput } from "../forms/ResAutostartInput";
@ -21,7 +26,6 @@ import { VMDisksList } from "../forms/VMDisksList";
import { VMNetworksList } from "../forms/VMNetworksList"; import { VMNetworksList } from "../forms/VMNetworksList";
import { VMSelectIsoInput } from "../forms/VMSelectIsoInput"; import { VMSelectIsoInput } from "../forms/VMSelectIsoInput";
import { VMScreenshot } from "./VMScreenshot"; import { VMScreenshot } from "./VMScreenshot";
import { XMLAsyncWidget } from "../XMLWidget";
interface DetailsProps { interface DetailsProps {
vm: VMInfo; vm: VMInfo;
@ -31,6 +35,7 @@ interface DetailsProps {
} }
export function VMDetails(p: DetailsProps): React.ReactElement { export function VMDetails(p: DetailsProps): React.ReactElement {
const [groupsList, setGroupsList] = React.useState<string[] | any>();
const [isoList, setIsoList] = React.useState<IsoFile[] | any>(); const [isoList, setIsoList] = React.useState<IsoFile[] | any>();
const [vcpuCombinations, setVCPUCombinations] = React.useState< const [vcpuCombinations, setVCPUCombinations] = React.useState<
number[] | any number[] | any
@ -41,6 +46,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
>(); >();
const load = async () => { const load = async () => {
setGroupsList(await GroupApi.GetList());
setIsoList(await IsoFilesApi.GetList()); setIsoList(await IsoFilesApi.GetList());
setVCPUCombinations(await ServerApi.NumberVCPUs()); setVCPUCombinations(await ServerApi.NumberVCPUs());
setNetworksList(await NetworkApi.GetList()); setNetworksList(await NetworkApi.GetList());
@ -54,6 +60,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
errMsg="Failed to load the list of ISO files" errMsg="Failed to load the list of ISO files"
build={() => ( build={() => (
<VMDetailsInner <VMDetailsInner
groupsList={groupsList}
isoList={isoList} isoList={isoList}
vcpuCombinations={vcpuCombinations} vcpuCombinations={vcpuCombinations}
networksList={networksList} networksList={networksList}
@ -74,6 +81,7 @@ enum VMTab {
} }
type DetailsInnerProps = DetailsProps & { type DetailsInnerProps = DetailsProps & {
groupsList: string[];
isoList: IsoFile[]; isoList: IsoFile[];
vcpuCombinations: number[]; vcpuCombinations: number[];
networksList: NetworkInfo[]; networksList: NetworkInfo[];
@ -116,6 +124,8 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement {
} }
function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement { function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
const [addGroup, setAddGroup] = React.useState(false);
return ( return (
<Grid container spacing={2}> <Grid container spacing={2}>
{ {
@ -174,6 +184,50 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
}} }}
multiline={true} 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> </EditSection>
{/* General section */} {/* General section */}