13 Commits

36 changed files with 918 additions and 327 deletions

View File

@@ -125,6 +125,44 @@ dependencies = [
"syn",
]
[[package]]
name = "actix-multipart"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53"
dependencies = [
"actix-multipart-derive",
"actix-utils",
"actix-web",
"derive_more 0.99.18",
"futures-core",
"futures-util",
"httparse",
"local-waker",
"log",
"memchr",
"mime",
"rand",
"serde",
"serde_json",
"serde_plain",
"tempfile",
"tokio",
]
[[package]]
name = "actix-multipart-derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b"
dependencies = [
"darling",
"parse-size",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "actix-remote-ip"
version = "0.1.0"
@@ -622,6 +660,7 @@ dependencies = [
"actix",
"actix-cors",
"actix-identity",
"actix-multipart",
"actix-remote-ip",
"actix-session",
"actix-web",
@@ -847,6 +886,41 @@ dependencies = [
"cipher",
]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "deranged"
version = "0.3.11"
@@ -1399,6 +1473,12 @@ dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
@@ -1779,6 +1859,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "parse-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]]
name = "paste"
version = "1.0.15"
@@ -2243,6 +2329,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_plain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"

View File

@@ -25,6 +25,7 @@ actix = "0.13.5"
actix-identity = "0.8.0"
actix-session = { version = "0.10.1", features = ["cookie-session"] }
actix-cors = "0.7.0"
actix-multipart = { version ="0.7.2", features = ["derive"] }
actix-remote-ip = "0.1.0"
futures-util = "0.3.30"
uuid = { version = "1.10.0", features = ["v4", "serde"] }

View File

@@ -1,4 +1,5 @@
use crate::devices::device::{DeviceId, DeviceRelayID};
use crate::ota::ota_update::OTAPlatform;
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
@@ -296,12 +297,17 @@ impl AppConfig {
/// Get the directory that will store OTA updates
pub fn ota_dir(&self) -> PathBuf {
self.logs_dir().join("ota")
self.storage_path().join("ota")
}
/// Get the directory that will store OTA updates of a given device reference
pub fn ota_of_device(&self, dev_ref: &str) -> PathBuf {
self.ota_dir().join(dev_ref)
/// Get the directory that will store OTA updates for a given platform
pub fn ota_platform_dir(&self, platform: OTAPlatform) -> PathBuf {
self.ota_dir().join(platform.to_string())
}
/// Get the path to the file that will contain an OTA update
pub fn path_ota_update(&self, platform: OTAPlatform, version: &semver::Version) -> PathBuf {
self.ota_platform_dir(platform).join(version.to_string())
}
}

View File

@@ -13,6 +13,9 @@ pub const MAX_INACTIVITY_DURATION: u64 = 3600;
/// Maximum session duration (1 day)
pub const MAX_SESSION_DURATION: u64 = 3600 * 24;
/// Maximum firmware size (in bytes)
pub const MAX_FIRMWARE_SIZE: usize = 50 * 1000 * 1000;
/// List of routes that do not require authentication
pub const ROUTES_WITHOUT_AUTH: [&str; 2] =
["/web_api/server/config", "/web_api/auth/password_auth"];

View File

@@ -9,9 +9,9 @@ use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct DeviceInfo {
/// Device reference
reference: String,
pub reference: String,
/// Device firmware / software version
version: semver::Version,
pub version: semver::Version,
/// Maximum number of relay that the device can support
pub max_relays: usize,
}
@@ -62,6 +62,9 @@ pub struct Device {
///
/// There cannot be more than [info.max_relays] relays
pub relays: Vec<DeviceRelay>,
/// Desired version, ie. the version of the software we would to seen run on the device
#[serde(skip_serializing_if = "Option::is_none")]
pub desired_version: Option<semver::Version>,
}
/// Structure that contains information about the minimal expected execution

View File

@@ -84,6 +84,7 @@ impl DevicesList {
validated: false,
enabled: false,
relays: vec![],
desired_version: None,
};
// First, write CSR
@@ -186,6 +187,24 @@ impl DevicesList {
Ok(())
}
/// Set a device desired version
pub fn set_desired_version(
&mut self,
id: &DeviceId,
version: Option<semver::Version>,
) -> anyhow::Result<()> {
let dev = self
.0
.get_mut(id)
.ok_or(DevicesListError::UpdateDeviceFailedDeviceNotFound)?;
dev.desired_version = version;
self.persist_dev_config(id)?;
Ok(())
}
/// Get single certificate information
fn get_cert(&self, id: &DeviceId) -> anyhow::Result<X509> {
let dev = self

View File

@@ -195,6 +195,27 @@ impl Handler<UpdateDeviceGeneralInfo> for EnergyActor {
}
}
/// Set device desired version
#[derive(Message)]
#[rtype(result = "anyhow::Result<()>")]
pub struct SetDesiredVersion(pub DeviceId, pub Option<semver::Version>);
impl Handler<SetDesiredVersion> for EnergyActor {
type Result = anyhow::Result<()>;
fn handle(&mut self, msg: SetDesiredVersion, _ctx: &mut Context<Self>) -> Self::Result {
log::info!(
"Requested to update device desired version {:?} => {:#?}",
&msg.0,
&msg.1
);
self.devices.set_desired_version(&msg.0, msg.1)?;
Ok(())
}
}
/// Delete a device
#[derive(Message)]
#[rtype(result = "anyhow::Result<()>")]

View File

@@ -4,5 +4,6 @@ pub mod crypto;
pub mod devices;
pub mod energy;
pub mod logs;
pub mod ota;
pub mod server;
pub mod utils;

View File

@@ -0,0 +1,2 @@
pub mod ota_manager;
pub mod ota_update;

View File

@@ -0,0 +1,51 @@
use crate::app_config::AppConfig;
use crate::ota::ota_update::{OTAPlatform, OTAUpdate};
use crate::utils::files_utils;
use std::os::unix::fs::MetadataExt;
use std::str::FromStr;
/// Check out whether a given update exists or not
pub fn update_exists(platform: OTAPlatform, version: &semver::Version) -> anyhow::Result<bool> {
Ok(AppConfig::get()
.path_ota_update(platform, version)
.is_file())
}
/// Save a new firmware update
pub fn save_update(
platform: OTAPlatform,
version: &semver::Version,
update: &[u8],
) -> anyhow::Result<()> {
let path = AppConfig::get().path_ota_update(platform, version);
files_utils::create_directory_if_missing(path.parent().unwrap())?;
std::fs::write(path, update)?;
Ok(())
}
/// Get the content of an OTA update
pub fn get_ota_update(platform: OTAPlatform, version: &semver::Version) -> anyhow::Result<Vec<u8>> {
let path = AppConfig::get().path_ota_update(platform, version);
Ok(std::fs::read(path)?)
}
/// Get the list of OTA software updates for a platform
pub fn get_ota_updates_for_platform(platform: OTAPlatform) -> anyhow::Result<Vec<OTAUpdate>> {
let ota_path = AppConfig::get().ota_platform_dir(platform);
let mut out = Vec::new();
for e in std::fs::read_dir(ota_path)? {
let e = e?;
out.push(OTAUpdate {
platform,
version: semver::Version::from_str(e.file_name().to_str().unwrap_or("bad"))?,
file_size: e.metadata()?.size(),
});
}
Ok(out)
}

View File

@@ -0,0 +1,31 @@
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone, Eq, PartialEq)]
pub enum OTAPlatform {
#[serde(rename = "Wt32-Eth01")]
Wt32Eth01,
}
impl Display for OTAPlatform {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(&self).unwrap().replace('"', "");
write!(f, "{s}")
}
}
impl FromStr for OTAPlatform {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(serde_json::from_str::<Self>(&format!("\"{s}\""))?)
}
}
/// Single OTA update information
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct OTAUpdate {
pub platform: OTAPlatform,
pub version: semver::Version,
pub file_size: u64,
}

View File

@@ -0,0 +1,21 @@
use crate::ota::ota_manager;
use crate::ota::ota_update::OTAPlatform;
use crate::server::custom_error::HttpResult;
use actix_web::{web, HttpResponse};
#[derive(serde::Deserialize)]
pub struct FirmwarePath {
platform: OTAPlatform,
version: semver::Version,
}
/// Download firmware update
pub async fn retrieve_firmware(path: web::Path<FirmwarePath>) -> HttpResult {
if !ota_manager::update_exists(path.platform, &path.version)? {
return Ok(HttpResponse::NotFound().json("The requested firmware was not found!"));
}
let firmware = ota_manager::get_ota_update(path.platform, &path.version)?;
Ok(HttpResponse::Ok().body(firmware))
}

View File

@@ -2,12 +2,15 @@ use crate::app_config::AppConfig;
use crate::devices::device::{DeviceId, DeviceInfo};
use crate::energy::energy_actor;
use crate::energy::energy_actor::RelaySyncStatus;
use crate::ota::ota_manager;
use crate::ota::ota_update::OTAPlatform;
use crate::server::custom_error::HttpResult;
use crate::server::devices_api::jwt_parser::JWTRequest;
use crate::server::WebEnergyActor;
use actix_web::{web, HttpResponse};
use openssl::nid::Nid;
use openssl::x509::X509Req;
use std::str::FromStr;
#[derive(Debug, serde::Deserialize)]
pub struct EnrollRequest {
@@ -135,6 +138,7 @@ struct Claims {
#[derive(serde::Serialize)]
struct SyncResult {
relays: Vec<RelaySyncStatus>,
available_update: Option<semver::Version>,
}
/// Synchronize device
@@ -142,8 +146,25 @@ pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) ->
let (device, claims) = body.0.parse_jwt::<Claims>(actor.clone()).await?;
let relays = actor
.send(energy_actor::SynchronizeDevice(device.id, claims.info))
.send(energy_actor::SynchronizeDevice(
device.id,
claims.info.clone(),
))
.await??;
Ok(HttpResponse::Ok().json(SyncResult { relays }))
let mut available_update = None;
// Check if the version is available
if let Some(desired) = device.desired_version {
if claims.info.version < desired
&& ota_manager::update_exists(OTAPlatform::from_str(&claims.info.reference)?, &desired)?
{
available_update = Some(desired);
}
}
Ok(HttpResponse::Ok().json(SyncResult {
relays,
available_update,
}))
}

View File

@@ -1,4 +1,5 @@
pub mod device_logging_controller;
pub mod devices_ota;
pub mod jwt_parser;
pub mod mgmt_controller;
pub mod utils_controller;

View File

@@ -3,7 +3,9 @@ use crate::constants;
use crate::crypto::pki;
use crate::energy::energy_actor::EnergyActorAddr;
use crate::server::auth_middleware::AuthChecker;
use crate::server::devices_api::{device_logging_controller, mgmt_controller, utils_controller};
use crate::server::devices_api::{
device_logging_controller, devices_ota, mgmt_controller, utils_controller,
};
use crate::server::unsecure_server::*;
use crate::server::web_api::*;
use crate::server::web_app_controller;
@@ -181,12 +183,25 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
web::delete().to(devices_controller::delete_device),
)
// OTA API
// TODO : list supported platform references
// TODO : upload a new software update
// TODO : list ota software update per platform
.route(
"/web_api/ota/supported_platforms",
web::get().to(ota_controller::supported_platforms),
)
.route(
"/web_api/ota/{platform}/{version}",
web::post().to(ota_controller::upload_firmware),
)
// TODO : list all ota software updates
.route(
"/web_api/ota/{platform}",
web::get().to(ota_controller::list_updates_platform),
)
// TODO : download a OTA file
// TODO : delete an OTA file
// TODO : deploy an update to a device
.route(
"/web_api/ota/set_desired_version",
web::post().to(ota_controller::set_desired_version),
)
// Logging controller API
.route(
"/web_api/logging/logs",
@@ -238,6 +253,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/devices_api/mgmt/sync",
web::post().to(mgmt_controller::sync_device),
)
.route(
"/devices_api/ota/{platform}/{version}",
web::get().to(devices_ota::retrieve_firmware),
)
.route(
"/devices_api/logging/record",
web::post().to(device_logging_controller::report_log),

View File

@@ -2,5 +2,6 @@ pub mod auth_controller;
pub mod devices_controller;
pub mod energy_controller;
pub mod logging_controller;
pub mod ota_controller;
pub mod relays_controller;
pub mod server_controller;

View File

@@ -0,0 +1,111 @@
use crate::constants;
use crate::devices::device::DeviceId;
use crate::energy::energy_actor;
use crate::ota::ota_manager;
use crate::ota::ota_update::OTAPlatform;
use crate::server::custom_error::HttpResult;
use crate::server::WebEnergyActor;
use actix_multipart::form::tempfile::TempFile;
use actix_multipart::form::MultipartForm;
use actix_web::{web, HttpResponse};
pub async fn supported_platforms() -> HttpResult {
Ok(HttpResponse::Ok().json(vec![OTAPlatform::Wt32Eth01]))
}
#[derive(Debug, MultipartForm)]
pub struct UploadForm {
#[multipart(rename = "firmware")]
firmware: Vec<TempFile>,
}
#[derive(serde::Deserialize)]
pub struct UploadPath {
platform: OTAPlatform,
version: semver::Version,
}
pub async fn upload_firmware(
MultipartForm(form): MultipartForm<UploadForm>,
path: web::Path<UploadPath>,
) -> HttpResult {
if ota_manager::update_exists(path.platform, &path.version)? {
return Ok(HttpResponse::Conflict()
.json("A firmware with the same version has already been uploaded on the platform!"));
}
let Some(file) = form.firmware.first() else {
return Ok(HttpResponse::BadRequest().json("No firmware specified!"));
};
if file.size == 0 {
return Ok(HttpResponse::BadRequest().json("Uploaded file is empty!"));
}
if file.size > constants::MAX_FIRMWARE_SIZE {
return Ok(HttpResponse::BadRequest().json("Uploaded file is too heavy!"));
}
let content = std::fs::read(file.file.path())?;
ota_manager::save_update(path.platform, &path.version, &content)?;
Ok(HttpResponse::Accepted().body("OTA update successfully saved."))
}
#[derive(serde::Deserialize)]
pub struct ListOTAPath {
platform: OTAPlatform,
}
/// List OTA software updates for a given platform
pub async fn list_updates_platform(path: web::Path<ListOTAPath>) -> HttpResult {
let list = ota_manager::get_ota_updates_for_platform(path.platform)?;
Ok(HttpResponse::Ok().json(list))
}
#[derive(serde::Deserialize)]
pub struct SetDesiredDeviceVersion {
devices: Option<Vec<DeviceId>>,
platform: Option<OTAPlatform>,
version: semver::Version,
}
pub async fn set_desired_version(
actor: WebEnergyActor,
body: web::Json<SetDesiredDeviceVersion>,
) -> HttpResult {
if body.devices.is_none() && body.platform.is_none() {
return Ok(
HttpResponse::BadRequest().json("Must specify one filter to select target devices!")
);
}
let devices = actor.send(energy_actor::GetDeviceLists).await?;
for d in devices {
// Filter per platform
if let Some(p) = body.platform {
if d.info.reference != p.to_string() {
continue;
}
}
// Filter per device
if let Some(ids) = &body.devices {
if !ids.contains(&d.id) {
continue;
}
}
actor
.send(energy_actor::SetDesiredVersion(
d.id,
Some(body.version.clone()),
))
.await??;
}
Ok(HttpResponse::Ok().finish())
}

View File

@@ -13,22 +13,22 @@
"@fontsource/roboto": "^5.1.0",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@mui/icons-material": "^6.1.1",
"@mui/material": "^6.1.1",
"@mui/x-charts": "^7.18.0",
"@mui/x-date-pickers": "^7.18.0",
"date-and-time": "^3.5.0",
"@mui/icons-material": "^6.1.2",
"@mui/material": "^6.1.2",
"@mui/x-charts": "^7.19.0",
"@mui/x-date-pickers": "^7.19.0",
"date-and-time": "^3.6.0",
"dayjs": "^1.11.13",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.3.10",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@vitejs/plugin-react": "^4.3.1",
"@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0",
"@vitejs/plugin-react": "^4.3.2",
"eslint": "^8.57.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12",
@@ -50,11 +50,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz",
"integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==",
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.24.7",
"@babel/highlight": "^7.25.7",
"picocolors": "^1.0.0"
},
"engines": {
@@ -62,30 +63,32 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz",
"integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz",
"integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
"integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz",
"integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.7",
"@babel/helper-compilation-targets": "^7.24.7",
"@babel/helper-module-transforms": "^7.24.7",
"@babel/helpers": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/template": "^7.24.7",
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7",
"@babel/code-frame": "^7.25.7",
"@babel/generator": "^7.25.7",
"@babel/helper-compilation-targets": "^7.25.7",
"@babel/helper-module-transforms": "^7.25.7",
"@babel/helpers": "^7.25.7",
"@babel/parser": "^7.25.7",
"@babel/template": "^7.25.7",
"@babel/traverse": "^7.25.7",
"@babel/types": "^7.25.7",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -110,28 +113,30 @@
}
},
"node_modules/@babel/generator": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz",
"integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7",
"@babel/types": "^7.25.7",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz",
"integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz",
"integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.24.7",
"@babel/helper-validator-option": "^7.24.7",
"browserslist": "^4.22.2",
"@babel/compat-data": "^7.25.7",
"@babel/helper-validator-option": "^7.25.7",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
@@ -144,67 +149,35 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
"dependencies": {
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
"dependencies": {
"@babel/template": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
"dependencies": {
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz",
"integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
"@babel/traverse": "^7.25.7",
"@babel/types": "^7.25.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz",
"integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz",
"integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.24.7",
"@babel/helper-module-imports": "^7.24.7",
"@babel/helper-simple-access": "^7.24.7",
"@babel/helper-split-export-declaration": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7"
"@babel/helper-module-imports": "^7.25.7",
"@babel/helper-simple-access": "^7.25.7",
"@babel/helper-validator-identifier": "^7.25.7",
"@babel/traverse": "^7.25.7"
},
"engines": {
"node": ">=6.9.0"
@@ -223,73 +196,68 @@
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
"integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz",
"integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
"dependencies": {
"@babel/types": "^7.24.7"
"@babel/traverse": "^7.25.7",
"@babel/types": "^7.25.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
"integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz",
"integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz",
"integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz",
"integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz",
"integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz",
"integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.24.7",
"@babel/types": "^7.24.7"
"@babel/template": "^7.25.7",
"@babel/types": "^7.25.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz",
"integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/helper-validator-identifier": "^7.25.7",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
@@ -299,9 +267,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz",
"integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.7"
},
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -352,31 +324,30 @@
}
},
"node_modules/@babel/template": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz",
"integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/types": "^7.24.7"
"@babel/code-frame": "^7.25.7",
"@babel/parser": "^7.25.7",
"@babel/types": "^7.25.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz",
"integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.7",
"@babel/helper-environment-visitor": "^7.24.7",
"@babel/helper-function-name": "^7.24.7",
"@babel/helper-hoist-variables": "^7.24.7",
"@babel/helper-split-export-declaration": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/types": "^7.24.7",
"@babel/code-frame": "^7.25.7",
"@babel/generator": "^7.25.7",
"@babel/parser": "^7.25.7",
"@babel/template": "^7.25.7",
"@babel/types": "^7.25.7",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -385,12 +356,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz",
"integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/helper-string-parser": "^7.25.7",
"@babel/helper-validator-identifier": "^7.25.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -1151,9 +1123,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.1.tgz",
"integrity": "sha512-VdQC1tPIIcZAnf62L2M1eQif0x2vlKg3YK4kGYbtijSH4niEgI21GnstykW1vQIs+Bc6L+Hua2GATYVjilJ22A==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.2.tgz",
"integrity": "sha512-1oE4U38/TtzLWRYWEm/m70dUbpcvBx0QvDVg6NtpOmSNQC1Mbx0X/rNvYDdZnn8DIsAiVQ+SZ3am6doSswUQ4g==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1161,9 +1133,9 @@
}
},
"node_modules/@mui/icons-material": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.1.tgz",
"integrity": "sha512-sy/YKwcLPW8VcacNP2uWMYR9xyWuwO9NN9FXuGEU90bRshBXj8pdKk+joe3TCW7oviVS3zXLHlc94wQ0jNsQRQ==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.2.tgz",
"integrity": "sha512-7NNcjW5JoT9jHagrVbARA1o41vQY2xezDamtke+mEKKZmsJyejfRBOacSrPDfjZQ//lyhIjNKyzAwisxYJR47w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6"
@@ -1176,7 +1148,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^6.1.1",
"@mui/material": "^6.1.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -1187,16 +1159,16 @@
}
},
"node_modules/@mui/material": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.1.tgz",
"integrity": "sha512-b+eULldTqtqTCbN++2BtBWCir/1LwEYw+2mIlOt2GiEUh1EBBw4/wIukGKKNt3xrCZqRA80yLLkV6tF61Lq3cA==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.2.tgz",
"integrity": "sha512-5TtHeAVX9D5d2LYfB1GAUn29BcVETVsrQ76Dwb2SpAfQGW3JVy4deJCAd0RrIkI3eEUrsl0E4xuBdreszxdTTg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/core-downloads-tracker": "^6.1.1",
"@mui/system": "^6.1.1",
"@mui/core-downloads-tracker": "^6.1.2",
"@mui/system": "^6.1.2",
"@mui/types": "^7.2.17",
"@mui/utils": "^6.1.1",
"@mui/utils": "^6.1.2",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.11",
"clsx": "^2.1.1",
@@ -1215,7 +1187,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^6.1.1",
"@mui/material-pigment-css": "^6.1.2",
"@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"
@@ -1236,13 +1208,13 @@
}
},
"node_modules/@mui/private-theming": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz",
"integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.2.tgz",
"integrity": "sha512-S8WcjZdNdi++8UhrrY8Lton5h/suRiQexvdTfdcPAlbajlvgM+kx+uJstuVIEyTb3gMkxzIZep87knZ0tqcR0g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/utils": "^6.1.1",
"@mui/utils": "^6.1.2",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1263,9 +1235,9 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz",
"integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.2.tgz",
"integrity": "sha512-uKOfWkR23X39xj7th2nyTcCHqInTAXtUnqD3T5qRVdJcOPvu1rlgTleTwJC/FJvWZJBU6ieuTWDhbcx5SNViHQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
@@ -1296,16 +1268,16 @@
}
},
"node_modules/@mui/system": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz",
"integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.2.tgz",
"integrity": "sha512-mzW7F1ZMIYS1aLON48Nrk9c65OrVEVQ+R4lUcTWs1lCSul0VGK23eo4dmY0NX5PS7Oe4xz3P5B9tQZZ7SYgxcg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/private-theming": "^6.1.1",
"@mui/styled-engine": "^6.1.1",
"@mui/private-theming": "^6.1.2",
"@mui/styled-engine": "^6.1.2",
"@mui/types": "^7.2.17",
"@mui/utils": "^6.1.1",
"@mui/utils": "^6.1.2",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@@ -1350,14 +1322,14 @@
}
},
"node_modules/@mui/utils": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz",
"integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.2.tgz",
"integrity": "sha512-6+B1YZ8cCBWD1fc3RjqpclF9UA0MLUiuXhyCO+XowD/Z2ku5IlxeEhHHlgglyBWFGMu4kib4YU3CDsG5/zVjJQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/types": "^7.2.17",
"@types/prop-types": "^15.7.12",
"@types/prop-types": "^15.7.13",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^18.3.1"
@@ -1380,14 +1352,14 @@
}
},
"node_modules/@mui/x-charts": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.18.0.tgz",
"integrity": "sha512-XvCX6sBj2aRw7MZcOPjKTEIwhj4j+ixw256bArQScSd+Y2BuHxAyfl/L6NPPWamckAPqJwjQ678jFH2s5t1f1w==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.19.0.tgz",
"integrity": "sha512-gOiSmb+bLIoGOGs+5uzEb7sau7KmjAKJI4+hORCjHUAOefmtHdiw39gZVne1Gcah+h6BIrWMkNJHk5djcCaGsg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/utils": "^5.16.6",
"@mui/x-charts-vendor": "7.18.0",
"@mui/x-charts-vendor": "7.19.0",
"@mui/x-internals": "7.18.0",
"@react-spring/rafz": "^9.7.4",
"@react-spring/web": "^9.7.4",
@@ -1415,9 +1387,9 @@
}
},
"node_modules/@mui/x-charts-vendor": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-7.18.0.tgz",
"integrity": "sha512-YPL7SP6W7t9jBIrK8WjmCtW/YoUmgr0BA7j2QE9bKX4VJLEUU498fEKik0ZJZyzyR7ov0MD3m7S+YCw1o5sqkw==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-7.19.0.tgz",
"integrity": "sha512-idHOi6U8mmX1ezKclv2p4QBrWxQIUSS+bd7rp2PGnf7XAPtUY/X9VTZvU1mDRawtyKjyzQD+vmSbYx9piytqAg==",
"license": "MIT AND ISC",
"dependencies": {
"@babel/runtime": "^7.25.6",
@@ -1468,9 +1440,9 @@
}
},
"node_modules/@mui/x-date-pickers": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.18.0.tgz",
"integrity": "sha512-12tXIoMj9vpS8fS/bS3kWPCoVrH38vNGCxgplI0vOnUrN9rJuYJz3agLPJe1S0xciTw+9W8ZSe3soaW+owoz1Q==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.19.0.tgz",
"integrity": "sha512-OIQ+IxgL2Si7DP68sw1ImcHXZtAmklHcyo/oqP4HuJZ2lVnP5sJkoXrksfumL1wjWKJkecONFz3unAqViKXzCQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.6",
@@ -2068,14 +2040,15 @@
"license": "MIT"
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
"integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
"version": "18.3.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz",
"integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2101,17 +2074,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz",
"integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.7.0",
"@typescript-eslint/type-utils": "8.7.0",
"@typescript-eslint/utils": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0",
"@typescript-eslint/scope-manager": "8.8.0",
"@typescript-eslint/type-utils": "8.8.0",
"@typescript-eslint/utils": "8.8.0",
"@typescript-eslint/visitor-keys": "8.8.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -2135,16 +2108,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz",
"integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "8.7.0",
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/typescript-estree": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0",
"@typescript-eslint/scope-manager": "8.8.0",
"@typescript-eslint/types": "8.8.0",
"@typescript-eslint/typescript-estree": "8.8.0",
"@typescript-eslint/visitor-keys": "8.8.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2164,14 +2137,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
"integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0"
"@typescript-eslint/types": "8.8.0",
"@typescript-eslint/visitor-keys": "8.8.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2182,14 +2155,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz",
"integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.7.0",
"@typescript-eslint/utils": "8.7.0",
"@typescript-eslint/typescript-estree": "8.8.0",
"@typescript-eslint/utils": "8.8.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -2207,9 +2180,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
"integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2221,14 +2194,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
"integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/visitor-keys": "8.7.0",
"@typescript-eslint/types": "8.8.0",
"@typescript-eslint/visitor-keys": "8.8.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2250,16 +2223,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
"integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.7.0",
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/typescript-estree": "8.7.0"
"@typescript-eslint/scope-manager": "8.8.0",
"@typescript-eslint/types": "8.8.0",
"@typescript-eslint/typescript-estree": "8.8.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2273,13 +2246,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
"integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.7.0",
"@typescript-eslint/types": "8.8.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -2298,14 +2271,15 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
"integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz",
"integrity": "sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.24.5",
"@babel/plugin-transform-react-jsx-self": "^7.24.5",
"@babel/plugin-transform-react-jsx-source": "^7.24.1",
"@babel/core": "^7.25.2",
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
"@babel/plugin-transform-react-jsx-source": "^7.24.7",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.14.2"
},
@@ -2369,6 +2343,7 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
@@ -2428,9 +2403,9 @@
}
},
"node_modules/browserslist": {
"version": "4.23.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
"integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
"integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
"dev": true,
"funding": [
{
@@ -2446,11 +2421,12 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001629",
"electron-to-chromium": "^1.4.796",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.16"
"caniuse-lite": "^1.0.30001663",
"electron-to-chromium": "^1.5.28",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0"
},
"bin": {
"browserslist": "cli.js"
@@ -2468,9 +2444,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001638",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz",
"integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==",
"version": "1.0.30001667",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
"dev": true,
"funding": [
{
@@ -2485,12 +2461,14 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -2512,6 +2490,7 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
@@ -2519,7 +2498,8 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
@@ -2684,9 +2664,9 @@
}
},
"node_modules/date-and-time": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.5.0.tgz",
"integrity": "sha512-JU4y0CIzAU+Cy2GOToby243B54elP8JdPidj4xHJPO4rlResPXKujRJp/MJzFeMWIdTbQ/A0xgd6guipi1mYIw==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.6.0.tgz",
"integrity": "sha512-V99gLaMqNQxPCObBumb31Bfy3OByXnpqUM0yHPi/aBQE61g42A2rGk6Z2CDnpLrWsOFLQwOgl4Vgshw6D44ebw==",
"license": "MIT"
},
"node_modules/dayjs": {
@@ -2749,10 +2729,11 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.815",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.815.tgz",
"integrity": "sha512-OvpTT2ItpOXJL7IGcYakRjHCt8L5GrrN/wHCQsRB4PQa1X9fe+X9oen245mIId7s14xvArCGSTIq644yPUKKLg==",
"dev": true
"version": "1.5.32",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz",
"integrity": "sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==",
"dev": true,
"license": "ISC"
},
"node_modules/error-ex": {
"version": "1.3.2",
@@ -2802,10 +2783,11 @@
}
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
@@ -2814,6 +2796,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
@@ -3352,6 +3335,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
@@ -3528,14 +3512,15 @@
}
},
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=4"
"node": ">=6"
}
},
"node_modules/json-buffer": {
@@ -3642,6 +3627,7 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
}
@@ -3717,10 +3703,11 @@
"dev": true
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"dev": true,
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
@@ -4261,6 +4248,7 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -4360,9 +4348,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
"integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"dev": true,
"funding": [
{
@@ -4378,9 +4366,10 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
"escalade": "^3.2.0",
"picocolors": "^1.1.0"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -4494,7 +4483,8 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
"dev": true,
"license": "ISC"
},
"node_modules/yaml": {
"version": "1.10.2",

View File

@@ -15,22 +15,22 @@
"@fontsource/roboto": "^5.1.0",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@mui/icons-material": "^6.1.1",
"@mui/material": "^6.1.1",
"@mui/x-charts": "^7.18.0",
"@mui/x-date-pickers": "^7.18.0",
"date-and-time": "^3.5.0",
"@mui/icons-material": "^6.1.2",
"@mui/material": "^6.1.2",
"@mui/x-charts": "^7.19.0",
"@mui/x-date-pickers": "^7.19.0",
"date-and-time": "^3.6.0",
"dayjs": "^1.11.13",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.3.10",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@vitejs/plugin-react": "^4.3.1",
"@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0",
"@vitejs/plugin-react": "^4.3.2",
"eslint": "^8.57.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12",

View File

@@ -37,6 +37,7 @@ export interface Device {
validated: boolean;
enabled: boolean;
relays: DeviceRelay[];
desired_version?: string;
}
export interface UpdatedInfo {

View File

@@ -41,6 +41,10 @@ export function GeneralDeviceInfo(p: {
value={p.device.info.reference}
/>
<DeviceInfoProperty label="Version" value={p.device.info.version} />
<DeviceInfoProperty
label="Desired version"
value={p.device.desired_version ?? "None"}
/>
<DeviceInfoProperty label="Name" value={p.device.name} />
<DeviceInfoProperty
label="Description"

View File

@@ -53,6 +53,8 @@
"sync_response.h": "c",
"gpio.h": "c",
"esp_system.h": "c",
"relays.h": "c"
"relays.h": "c",
"esp_app_desc.h": "c",
"ota.h": "c"
}
}

View File

@@ -1,3 +1,18 @@
# ESP32 device
ESP32 client device, using `W32-ETH01` device
## Some commands
Create a new firmware build:
```bash
idf.py build
```
Upload firmware to central backend, in dev mode:
```bash
curl -k -X POST https://localhost:8443/web_api/ota/Wt32-Eth01/$(cat version.txt) --form firmware="@build/main.bin"
curl -k -X POST https://localhost:8443/web_api/ota/set_desired_version --header "Content-Type: application/json" --data "{\"platform\": \"Wt32-Eth01\", \"version\": \"$(cat version.txt)\"}"
```

View File

@@ -0,0 +1,5 @@
#!/bin/sh
idf.py build && \
curl -k -X POST https://localhost:8443/web_api/ota/Wt32-Eth01/$(cat version.txt) --form firmware="@build/main.bin" && \
curl -k -X POST https://localhost:8443/web_api/ota/set_desired_version --header "Content-Type: application/json" --data "{\"platform\": \"Wt32-Eth01\", \"version\": \"$(cat version.txt)\"}"

View File

@@ -1,4 +1,4 @@
idf_component_register(SRCS "relays.c" "sync_response.c" "jwt.c" "secure_api.c" "http_client.c" "ethernet.c" "unsecure_api.c" "system.c" "crypto.c" "random.c" "storage.c" "main.c"
idf_component_register(SRCS "ota.c" "relays.c" "sync_response.c" "jwt.c" "secure_api.c" "http_client.c" "ethernet.c" "unsecure_api.c" "system.c" "crypto.c" "random.c" "storage.c" "main.c"
"dev_name.c"
INCLUDE_DIRS ".")

View File

@@ -5,11 +5,6 @@
*/
#define DEV_REFERENCE "Wt32-Eth01"
/**
* Device version
*/
#define DEV_VERSION "0.0.1"
/**
* Backend unsecure API URL
*/

View File

@@ -1,6 +1,7 @@
#include <stdio.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_app_desc.h"
#include "dev_name.h"
#include "storage.h"
@@ -11,6 +12,7 @@
#include "ethernet.h"
#include "constants.h"
#include "relays.h"
#include "ota.h"
static const char *TAG = "main";
@@ -20,7 +22,7 @@ void app_main(void)
system_show_free_memory();
ESP_LOGI(TAG, "SolarEnergy WT32-ETH01 device");
ESP_LOGI(TAG, "SolarEnergy WT32-ETH01 device version %s", esp_app_get_description()->version);
// Turn off all relays
relays_turn_off_all();
@@ -171,6 +173,7 @@ void app_main(void)
// Main loop
ESP_LOGI(TAG, "Starting main loop");
secure_api_report_log_message(Info, "Starting program main loop");
size_t fails = 0;
while (true)
@@ -204,6 +207,32 @@ void app_main(void)
sync_response_print(res);
// Check for firmware update
if (res->available_update)
{
ESP_LOGI(TAG, "Will perform system upgrade to version %s!", res->available_update);
relays_turn_off_all();
secure_api_report_log_message(Info, "Device is starting the OTA procedure...");
if (ota_perform_update(res->available_update))
{
ESP_LOGI(TAG, "OTA update succesfully executed, will reboot...");
secure_api_report_log_message(Info, "Device successfully updated!");
}
else
{
ESP_LOGE(TAG, "OTA update failed! Will reboot...");
secure_api_report_log_message(Error, "Device update failed!");
}
secure_api_report_log_message(Info, "Device will restart after OTA procedure...");
system_sleep(SYNC_TIME_INTERVAL);
reboot();
}
// Update relays configuration
for (size_t i = 0; i < relays_count(); i++)
{
relays_set(i, sync_response_is_relay_on(res, i));

7
esp32_device/main/ota.c Normal file
View File

@@ -0,0 +1,7 @@
#include "ota.h"
bool ota_perform_update(const char *version)
{
// TODO
return false;
}

24
esp32_device/main/ota.h Normal file
View File

@@ -0,0 +1,24 @@
/**
* OTA functions
*/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Update device to a desired version
*
* Returns TRUE in case of success / FALSE otherwise
*/
bool ota_perform_update(const char *version);
#ifdef __cplusplus
}
#endif

View File

@@ -14,6 +14,7 @@
#include "relays.h"
#include "esp_log.h"
#include "esp_app_desc.h"
static const char *TAG = "secure_api";
@@ -118,11 +119,13 @@ fail:
*/
static cJSON *genDevInfo()
{
const esp_app_desc_t *desc = esp_app_get_description();
cJSON *json = cJSON_CreateObject();
if (!json)
return NULL;
cJSON_AddStringToObject(json, "reference", DEV_REFERENCE);
cJSON_AddStringToObject(json, "version", DEV_VERSION);
cJSON_AddStringToObject(json, "version", desc->version);
cJSON_AddNumberToObject(json, "max_relays", relays_count());
return json;
}
@@ -196,6 +199,77 @@ char *secure_api_get_dev_certificate()
return res;
}
void secure_api_report_log_message(enum LogMessageSeverity severity, const char *msg)
{
// Prepare signed payload
cJSON *obj = cJSON_CreateObject();
if (!obj)
{
ESP_LOGE(TAG, "Failed allocate memory to store JSON object!");
return;
}
char *severity_s;
switch (severity)
{
case Info:
severity_s = "Info";
break;
case Warn:
severity_s = "Warn";
break;
case Error:
severity_s = "Error";
break;
default:
severity_s = "Debug";
break;
}
cJSON_AddStringToObject(obj, "severity", severity_s);
cJSON_AddStringToObject(obj, "message", msg);
char *payload = jwt_gen(obj);
cJSON_Delete(obj);
if (!payload)
{
ESP_LOGE(TAG, "Failed to build log report request!");
return;
}
// Prepare request body
cJSON *json_body = cJSON_CreateObject();
if (!json_body)
{
ESP_LOGE(TAG, "Failed to allocated memory to store log report request body!");
free(payload);
return;
}
cJSON_AddStringToObject(json_body, "payload", payload);
free(payload);
char *body = cJSON_PrintUnformatted(json_body);
cJSON_Delete(json_body);
if (!body)
{
ESP_LOGE(TAG, "Failed to allocated memory to store encoded log report request body!");
return;
}
// Send request
char *res = process_secure_request("/devices_api/logging/record", body);
free(body);
if (!res)
{
ESP_LOGE(TAG, "Log reporting failed!");
}
free(res);
}
sync_response *secure_api_sync_device()
{
cJSON *obj = cJSON_CreateObject();

View File

@@ -29,6 +29,17 @@ extern "C"
DevEnrollValidated = 3,
};
/**
* Log message severity
*/
enum LogMessageSeverity
{
Debug = 0,
Info,
Warn,
Error
};
/**
* Get current device enrollment status
*/
@@ -45,6 +56,11 @@ extern "C"
*/
char *secure_api_get_dev_certificate();
/**
* Report log message to backend
*/
void secure_api_report_log_message(enum LogMessageSeverity severity, const char *msg);
/**
* Synchronise device with central backend
*

View File

@@ -1,12 +1,14 @@
#include "sync_response.h"
#include <stdlib.h>
#include <string.h>
#include <esp_log.h>
const static char *TAG = "sync_response";
sync_response *sync_response_parse(cJSON *res)
{
// Parse relays information
cJSON *relays_json = cJSON_GetObjectItem(res, "relays");
if (relays_json == NULL)
{
@@ -45,12 +47,26 @@ sync_response *sync_response_parse(cJSON *res)
}
}
// Parse firmware update information
cJSON *update = cJSON_GetObjectItem(res, "available_update");
if (update != NULL)
{
char *val = cJSON_GetStringValue(update);
sync_res->available_update = val != NULL ? strdup(val) : NULL;
}
return sync_res;
}
void sync_response_print(sync_response *res)
{
ESP_LOGI(TAG, " === sync response begin === ");
if (res->available_update != NULL)
{
ESP_LOGI(TAG, "> AVAILABLE UPDATE! Version %s", res->available_update);
}
ESP_LOGI(TAG, "# of relays: %d", res->len);
for (size_t i = 0; i < res->len; i++)
ESP_LOGI(TAG, "Relay[%d]=%s", i, res->relays[i] ? "ON" : "off");
@@ -59,8 +75,13 @@ void sync_response_print(sync_response *res)
void sync_response_free(sync_response *res)
{
if (res != NULL)
free(res);
if (res == NULL)
return;
if (res->available_update != NULL)
free(res->available_update);
free(res);
}
bool sync_response_is_relay_on(sync_response *res, int relay_number)

View File

@@ -16,6 +16,7 @@ extern "C"
typedef struct sync_response
{
size_t len;
char *available_update;
bool relays[];
} sync_response;

View File

@@ -339,14 +339,14 @@ CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set
CONFIG_ESPTOOLPY_FLASHFREQ="40m"
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
CONFIG_ESPTOOLPY_BEFORE_RESET=y
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
@@ -360,12 +360,12 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
#
# Partition Table
#
CONFIG_PARTITION_TABLE_SINGLE_APP=y
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
CONFIG_PARTITION_TABLE_TWO_OTA=y
# CONFIG_PARTITION_TABLE_CUSTOM is not set
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table

View File

@@ -229,7 +229,7 @@ CONFIG_IDF_TOOLCHAIN="gcc"
CONFIG_IDF_TARGET_ARCH_XTENSA=y
CONFIG_IDF_TARGET_ARCH="xtensa"
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_INIT_VERSION="5.2.2"
CONFIG_IDF_INIT_VERSION="$IDF_INIT_VERSION"
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000
@@ -339,14 +339,14 @@ CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set
CONFIG_ESPTOOLPY_FLASHFREQ="40m"
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
CONFIG_ESPTOOLPY_BEFORE_RESET=y
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
@@ -360,12 +360,12 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
#
# Partition Table
#
CONFIG_PARTITION_TABLE_SINGLE_APP=y
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
CONFIG_PARTITION_TABLE_TWO_OTA=y
# CONFIG_PARTITION_TABLE_CUSTOM is not set
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
@@ -1127,14 +1127,13 @@ CONFIG_HEAP_TRACING_OFF=y
# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set
# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set
# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set
# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_DEFAULT_LEVEL=4
CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y
# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set
# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set
CONFIG_LOG_MAXIMUM_LEVEL=3
CONFIG_LOG_MAXIMUM_LEVEL=4
# CONFIG_LOG_MASTER_LEVEL is not set
CONFIG_LOG_COLORS=y
CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y

1
esp32_device/version.txt Normal file
View File

@@ -0,0 +1 @@
1.0.0