297 Commits

Author SHA1 Message Date
a0081c03ea Merge pull request 'Update dependency typescript-eslint to ^8.36.0' () from renovate/typescript-eslint-8.x into master 2025-07-10 03:10:49 +00:00
52ae7de998 Merge pull request 'Update Rust crate clap to 4.5.41' () from renovate/clap-4.x into master 2025-07-10 03:03:00 +00:00
9f919ab26d Update dependency typescript-eslint to ^8.36.0 2025-07-10 00:20:31 +00:00
661a74f632 Update Rust crate clap to 4.5.41 2025-07-10 00:20:25 +00:00
61072285cb Merge pull request 'Update dependency globals to ^16.3.0' () from renovate/globals-16.x into master 2025-07-09 02:49:28 +00:00
f9c14fa335 Merge pull request 'Update dependency @typescript-eslint/parser to ^8.36.0' () from renovate/typescript-eslint-parser-8.x into master 2025-07-09 02:41:50 +00:00
0f3225c8b0 Update dependency globals to ^16.3.0 2025-07-09 00:19:38 +00:00
77ee9773df Update dependency @typescript-eslint/parser to ^8.36.0 2025-07-09 00:19:33 +00:00
974fd928c7 Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to ^8.36.0' () from renovate/typescript-eslint-eslint-plugin-8.x into master 2025-07-08 02:58:47 +00:00
44862d68ba Update dependency @typescript-eslint/eslint-plugin to ^8.36.0 2025-07-08 00:19:36 +00:00
70ef361c6c Merge pull request 'Update dependency @eslint/js to ^9.30.1' () from renovate/eslint-js-9.x into master 2025-07-07 02:26:09 +00:00
55053a2e37 Merge pull request 'Update Rust crate reqwest to 0.12.22' () from renovate/reqwest-0.x into master 2025-07-07 02:22:36 +00:00
18e336e12b Update dependency @eslint/js to ^9.30.1 2025-07-07 00:19:00 +00:00
98fe611be3 Update Rust crate reqwest to 0.12.22 2025-07-07 00:18:56 +00:00
11878d22e8 Merge pull request 'Update dependency @typescript-eslint/parser to ^8.35.1' () from renovate/typescript-eslint-parser-8.x into master 2025-07-06 02:25:31 +00:00
d09483098a Merge pull request 'Update dependency @fluentui/react-icons to ^2.0.305' () from renovate/fluentui-react-icons-2.x into master 2025-07-06 02:22:01 +00:00
a01a660f99 Update dependency @typescript-eslint/parser to ^8.35.1 2025-07-06 00:19:19 +00:00
caa33fdebf Update dependency @fluentui/react-icons to ^2.0.305 2025-07-06 00:19:13 +00:00
027fb1eecf Merge pull request 'Update dependency @fluentui/react-components to ^9.66.6' () from renovate/fluentui-react-components-9.x into master 2025-07-05 02:39:02 +00:00
68006504eb Update dependency @fluentui/react-components to ^9.66.6 2025-07-05 00:12:55 +00:00
8210cbfb4f Update dependency @typescript-eslint/eslint-plugin to ^8.35.1 2025-07-04 02:52:02 +00:00
a3959d7abc Update dependency @fluentui/react-components to ^9.66.5 2025-07-04 00:12:50 +00:00
9af843c287 Fix cargo clippy issue 2025-07-03 08:29:07 +02:00
adf7066ba0 Update dependency @typescript-eslint/parser to ^8.35.0 2025-06-26 00:12:27 +00:00
d4c3e40abf Update dependency @vitejs/plugin-react to ^4.6.0 2025-06-25 00:12:14 +00:00
78fc0b368c Update dependency @fluentui/react-components to ^9.66.3 2025-06-24 00:14:25 +00:00
0f2e072976 Fix VM memory size 2025-06-23 14:56:00 +00:00
a5d329eca8 Fix VM memory size 2025-06-23 14:55:09 +00:00
b0ee9288a6 Update Rust crate actix-web to 4.11.0 2025-06-23 00:24:53 +00:00
498ee9f861 Update dependency typescript-eslint to ^8.34.1 2025-06-22 00:23:12 +00:00
e1fd104fec Update dependency globals to ^16.2.0 2025-06-21 00:23:54 +00:00
c6bff2d793 Update dependency @fluentui/react-icons to ^2.0.304 2025-06-20 00:25:35 +00:00
f2626f9987 Update dependency @fluentui/react-components to ^9.66.2 2025-06-19 00:23:44 +00:00
39873cfa40 Update dependency @typescript-eslint/eslint-plugin to ^8.34.1 2025-06-18 00:25:02 +00:00
e3df9e63e6 Update dependency @typescript-eslint/parser to ^8.34.1 2025-06-17 00:30:49 +00:00
6eb54e8653 Update dependency @fluentui/react-components to ^9.66.1 2025-06-16 00:25:16 +00:00
b10caff599 Update dependency @eslint/js to ^9.29.0 2025-06-15 00:25:10 +00:00
80921b12fa Update dependency @fluentui/react-icons to ^2.0.303 2025-06-14 00:30:47 +00:00
5bdf6d03a4 Update Rust crate rust-embed to 8.7.2 2025-06-13 00:30:53 +00:00
73e02cd6b8 Update Rust crate reqwest to 0.12.20 2025-06-12 00:26:20 +00:00
f6ed37458e Update Rust crate futures-util to 0.3.31 2025-06-11 00:26:27 +00:00
082bfc5cb9 Update Rust crate clap to 4.5.40 2025-06-10 00:26:16 +00:00
c2227c9129 Update dependency @types/react to ^18.3.23 2025-06-09 00:25:33 +00:00
eb33e93b08 Update dependency @fluentui/react-icons to ^2.0.302 2025-06-07 00:31:37 +00:00
2e393b8777 Update dependency vite to ^6.3.5 2025-06-06 00:31:13 +00:00
7daafdc1aa Fix cargo clippy issues 2025-06-05 08:54:53 +00:00
f3e14df55f Update dependency typescript-eslint to ^8.32.1 2025-05-15 00:19:30 +00:00
4caa2c416b Update dependency @typescript-eslint/parser to ^8.32.1 2025-05-14 00:20:15 +00:00
b4913054f0 Update dependency @typescript-eslint/eslint-plugin to ^8.32.1 2025-05-13 00:21:11 +00:00
8f2337c325 Update Rust crate clap to 4.5.38 2025-05-12 00:20:13 +00:00
4ccdea317b Update dependency typescript-eslint to ^8.32.0 2025-05-11 00:18:37 +00:00
d8ac0d1f0f Update dependency @fluentui/react-icons to ^2.0.300 2025-05-10 00:19:30 +00:00
652fab1511 Update dependency globals to ^16.1.0 2025-05-09 00:18:59 +00:00
e687f94daa Update dependency @typescript-eslint/parser to ^8.32.0 2025-05-08 00:28:02 +00:00
ee6991c240 Update dependency @types/react to ^18.3.21 2025-05-07 00:27:55 +00:00
1efaeb9264 Update dependency @typescript-eslint/eslint-plugin to ^8.32.0 2025-05-06 00:27:28 +00:00
60f96bbc45 Update dependency eslint to ^9.26.0 2025-05-04 00:27:34 +00:00
a3752ee654 Update dependency @eslint/js to ^9.26.0 2025-05-03 00:27:24 +00:00
dd39e3d7ba Update dependency typescript-eslint to ^8.31.1 2025-05-02 00:22:34 +00:00
dc07e055f8 Update dependency @types/react-dom to ^18.3.7 2025-05-01 00:22:41 +00:00
5ee8c7fce3 Update dependency @typescript-eslint/parser to ^8.31.1 2025-04-30 00:22:36 +00:00
5a796fecca Update dependency @typescript-eslint/eslint-plugin to ^8.31.1 2025-04-29 00:22:26 +00:00
48fc0a03a5 Update dependency eslint to ^9.25.1 2025-04-28 00:22:12 +00:00
8e679210ea Update dependency @typescript-eslint/parser to ^8.31.0 2025-04-27 00:21:37 +00:00
aa1f8e12eb Update dependency @fluentui/react-icons to ^2.0.298 2025-04-26 00:26:13 +00:00
d33700f9ae Update dependency @fluentui/react-components to ^9.63.0 2025-04-25 00:22:55 +00:00
46e689045e Update dependency @typescript-eslint/eslint-plugin to ^8.31.0 2025-04-24 00:22:45 +00:00
18c9eb7f23 Update dependency eslint-plugin-react-refresh to ^0.4.20 2025-04-23 00:22:17 +00:00
9110c66f05 Update dependency @eslint/js to ^9.25.1 2025-04-22 00:30:49 +00:00
77c0869f74 Update dependency @vitejs/plugin-react to ^4.4.1 2025-04-21 00:31:01 +00:00
3726f8ba2d Update dependency @eslint/js to ^9.25.0 2025-04-20 00:30:36 +00:00
89ca5493e1 Update Rust crate clap to 4.5.37 2025-04-19 00:31:32 +00:00
b06926e3a5 Update dependency @typescript-eslint/eslint-plugin to ^8.30.1 2025-04-18 00:34:38 +00:00
5b7709f50a Update dependency @fluentui/react-components to ^9.62.0 2025-04-17 00:29:52 +00:00
79ef24e414 Update dependency @eslint/js to ^9.24.0 2025-04-16 00:30:06 +00:00
d575feac81 Update Rust crate anyhow to 1.0.98 2025-04-15 00:29:57 +00:00
00343abdb0 Update dependency vite to ^6.2.6 2025-04-14 00:29:57 +00:00
3ee3f0119b Update dependency @fluentui/react-icons to ^2.0.297 2025-04-13 00:30:19 +00:00
b18f18a986 Update Rust crate clap to 4.5.36 2025-04-12 00:30:50 +00:00
e7ad0ffd4f Update dependency typescript to ^5.8.3 2025-04-11 00:30:22 +00:00
670ceab69e Update dependency eslint-plugin-react-refresh to ^0.4.19 2025-04-10 00:30:29 +00:00
9c2eedc7d1 Update dependency @types/react-dom to ^18.3.6 2025-04-09 00:30:31 +00:00
f44d318b16 Update dependency @fluentui/react-icons to ^2.0.294 2025-04-08 00:34:39 +00:00
85b33a32c4 Update dependency @fluentui/react-components to ^9.61.6 2025-04-07 00:36:40 +00:00
36478bd61e Update Rust crate env_logger to 0.11.8 2025-04-06 00:38:04 +00:00
6f2c00b2a4 Update Rust crate clap to 4.5.35 2025-04-05 00:37:21 +00:00
4c61610ff2 Update Rust crate thiserror to 2.0.12 2025-04-04 00:38:53 +00:00
52b749c9a9 Update Rust crate serde to 1.0.219 2025-04-03 00:39:07 +00:00
8eccffb176 Merge pull request 'Update react' () from renovate/react into master
Reviewed-on: 
2025-04-02 07:11:51 +00:00
35fc98e653 Update react 2025-04-02 00:40:01 +00:00
1f21bc5ba0 Update Rust crate rust-embed to 8.6.0 2025-04-01 02:09:39 +00:00
7b43701b1c Update Rust crate mime_guess to 2.0.5 2025-03-31 20:54:43 +00:00
5c62dd0c27 Update Rust crate lazy_static to 1.5.0 2025-03-31 00:38:57 +00:00
6b6ea9ea83 Update Rust crate lazy-regex to 3.4.1 2025-03-30 02:25:05 +00:00
1d5b132e7e Update Rust crate anyhow to 1.0.97 2025-03-29 23:01:36 +00:00
97f65931c8 Update Rust crate actix-cors to 0.7.1 2025-03-29 18:17:24 +00:00
ccb0c6cd6e Update renovate.json 2025-03-29 17:32:11 +00:00
4ee52e83fd Update dependency globals to v16 2025-03-29 12:55:23 +00:00
296d168995 Update renovate.json 2025-03-29 09:03:26 +00:00
8f3ff52dda Merge pull request 'Update dependency @types/react to v18.3.20' () from renovate/react-18.x-lockfile into master
Reviewed-on: 
2025-03-29 08:56:59 +00:00
0e0af55a85 Merge pull request 'Update Rust crate basic-jwt to 0.3.0' () from renovate/basic-jwt-0.x into master
Reviewed-on: 
2025-03-29 08:56:49 +00:00
2518d202da Update Rust crate basic-jwt to 0.3.0 2025-03-29 00:34:41 +00:00
aa7c217b6c Update dependency @types/react to v18.3.20 2025-03-29 00:34:38 +00:00
6b699994cf Rollback to React 18 (waiting for fluentui to upgrade) 2025-03-28 16:52:06 +01:00
e13ae9f062 Updated frontend dependencies 2025-03-28 16:44:52 +01:00
77b1117fa1 Update VirteWebRemote backend dependencies 2025-03-28 16:32:24 +01:00
daa0a9f391 Merge pull request 'Update Rust crate actix-cors to v0.7.1' () from renovate/actix-cors-0.x-lockfile into master
Reviewed-on: 
2025-03-28 15:22:54 +00:00
f94d1b1c6b Merge pull request 'Update Rust crate reqwest to v0.12.14' () from renovate/reqwest-0.x-lockfile into master
Reviewed-on: 
2025-03-28 15:22:48 +00:00
b3b1592d93 Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.26.1' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2025-03-28 15:22:43 +00:00
8bf7129ee1 Merge pull request 'Update dependency @typescript-eslint/parser to v8.26.1' () from renovate/typescript-eslint-parser-8.x-lockfile into master
Reviewed-on: 
2025-03-28 15:22:37 +00:00
5c841e6a85 Update dependency @typescript-eslint/parser to v8.26.1 2025-03-15 00:09:20 +00:00
369efd442f Update dependency @typescript-eslint/eslint-plugin to v8.26.1 2025-03-14 00:09:56 +00:00
01c0f20cf4 Update Rust crate reqwest to v0.12.14 2025-03-14 00:09:51 +00:00
c96ca68f7e Merge pull request 'Update Rust crate reqwest to v0.12.13' () from renovate/reqwest-0.x-lockfile into master
Reviewed-on: 
2025-03-13 15:41:16 +00:00
8121212723 Merge pull request 'Update dependency @fluentui/react-components to v9.60.1' () from renovate/fluentui-react-components-9.x-lockfile into master
Reviewed-on: 
2025-03-13 15:31:15 +00:00
cd9867c5b0 Update dependency @fluentui/react-components to v9.60.1 2025-03-13 00:09:48 +00:00
cbc432a304 Update Rust crate reqwest to v0.12.13 2025-03-12 00:24:15 +00:00
25d4d3e0f3 Update Rust crate actix-cors to v0.7.1 2025-03-12 00:24:10 +00:00
855ec54e83 Merge pull request 'Update Rust crate clap to v4.5.32' () from renovate/clap-4.x-lockfile into master
Reviewed-on: 
2025-03-11 13:07:09 +00:00
10ab40fcd5 Merge pull request 'Update Rust crate serde to v1.0.219' () from renovate/serde-1.x-lockfile into master
Reviewed-on: 
2025-03-11 13:07:02 +00:00
92d53430af Update Rust crate serde to v1.0.219 2025-03-11 00:23:27 +00:00
4d8387a854 Update Rust crate clap to v4.5.32 2025-03-11 00:23:23 +00:00
68b2bb8e0c Merge pull request 'Update dependency @fluentui/react-icons to v2.0.279' () from renovate/fluentui-react-icons-2.x-lockfile into master
Reviewed-on: 
2025-03-10 17:58:41 +00:00
02a120c220 Merge pull request 'Update Rust crate rust-embed to v8.6.0' () from renovate/rust-embed-8.x-lockfile into master
Reviewed-on: 
2025-03-10 17:58:31 +00:00
e16167991b Merge pull request 'Update Rust crate uuid to v1.15.1' () from renovate/uuid-1.x-lockfile into master
Reviewed-on: 
2025-03-10 17:58:19 +00:00
5101d43c05 Merge pull request 'Update dependency @fluentui/react-components to v9.60.0' () from renovate/fluentui-react-components-9.x-lockfile into master
Reviewed-on: 
2025-03-10 17:58:10 +00:00
c32536f260 Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.26.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2025-03-10 17:58:00 +00:00
e89818cc35 Update dependency @typescript-eslint/eslint-plugin to v8.26.0 2025-03-09 00:22:01 +00:00
c7e0a6ddce Update dependency @fluentui/react-components to v9.60.0 2025-03-08 00:22:42 +00:00
4ef2f69d92 Update Rust crate uuid to v1.15.1 2025-03-08 00:22:32 +00:00
6427593fa8 Update Rust crate rust-embed to v8.6.0 2025-03-07 00:22:20 +00:00
3336c9e8bf Update dependency @fluentui/react-icons to v2.0.279 2025-03-07 00:22:14 +00:00
97f8e85268 Merge pull request 'Update Rust crate light-openid to v1.0.3' () from renovate/light-openid-1.x-lockfile into master
Reviewed-on: 
2025-03-06 20:28:29 +00:00
144d10164d Merge pull request 'Update Rust crate log to v0.4.26' () from renovate/log-0.x-lockfile into master
Reviewed-on: 
2025-03-06 20:28:00 +00:00
8cd5e61a6a Update Rust crate log to v0.4.26 2025-03-06 00:22:17 +00:00
3a3110114b Update Rust crate light-openid to v1.0.3 2025-03-06 00:22:15 +00:00
1b83c8fa07 Merge pull request 'Update dependency @typescript-eslint/parser to v8.26.0' () from renovate/typescript-eslint-parser-8.x-lockfile into master
Reviewed-on: 
2025-03-05 19:19:57 +00:00
9037842c0c Merge pull request 'Update Rust crate anyhow to v1.0.97' () from renovate/anyhow-1.x-lockfile into master
Reviewed-on: 
2025-03-05 19:19:45 +00:00
df3724a17d Merge pull request 'Update Rust crate clap to v4.5.31' () from renovate/clap-4.x-lockfile into master
Reviewed-on: 
2025-03-05 19:19:38 +00:00
31eb1977c3 Update dependency @typescript-eslint/parser to v8.26.0 2025-03-05 00:22:26 +00:00
61ec9f6d91 Update Rust crate clap to v4.5.31 2025-03-05 00:22:18 +00:00
aa6d45a5e8 Update Rust crate anyhow to v1.0.97 2025-03-05 00:22:14 +00:00
cb0eba7fca Add new info on README 2025-03-04 18:43:30 +00:00
3e4face5c9 Merge pull request 'Update dependency @fluentui/react-components to v9.58.3' () from renovate/fluentui-react-components-9.x-lockfile into master
Reviewed-on: 
2025-03-04 18:41:51 +00:00
217e476811 Merge pull request 'Update dependency @fluentui/react-icons to v2.0.274' () from renovate/fluentui-react-icons-2.x-lockfile into master
Reviewed-on: 
2025-03-04 18:41:38 +00:00
76a81e0ff9 Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.24.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2025-03-04 18:41:32 +00:00
52ca4674d7 Merge pull request 'Update Rust crate uuid to v1.13.1' () from renovate/uuid-1.x-lockfile into master
Reviewed-on: 
2025-03-04 18:40:48 +00:00
5417e70bff Merge pull request 'Update Rust crate anyhow to v1.0.96' () from renovate/anyhow-1.x-lockfile into master
Reviewed-on: 
2025-03-04 18:40:42 +00:00
ba45b119a0 Merge pull request 'Update Rust crate thiserror to v2.0.12' () from renovate/thiserror-2.x-lockfile into master
Reviewed-on: 
2025-03-04 18:40:18 +00:00
947aa258f8 Update Rust crate thiserror to v2.0.12 2025-03-04 00:22:32 +00:00
964e01cc8d Update Rust crate anyhow to v1.0.96 2025-02-22 00:11:11 +00:00
f84f8aefc1 Merge pull request 'Update Rust crate clap to v4.5.29' () from renovate/clap-4.x-lockfile into master
Reviewed-on: 
2025-02-21 14:48:55 +00:00
1db549e2ee Merge pull request 'Update Rust crate serde to v1.0.218' () from renovate/serde-1.x-lockfile into master
Reviewed-on: 
2025-02-21 14:44:32 +00:00
fe00953be3 Update Rust crate serde to v1.0.218 2025-02-21 00:11:10 +00:00
a223335cee Update Rust crate clap to v4.5.29 2025-02-12 00:09:51 +00:00
417ea655ab Update dependency @typescript-eslint/eslint-plugin to v8.24.0 2025-02-11 00:09:08 +00:00
2c7f5c9751 Merge pull request 'Update dependency eslint-plugin-react-refresh to v0.4.19' () from renovate/eslint-plugin-react-refresh-0.x-lockfile into master
Reviewed-on: 
2025-02-10 18:37:20 +00:00
04fa264e1c Update dependency eslint-plugin-react-refresh to v0.4.19 2025-02-10 00:13:11 +00:00
8716b32739 Update dependency @fluentui/react-components to v9.58.3 2025-02-08 00:13:19 +00:00
39357529e8 Merge pull request 'Update dependency vite to v6.1.0' () from renovate/vite-6.x-lockfile into master
Reviewed-on: 
2025-02-06 22:30:29 +00:00
d7d1211ee6 Update dependency vite to v6.1.0 2025-02-06 00:13:48 +00:00
19b2854f56 Update Rust crate uuid to v1.13.1 2025-02-06 00:13:35 +00:00
aa2498d1ef Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.23.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2025-02-04 21:27:36 +00:00
a8aba27c87 Merge pull request 'Update Rust crate clap to v4.5.28' () from renovate/clap-4.x-lockfile into master
Reviewed-on: 
2025-02-04 07:02:57 +00:00
ca73e7b6fa Update dependency @typescript-eslint/eslint-plugin to v8.23.0 2025-02-04 00:13:20 +00:00
6c59c4ae7b Update Rust crate clap to v4.5.28 2025-02-04 00:13:16 +00:00
bb631188ae Update dependency @fluentui/react-icons to v2.0.274 2025-02-01 00:26:43 +00:00
869ef03a9f Merge pull request 'Update dependency @fluentui/react-components to v9.58.2' () from renovate/fluentui-react-components-9.x-lockfile into master
Reviewed-on: 
2025-01-31 07:09:47 +00:00
bd16ab08c2 Merge pull request 'Update dependency @typescript-eslint/parser to v8.22.0' () from renovate/typescript-eslint-parser-8.x-lockfile into master
Reviewed-on: 
2025-01-30 07:22:57 +00:00
ad8c41ea58 Update dependency @typescript-eslint/parser to v8.22.0 2025-01-30 00:31:12 +00:00
b051476f9d Update dependency @fluentui/react-components to v9.58.2 2025-01-30 00:27:11 +00:00
bd22186c0f Merge pull request 'Update dependency @fluentui/react-components to v9.58.1' () from renovate/fluentui-react-components-9.x-lockfile into master
Reviewed-on: 
2025-01-28 22:18:28 +00:00
78046a9b3a Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.22.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2025-01-28 06:58:27 +00:00
f5de43a67a Update dependency @typescript-eslint/eslint-plugin to v8.22.0 2025-01-28 00:31:54 +00:00
2861038d71 Update dependency @fluentui/react-components to v9.58.1 2025-01-28 00:27:53 +00:00
fe2b6d5dd6 Update README.md 2025-01-24 07:31:01 +00:00
f7aebd7d6a Update dependency @typescript-eslint/parser to v8.21.0 2025-01-24 00:27:46 +00:00
84ad543948 Update dependency @typescript-eslint/eslint-plugin to v8.21.0 2025-01-23 01:11:06 +00:00
5a7d9e178d Update dependency @fluentui/react-components to v9.58.0 2025-01-23 00:28:03 +00:00
9020389714 Update dependency vite to v6.0.11 2025-01-22 01:13:21 +00:00
4f0e2cb722 Update Rust crate uuid to v1.12.1 2025-01-22 00:27:49 +00:00
316b9efef8 Merge pull request 'Update Rust crate clap to v4.5.27' () from renovate/clap-4.x-lockfile into master
Reviewed-on: 
2025-01-21 07:05:22 +00:00
3e67d0bdb3 Update dependency vite to v6.0.10 2025-01-21 00:28:01 +00:00
3281bf4b36 Update Rust crate clap to v4.5.27 2025-01-21 00:27:57 +00:00
b4c4920e48 Merge pull request 'Update dependency @typescript-eslint/parser to v8.20.0' () from renovate/typescript-eslint-parser-8.x-lockfile into master
Reviewed-on: 
2025-01-19 22:09:37 +00:00
cc14077a82 Merge pull request 'Update Rust crate log to v0.4.25' () from renovate/log-0.x-lockfile into master
Reviewed-on: 
2025-01-19 22:09:30 +00:00
f1301f7eb0 Merge pull request 'Update Rust crate uuid to v1.12.0' () from renovate/uuid-1.x-lockfile into master
Reviewed-on: 
2025-01-19 22:09:22 +00:00
94ae206bbf Update dependency @fluentui/react-icons to v2.0.271 2025-01-18 00:27:27 +00:00
e1d2bd7f4f Update Rust crate uuid to v1.12.0 2025-01-15 00:52:14 +00:00
fc698ea66e Update Rust crate log to v0.4.25 2025-01-15 00:52:06 +00:00
9211b43e50 Update dependency @typescript-eslint/parser to v8.20.0 2025-01-14 00:52:26 +00:00
026aa1ecfd Update dependency @typescript-eslint/eslint-plugin to v8.20.0 2025-01-14 00:52:11 +00:00
de3c18c658 Update dependency eslint-plugin-react-refresh to v0.4.18 2025-01-12 01:19:00 +00:00
d74831aa9f Update Rust crate uuid to v1.11.1 2025-01-12 00:50:44 +00:00
7da20d879b Update Rust crate thiserror to v2.0.11 2025-01-11 01:21:18 +00:00
987c95221f Update Rust crate log to v0.4.24 2025-01-11 00:24:55 +00:00
9c64f9a2a7 Update dependency @fluentui/react-components to v9.57.0 2025-01-10 01:23:48 +00:00
6d2a2683bc Update Rust crate clap to v4.5.26 2025-01-10 00:24:46 +00:00
80fd39dd24 Update dependency typescript to v5.7.3 2025-01-09 01:52:32 +00:00
85b6ca9176 Update Rust crate thiserror to v2.0.10 2025-01-09 00:24:45 +00:00
0b71315321 Update Rust crate clap to v4.5.24 2025-01-08 00:30:17 +00:00
9246ced2e1 Update dependency @typescript-eslint/parser to v8.19.1 2025-01-07 01:27:47 +00:00
b0c97052f5 Update dependency @typescript-eslint/eslint-plugin to v8.19.1 2025-01-07 00:29:06 +00:00
70b1c290d0 Update Rust crate thiserror to v2.0.9 2025-01-04 00:25:58 +00:00
50df3e92fd Merge pull request 'Update Rust crate thiserror to v2.0.8' () from renovate/thiserror-2.x-lockfile into master
Reviewed-on: 
2025-01-03 08:22:45 +00:00
14e4ff41a9 Merge pull request 'Update Rust crate env_logger to v0.11.6' () from renovate/env_logger-0.x-lockfile into master
Reviewed-on: 
2025-01-03 08:22:38 +00:00
744537a906 Update dependency vite to v6.0.7 2025-01-03 00:25:07 +00:00
146a4e0885 Update Rust crate reqwest to v0.12.12 2025-01-01 00:25:31 +00:00
74e1ed9ca1 Update dependency @typescript-eslint/parser to v8.19.0 2024-12-31 00:59:09 +00:00
ca3d2010ef Update dependency @typescript-eslint/eslint-plugin to v8.19.0 2024-12-31 00:24:55 +00:00
a7df69cd6f Update Rust crate lazy-regex to v3.4.1 2024-12-29 00:56:53 +00:00
446001d9ff Update Rust crate serde to v1.0.217 2024-12-28 01:51:22 +00:00
1a7aa5dab0 Update Rust crate reqwest to v0.12.11 2024-12-28 00:26:50 +00:00
8f6892e498 Update dependency vite to v6.0.6 2024-12-27 01:18:21 +00:00
5c8df481ac Update Rust crate reqwest to v0.12.10 2024-12-27 00:25:19 +00:00
d8e1ce7fbe Update dependency @typescript-eslint/parser to v8.18.2 2024-12-24 00:49:34 +00:00
5ae6f8f1bf Update dependency @typescript-eslint/eslint-plugin to v8.18.2 2024-12-24 00:25:05 +00:00
807c2c310a Update Rust crate anyhow to v1.0.95 2024-12-23 00:25:03 +00:00
ea5b072511 Update dependency vite to v6.0.5 2024-12-22 00:25:42 +00:00
1f5c22828d Update dependency @types/react to v18.3.18 2024-12-21 00:25:57 +00:00
ba1073a8bd Update Rust crate env_logger to v0.11.6 2024-12-21 00:25:54 +00:00
da1c962223 Update dependency @fluentui/react-icons to v2.0.270 2024-12-20 01:22:22 +00:00
dab3034206 Update dependency @fluentui/react-components to v9.56.8 2024-12-20 00:25:52 +00:00
75b97fb3e2 Update dependency @fluentui/react-components to v9.56.7 2024-12-19 00:25:36 +00:00
e9c4a4d1b4 Update Rust crate thiserror to v2.0.8 2024-12-19 00:25:30 +00:00
5c4e1c5425 Update dependency @typescript-eslint/parser to v8.18.1 2024-12-18 01:35:39 +00:00
bcff61f1f6 Update dependency @typescript-eslint/eslint-plugin to v8.18.1 2024-12-18 00:07:58 +00:00
9a970ce347 Update dependency @types/react to v18.3.17 2024-12-17 01:36:43 +00:00
9e1287509a Update dependency @fluentui/react-components to v9.56.6 2024-12-17 00:08:13 +00:00
09f9a364ae Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.18.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2024-12-16 09:59:48 +00:00
30589a6e6a Update dependency @typescript-eslint/parser to v8.18.0 2024-12-15 00:08:21 +00:00
a87b275796 Update dependency @typescript-eslint/eslint-plugin to v8.18.0 2024-12-15 00:08:17 +00:00
4454f4a10d Update dependency @fluentui/react-icons to v2.0.269 2024-12-14 01:26:08 +00:00
0a85dc65f2 Update Rust crate thiserror to v2.0.7 2024-12-14 00:09:22 +00:00
77a9c954de Merge pull request 'Update dependency @types/react-dom to v18.3.5' () from renovate/react-dom-18.x-lockfile into master
Reviewed-on: 
2024-12-13 19:31:28 +00:00
bf13e3ac8a Merge pull request 'Update dependency @types/react to v18.3.16' () from renovate/react-18.x-lockfile into master
Reviewed-on: 
2024-12-13 19:31:21 +00:00
7fbdd9c3d3 Update dependency @types/react-dom to v18.3.5 2024-12-13 00:08:28 +00:00
8c3d6240eb Update dependency @types/react to v18.3.16 2024-12-13 00:08:25 +00:00
d344b16a8c Update dependency @fluentui/react-icons to v2.0.267 2024-12-12 01:33:08 +00:00
aa6b20b202 Update Rust crate serde to v1.0.216 2024-12-12 00:08:35 +00:00
3dfa2250ce Merge pull request 'Update dependency @types/react to v18.3.15' () from renovate/react-18.x-lockfile into master
Reviewed-on: 
2024-12-11 18:50:53 +00:00
386a4af108 Update dependency @types/react-dom to v18.3.4 2024-12-11 00:24:50 +00:00
76b234febb Update dependency @types/react to v18.3.15 2024-12-11 00:24:47 +00:00
45880e212d Update dependency @types/react-dom to v18.3.3 2024-12-10 01:31:43 +00:00
023ab429fe Update dependency @fluentui/react-components to v9.56.5 2024-12-10 00:25:00 +00:00
0f9ad94368 Update dependency @typescript-eslint/eslint-plugin to v8.17.0 2024-12-09 01:43:00 +00:00
d75b0287ad Update Rust crate thiserror to v2.0.6 2024-12-09 00:24:52 +00:00
0f24e6dc3e Update dependency vite to v6.0.3 2024-12-08 01:39:21 +00:00
94ad0398de Update Rust crate thiserror to v2.0.5 2024-12-08 00:25:07 +00:00
5305083eca Update dependency @types/react-dom to v18.3.2 2024-12-07 02:08:46 +00:00
eef867ad19 Update dependency @fluentui/react-components to v9.56.4 2024-12-07 00:29:26 +00:00
10569459c3 Fix Dockerfile entrypoint specification 2024-12-06 19:09:17 +01:00
4c6608bf55 Add groups support ()
Reviewed-on: 
2024-12-06 18:06:01 +00:00
aa9222bd22 Update dependency @types/react to v18.3.14 2024-12-06 02:30:38 +00:00
191b041993 Update Rust crate clap to v4.5.23 2024-12-06 00:26:21 +00:00
00b9bd4ab4 Merge pull request 'Update dependency eslint to v9' () from renovate/eslint-9.x into master
Reviewed-on: 
2024-12-05 22:07:48 +00:00
1c76b51a02 Merge pull request 'Update Rust crate anyhow to v1.0.94' () from renovate/anyhow-1.x-lockfile into master
Reviewed-on: 
2024-12-05 22:07:41 +00:00
533b8d68b9 Merge pull request 'Update dependency @types/react to v18.3.13' () from renovate/react-18.x-lockfile into master
Reviewed-on: 
2024-12-05 22:07:35 +00:00
8678ecdb32 Update dependency @types/react to v18.3.13 2024-12-05 00:26:03 +00:00
f817472485 Update Rust crate thiserror to v2.0.4 2024-12-05 00:26:01 +00:00
46db385215 Update Rust crate clap to v4.5.22 2024-12-04 00:26:50 +00:00
31c45adbc3 Update Rust crate anyhow to v1.0.94 2024-12-04 00:26:47 +00:00
45b8c1f012 Update dependency vite to v6.0.2 2024-12-03 01:04:53 +00:00
1ef5bf8848 Update dependency eslint-plugin-react-refresh to v0.4.16 2024-12-03 00:24:40 +00:00
ade396460a Update dependency eslint to v9 2024-12-01 00:24:55 +00:00
184a106542 Centralize rights management 2024-11-30 10:26:14 +01:00
74ab902180 Updated project dependencies 2024-11-30 09:28:09 +01:00
017ce989c0 Update node Docker tag to v23 2024-10-19 02:01:00 +00:00
e8f1b66670 Update Rust crate anyhow to v1.0.90 2024-10-19 00:30:34 +00:00
d0b34f038d Update dependency @typescript-eslint/eslint-plugin to v8.10.0 2024-10-18 00:30:25 +00:00
9465a5b04f Update Rust crate uuid to v1.11.0 2024-10-17 00:29:39 +00:00
a09ca8805c Update dependency @fluentui/react-components to v9.55.1 2024-10-16 00:29:43 +00:00
5dbd41ab19 Update dependency @typescript-eslint/eslint-plugin to v8.9.0 2024-10-15 00:29:17 +00:00
b8b3b48f6b Update dependency eslint-plugin-react-hooks to v5 2024-10-12 00:29:04 +00:00
08c9eee0f2 Update dependency @fluentui/react-components to v9.55.0 2024-10-10 00:28:22 +00:00
9b467bf907 Update dependency typescript to v5.6.3 2024-10-09 00:28:49 +00:00
e0a97d8604 Update dependency @typescript-eslint/eslint-plugin to v8.8.1 2024-10-08 00:28:07 +00:00
b3be9a519a Update Rust crate futures-util to v0.3.31 2024-10-06 00:29:10 +00:00
e8dc523264 Update dependency @types/react to v18.3.11 2024-10-03 00:29:10 +00:00
0053dec503 Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.8.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2024-10-02 20:13:49 +00:00
84feab23c6 Update dependency @typescript-eslint/parser to v8.8.0 2024-10-02 01:23:54 +00:00
e14e525954 Update Rust crate clap to v4.5.19 2024-10-02 00:32:29 +00:00
57d98e3848 Update dependency @typescript-eslint/eslint-plugin to v8.8.0 2024-10-01 00:32:41 +00:00
e92aff4570 Update Rust crate reqwest to v0.12.8 2024-10-01 00:32:38 +00:00
682036986d Update dependency @vitejs/plugin-react to v4.3.2 2024-09-30 00:32:57 +00:00
c7cfb71935 Update dependency @typescript-eslint/parser to v8.7.0 2024-09-29 00:31:51 +00:00
4f19bd9b6c Update dependency vite to v5.4.8 2024-09-28 01:22:39 +00:00
329254a432 Update dependency @types/react to v18.3.10 2024-09-28 00:32:00 +00:00
b11a265e56 Merge pull request 'Update dependency @typescript-eslint/eslint-plugin to v8.7.0' () from renovate/typescript-eslint-eslint-plugin-8.x-lockfile into master
Reviewed-on: 
2024-09-27 05:28:32 +00:00
d20c910cd6 Merge pull request 'Update dependency @typescript-eslint/parser to v8.6.0' () from renovate/typescript-eslint-parser-8.x-lockfile into master
Reviewed-on: 
2024-09-27 05:28:19 +00:00
4f3380b029 Merge pull request 'Update dependency @types/react to v18.3.7' () from renovate/react-18.x-lockfile into master
Reviewed-on: 
2024-09-27 05:26:29 +00:00
18dda6ef87 Merge pull request 'Update dependency vite to v5.4.7' () from renovate/vite-5.x-lockfile into master
Reviewed-on: 
2024-09-27 05:26:09 +00:00
dbd7f119ac Update dependency @fluentui/react-components to v9.54.17 2024-09-27 00:34:06 +00:00
4c0fe6214b Update dependency @fluentui/react-components to v9.54.16 2024-09-24 00:13:28 +00:00
c96b9677ec Update dependency vite to v5.4.7 2024-09-21 00:34:57 +00:00
e90fd1032b Update dependency @typescript-eslint/parser to v8.6.0 2024-09-20 00:35:07 +00:00
7f863b91dc Update dependency @types/react to v18.3.7 2024-09-18 00:34:11 +00:00
37 changed files with 5100 additions and 2926 deletions

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

@ -1,4 +1,11 @@
# VirtWeb Remote
WIP project
Web UI that allows to start and stop VMs managed by VirtWEB without having to expose the VirtWEB directly on the Internet.
This project aims to use the VirtWeb API to start and stop VM without directly exposing the VirtWEB API to the Internet.
VirtWebRemote rely on OpenID to authenticate users.
VirtWebRemote authenticates against VirtWEB API using an API token. Both the token ID and private key are required to be able to authenticate against the VirtWEB API.
## Docker image options
```bash
docker run --rm -it pierre42100/virtweb_remote --help
```

1554
remote_backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -1,28 +1,28 @@
[package]
name = "remote_backend"
version = "0.1.0"
edition = "2021"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.21"
env_logger = "0.11.3"
clap = { version = "4.5.4", features = ["derive", "env"] }
serde = { version = "1.0.200", features = ["derive"] }
light-openid = { version = "1.0.2", features = ["crypto-wrapper"] }
basic-jwt = "0.2.0"
actix-web = "4.5.1"
log = "0.4.27"
env_logger = "0.11.8"
clap = { version = "4.5.41", features = ["derive", "env"] }
serde = { version = "1.0.219", features = ["derive"] }
light-openid = { version = "1.0.4", features = ["crypto-wrapper"] }
basic-jwt = "0.3.0"
actix-web = "4.11.0"
actix-remote-ip = "0.1.0"
actix-session = { version = "0.10.0", features = ["cookie-session"] }
actix-identity = "0.7.1"
actix-cors = "0.7.0"
lazy_static = "1.4.0"
anyhow = "1.0.83"
reqwest = { version = "0.12.4", features = ["json"] }
thiserror = "1.0.59"
uuid = { version = "1.8.0", features = ["v4", "serde"] }
futures-util = "0.3.30"
lazy-regex = "3.1.0"
mime_guess = "2.0.4"
rust-embed = { version = "8.3.0" }
actix-session = { version = "0.10.1", features = ["cookie-session"] }
actix-identity = "0.8.0"
actix-cors = "0.7.1"
lazy_static = "1.5.0"
anyhow = "1.0.98"
reqwest = { version = "0.12.22", features = ["json"] }
thiserror = "2.0.12"
uuid = { version = "1.16.0", features = ["v4", "serde"] }
futures-util = "0.3.31"
lazy-regex = "3.4.1"
mime_guess = "2.0.5"
rust-embed = { version = "8.7.2" }

@ -6,4 +6,4 @@ RUN apt-get update \
COPY remote_backend /usr/local/bin/remote_backend
ENTRYPOINT /usr/local/bin/remote_backend
ENTRYPOINT ["/usr/local/bin/remote_backend"]

@ -29,7 +29,7 @@ pub struct AppConfig {
#[arg(
long,
env,
default_value = "http://localhost:9001/.well-known/openid-configuration"
default_value = "http://localhost:9001/dex/.well-known/openid-configuration"
)]
pub oidc_configuration_url: String,

@ -1,6 +1,6 @@
use actix_remote_ip::RemoteIP;
use actix_web::web::Data;
use actix_web::{web, HttpResponse, Responder};
use actix_web::{HttpResponse, Responder, web};
use light_openid::basic_state_manager::BasicStateManager;
use crate::app_config::AppConfig;

@ -0,0 +1,79 @@
use crate::controllers::HttpResult;
use crate::virtweb_client;
use crate::virtweb_client::{GroupID, VMUuid};
use actix_web::{HttpResponse, web};
#[derive(serde::Deserialize)]
pub struct GroupIDInPath {
gid: GroupID,
}
#[derive(serde::Deserialize)]
pub struct VMIDInQuery {
vm_id: Option<VMUuid>,
}
/// Get the state of one or all VM
pub async fn vm_state(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_state(&path.gid, query.vm_id).await?))
}
/// Start one or all VM
pub async fn vm_start(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_start(&path.gid, query.vm_id).await?))
}
/// Shutdown one or all VM
pub async fn vm_shutdown(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_shutdown(&path.gid, query.vm_id).await?))
}
/// Kill one or all VM
pub async fn vm_kill(path: web::Path<GroupIDInPath>, query: web::Query<VMIDInQuery>) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_kill(&path.gid, query.vm_id).await?))
}
/// Reset one or all VM
pub async fn vm_reset(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_reset(&path.gid, query.vm_id).await?))
}
/// Suspend one or all VM
pub async fn vm_suspend(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_suspend(&path.gid, query.vm_id).await?))
}
/// Resume one or all VM
pub async fn vm_resume(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::group_vm_resume(&path.gid, query.vm_id).await?))
}
/// Screenshot one or all VM
pub async fn vm_screenshot(
path: web::Path<GroupIDInPath>,
query: web::Query<VMIDInQuery>,
) -> HttpResult {
let screenshot = virtweb_client::group_vm_screenshot(&path.gid, query.vm_id).await?;
Ok(HttpResponse::Ok()
.insert_header(("content-type", "image/png"))
.body(screenshot))
}

@ -1,11 +1,11 @@
use actix_web::HttpResponse;
use actix_web::body::BoxBody;
use actix_web::http::StatusCode;
use actix_web::HttpResponse;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
pub mod auth_controller;
pub mod group_controller;
pub mod server_controller;
pub mod static_controller;
pub mod sys_info_controller;
@ -37,7 +37,7 @@ impl actix_web::error::ResponseError for HttpErr {
}
}
fn error_response(&self) -> HttpResponse<BoxBody> {
log::error!("Error while processing request! {}", self);
log::error!("Error while processing request! {self}");
HttpResponse::InternalServerError().body("Failed to execute request!")
}
@ -51,7 +51,7 @@ impl From<anyhow::Error> for HttpErr {
impl From<Box<dyn Error>> for HttpErr {
fn from(value: Box<dyn Error>) -> Self {
HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
HttpErr::Err(std::io::Error::other(value.to_string()).into())
}
}
@ -81,7 +81,7 @@ impl From<reqwest::header::ToStrError> for HttpErr {
impl From<actix_web::Error> for HttpErr {
fn from(value: actix_web::Error) -> Self {
HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
HttpErr::Err(std::io::Error::other(value.to_string()).into())
}
}

@ -1,6 +1,8 @@
use crate::app_config::AppConfig;
use crate::controllers::HttpResult;
use crate::extractors::auth_extractor::AuthExtractor;
use crate::virtweb_client;
use crate::virtweb_client::{GroupID, VMCaps, VMInfo};
use actix_web::HttpResponse;
#[derive(serde::Serialize)]
@ -15,3 +17,75 @@ pub async fn config(auth: AuthExtractor) -> HttpResult {
disable_auth: AppConfig::get().unsecure_disable_login,
}))
}
#[derive(Default, Debug, serde::Serialize)]
pub struct Rights {
groups: Vec<GroupInfo>,
vms: Vec<VMInfoAndCaps>,
sys_info: bool,
}
#[derive(Debug, serde::Serialize)]
pub struct GroupInfo {
id: GroupID,
vms: Vec<VMInfo>,
#[serde(flatten)]
caps: VMCaps,
}
#[derive(Debug, serde::Serialize)]
pub struct VMInfoAndCaps {
#[serde(flatten)]
info: VMInfo,
#[serde(flatten)]
caps: VMCaps,
}
pub async fn rights() -> HttpResult {
let rights = virtweb_client::get_token_info().await?;
let mut res = Rights {
groups: vec![],
vms: vec![],
sys_info: rights.can_retrieve_system_info(),
};
for g in rights.list_groups() {
let group_vms = virtweb_client::group_vm_info(&g).await?;
res.groups.push(GroupInfo {
id: g.clone(),
vms: group_vms,
caps: VMCaps {
can_get_state: rights.is_route_allowed("GET", &g.route_vm_state(None)),
can_start: rights.is_route_allowed("GET", &g.route_vm_start(None)),
can_shutdown: rights.is_route_allowed("GET", &g.route_vm_shutdown(None)),
can_kill: rights.is_route_allowed("GET", &g.route_vm_kill(None)),
can_reset: rights.is_route_allowed("GET", &g.route_vm_reset(None)),
can_suspend: rights.is_route_allowed("GET", &g.route_vm_suspend(None)),
can_resume: rights.is_route_allowed("GET", &g.route_vm_resume(None)),
can_screenshot: rights.is_route_allowed("GET", &g.route_vm_screenshot(None)),
},
})
}
for v in rights.list_vm() {
let vm_info = virtweb_client::vm_info(v).await?;
res.vms.push(VMInfoAndCaps {
info: vm_info,
caps: VMCaps {
can_get_state: rights.is_route_allowed("GET", &v.route_state()),
can_start: rights.is_route_allowed("GET", &v.route_start()),
can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()),
can_kill: rights.is_route_allowed("GET", &v.route_kill()),
can_reset: rights.is_route_allowed("GET", &v.route_reset()),
can_suspend: rights.is_route_allowed("GET", &v.route_suspend()),
can_resume: rights.is_route_allowed("GET", &v.route_resume()),
can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()),
},
})
}
Ok(HttpResponse::Ok().json(res))
}

@ -18,7 +18,7 @@ mod serve_static_debug {
#[cfg(not(debug_assertions))]
mod serve_static_release {
use actix_web::{web, HttpResponse, Responder};
use actix_web::{HttpResponse, Responder, web};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]

@ -2,20 +2,6 @@ use crate::controllers::HttpResult;
use crate::virtweb_client;
use actix_web::HttpResponse;
#[derive(serde::Serialize)]
struct SysInfoStatus {
allowed: bool,
}
/// Check if system info can be retrieved
pub async fn config() -> HttpResult {
let info = virtweb_client::get_token_info().await?;
Ok(HttpResponse::Ok().json(SysInfoStatus {
allowed: info.can_retrieve_system_info(),
}))
}
/// Get current system status
pub async fn status() -> HttpResult {
Ok(HttpResponse::Ok().json(virtweb_client::get_server_info().await?))

@ -3,55 +3,7 @@
use crate::controllers::HttpResult;
use crate::virtweb_client;
use crate::virtweb_client::VMUuid;
use actix_web::{web, HttpResponse};
#[derive(Debug, serde::Serialize)]
pub struct VMInfoAndCaps {
uiid: VMUuid,
name: String,
description: Option<String>,
architecture: String,
memory: usize,
number_vcpu: usize,
can_get_state: bool,
can_start: bool,
can_shutdown: bool,
can_kill: bool,
can_reset: bool,
can_suspend: bool,
can_resume: bool,
can_screenshot: bool,
}
/// Get the list of VMs that can be controlled by VirtWeb remote
pub async fn list() -> HttpResult {
let rights = virtweb_client::get_token_info().await?;
let mut res = vec![];
for v in rights.list_vm() {
let vm_info = virtweb_client::vm_info(v).await?;
res.push(VMInfoAndCaps {
uiid: vm_info.uuid,
name: vm_info.name,
description: vm_info.description.clone(),
architecture: vm_info.architecture.to_string(),
memory: vm_info.memory,
number_vcpu: vm_info.number_vcpu,
can_get_state: rights.is_route_allowed("GET", &v.route_state()),
can_start: rights.is_route_allowed("GET", &v.route_start()),
can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()),
can_kill: rights.is_route_allowed("GET", &v.route_kill()),
can_reset: rights.is_route_allowed("GET", &v.route_reset()),
can_suspend: rights.is_route_allowed("GET", &v.route_suspend()),
can_resume: rights.is_route_allowed("GET", &v.route_resume()),
can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()),
})
}
Ok(HttpResponse::Ok().json(res))
}
use actix_web::{HttpResponse, web};
#[derive(serde::Deserialize)]
pub struct ReqPath {

@ -1,7 +1,7 @@
use actix_identity::Identity;
use actix_web::dev::Payload;
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest};
use futures_util::future::{ready, Ready};
use futures_util::future::{Ready, ready};
use std::fmt::Display;
pub struct AuthExtractor {

@ -1,18 +1,19 @@
use actix_cors::Cors;
use actix_identity::config::LogoutBehaviour;
use actix_identity::IdentityMiddleware;
use actix_identity::config::LogoutBehaviour;
use actix_remote_ip::RemoteIPConfig;
use actix_session::storage::CookieSessionStore;
use actix_session::SessionMiddleware;
use actix_session::storage::CookieSessionStore;
use actix_web::cookie::{Key, SameSite};
use actix_web::middleware::Logger;
use actix_web::web::Data;
use actix_web::{web, App, HttpServer};
use actix_web::{App, HttpServer, web};
use light_openid::basic_state_manager::BasicStateManager;
use remote_backend::app_config::AppConfig;
use remote_backend::constants;
use remote_backend::controllers::{
auth_controller, server_controller, static_controller, sys_info_controller, vm_controller,
auth_controller, group_controller, server_controller, static_controller, sys_info_controller,
vm_controller,
};
use remote_backend::middlewares::auth_middleware::AuthChecker;
use std::time::Duration;
@ -82,8 +83,44 @@ async fn main() -> std::io::Result<()> {
"/api/auth/sign_out",
web::get().to(auth_controller::sign_out),
)
.route(
"/api/server/rights",
web::get().to(server_controller::rights),
)
// Groups routes
.route(
"/api/group/{gid}/vm/state",
web::get().to(group_controller::vm_state),
)
.route(
"/api/group/{gid}/vm/start",
web::get().to(group_controller::vm_start),
)
.route(
"/api/group/{gid}/vm/shutdown",
web::get().to(group_controller::vm_shutdown),
)
.route(
"/api/group/{gid}/vm/kill",
web::get().to(group_controller::vm_kill),
)
.route(
"/api/group/{gid}/vm/reset",
web::get().to(group_controller::vm_reset),
)
.route(
"/api/group/{gid}/vm/suspend",
web::get().to(group_controller::vm_suspend),
)
.route(
"/api/group/{gid}/vm/resume",
web::get().to(group_controller::vm_resume),
)
.route(
"/api/group/{gid}/vm/screenshot",
web::get().to(group_controller::vm_screenshot),
)
// VM routes
.route("/api/vm/list", web::get().to(vm_controller::list))
.route("/api/vm/{uid}/state", web::get().to(vm_controller::state))
.route("/api/vm/{uid}/start", web::get().to(vm_controller::start))
.route(
@ -102,10 +139,6 @@ async fn main() -> std::io::Result<()> {
web::get().to(vm_controller::screenshot),
)
// Sys info routes
.route(
"/api/sysinfo/config",
web::get().to(sys_info_controller::config),
)
.route(
"/api/sysinfo/status",
web::get().to(sys_info_controller::status),

@ -1,4 +1,4 @@
use std::future::{ready, Ready};
use std::future::{Ready, ready};
use std::rc::Rc;
use crate::app_config::AppConfig;
@ -7,8 +7,8 @@ use crate::extractors::auth_extractor::AuthExtractor;
use actix_web::body::EitherBody;
use actix_web::dev::Payload;
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error, FromRequest, HttpResponse,
dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready},
};
use futures_util::future::LocalBoxFuture;

@ -1,6 +1,7 @@
use crate::app_config::AppConfig;
use crate::utils::time;
use lazy_regex::regex;
use std::collections::HashMap;
use std::fmt::Display;
use std::str::FromStr;
use thiserror::Error;
@ -12,9 +13,105 @@ pub enum VirtWebClientError {
InvalidStatusCode(u16),
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Eq, PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GroupID(String);
impl GroupID {
pub fn route_vm_info(&self) -> String {
format!("/api/group/{}/vm/info", self.0)
}
pub fn route_vm_state(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/state{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_start(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/start{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_shutdown(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/shutdown{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_suspend(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/suspend{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_resume(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/resume{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_kill(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/kill{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_reset(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/reset{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_screenshot(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/screenshot{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Hash)]
pub struct VMUuid(Uuid);
#[derive(Default, serde::Deserialize, serde::Serialize)]
pub struct TreatmentResult {
ok: usize,
failed: usize,
}
impl VMUuid {
pub fn route_info(&self) -> String {
format!("/api/vm/{}", self.0)
@ -69,7 +166,7 @@ pub struct TokenClaims {
pub nonce: String,
}
#[derive(serde::Deserialize, Debug)]
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMInfo {
pub uuid: VMUuid,
pub name: String,
@ -79,6 +176,18 @@ pub struct VMInfo {
pub number_vcpu: usize,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMCaps {
pub can_get_state: bool,
pub can_start: bool,
pub can_shutdown: bool,
pub can_kill: bool,
pub can_reset: bool,
pub can_suspend: bool,
pub can_resume: bool,
pub can_screenshot: bool,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMState {
pub state: String,
@ -147,6 +256,16 @@ impl TokenInfo {
false
}
/// List the groups with access
pub fn list_groups(&self) -> Vec<GroupID> {
self.rights
.iter()
.filter(|r| r.verb == "GET")
.filter(|r| regex!("^/api/group/[^/]+/vm/info$").is_match(&r.path))
.map(|r| GroupID(r.path.split("/").nth(3).unwrap().to_string()))
.collect::<Vec<_>>()
}
/// List the virtual machines with access
pub fn list_vm(&self) -> Vec<VMUuid> {
self.rights
@ -168,12 +287,13 @@ async fn request<D: Display>(uri: D) -> anyhow::Result<reqwest::Response> {
let url = format!("{}{}", AppConfig::get().virtweb_base_url, uri);
log::debug!("Will query {uri}...");
let uri = uri.to_string();
let jwt = TokenClaims {
sub: AppConfig::get().virtweb_token_id.to_string(),
iat: time() - 60 * 2,
exp: time() + 60 * 3,
verb: "GET".to_string(),
path: uri.to_string(),
path: uri.split_once('?').map(|s| s.0).unwrap_or(&uri).to_string(),
nonce: Uuid::new_v4().to_string(),
};
let jwt = AppConfig::get().token_private_key().sign_jwt(&jwt)?;
@ -260,6 +380,73 @@ pub async fn vm_screenshot(id: VMUuid) -> anyhow::Result<Vec<u8>> {
.to_vec())
}
/// Get the VM of a group
pub async fn group_vm_info(id: &GroupID) -> anyhow::Result<Vec<VMInfo>> {
json_request(id.route_vm_info()).await
}
/// Get the state of one or all VMs of a group
pub async fn group_vm_state(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<HashMap<VMUuid, String>> {
json_request(id.route_vm_state(vm_id)).await
}
/// Start one or all VMs of a group
pub async fn group_vm_start(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_start(vm_id)).await
}
/// Shutdown one or all VMs of a group
pub async fn group_vm_shutdown(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_shutdown(vm_id)).await
}
/// Kill one or all VMs of a group
pub async fn group_vm_kill(id: &GroupID, vm_id: Option<VMUuid>) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_kill(vm_id)).await
}
/// Reset one or all VMs of a group
pub async fn group_vm_reset(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_reset(vm_id)).await
}
/// Suspend one or all VMs of a group
pub async fn group_vm_suspend(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_suspend(vm_id)).await
}
/// Resume one or all VMs of a group
pub async fn group_vm_resume(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_resume(vm_id)).await
}
/// Get the screenshot of one or all VMs of a group
pub async fn group_vm_screenshot(id: &GroupID, vm_id: Option<VMUuid>) -> anyhow::Result<Vec<u8>> {
Ok(request(id.route_vm_screenshot(vm_id))
.await?
.bytes()
.await?
.to_vec())
}
/// Get current server information
pub async fn get_server_info() -> anyhow::Result<SystemInfo> {
json_request("/api/server/info").await

@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

File diff suppressed because it is too large Load Diff

@ -10,22 +10,25 @@
"preview": "vite preview"
},
"dependencies": {
"@fluentui/react-components": "^9.49.2",
"@fluentui/react-icons": "^2.0.239",
"filesize": "^10.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"@fluentui/react-components": "^9.66.6",
"@fluentui/react-icons": "^2.0.305",
"filesize": "^10.1.6",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.2.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"vite": "^5.2.11"
"@eslint/js": "^9.30.1",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^8.36.0",
"@vitejs/plugin-react": "^4.6.0",
"eslint": "^9.26.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.36.0",
"vite": "^6.3.5"
}
}

@ -5,6 +5,8 @@ import {
typographyStyles,
} from "@fluentui/react-components";
import {
AppsListDetailFilled,
AppsListDetailRegular,
DesktopFilled,
DesktopRegular,
InfoFilled,
@ -12,12 +14,13 @@ import {
bundleIcon,
} from "@fluentui/react-icons";
import React from "react";
import { ServerApi } from "./api/ServerApi";
import { Rights, ServerApi } from "./api/ServerApi";
import { AuthRouteWidget } from "./routes/AuthRouteWidget";
import { AsyncWidget } from "./widgets/AsyncWidget";
import { MainMenu } from "./widgets/MainMenu";
import { SystemInfoWidget } from "./widgets/SystemInfoWidget";
import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget";
import { GroupsWidget } from "./widgets/GroupsWidget";
const useStyles = makeStyles({
title: typographyStyles.title2,
@ -27,6 +30,8 @@ const InfoIcon = bundleIcon(InfoFilled, InfoRegular);
const DesktopIcon = bundleIcon(DesktopFilled, DesktopRegular);
const AppListIcon = bundleIcon(AppsListDetailFilled, AppsListDetailRegular);
export function App() {
return (
<AsyncWidget
@ -40,45 +45,79 @@ export function App() {
}
function AppInner(): React.ReactElement {
const styles = useStyles();
const [tab, setTab] = React.useState<"vm" | "info">("vm");
if (!ServerApi.Config.authenticated && !ServerApi.Config.disable_auth)
return <AuthRouteWidget />;
return <AuthenticatedApp />;
}
function AuthenticatedApp(): React.ReactElement {
const styles = useStyles();
const [tab, setTab] = React.useState<"group" | "vm" | "info">("group");
const [rights, setRights] = React.useState<Rights | undefined>();
const load = async () => {
const rights = await ServerApi.GetRights();
setRights(rights);
if (rights!.groups.length > 0) setTab("group");
else if (rights!.vms.length > 0) setTab("vm");
else setTab("info");
};
return (
<div
style={{
width: "95%",
maxWidth: "1000px",
margin: "50px auto",
<AsyncWidget
loadKey={1}
load={load}
errMsg="Failed to retrieve application rights!"
build={() => {
return (
<div
style={{
width: "95%",
maxWidth: "1000px",
margin: "50px auto",
}}
>
<span className={styles.title}>VirtWebRemote</span>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginTop: "30px",
}}
>
<TabList
selectedValue={tab}
onTabSelect={(_, d) => setTab(d.value as any)}
>
{rights!.groups.length > 0 && (
<Tab value="group" icon={<AppListIcon />}>
Groups
</Tab>
)}
{rights!.vms.length > 0 && (
<Tab value="vm" icon={<DesktopIcon />}>
Virtual machines
</Tab>
)}
{rights!.sys_info && (
<Tab value="info" icon={<InfoIcon />}>
System info
</Tab>
)}
</TabList>
<div>
<MainMenu />
</div>
</div>
{tab === "group" && <GroupsWidget rights={rights!} />}
{tab === "vm" && <VirtualMachinesWidget rights={rights!} />}
{tab === "info" && <SystemInfoWidget />}
</div>
);
}}
>
<span className={styles.title}>VirtWebRemote</span>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginTop: "30px",
}}
>
<TabList
selectedValue={tab}
onTabSelect={(_, d) => setTab(d.value as any)}
>
<Tab value="vm" icon={<DesktopIcon />}>
Virtual machines
</Tab>
<Tab value="info" icon={<InfoIcon />}>
System info
</Tab>
</TabList>
<div>
<MainMenu />
</div>
</div>
{tab === "vm" && <VirtualMachinesWidget />}
{tab === "info" && <SystemInfoWidget />}
</div>
/>
);
}

@ -0,0 +1,107 @@
import { APIClient } from "./ApiClient";
import { VMGroup } from "./ServerApi";
import { VMInfo, VMState } from "./VMApi";
export interface GroupVMState {
[key: string]: VMState;
}
export interface TreatmentResult {
ok: number;
failed: number;
}
export class GroupApi {
/**
* Get the state of the VMs of a group
*/
static async State(g: VMGroup): Promise<GroupVMState> {
return (
await APIClient.exec({ method: "GET", uri: `/group/${g.id}/vm/state` })
).data;
}
/**
* Request to start the VM of a group
*/
static async StartVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/start` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
/**
* Request to suspend the VM of a group
*/
static async SuspendVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/suspend` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
/**
* Request to resume the VM of a group
*/
static async ResumeVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/resume` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
/**
* Request to shutdown the VM of a group
*/
static async ShutdownVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/shutdown` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
/**
* Request to kill the VM of a group
*/
static async KillVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/kill` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
/**
* Request to reset the VM of a group
*/
static async ResetVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/reset` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
/**
* Request a screenshot of the VM of group
*/
static async ScreenshotVM(g: VMGroup, vm?: VMInfo): Promise<Blob> {
return (
await APIClient.exec({
method: "GET",
uri: `/group/${g.id}/vm/screenshot` + (vm ? `?vm_id=${vm.uuid}` : ""),
})
).data;
}
}

@ -1,10 +1,24 @@
import { APIClient } from "./ApiClient";
import { VMCaps, VMInfo, VMInfoAndCaps } from "./VMApi";
export interface ServerConfig {
authenticated: boolean;
disable_auth: boolean;
}
export interface Rights {
groups: VMGroup[];
vms: VMInfoAndCaps[];
sys_info: boolean;
}
export type VMGroup = VMGroupInfo & VMCaps;
export interface VMGroupInfo {
id: string;
vms: VMInfo[];
}
let config: ServerConfig | null = null;
export class ServerApi {
@ -27,4 +41,16 @@ export class ServerApi {
if (config === null) throw new Error("Missing configuration!");
return config;
}
/**
* Get application rights
*/
static async GetRights(): Promise<Rights> {
return (
await APIClient.exec({
uri: "/server/rights",
method: "GET",
})
).data;
}
}

@ -1,9 +1,5 @@
import { APIClient } from "./ApiClient";
export interface SysInfoConfig {
allowed: boolean;
}
export interface LoadAverage {
one: number;
five: number;
@ -24,14 +20,6 @@ export interface SysInfoStatus {
}
export class SysInfoApi {
/**
* Get system info configuration (ie. check if it allowed)
*/
static async GetConfig(): Promise<SysInfoConfig> {
return (await APIClient.exec({ method: "GET", uri: "/sysinfo/config" }))
.data;
}
/**
* Get system status
*/

@ -1,12 +1,15 @@
import { APIClient } from "./ApiClient";
export interface VMInfo {
uiid: string;
uuid: string;
name: string;
description?: string;
architecture: string;
memory: number;
number_vcpu: number;
}
export interface VMCaps {
can_get_state: boolean;
can_start: boolean;
can_shutdown: boolean;
@ -17,6 +20,8 @@ export interface VMInfo {
can_screenshot: boolean;
}
export type VMInfoAndCaps = VMInfo & VMCaps;
export type VMState =
| "NoState"
| "Running"
@ -29,19 +34,12 @@ export type VMState =
| "Other";
export class VMApi {
/**
* Get the list of VM that can be managed by this console
*/
static async GetList(): Promise<VMInfo[]> {
return (await APIClient.exec({ method: "GET", uri: "/vm/list" })).data;
}
/**
* Get the state of a VM
*/
static async State(vm: VMInfo): Promise<VMState> {
return (
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/state` })
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/state` })
).data.state;
}
@ -49,42 +47,42 @@ export class VMApi {
* Request to start VM
*/
static async StartVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/start` });
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/start` });
}
/**
* Request to suspend VM
*/
static async SuspendVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/suspend` });
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/suspend` });
}
/**
* Request to resume VM
*/
static async ResumeVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/resume` });
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/resume` });
}
/**
* Request to shutdown VM
*/
static async ShutdownVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/shutdown` });
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/shutdown` });
}
/**
* Request to kill VM
*/
static async KillVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/kill` });
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/kill` });
}
/**
* Request to reset VM
*/
static async ResetVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/reset` });
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uuid}/reset` });
}
/**
@ -93,7 +91,7 @@ export class VMApi {
static async Screenshot(vm: VMInfo): Promise<Blob> {
return (
await APIClient.exec({
uri: `/vm/${vm.uiid}/screenshot`,
uri: `/vm/${vm.uuid}/screenshot`,
method: "GET",
})
).data;

@ -20,7 +20,7 @@ type ThemeContext = { theme: Theme; set: (theme: Theme) => void };
const ThemeContextK = React.createContext<ThemeContext | null>(null);
export function ThemeProvider(p: React.PropsWithChildren): React.ReactElement {
const [theme, setTheme] = React.useState<Theme>("highcontrast");
const [theme, setTheme] = React.useState<Theme>("teamsdark");
let fluentTheme = teamsHighContrastTheme;
switch (theme) {

@ -0,0 +1,177 @@
import { Button, Spinner, Toolbar, Tooltip } from "@fluentui/react-components";
import {
ArrowResetRegular,
PauseRegular,
PlayCircleRegular,
PlayFilled,
PowerRegular,
StopRegular,
} from "@fluentui/react-icons";
import React from "react";
import { GroupApi, TreatmentResult } from "../api/GroupApi";
import { VMGroup } from "../api/ServerApi";
import { VMInfo, VMState } from "../api/VMApi";
import { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
import { useToast } from "../hooks/providers/ToastProvider";
export function GroupVMAction(p: {
group: VMGroup;
state?: VMState;
vm?: VMInfo;
}): React.ReactElement {
return (
<Toolbar>
<GroupVMButton
enabled={p.group.can_start}
icon={<PlayFilled />}
tooltip="Start"
group={p.group}
vm={p.vm}
allowedStates={["Shutdown", "Shutoff", "Crashed"]}
currState={p.state}
needConfirm={false}
action={GroupApi.StartVM}
/>
<GroupVMButton
enabled={p.group.can_suspend}
icon={<PauseRegular />}
tooltip="Suspend"
group={p.group}
vm={p.vm}
allowedStates={["Running"]}
currState={p.state}
needConfirm={true}
action={GroupApi.SuspendVM}
/>
<GroupVMButton
enabled={p.group.can_resume}
icon={<PlayCircleRegular />}
tooltip="Resume"
group={p.group}
vm={p.vm}
allowedStates={["Paused", "PowerManagementSuspended"]}
currState={p.state}
needConfirm={false}
action={GroupApi.ResumeVM}
/>
<GroupVMButton
enabled={p.group.can_shutdown}
icon={<PowerRegular />}
tooltip="Shutdown"
group={p.group}
vm={p.vm}
allowedStates={["Running"]}
currState={p.state}
needConfirm={true}
action={GroupApi.ShutdownVM}
/>
<GroupVMButton
enabled={p.group.can_kill}
icon={<StopRegular />}
tooltip="Kill"
group={p.group}
vm={p.vm}
allowedStates={[
"Running",
"Paused",
"PowerManagementSuspended",
"Blocked",
]}
currState={p.state}
needConfirm={true}
action={GroupApi.KillVM}
/>
<GroupVMButton
enabled={p.group.can_reset}
icon={<ArrowResetRegular />}
tooltip="Reset"
group={p.group}
vm={p.vm}
allowedStates={[
"Running",
"Paused",
"PowerManagementSuspended",
"Blocked",
]}
currState={p.state}
needConfirm={true}
action={GroupApi.ResetVM}
/>
</Toolbar>
);
}
function GroupVMButton(p: {
enabled: boolean;
icon: React.ReactElement;
action: (group: VMGroup, vm?: VMInfo) => Promise<TreatmentResult>;
tooltip: string;
currState?: VMState;
allowedStates: VMState[];
group: VMGroup;
vm?: VMInfo;
needConfirm: boolean;
}): React.ReactElement {
const toast = useToast();
const confirm = useConfirm();
const alert = useAlert();
const [running, setRunning] = React.useState(false);
const target = p.vm
? `the VM ${p.vm.name}`
: `all the VM of the group ${p.group.id}`;
const allowed =
!p.vm || (p.currState && p.allowedStates.includes(p.currState));
const perform = async () => {
if (running || !allowed) return;
try {
if (
(!p.vm || p.needConfirm) &&
!(await confirm(
`Do you want to perform ${p.tooltip} action on ${target}?`,
`Confirmation`,
p.tooltip
))
) {
return;
}
setRunning(true);
const result = await p.action(p.group, p.vm);
toast(
p.tooltip,
`${p.tooltip} action on ${target}: ${result.ok} OK / ${result.failed} Failed`,
"success"
);
} catch (e) {
console.error("Failed to perform group action!", e);
alert(`Failed to perform ${p.tooltip} action on ${target}: ${e}`);
} finally {
setRunning(false);
}
};
if (!p.enabled) return <></>;
return (
<Tooltip
content={`${p.tooltip} ${target}`}
relationship="description"
withArrow
>
<Button
icon={running ? <Spinner size="tiny" /> : p.icon}
onClick={allowed ? perform : undefined}
disabled={!allowed}
appearance="subtle"
/>
</Tooltip>
);
}

@ -0,0 +1,171 @@
import {
Button,
Card,
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogSurface,
DialogTitle,
DialogTrigger,
Table,
TableBody,
TableCell,
TableCellActions,
TableCellLayout,
TableHeader,
TableHeaderCell,
TableRow,
Title3,
Tooltip,
} from "@fluentui/react-components";
import { Desktop24Regular, ScreenshotRegular } from "@fluentui/react-icons";
import { filesize } from "filesize";
import React from "react";
import { GroupApi, GroupVMState } from "../api/GroupApi";
import { Rights, VMGroup } from "../api/ServerApi";
import { VMInfo } from "../api/VMApi";
import { useToast } from "../hooks/providers/ToastProvider";
import { GroupVMAction } from "./GroupVMAction";
import { VMLiveScreenshot } from "./VMLiveScreenshot";
export function GroupsWidget(p: { rights: Rights }): React.ReactElement {
return (
<>
{p.rights.groups.map((g) => (
<GroupInfo group={g} />
))}
</>
);
}
function GroupInfo(p: { group: VMGroup }): React.ReactElement {
const toast = useToast();
const [state, setState] = React.useState<GroupVMState | undefined>();
const [screenshotVM, setScreenshotVM] = React.useState<VMInfo | undefined>();
const load = async () => {
const newState = await GroupApi.State(p.group);
if (state !== newState) setState(newState);
};
const screenshot = (vm: VMInfo) => {
setScreenshotVM(vm);
};
React.useEffect(() => {
const interval = setInterval(async () => {
try {
if (p.group.can_get_state) await load();
} catch (e) {
console.error(e);
toast(
"Error",
`Failed to refresh group ${p.group.id} VMs status!`,
"error"
);
}
}, 1000);
return () => clearInterval(interval);
});
return (
<>
<Card
style={{
margin: "50px 10px",
display: "flex",
flexDirection: "column",
}}
>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Title3 style={{ marginLeft: "10px" }}>{p.group.id}</Title3>
<GroupVMAction group={p.group} />
</div>
<Table sortable>
<TableHeader>
<TableRow>
<TableHeaderCell>VM</TableHeaderCell>
<TableHeaderCell>Resources</TableHeaderCell>
<TableHeaderCell>State</TableHeaderCell>
<TableHeaderCell>Actions</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{p.group.vms.map((item) => (
<TableRow key={item.uuid}>
<TableCell>
<TableCellLayout
media={<Desktop24Regular />}
appearance="primary"
description={item.description}
>
{item.name}
</TableCellLayout>
<TableCellActions>
{state?.[item.uuid] === "Running" && (
<Tooltip
relationship="description"
content={"Take a screenshot of the VM screen"}
withArrow
>
<Button
icon={<ScreenshotRegular />}
appearance="subtle"
aria-label="Edit"
disabled={!p.group.can_screenshot}
onClick={() => screenshot(item)}
/>
</Tooltip>
)}
</TableCellActions>
</TableCell>
<TableCell>
{item.architecture} &bull; RAM :{" "}
{filesize(item.memory)} &bull;{" "}
{item.number_vcpu} vCPU
</TableCell>
<TableCell>{state?.[item.uuid] ?? ""}</TableCell>
<TableCell>
<GroupVMAction
group={p.group}
state={state?.[item.uuid]}
vm={item}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
<Dialog
open={!!screenshotVM}
onOpenChange={(_event, _data) => {
if (!screenshotVM) setScreenshotVM(undefined);
}}
>
<DialogSurface>
<DialogBody>
<DialogTitle>
<em>{screenshotVM?.name}</em> screen
</DialogTitle>
<DialogContent>
<VMLiveScreenshot vm={screenshotVM!} group={p.group} />
</DialogContent>
<DialogActions>
<DialogTrigger disableButtonEnhancement>
<Button
appearance="secondary"
onClick={() => setScreenshotVM(undefined)}
>
Close
</Button>
</DialogTrigger>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</>
);
}

@ -1,47 +1,13 @@
import React from "react";
import { SysInfoApi, SysInfoConfig, SysInfoStatus } from "../api/SysInfoApi";
import { AsyncWidget } from "./AsyncWidget";
import { SectionContainer } from "./SectionContainer";
import { Field, ProgressBar } from "@fluentui/react-components";
import { filesize } from "filesize";
import { format_duration } from "../utils/time_utils";
import React from "react";
import { SysInfoApi, SysInfoStatus } from "../api/SysInfoApi";
import { useToast } from "../hooks/providers/ToastProvider";
import { format_duration } from "../utils/time_utils";
import { AsyncWidget } from "./AsyncWidget";
import { SectionContainer } from "./SectionContainer";
export function SystemInfoWidget(): React.ReactElement {
const [config, setConfig] = React.useState<SysInfoConfig | undefined>();
const load = async () => {
setConfig(await SysInfoApi.GetConfig());
};
return (
<SectionContainer>
<AsyncWidget
loadKey={1}
load={load}
errMsg="Failed to check system configuration!"
loadingMessage="Checking server configuration..."
build={() =>
config?.allowed ? (
<SystemInfoWidgetInner />
) : (
<SystemInfoWidgetUnavailable />
)
}
/>
</SectionContainer>
);
}
function SystemInfoWidgetUnavailable(): React.ReactElement {
return (
<p style={{ textAlign: "center" }}>
Unfortunatley, system information is available. (not enough privileges)
</p>
);
}
function SystemInfoWidgetInner(): React.ReactElement {
const toast = useToast();
const [status, setStatus] = React.useState<SysInfoStatus | undefined>();
@ -63,49 +29,51 @@ function SystemInfoWidgetInner(): React.ReactElement {
});
return (
<AsyncWidget
loadKey={1}
load={load}
loadingMessage="Loading system status..."
errMsg="Failed to load system status!"
build={() => (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
}}
>
<Field
validationMessage={`${filesize(
status!.system.used_memory
)} of memory used out of ${filesize(
status!.system.available_memory + status!.system.used_memory
)}`}
validationState="none"
style={{ flex: 2 }}
<SectionContainer>
<AsyncWidget
loadKey={1}
load={load}
loadingMessage="Loading system status..."
errMsg="Failed to load system status!"
build={() => (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
}}
>
<ProgressBar
value={
status!.system.used_memory /
(status!.system.available_memory + status!.system.used_memory)
}
/>
</Field>
<div style={{ width: "20px" }}></div>
<div style={{ flex: 1 }}>
<p>
Load average: {status!.system.load_average.one}{" "}
{status!.system.load_average.five}{" "}
{status!.system.load_average.fifteen}
</p>
<UptimeWidget uptime={status!.system.uptime} />
Number physical cores: {status!.system.physical_core_count}
<Field
validationMessage={`${filesize(
status!.system.used_memory
)} of memory used out of ${filesize(
status!.system.available_memory + status!.system.used_memory
)}`}
validationState="none"
style={{ flex: 2 }}
>
<ProgressBar
value={
status!.system.used_memory /
(status!.system.available_memory + status!.system.used_memory)
}
/>
</Field>
<div style={{ width: "20px" }}></div>
<div style={{ flex: 1 }}>
<p>
Load average: {status!.system.load_average.one}{" "}
{status!.system.load_average.five}{" "}
{status!.system.load_average.fifteen}
</p>
<UptimeWidget uptime={status!.system.uptime} />
Number physical cores: {status!.system.physical_core_count}
</div>
</div>
</div>
)}
/>
)}
/>
</SectionContainer>
);
}

@ -1,8 +1,13 @@
import React from "react";
import { GroupApi } from "../api/GroupApi";
import { VMGroup } from "../api/ServerApi";
import { VMApi, VMInfo } from "../api/VMApi";
import { useToast } from "../hooks/providers/ToastProvider";
export function VMLiveScreenshot(p: { vm: VMInfo }): React.ReactElement {
export function VMLiveScreenshot(p: {
vm: VMInfo;
group?: VMGroup;
}): React.ReactElement {
const toast = useToast();
const [screenshotURL, setScreenshotURL] = React.useState<
@ -14,7 +19,9 @@ export function VMLiveScreenshot(p: { vm: VMInfo }): React.ReactElement {
React.useEffect(() => {
const refresh = async () => {
try {
const screenshot = await VMApi.Screenshot(p.vm);
const screenshot = p.group
? await GroupApi.ScreenshotVM(p.group, p.vm)
: await VMApi.Screenshot(p.vm);
const u = URL.createObjectURL(screenshot);
setScreenshotURL(u);
} catch (e) {

@ -21,10 +21,10 @@ import {
} from "@fluentui/react-icons";
import { filesize } from "filesize";
import React from "react";
import { VMApi, VMInfo, VMState } from "../api/VMApi";
import { Rights } from "../api/ServerApi";
import { VMApi, VMInfo, VMInfoAndCaps, VMState } from "../api/VMApi";
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
import { useToast } from "../hooks/providers/ToastProvider";
import { AsyncWidget } from "./AsyncWidget";
import { SectionContainer } from "./SectionContainer";
import { VMLiveScreenshot } from "./VMLiveScreenshot";
@ -33,43 +33,28 @@ const useStyles = makeStyles({
caption1: typographyStyles.caption1,
});
export function VirtualMachinesWidget(): React.ReactElement {
const [list, setList] = React.useState<VMInfo[] | undefined>();
const load = async () => {
setList(await VMApi.GetList());
};
export function VirtualMachinesWidget(p: {
rights: Rights;
}): React.ReactElement {
return (
<SectionContainer>
<AsyncWidget
loadKey={1}
load={load}
loadingMessage="Loading the list virtual machines..."
errMsg="Failed to load the list of virtual machines!"
build={() => <VirtualMachinesWidgetInner list={list!} />}
/>
<div
style={{
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
}}
>
{p.rights.vms.map((v, n) => (
<VMWidget key={n} vm={v} />
))}
</div>
</SectionContainer>
);
}
function VirtualMachinesWidgetInner(p: { list: VMInfo[] }): React.ReactElement {
return (
<div
style={{
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
}}
>
{p.list.map((v, n) => (
<VMWidget key={n} vm={v} />
))}{" "}
</div>
);
}
function VMWidget(p: { vm: VMInfo }): React.ReactElement {
function VMWidget(p: { vm: VMInfoAndCaps }): React.ReactElement {
const toast = useToast();
const [state, setState] = React.useState<VMState | undefined>();
@ -122,7 +107,7 @@ function VMWidget(p: { vm: VMInfo }): React.ReactElement {
}
/>
<p className={styles.caption1} style={{ margin: "0px auto" }}>
{p.vm.architecture} &bull; RAM : {filesize(p.vm.memory * 1000 * 1000)}{" "}
{p.vm.architecture} &bull; RAM : {filesize(p.vm.memory)}{" "}
&bull; {p.vm.number_vcpu} vCPU
</p>
@ -204,7 +189,10 @@ function VMWidget(p: { vm: VMInfo }): React.ReactElement {
);
}
function VMPreview(p: { vm: VMInfo; state?: VMState }): React.ReactElement {
function VMPreview(p: {
vm: VMInfoAndCaps;
state?: VMState;
}): React.ReactElement {
const styles = useStyles();
if (!p.vm.can_screenshot || p.state !== "Running") {
return (

@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

@ -1,25 +1,7 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

@ -1,11 +1,24 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

@ -1,9 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"matchUpdateTypes": ["major", "minor", "patch"],
"automerge": true
}
]
"extends": ["local>renovate/presets"]
}