Compare commits
8 Commits
20250531v2
...
33d70c2ee0
Author | SHA1 | Date | |
---|---|---|---|
33d70c2ee0 | |||
b3f56cea81 | |||
9bd702d60f | |||
c8b42626a9 | |||
4ef15507d9 | |||
8a4b3a4db6 | |||
8bce9ca9b7 | |||
5574037b73 |
51
virtweb_backend/Cargo.lock
generated
51
virtweb_backend/Cargo.lock
generated
@ -1622,18 +1622,23 @@ version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1839,6 +1844,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iri-string"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@ -2773,9 +2788,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.15"
|
||||
version = "0.12.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||
checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@ -2798,23 +2813,22 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2923,15 +2937,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.12.0"
|
||||
@ -3522,6 +3527,24 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
|
@ -27,7 +27,7 @@ futures-util = "0.3.31"
|
||||
anyhow = "1.0.98"
|
||||
actix-multipart = "0.7.2"
|
||||
tempfile = "3.20.0"
|
||||
reqwest = { version = "0.12.15", features = ["stream"] }
|
||||
reqwest = { version = "0.12.19", features = ["stream"] }
|
||||
url = "2.5.4"
|
||||
virt = "0.4.2"
|
||||
sysinfo = { version = "0.35.1", features = ["serde"] }
|
||||
|
@ -122,19 +122,22 @@ pub const API_TOKEN_DESCRIPTION_MAX_LENGTH: usize = 30;
|
||||
pub const API_TOKEN_RIGHT_PATH_MAX_LENGTH: usize = 255;
|
||||
|
||||
/// Qemu image program path
|
||||
pub const QEMU_IMAGE_PROGRAM: &str = "/usr/bin/qemu-img";
|
||||
pub const PROGRAM_QEMU_IMAGE: &str = "/usr/bin/qemu-img";
|
||||
|
||||
/// IP program path
|
||||
pub const IP_PROGRAM: &str = "/usr/sbin/ip";
|
||||
pub const PROGRAM_IP: &str = "/usr/sbin/ip";
|
||||
|
||||
/// Copy program path
|
||||
pub const COPY_PROGRAM: &str = "/bin/cp";
|
||||
pub const PROGRAM_COPY: &str = "/bin/cp";
|
||||
|
||||
/// Gzip program path
|
||||
pub const GZIP_PROGRAM: &str = "/usr/bin/gzip";
|
||||
pub const PROGRAM_GZIP: &str = "/usr/bin/gzip";
|
||||
|
||||
/// Bash program
|
||||
pub const BASH_PROGRAM: &str = "/usr/bin/bash";
|
||||
pub const PROGRAM_BASH: &str = "/usr/bin/bash";
|
||||
|
||||
/// DD program
|
||||
pub const DD_PROGRAM: &str = "/usr/bin/dd";
|
||||
pub const PROGRAM_DD: &str = "/usr/bin/dd";
|
||||
|
||||
/// cloud-localds program
|
||||
pub const PROGRAM_CLOUD_LOCALDS: &str = "/usr/bin/cloud-localds";
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::libvirt_lib_structures::XMLUuid;
|
||||
use crate::utils::cloud_init_utils::CloudInitConfig;
|
||||
|
||||
/// VirtWeb specific metadata
|
||||
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
|
||||
@ -8,6 +9,8 @@ pub struct DomainMetadataVirtWebXML {
|
||||
pub ns: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub group: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cloud_init: Option<CloudInitConfig>,
|
||||
}
|
||||
|
||||
/// Domain metadata
|
||||
|
@ -4,6 +4,7 @@ use crate::libvirt_lib_structures::XMLUuid;
|
||||
use crate::libvirt_lib_structures::domain::*;
|
||||
use crate::libvirt_rest_structures::LibVirtStructError;
|
||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
||||
use crate::utils::cloud_init_utils::CloudInitConfig;
|
||||
use crate::utils::file_size_utils::FileSize;
|
||||
use crate::utils::files_utils;
|
||||
use crate::utils::vm_file_disks_utils::{VMDiskBus, VMDiskFormat, VMFileDisk};
|
||||
@ -94,6 +95,9 @@ pub struct VMInfo {
|
||||
pub tpm_module: bool,
|
||||
/// Strings injected as OEM Strings in SMBios configuration
|
||||
pub oem_strings: Vec<String>,
|
||||
/// Cloud init configuration
|
||||
#[serde(default)]
|
||||
pub cloud_init: CloudInitConfig,
|
||||
}
|
||||
|
||||
impl VMInfo {
|
||||
@ -340,6 +344,7 @@ impl VMInfo {
|
||||
virtweb: DomainMetadataVirtWebXML {
|
||||
ns: "https://virtweb.communiquons.org".to_string(),
|
||||
group: self.group.clone().map(|g| g.0),
|
||||
cloud_init: Some(self.cloud_init.clone()),
|
||||
},
|
||||
}),
|
||||
os: OSXML {
|
||||
@ -582,6 +587,13 @@ impl VMInfo {
|
||||
.and_then(|s| s.oem_strings)
|
||||
.map(|s| s.entries.iter().map(|o| o.content.to_string()).collect())
|
||||
.unwrap_or_default(),
|
||||
cloud_init: domain
|
||||
.metadata
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.virtweb
|
||||
.cloud_init
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -47,13 +47,17 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
log::debug!("Checking for required programs");
|
||||
exec_utils::check_program(
|
||||
constants::QEMU_IMAGE_PROGRAM,
|
||||
constants::PROGRAM_QEMU_IMAGE,
|
||||
"QEMU disk image utility is required to manipulate QCow2 files!",
|
||||
);
|
||||
exec_utils::check_program(
|
||||
constants::IP_PROGRAM,
|
||||
constants::PROGRAM_IP,
|
||||
"ip is required to access bridges information!",
|
||||
);
|
||||
exec_utils::check_program(
|
||||
constants::PROGRAM_CLOUD_LOCALDS,
|
||||
"cloud-localds from package cloud-image-utils is required to build cloud-init images!",
|
||||
);
|
||||
|
||||
log::debug!("Create required directory, if missing");
|
||||
files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap();
|
||||
|
19
virtweb_backend/src/utils/cloud_init_utils.rs
Normal file
19
virtweb_backend/src/utils/cloud_init_utils.rs
Normal file
@ -0,0 +1,19 @@
|
||||
/// VM Cloud Init configuration
|
||||
///
|
||||
/// RedHat documentation: https://docs.redhat.com/fr/documentation/red_hat_enterprise_linux/9/html/configuring_and_managing_cloud-init_for_rhel_9/configuring-cloud-init_cloud-content
|
||||
/// cloud-localds source code: https://github.com/canonical/cloud-utils/blob/main/bin/cloud-localds
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
|
||||
pub struct CloudInitConfig {
|
||||
attach_config: bool,
|
||||
/// Main user data
|
||||
user_data: String,
|
||||
/// Instance ID, set in metadata file
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instance_id: Option<String>,
|
||||
/// Local hostname, set in metadata file
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
local_hostname: Option<String>,
|
||||
/// Network configuration
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
network_configuration: Option<String>,
|
||||
}
|
@ -124,7 +124,7 @@ impl DiskFileInfo {
|
||||
}
|
||||
|
||||
DiskFileFormat::QCow2 { virtual_size } => {
|
||||
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
|
||||
cmd.arg("create")
|
||||
.arg("-f")
|
||||
.arg("qcow2")
|
||||
@ -161,7 +161,7 @@ impl DiskFileInfo {
|
||||
let mut cmd = match (self.format, dest_format) {
|
||||
// Decompress QCow2
|
||||
(DiskFileFormat::CompressedQCow2, DiskFileFormat::QCow2 { .. }) => {
|
||||
let mut cmd = Command::new(constants::GZIP_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_GZIP);
|
||||
cmd.arg("--keep")
|
||||
.arg("--decompress")
|
||||
.arg("--to-stdout")
|
||||
@ -172,7 +172,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Compress QCow2
|
||||
(DiskFileFormat::QCow2 { .. }, DiskFileFormat::CompressedQCow2) => {
|
||||
let mut cmd = Command::new(constants::GZIP_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_GZIP);
|
||||
cmd.arg("--keep")
|
||||
.arg("--to-stdout")
|
||||
.arg(&self.file_path)
|
||||
@ -182,7 +182,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Convert QCow2 to Raw file
|
||||
(DiskFileFormat::QCow2 { .. }, DiskFileFormat::Raw { is_sparse }) => {
|
||||
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
|
||||
cmd.arg("convert")
|
||||
.arg("-f")
|
||||
.arg("qcow2")
|
||||
@ -201,7 +201,7 @@ impl DiskFileInfo {
|
||||
// Clone a QCow file, using qemu-image instead of cp might improve "sparsification" of
|
||||
// file
|
||||
(DiskFileFormat::QCow2 { .. }, DiskFileFormat::QCow2 { .. }) => {
|
||||
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
|
||||
cmd.arg("convert")
|
||||
.arg("-f")
|
||||
.arg("qcow2")
|
||||
@ -214,7 +214,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Convert Raw to QCow2 file
|
||||
(DiskFileFormat::Raw { .. }, DiskFileFormat::QCow2 { .. }) => {
|
||||
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
|
||||
cmd.arg("convert")
|
||||
.arg("-f")
|
||||
.arg("raw")
|
||||
@ -228,7 +228,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Render raw file non sparse
|
||||
(DiskFileFormat::Raw { is_sparse: true }, DiskFileFormat::Raw { is_sparse: false }) => {
|
||||
let mut cmd = Command::new(constants::COPY_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_COPY);
|
||||
cmd.arg("--sparse=never")
|
||||
.arg(&self.file_path)
|
||||
.arg(&temp_file);
|
||||
@ -237,7 +237,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Render raw file sparse
|
||||
(DiskFileFormat::Raw { is_sparse: false }, DiskFileFormat::Raw { is_sparse: true }) => {
|
||||
let mut cmd = Command::new(constants::DD_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_DD);
|
||||
cmd.arg("conv=sparse")
|
||||
.arg(format!("if={}", self.file_path.display()))
|
||||
.arg(format!("of={}", temp_file.display()));
|
||||
@ -246,7 +246,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Compress Raw
|
||||
(DiskFileFormat::Raw { .. }, DiskFileFormat::CompressedRaw) => {
|
||||
let mut cmd = Command::new(constants::GZIP_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_GZIP);
|
||||
cmd.arg("--keep")
|
||||
.arg("--to-stdout")
|
||||
.arg(&self.file_path)
|
||||
@ -256,7 +256,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Decompress Raw to not sparse file
|
||||
(DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: false }) => {
|
||||
let mut cmd = Command::new(constants::GZIP_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_GZIP);
|
||||
cmd.arg("--keep")
|
||||
.arg("--decompress")
|
||||
.arg("--to-stdout")
|
||||
@ -268,12 +268,12 @@ impl DiskFileInfo {
|
||||
// Decompress Raw to sparse file
|
||||
// https://benou.fr/www/ben/decompressing-sparse-files.html
|
||||
(DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: true }) => {
|
||||
let mut cmd = Command::new(constants::BASH_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_BASH);
|
||||
cmd.arg("-c").arg(format!(
|
||||
"{} -d -c {} | {} conv=sparse of={}",
|
||||
constants::GZIP_PROGRAM,
|
||||
constants::PROGRAM_GZIP,
|
||||
self.file_path.display(),
|
||||
constants::DD_PROGRAM,
|
||||
constants::PROGRAM_DD,
|
||||
temp_file.display()
|
||||
));
|
||||
cmd
|
||||
@ -281,7 +281,7 @@ impl DiskFileInfo {
|
||||
|
||||
// Dumb copy of file
|
||||
(a, b) if a == b => {
|
||||
let mut cmd = Command::new(constants::COPY_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_COPY);
|
||||
cmd.arg("--sparse=auto")
|
||||
.arg(&self.file_path)
|
||||
.arg(&temp_file);
|
||||
@ -341,7 +341,7 @@ struct QCowInfoOutput {
|
||||
/// Get QCow2 virtual size
|
||||
fn qcow_virt_size(path: &Path) -> anyhow::Result<FileSize> {
|
||||
// Run qemu-img
|
||||
let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
|
||||
cmd.args([
|
||||
"info",
|
||||
path.to_str().unwrap_or(""),
|
||||
@ -353,7 +353,7 @@ fn qcow_virt_size(path: &Path) -> anyhow::Result<FileSize> {
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} info failed, status: {}, stderr: {}",
|
||||
constants::QEMU_IMAGE_PROGRAM,
|
||||
constants::PROGRAM_QEMU_IMAGE,
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod cloud_init_utils;
|
||||
pub mod exec_utils;
|
||||
pub mod file_disks_utils;
|
||||
pub mod file_size_utils;
|
||||
|
@ -145,13 +145,13 @@ struct IPBridgeInfo {
|
||||
|
||||
/// Get the list of bridge interfaces
|
||||
pub fn bridges_list() -> anyhow::Result<Vec<String>> {
|
||||
let mut cmd = Command::new(constants::IP_PROGRAM);
|
||||
let mut cmd = Command::new(constants::PROGRAM_IP);
|
||||
cmd.args(["-json", "link", "show", "type", "bridge"]);
|
||||
let output = cmd.output()?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} failed, status: {}, stderr: {}",
|
||||
constants::IP_PROGRAM,
|
||||
constants::PROGRAM_IP,
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
@ -5,9 +5,9 @@
|
||||
sudo apt install libvirt-dev
|
||||
```
|
||||
|
||||
2. Libvirt must also be installed:
|
||||
2. Libvirt and cloud image utilities must also be installed:
|
||||
```bash
|
||||
sudo apt install qemu-kvm libvirt-daemon-system
|
||||
sudo apt install qemu-kvm libvirt-daemon-system cloud-image-utils
|
||||
```
|
||||
|
||||
3. Allow the current user to manage VMs:
|
||||
|
@ -12,10 +12,10 @@ The release file will be available in `virtweb_backend/target/release/virtweb_ba
|
||||
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
|
||||
In order to work properly, VirtWeb relies on `libvirt`, `qemu` and `kvm`:
|
||||
In order to work properly, VirtWeb relies on `libvirt`, `qemu`, `kvm` and `cloud-localds`:
|
||||
|
||||
```bash
|
||||
sudo apt install qemu-kvm libvirt-daemon-system libvirt0 libvirt-clients libvirt-daemon bridge-utils
|
||||
sudo apt install qemu-kvm libvirt-daemon-system libvirt0 libvirt-clients libvirt-daemon bridge-utils cloud-image-utils
|
||||
```
|
||||
|
||||
## Dedicated user
|
||||
|
110
virtweb_frontend/package-lock.json
generated
110
virtweb_frontend/package-lock.json
generated
@ -13,8 +13,8 @@
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/icons-material": "^7.1.1",
|
||||
"@mui/material": "^7.1.1",
|
||||
"@mui/x-charts": "^8.3.1",
|
||||
"@mui/x-data-grid": "^8.3.1",
|
||||
"date-and-time": "^3.6.0",
|
||||
@ -22,7 +22,7 @@
|
||||
"humanize-duration": "^3.32.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-router-dom": "^7.6.2",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-vnc": "^3.1.0",
|
||||
"uuid": "^11.1.0",
|
||||
@ -32,14 +32,14 @@
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@types/humanize-duration": "^3.27.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@types/react": "^19.1.6",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-react-dom": "^1.49.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"eslint-plugin-react-x": "^1.49.0",
|
||||
"globals": "^16.1.0",
|
||||
@ -959,9 +959,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz",
|
||||
"integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz",
|
||||
"integrity": "sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -969,9 +969,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz",
|
||||
"integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.1.tgz",
|
||||
"integrity": "sha512-X37+Yc8QpEnl0sYmz+WcLFy2dWgNRzbswDzLPXG7QU1XDVlP5TPp1HXjdmCupOWLL/I9m1fyhcyZl8/HPpp/Cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
@ -984,7 +984,7 @@
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/material": "^7.1.1",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
@ -995,16 +995,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz",
|
||||
"integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.1.tgz",
|
||||
"integrity": "sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/core-downloads-tracker": "^7.1.0",
|
||||
"@mui/system": "^7.1.0",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"@mui/core-downloads-tracker": "^7.1.1",
|
||||
"@mui/system": "^7.1.1",
|
||||
"@mui/types": "^7.4.3",
|
||||
"@mui/utils": "^7.1.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"clsx": "^2.1.1",
|
||||
@ -1023,7 +1023,7 @@
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material-pigment-css": "^7.1.0",
|
||||
"@mui/material-pigment-css": "^7.1.1",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
@ -1044,13 +1044,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
|
||||
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz",
|
||||
"integrity": "sha512-M8NbLUx+armk2ZuaxBkkMk11ultnWmrPlN0Xe3jUEaBChg/mcxa5HWIWS1EE4DF36WRACaAHVAvyekWlDQf0PQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"@mui/utils": "^7.1.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -1071,9 +1071,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
|
||||
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.1.tgz",
|
||||
"integrity": "sha512-R2wpzmSN127j26HrCPYVQ53vvMcT5DaKLoWkrfwUYq3cYytL6TQrCH8JBH3z79B6g4nMZZVoaXrxO757AlShaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
@ -1105,16 +1105,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz",
|
||||
"integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.1.tgz",
|
||||
"integrity": "sha512-Kj1uhiqnj4Zo7PDjAOghtXJtNABunWvhcRU0O7RQJ7WOxeynoH6wXPcilphV8QTFtkKaip8EiNJRiCD+B3eROA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/private-theming": "^7.1.0",
|
||||
"@mui/styled-engine": "^7.1.0",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"@mui/private-theming": "^7.1.1",
|
||||
"@mui/styled-engine": "^7.1.1",
|
||||
"@mui/types": "^7.4.3",
|
||||
"@mui/utils": "^7.1.1",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
@ -1145,9 +1145,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz",
|
||||
"integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==",
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz",
|
||||
"integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
@ -1162,13 +1162,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.1.tgz",
|
||||
"integrity": "sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/types": "^7.4.3",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
@ -1598,18 +1598,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz",
|
||||
"integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==",
|
||||
"version": "19.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
||||
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
|
||||
"integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
|
||||
"version": "19.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
|
||||
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@ -3989,9 +3989,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
|
||||
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.2.tgz",
|
||||
"integrity": "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
@ -4011,12 +4011,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
|
||||
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.2.tgz",
|
||||
"integrity": "sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.6.0"
|
||||
"react-router": "7.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
|
@ -15,8 +15,8 @@
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/icons-material": "^7.1.1",
|
||||
"@mui/material": "^7.1.1",
|
||||
"@mui/x-charts": "^8.3.1",
|
||||
"@mui/x-data-grid": "^8.3.1",
|
||||
"date-and-time": "^3.6.0",
|
||||
@ -24,7 +24,7 @@
|
||||
"humanize-duration": "^3.32.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-router-dom": "^7.6.2",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-vnc": "^3.1.0",
|
||||
"uuid": "^11.1.0",
|
||||
@ -34,14 +34,14 @@
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@types/humanize-duration": "^3.27.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@types/react": "^19.1.6",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-react-dom": "^1.49.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"eslint-plugin-react-x": "^1.49.0",
|
||||
"globals": "^16.1.0",
|
||||
|
Reference in New Issue
Block a user