Compare commits
	
		
			2 Commits
		
	
	
		
			renovate/m
			...
			982f7b71d9
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 982f7b71d9 | |||
| ef555378ea | 
							
								
								
									
										39
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ name: default
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: web_build
 | 
					  - name: web_build
 | 
				
			||||||
    image: node:23
 | 
					    image: node:21
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - name: web_app
 | 
					      - name: web_app
 | 
				
			||||||
        path: /tmp/web_build
 | 
					        path: /tmp/web_build
 | 
				
			||||||
@@ -46,11 +46,6 @@ steps:
 | 
				
			|||||||
        path: /usr/local/cargo/registry
 | 
					        path: /usr/local/cargo/registry
 | 
				
			||||||
      - name: web_app
 | 
					      - name: web_app
 | 
				
			||||||
        path: /tmp/web_build
 | 
					        path: /tmp/web_build
 | 
				
			||||||
      - name: releases
 | 
					 | 
				
			||||||
        path: /tmp/releases
 | 
					 | 
				
			||||||
    when:
 | 
					 | 
				
			||||||
      event:
 | 
					 | 
				
			||||||
        - tag
 | 
					 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - backend_check
 | 
					      - backend_check
 | 
				
			||||||
      - web_build
 | 
					      - web_build
 | 
				
			||||||
@@ -59,41 +54,13 @@ steps:
 | 
				
			|||||||
      - mv /tmp/web_build/dist static
 | 
					      - mv /tmp/web_build/dist static
 | 
				
			||||||
      - cargo build --release
 | 
					      - cargo build --release
 | 
				
			||||||
      - ls -lah target/release/central_backend
 | 
					      - ls -lah target/release/central_backend
 | 
				
			||||||
      - mv target/release/central_backend /tmp/releases/central_backend
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Build ESP32 program
 | 
					 | 
				
			||||||
  - name: esp32_compile
 | 
					  - name: esp32_compile
 | 
				
			||||||
    image: espressif/idf:v5.5.1
 | 
					    image: espressif/idf:v5.3.1
 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - name: releases
 | 
					 | 
				
			||||||
        path: /tmp/releases
 | 
					 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - cd esp32_device
 | 
					      - cd esp32_device
 | 
				
			||||||
      - /opt/esp/entrypoint.sh idf.py build
 | 
					      - /opt/esp/entrypoint.sh idf.py build
 | 
				
			||||||
      - ls -lah build/main.bin
 | 
					      - ls -lah build/main.bin
 | 
				
			||||||
      - cp build/main.bin /tmp/releases/wt32-eth01.bin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Auto-release to Gitea
 | 
					 | 
				
			||||||
  - name: gitea_release
 | 
					 | 
				
			||||||
    image: plugins/gitea-release
 | 
					 | 
				
			||||||
    depends_on:
 | 
					 | 
				
			||||||
    - backend_compile
 | 
					 | 
				
			||||||
    - esp32_compile
 | 
					 | 
				
			||||||
    when:
 | 
					 | 
				
			||||||
      event:
 | 
					 | 
				
			||||||
        - tag
 | 
					 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - name: releases
 | 
					 | 
				
			||||||
        path: /tmp/releases
 | 
					 | 
				
			||||||
    environment:
 | 
					 | 
				
			||||||
      PLUGIN_API_KEY:
 | 
					 | 
				
			||||||
        from_secret: GITEA_API_KEY # needs permission write:repository
 | 
					 | 
				
			||||||
    settings:
 | 
					 | 
				
			||||||
      base_url: https://gitea.communiquons.org
 | 
					 | 
				
			||||||
      files:
 | 
					 | 
				
			||||||
        - /tmp/releases/central_backend
 | 
					 | 
				
			||||||
        - /tmp/releases/wt32-eth01.bin
 | 
					 | 
				
			||||||
      checksum: sha512
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
volumes:
 | 
					volumes:
 | 
				
			||||||
@@ -101,5 +68,3 @@ volumes:
 | 
				
			|||||||
    temp: {}
 | 
					    temp: {}
 | 
				
			||||||
  - name: web_app
 | 
					  - name: web_app
 | 
				
			||||||
    temp: {}
 | 
					    temp: {}
 | 
				
			||||||
  - name: releases
 | 
					 | 
				
			||||||
    temp: {}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1708
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1708
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,46 +1,46 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "central_backend"
 | 
					name = "central_backend"
 | 
				
			||||||
version = "1.0.3"
 | 
					version = "1.0.2"
 | 
				
			||||||
edition = "2024"
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
log = "0.4.28"
 | 
					log = "0.4.22"
 | 
				
			||||||
env_logger = "0.11.8"
 | 
					env_logger = "0.11.5"
 | 
				
			||||||
lazy_static = "1.5.0"
 | 
					lazy_static = "1.5.0"
 | 
				
			||||||
dotenvy = "0.15.7"
 | 
					dotenvy = "0.15.7"
 | 
				
			||||||
clap = { version = "4.5.51", features = ["derive", "env"] }
 | 
					clap = { version = "4.5.20", features = ["derive", "env"] }
 | 
				
			||||||
anyhow = "1.0.100"
 | 
					anyhow = "1.0.89"
 | 
				
			||||||
thiserror = "2.0.17"
 | 
					thiserror = "2.0.3"
 | 
				
			||||||
openssl = { version = "0.10.74" }
 | 
					openssl = { version = "0.10.66" }
 | 
				
			||||||
openssl-sys = "0.9.110"
 | 
					openssl-sys = "0.9.102"
 | 
				
			||||||
libc = "0.2.177"
 | 
					libc = "0.2.159"
 | 
				
			||||||
foreign-types-shared = "0.1.1"
 | 
					foreign-types-shared = "0.1.1"
 | 
				
			||||||
asn1 = "0.23.0"
 | 
					asn1 = "0.20"
 | 
				
			||||||
actix-web = { version = "4.11.0", features = ["openssl"] }
 | 
					actix-web = { version = "4", features = ["openssl"] }
 | 
				
			||||||
futures = "0.3.31"
 | 
					futures = "0.3.31"
 | 
				
			||||||
serde = { version = "1.0.228", features = ["derive"] }
 | 
					serde = { version = "1.0.215", features = ["derive"] }
 | 
				
			||||||
reqwest = { version = "0.12.24", features = ["json"] }
 | 
					reqwest = { version = "0.12.7", features = ["json"] }
 | 
				
			||||||
serde_json = "1.0.145"
 | 
					serde_json = "1.0.131"
 | 
				
			||||||
rand = "0.10.0-rc.0"
 | 
					rand = "0.8.5"
 | 
				
			||||||
actix = "0.13.5"
 | 
					actix = "0.13.5"
 | 
				
			||||||
actix-identity = "0.9.0"
 | 
					actix-identity = "0.8.0"
 | 
				
			||||||
actix-session = { version = "0.11.0", features = ["cookie-session"] }
 | 
					actix-session = { version = "0.10.1", features = ["cookie-session"] }
 | 
				
			||||||
actix-cors = "0.7.1"
 | 
					actix-cors = "0.7.0"
 | 
				
			||||||
actix-multipart = { version = "0.7.2", features = ["derive"] }
 | 
					actix-multipart = { version = "0.7.2", features = ["derive"] }
 | 
				
			||||||
actix-remote-ip = "0.1.0"
 | 
					actix-remote-ip = "0.1.0"
 | 
				
			||||||
futures-util = "0.3.31"
 | 
					futures-util = "0.3.31"
 | 
				
			||||||
uuid = { version = "1.18.1", features = ["v4", "serde"] }
 | 
					uuid = { version = "1.11.0", features = ["v4", "serde"] }
 | 
				
			||||||
semver = { version = "1.0.27", features = ["serde"] }
 | 
					semver = { version = "1.0.23", features = ["serde"] }
 | 
				
			||||||
lazy-regex = "3.4.2"
 | 
					lazy-regex = "3.3.0"
 | 
				
			||||||
tokio = { version = "1.48.0", features = ["full"] }
 | 
					tokio = { version = "1.40.0", features = ["full"] }
 | 
				
			||||||
tokio_schedule = "0.3.2"
 | 
					tokio_schedule = "0.3.2"
 | 
				
			||||||
mime_guess = "2.0.5"
 | 
					mime_guess = "2.0.5"
 | 
				
			||||||
rust-embed = "8.8.0"
 | 
					rust-embed = "8.5.0"
 | 
				
			||||||
jsonwebtoken = { version = "10.1.0", features = ["use_pem", "rust_crypto"] }
 | 
					jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
 | 
				
			||||||
prettytable-rs = "0.10.0"
 | 
					prettytable-rs = "0.10.0"
 | 
				
			||||||
chrono = "0.4.42"
 | 
					chrono = "0.4.38"
 | 
				
			||||||
serde_yml = "0.0.12"
 | 
					serde_yml = "0.0.12"
 | 
				
			||||||
bincode = "2.0.1"
 | 
					bincode = "=2.0.0-rc.3"
 | 
				
			||||||
fs4 = { version = "0.13.1", features = ["sync"] }
 | 
					fs4 = { version = "0.12.0", features = ["sync"] }
 | 
				
			||||||
zip = { version = "6.0.0", features = ["bzip2"] }
 | 
					zip = { version = "2.2.0", features = ["bzip2"] }
 | 
				
			||||||
walkdir = "2.5.0"
 | 
					walkdir = "2.5.0"
 | 
				
			||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
devices:
 | 
					 | 
				
			||||||
  - id: dev1
 | 
					 | 
				
			||||||
    info:
 | 
					 | 
				
			||||||
      reference: A
 | 
					 | 
				
			||||||
      version: 0.0.1
 | 
					 | 
				
			||||||
      max_relays: 1
 | 
					 | 
				
			||||||
    time_create: 1
 | 
					 | 
				
			||||||
    time_update: 1
 | 
					 | 
				
			||||||
    name: Dev1
 | 
					 | 
				
			||||||
    description: Day1
 | 
					 | 
				
			||||||
    validated: true
 | 
					 | 
				
			||||||
    enabled: true
 | 
					 | 
				
			||||||
    relays:
 | 
					 | 
				
			||||||
      - id: dcb3fd91-bf9b-4de3-99e5-92c1c7dd72e9
 | 
					 | 
				
			||||||
        name: R1
 | 
					 | 
				
			||||||
        enabled: true
 | 
					 | 
				
			||||||
        priority: 1
 | 
					 | 
				
			||||||
        consumption: 100
 | 
					 | 
				
			||||||
        minimal_uptime: 10
 | 
					 | 
				
			||||||
        minimal_downtime: 10
 | 
					 | 
				
			||||||
        depends_on: []
 | 
					 | 
				
			||||||
        conflicts_with: []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        on: false
 | 
					 | 
				
			||||||
        for: 5000
 | 
					 | 
				
			||||||
        forced_state:
 | 
					 | 
				
			||||||
          type: Off
 | 
					 | 
				
			||||||
          for_secs: 500
 | 
					 | 
				
			||||||
        should_be_on: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    online: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
curr_consumption: -10000
 | 
					 | 
				
			||||||
@@ -1,49 +0,0 @@
 | 
				
			|||||||
devices:
 | 
					 | 
				
			||||||
  - id: dev1
 | 
					 | 
				
			||||||
    info:
 | 
					 | 
				
			||||||
      reference: A
 | 
					 | 
				
			||||||
      version: 0.0.1
 | 
					 | 
				
			||||||
      max_relays: 1
 | 
					 | 
				
			||||||
    time_create: 1
 | 
					 | 
				
			||||||
    time_update: 1
 | 
					 | 
				
			||||||
    name: Dev1
 | 
					 | 
				
			||||||
    description: Day1
 | 
					 | 
				
			||||||
    validated: true
 | 
					 | 
				
			||||||
    enabled: true
 | 
					 | 
				
			||||||
    relays:
 | 
					 | 
				
			||||||
      - id: dcb3fd91-bf9b-4de3-99e5-92c1c7dd72e9
 | 
					 | 
				
			||||||
        name: R1
 | 
					 | 
				
			||||||
        enabled: true
 | 
					 | 
				
			||||||
        priority: 1
 | 
					 | 
				
			||||||
        consumption: 100
 | 
					 | 
				
			||||||
        minimal_uptime: 10
 | 
					 | 
				
			||||||
        minimal_downtime: 10
 | 
					 | 
				
			||||||
        depends_on: []
 | 
					 | 
				
			||||||
        conflicts_with: []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        on: false
 | 
					 | 
				
			||||||
        for: 500
 | 
					 | 
				
			||||||
        forced_state:
 | 
					 | 
				
			||||||
          type: On
 | 
					 | 
				
			||||||
          for_secs: 500
 | 
					 | 
				
			||||||
        should_be_on: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - id: dcb3fd91-bf9b-4de3-99e5-92c1c7dd72f0
 | 
					 | 
				
			||||||
        name: R2
 | 
					 | 
				
			||||||
        enabled: true
 | 
					 | 
				
			||||||
        priority: 1
 | 
					 | 
				
			||||||
        consumption: 100
 | 
					 | 
				
			||||||
        minimal_uptime: 10
 | 
					 | 
				
			||||||
        minimal_downtime: 10
 | 
					 | 
				
			||||||
        depends_on: [ ]
 | 
					 | 
				
			||||||
        conflicts_with: [ ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        on: false
 | 
					 | 
				
			||||||
        for: 500
 | 
					 | 
				
			||||||
        forced_state:
 | 
					 | 
				
			||||||
          type: None
 | 
					 | 
				
			||||||
        should_be_on: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    online: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
curr_consumption: 10000
 | 
					 | 
				
			||||||
@@ -16,13 +16,11 @@ impl CRLDistributionPointExt {
 | 
				
			|||||||
        let crl_bytes = asn1::write(|w| {
 | 
					        let crl_bytes = asn1::write(|w| {
 | 
				
			||||||
            w.write_element(&asn1::SequenceWriter::new(&|w| {
 | 
					            w.write_element(&asn1::SequenceWriter::new(&|w| {
 | 
				
			||||||
                w.write_element(&asn1::SequenceWriter::new(&|w| {
 | 
					                w.write_element(&asn1::SequenceWriter::new(&|w| {
 | 
				
			||||||
                    w.write_tlv(tag_a0, None, |w: &mut asn1::WriteBuf| {
 | 
					                    w.write_tlv(tag_a0, |w| {
 | 
				
			||||||
                        w.push_slice(&asn1::write(|w| {
 | 
					                        w.push_slice(&asn1::write(|w| {
 | 
				
			||||||
                            w.write_tlv(tag_a0, None, |w: &mut asn1::WriteBuf| {
 | 
					                            w.write_tlv(tag_a0, |w| {
 | 
				
			||||||
                                w.push_slice(&asn1::write(|w| {
 | 
					                                w.push_slice(&asn1::write(|w| {
 | 
				
			||||||
                                    w.write_tlv(tag_86, None, |b| {
 | 
					                                    w.write_tlv(tag_86, |b| b.push_slice(self.url.as_bytes()))?;
 | 
				
			||||||
                                        b.push_slice(self.url.as_bytes())
 | 
					 | 
				
			||||||
                                    })?;
 | 
					 | 
				
			||||||
                                    Ok(())
 | 
					                                    Ok(())
 | 
				
			||||||
                                })?)
 | 
					                                })?)
 | 
				
			||||||
                            })?;
 | 
					                            })?;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,10 +13,10 @@ use openssl::pkey::{PKey, Private};
 | 
				
			|||||||
use openssl::x509::extension::{
 | 
					use openssl::x509::extension::{
 | 
				
			||||||
    BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, SubjectKeyIdentifier,
 | 
					    BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, SubjectKeyIdentifier,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use openssl::x509::{CrlStatus, X509, X509Crl, X509Name, X509NameBuilder, X509Req};
 | 
					use openssl::x509::{CrlStatus, X509Crl, X509Name, X509NameBuilder, X509Req, X509};
 | 
				
			||||||
use openssl_sys::{
 | 
					use openssl_sys::{
 | 
				
			||||||
    X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set_issuer_name, X509_CRL_set_version,
 | 
					    X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate,
 | 
				
			||||||
    X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, X509_CRL_sign, X509_REVOKED_dup,
 | 
					    X509_CRL_set_issuer_name, X509_CRL_set_version, X509_CRL_sign, X509_REVOKED_dup,
 | 
				
			||||||
    X509_REVOKED_new, X509_REVOKED_set_revocationDate, X509_REVOKED_set_serialNumber,
 | 
					    X509_REVOKED_new, X509_REVOKED_set_revocationDate, X509_REVOKED_set_serialNumber,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -174,16 +174,17 @@ fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Option<Vec<u8>>, V
 | 
				
			|||||||
    cert_builder.set_not_after(¬_after)?;
 | 
					    cert_builder.set_not_after(¬_after)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Specify CRL URL
 | 
					    // Specify CRL URL
 | 
				
			||||||
    if let Some(issuer) = req.issuer
 | 
					    if let Some(issuer) = req.issuer {
 | 
				
			||||||
        && let Some(crl) = &issuer.crl
 | 
					        if let Some(crl) = &issuer.crl {
 | 
				
			||||||
    {
 | 
					            let crl_url = format!(
 | 
				
			||||||
        let crl_url = format!(
 | 
					                "{}/pki/{}",
 | 
				
			||||||
            "{}/pki/{}",
 | 
					                AppConfig::get().unsecure_origin(),
 | 
				
			||||||
            AppConfig::get().unsecure_origin(),
 | 
					                crl.file_name().unwrap().to_string_lossy()
 | 
				
			||||||
            crl.file_name().unwrap().to_string_lossy()
 | 
					            );
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cert_builder.append_extension(CRLDistributionPointExt { url: crl_url }.as_extension()?)?;
 | 
					            cert_builder
 | 
				
			||||||
 | 
					                .append_extension(CRLDistributionPointExt { url: crl_url }.as_extension()?)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // If cert is a CA or not
 | 
					    // If cert is a CA or not
 | 
				
			||||||
@@ -423,12 +424,12 @@ fn refresh_crl(d: &CertData, new_cert: Option<&X509>) -> anyhow::Result<()> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Add old entries
 | 
					        // Add old entries
 | 
				
			||||||
        if let Some(old_crl) = old_crl
 | 
					        if let Some(old_crl) = old_crl {
 | 
				
			||||||
            && let Some(entries) = old_crl.get_revoked()
 | 
					            if let Some(entries) = old_crl.get_revoked() {
 | 
				
			||||||
        {
 | 
					                for entry in entries {
 | 
				
			||||||
            for entry in entries {
 | 
					                    if X509_CRL_add0_revoked(crl, X509_REVOKED_dup(entry.as_ptr())) == 0 {
 | 
				
			||||||
                if X509_CRL_add0_revoked(crl, X509_REVOKED_dup(entry.as_ptr())) == 0 {
 | 
					                        return Err(PKIError::GenCRLError("X509_CRL_add0_revoked").into());
 | 
				
			||||||
                    return Err(PKIError::GenCRLError("X509_CRL_add0_revoked").into());
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -325,11 +325,9 @@ mod tests {
 | 
				
			|||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        dep_cycle_1.depends_on = vec![dep_cycle_3.id];
 | 
					        dep_cycle_1.depends_on = vec![dep_cycle_3.id];
 | 
				
			||||||
        assert!(
 | 
					        assert!(dep_cycle_1
 | 
				
			||||||
            dep_cycle_1
 | 
					            .error(&[dep_cycle_2.clone(), dep_cycle_3.clone()])
 | 
				
			||||||
                .error(&[dep_cycle_2.clone(), dep_cycle_3.clone()])
 | 
					            .is_some());
 | 
				
			||||||
                .is_some()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dep_cycle_1.depends_on = vec![];
 | 
					        dep_cycle_1.depends_on = vec![];
 | 
				
			||||||
        assert!(dep_cycle_1.error(&[dep_cycle_2, dep_cycle_3]).is_none());
 | 
					        assert!(dep_cycle_1.error(&[dep_cycle_2, dep_cycle_3]).is_none());
 | 
				
			||||||
@@ -353,29 +351,21 @@ mod tests {
 | 
				
			|||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert!(
 | 
					        assert!(target_relay
 | 
				
			||||||
            target_relay
 | 
					            .error(&[other_dep.clone(), second_dep.clone()])
 | 
				
			||||||
                .error(&[other_dep.clone(), second_dep.clone()])
 | 
					            .is_some());
 | 
				
			||||||
                .is_some()
 | 
					        assert!(target_relay
 | 
				
			||||||
        );
 | 
					            .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()])
 | 
				
			||||||
        assert!(
 | 
					            .is_some());
 | 
				
			||||||
            target_relay
 | 
					 | 
				
			||||||
                .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()])
 | 
					 | 
				
			||||||
                .is_some()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        second_dep.conflicts_with = vec![];
 | 
					        second_dep.conflicts_with = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert!(
 | 
					        assert!(target_relay
 | 
				
			||||||
            target_relay
 | 
					            .error(&[other_dep.clone(), second_dep.clone()])
 | 
				
			||||||
                .error(&[other_dep.clone(), second_dep.clone()])
 | 
					            .is_none());
 | 
				
			||||||
                .is_none()
 | 
					        assert!(target_relay
 | 
				
			||||||
        );
 | 
					            .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()])
 | 
				
			||||||
        assert!(
 | 
					            .is_none());
 | 
				
			||||||
            target_relay
 | 
					 | 
				
			||||||
                .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()])
 | 
					 | 
				
			||||||
                .is_none()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // self loop
 | 
					        // self loop
 | 
				
			||||||
        let mut self_loop = DeviceRelay {
 | 
					        let mut self_loop = DeviceRelay {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ use crate::devices::device::{
 | 
				
			|||||||
    Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
 | 
					    Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use crate::utils::time_utils::time_secs;
 | 
					use crate::utils::time_utils::time_secs;
 | 
				
			||||||
use openssl::x509::{X509, X509Req};
 | 
					use openssl::x509::{X509Req, X509};
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(thiserror::Error, Debug)]
 | 
					#[derive(thiserror::Error, Debug)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
use crate::app_config::{AppConfig, ConsumptionBackend};
 | 
					use crate::app_config::{AppConfig, ConsumptionBackend};
 | 
				
			||||||
use rand::{Rng, rng};
 | 
					use rand::{thread_rng, Rng};
 | 
				
			||||||
use std::num::ParseIntError;
 | 
					use std::num::ParseIntError;
 | 
				
			||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,7 +49,7 @@ pub async fn get_curr_consumption() -> anyhow::Result<EnergyConsumption> {
 | 
				
			|||||||
    match backend {
 | 
					    match backend {
 | 
				
			||||||
        ConsumptionBackend::Constant { value } => Ok(*value),
 | 
					        ConsumptionBackend::Constant { value } => Ok(*value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ConsumptionBackend::Random { min, max } => Ok(rng().random_range(*min..*max)),
 | 
					        ConsumptionBackend::Random { min, max } => Ok(thread_rng().gen_range(*min..*max)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ConsumptionBackend::File { path } => {
 | 
					        ConsumptionBackend::File { path } => {
 | 
				
			||||||
            let path = Path::new(path);
 | 
					            let path = Path::new(path);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ use crate::energy::consumption;
 | 
				
			|||||||
use crate::energy::consumption::EnergyConsumption;
 | 
					use crate::energy::consumption::EnergyConsumption;
 | 
				
			||||||
use crate::energy::consumption_cache::ConsumptionCache;
 | 
					use crate::energy::consumption_cache::ConsumptionCache;
 | 
				
			||||||
use crate::energy::consumption_history_file::ConsumptionHistoryFile;
 | 
					use crate::energy::consumption_history_file::ConsumptionHistoryFile;
 | 
				
			||||||
use crate::energy::engine::{EnergyEngine, RelayForcedState};
 | 
					use crate::energy::engine::EnergyEngine;
 | 
				
			||||||
use crate::utils::time_utils::time_secs;
 | 
					use crate::utils::time_utils::time_secs;
 | 
				
			||||||
use actix::prelude::*;
 | 
					use actix::prelude::*;
 | 
				
			||||||
use openssl::x509::X509Req;
 | 
					use openssl::x509::X509Req;
 | 
				
			||||||
@@ -328,19 +328,6 @@ impl Handler<UpdateDeviceRelay> for EnergyActor {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Message)]
 | 
					 | 
				
			||||||
#[rtype(result = "anyhow::Result<()>")]
 | 
					 | 
				
			||||||
pub struct SetRelayForcedState(pub DeviceRelayID, pub RelayForcedState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Handler<SetRelayForcedState> for EnergyActor {
 | 
					 | 
				
			||||||
    type Result = anyhow::Result<()>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn handle(&mut self, msg: SetRelayForcedState, _ctx: &mut Context<Self>) -> Self::Result {
 | 
					 | 
				
			||||||
        self.engine.relay_state(msg.0).set_forced(msg.1);
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Delete a device relay
 | 
					/// Delete a device relay
 | 
				
			||||||
#[derive(Message)]
 | 
					#[derive(Message)]
 | 
				
			||||||
#[rtype(result = "anyhow::Result<()>")]
 | 
					#[rtype(result = "anyhow::Result<()>")]
 | 
				
			||||||
@@ -421,7 +408,6 @@ pub struct ResRelayState {
 | 
				
			|||||||
    pub id: DeviceRelayID,
 | 
					    pub id: DeviceRelayID,
 | 
				
			||||||
    pub on: bool,
 | 
					    pub on: bool,
 | 
				
			||||||
    pub r#for: usize,
 | 
					    pub r#for: usize,
 | 
				
			||||||
    pub forced_state: RelayForcedState,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get the state of all relays
 | 
					/// Get the state of all relays
 | 
				
			||||||
@@ -441,7 +427,6 @@ impl Handler<GetAllRelaysState> for EnergyActor {
 | 
				
			|||||||
                id: d.id,
 | 
					                id: d.id,
 | 
				
			||||||
                on: state.is_on(),
 | 
					                on: state.is_on(),
 | 
				
			||||||
                r#for: state.state_for(),
 | 
					                r#for: state.state_for(),
 | 
				
			||||||
                forced_state: state.actual_forced_state(),
 | 
					 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
use prettytable::{Table, row};
 | 
					use prettytable::{row, Table};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
 | 
					use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
 | 
				
			||||||
@@ -25,83 +25,19 @@ impl DeviceState {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Default, Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
					 | 
				
			||||||
#[serde(tag = "type")]
 | 
					 | 
				
			||||||
pub enum SetRelayForcedStateReq {
 | 
					 | 
				
			||||||
    #[default]
 | 
					 | 
				
			||||||
    None,
 | 
					 | 
				
			||||||
    Off {
 | 
					 | 
				
			||||||
        for_secs: u64,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    On {
 | 
					 | 
				
			||||||
        for_secs: u64,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl SetRelayForcedStateReq {
 | 
					 | 
				
			||||||
    pub fn to_forced_state(&self) -> RelayForcedState {
 | 
					 | 
				
			||||||
        match &self {
 | 
					 | 
				
			||||||
            SetRelayForcedStateReq::None => RelayForcedState::None,
 | 
					 | 
				
			||||||
            SetRelayForcedStateReq::Off { for_secs } => RelayForcedState::Off {
 | 
					 | 
				
			||||||
                until: time_secs() + for_secs,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            SetRelayForcedStateReq::On { for_secs } => RelayForcedState::On {
 | 
					 | 
				
			||||||
                until: time_secs() + for_secs,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Default, Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
					 | 
				
			||||||
#[serde(tag = "type")]
 | 
					 | 
				
			||||||
pub enum RelayForcedState {
 | 
					 | 
				
			||||||
    #[default]
 | 
					 | 
				
			||||||
    None,
 | 
					 | 
				
			||||||
    Off {
 | 
					 | 
				
			||||||
        until: u64,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    On {
 | 
					 | 
				
			||||||
        until: u64,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Default, Clone)]
 | 
					#[derive(Default, Clone)]
 | 
				
			||||||
pub struct RelayState {
 | 
					pub struct RelayState {
 | 
				
			||||||
    on: bool,
 | 
					    on: bool,
 | 
				
			||||||
    since: usize,
 | 
					    since: usize,
 | 
				
			||||||
    forced_state: RelayForcedState,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl RelayState {
 | 
					impl RelayState {
 | 
				
			||||||
    /// Get actual forced state (returns None if state is expired)
 | 
					 | 
				
			||||||
    pub fn actual_forced_state(&self) -> RelayForcedState {
 | 
					 | 
				
			||||||
        match self.forced_state {
 | 
					 | 
				
			||||||
            RelayForcedState::Off { until } if until > time_secs() => {
 | 
					 | 
				
			||||||
                RelayForcedState::Off { until }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            RelayForcedState::On { until } if until > time_secs() => RelayForcedState::On { until },
 | 
					 | 
				
			||||||
            _ => RelayForcedState::None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn is_on(&self) -> bool {
 | 
					    pub fn is_on(&self) -> bool {
 | 
				
			||||||
        let forced_state = self.actual_forced_state();
 | 
					        self.on
 | 
				
			||||||
        (self.on || matches!(forced_state, RelayForcedState::On { .. }))
 | 
					 | 
				
			||||||
            && !matches!(forced_state, RelayForcedState::Off { .. })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn is_off(&self) -> bool {
 | 
					    fn is_off(&self) -> bool {
 | 
				
			||||||
        !self.is_on()
 | 
					        !self.on
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Check if relay state is enforced
 | 
					 | 
				
			||||||
    pub fn is_forced(&self) -> bool {
 | 
					 | 
				
			||||||
        self.actual_forced_state() != RelayForcedState::None
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn set_forced(&mut self, s: RelayForcedState) {
 | 
					 | 
				
			||||||
        self.since = time_secs() as usize;
 | 
					 | 
				
			||||||
        self.forced_state = s;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn state_for(&self) -> usize {
 | 
					    pub fn state_for(&self) -> usize {
 | 
				
			||||||
@@ -210,11 +146,7 @@ impl EnergyEngine {
 | 
				
			|||||||
                    r.name,
 | 
					                    r.name,
 | 
				
			||||||
                    r.consumption,
 | 
					                    r.consumption,
 | 
				
			||||||
                    format!("{} / {}", r.minimal_downtime, r.minimal_uptime),
 | 
					                    format!("{} / {}", r.minimal_downtime, r.minimal_uptime),
 | 
				
			||||||
                    status.is_on().to_string()
 | 
					                    status.is_on().to_string(),
 | 
				
			||||||
                        + match status.is_forced() {
 | 
					 | 
				
			||||||
                            true => " (Forced)",
 | 
					 | 
				
			||||||
                            false => "",
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                    status.since,
 | 
					                    status.since,
 | 
				
			||||||
                    match dev_online {
 | 
					                    match dev_online {
 | 
				
			||||||
                        true => "Online",
 | 
					                        true => "Online",
 | 
				
			||||||
@@ -260,28 +192,19 @@ impl EnergyEngine {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let mut new_relays_state = self.relays_state.clone();
 | 
					        let mut new_relays_state = self.relays_state.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Forcefully turn off disabled relays
 | 
					        // Forcefully turn off relays that belongs to offline devices
 | 
				
			||||||
        for d in devices {
 | 
					        for d in devices {
 | 
				
			||||||
            for r in &d.relays {
 | 
					            if !self.device_state(&d.id).is_online() {
 | 
				
			||||||
                if !r.enabled || !d.enabled {
 | 
					                for r in &d.relays {
 | 
				
			||||||
                    new_relays_state.get_mut(&r.id).unwrap().on = false;
 | 
					                    new_relays_state.get_mut(&r.id).unwrap().on = false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Apply forced relays state
 | 
					        // Forcefully turn off disabled relays
 | 
				
			||||||
        for d in devices {
 | 
					        for d in devices {
 | 
				
			||||||
            for r in &d.relays {
 | 
					            for r in &d.relays {
 | 
				
			||||||
                if self.relay_state(r.id).is_forced() {
 | 
					                if !r.enabled || !d.enabled {
 | 
				
			||||||
                    new_relays_state.get_mut(&r.id).unwrap().on = self.relay_state(r.id).is_on();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Forcefully turn off relays that belongs to offline devices
 | 
					 | 
				
			||||||
        for d in devices {
 | 
					 | 
				
			||||||
            if !self.device_state(&d.id).is_online() {
 | 
					 | 
				
			||||||
                for r in &d.relays {
 | 
					 | 
				
			||||||
                    new_relays_state.get_mut(&r.id).unwrap().on = false;
 | 
					                    new_relays_state.get_mut(&r.id).unwrap().on = false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -293,9 +216,7 @@ impl EnergyEngine {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            for d in devices {
 | 
					            for d in devices {
 | 
				
			||||||
                for r in &d.relays {
 | 
					                for r in &d.relays {
 | 
				
			||||||
                    if new_relays_state.get(&r.id).unwrap().is_off()
 | 
					                    if new_relays_state.get(&r.id).unwrap().is_off() {
 | 
				
			||||||
                        || new_relays_state.get(&r.id).unwrap().is_forced()
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        continue;
 | 
					                        continue;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -319,7 +240,7 @@ impl EnergyEngine {
 | 
				
			|||||||
            for d in devices {
 | 
					            for d in devices {
 | 
				
			||||||
                for r in &d.relays {
 | 
					                for r in &d.relays {
 | 
				
			||||||
                    let state = new_relays_state.get(&r.id).unwrap();
 | 
					                    let state = new_relays_state.get(&r.id).unwrap();
 | 
				
			||||||
                    if state.is_off() || state.is_forced() {
 | 
					                    if state.is_off() {
 | 
				
			||||||
                        continue;
 | 
					                        continue;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -350,9 +271,7 @@ impl EnergyEngine {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if new_relays_state.get(&r.id).unwrap().is_on()
 | 
					                if new_relays_state.get(&r.id).unwrap().is_on() {
 | 
				
			||||||
                    || new_relays_state.get(&r.id).unwrap().is_forced()
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -370,16 +289,12 @@ impl EnergyEngine {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                log::info!(
 | 
					                log::info!("Forcefully turn on relay {} to catch up running constraints (only {}s this day)", r.name, total_runtime);
 | 
				
			||||||
                    "Forcefully turn on relay {} to catch up running constraints (only {}s this day)",
 | 
					 | 
				
			||||||
                    r.name,
 | 
					 | 
				
			||||||
                    total_runtime
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                new_relays_state.get_mut(&r.id).unwrap().on = true;
 | 
					                new_relays_state.get_mut(&r.id).unwrap().on = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Order relays to select the ones with the most elevated priorities
 | 
					        // Order relays
 | 
				
			||||||
        let mut ordered_relays = devices
 | 
					        let mut ordered_relays = devices
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .filter(|d| self.device_state(&d.id).is_online() && d.enabled)
 | 
					            .filter(|d| self.device_state(&d.id).is_online() && d.enabled)
 | 
				
			||||||
@@ -389,13 +304,10 @@ impl EnergyEngine {
 | 
				
			|||||||
        ordered_relays.sort_by_key(|r| r.priority);
 | 
					        ordered_relays.sort_by_key(|r| r.priority);
 | 
				
			||||||
        ordered_relays.reverse();
 | 
					        ordered_relays.reverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Select relays to start, starting with those with highest priorities
 | 
					 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
            let mut changed = false;
 | 
					            let mut changed = false;
 | 
				
			||||||
            for relay in &ordered_relays {
 | 
					            for relay in &ordered_relays {
 | 
				
			||||||
                if new_relays_state.get(&relay.id).unwrap().is_on()
 | 
					                if new_relays_state.get(&relay.id).unwrap().is_on() {
 | 
				
			||||||
                    || new_relays_state.get(&relay.id).unwrap().is_forced()
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -467,7 +379,7 @@ impl EnergyEngine {
 | 
				
			|||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
    use crate::devices::device::{Device, DeviceId, DeviceRelayID};
 | 
					    use crate::devices::device::{Device, DeviceId, DeviceRelayID};
 | 
				
			||||||
    use crate::energy::consumption::EnergyConsumption;
 | 
					    use crate::energy::consumption::EnergyConsumption;
 | 
				
			||||||
    use crate::energy::engine::{EnergyEngine, SetRelayForcedStateReq};
 | 
					    use crate::energy::engine::EnergyEngine;
 | 
				
			||||||
    use crate::utils::time_utils::time_secs;
 | 
					    use crate::utils::time_utils::time_secs;
 | 
				
			||||||
    use rust_embed::Embed;
 | 
					    use rust_embed::Embed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -476,8 +388,6 @@ mod test {
 | 
				
			|||||||
        id: DeviceRelayID,
 | 
					        id: DeviceRelayID,
 | 
				
			||||||
        on: bool,
 | 
					        on: bool,
 | 
				
			||||||
        r#for: usize,
 | 
					        r#for: usize,
 | 
				
			||||||
        #[serde(default)]
 | 
					 | 
				
			||||||
        forced_state: SetRelayForcedStateReq,
 | 
					 | 
				
			||||||
        should_be_on: bool,
 | 
					        should_be_on: bool,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -525,7 +435,6 @@ mod test {
 | 
				
			|||||||
                let s = engine.relay_state(r.id);
 | 
					                let s = engine.relay_state(r.id);
 | 
				
			||||||
                s.on = r.on;
 | 
					                s.on = r.on;
 | 
				
			||||||
                s.since = time_secs() as usize - r.r#for;
 | 
					                s.since = time_secs() as usize - r.r#for;
 | 
				
			||||||
                s.forced_state = r.forced_state.to_forced_state()
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -147,7 +147,7 @@ pub fn relay_total_runtime_adjusted(relay: &DeviceRelay) -> usize {
 | 
				
			|||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use crate::devices::device::DeviceRelayID;
 | 
					    use crate::devices::device::DeviceRelayID;
 | 
				
			||||||
    use crate::energy::relay_state_history::{RelayStateHistory, relay_total_runtime};
 | 
					    use crate::energy::relay_state_history::{relay_total_runtime, RelayStateHistory};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_relay_state_history() {
 | 
					    fn test_relay_state_history() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ use central_backend::energy::energy_actor::EnergyActor;
 | 
				
			|||||||
use central_backend::server::servers;
 | 
					use central_backend::server::servers;
 | 
				
			||||||
use central_backend::utils::files_utils::create_directory_if_missing;
 | 
					use central_backend::utils::files_utils::create_directory_if_missing;
 | 
				
			||||||
use futures::future;
 | 
					use futures::future;
 | 
				
			||||||
use tokio_schedule::{Job, every};
 | 
					use tokio_schedule::{every, Job};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main]
 | 
					#[actix_web::main]
 | 
				
			||||||
async fn main() -> std::io::Result<()> {
 | 
					async fn main() -> std::io::Result<()> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
use actix_identity::Identity;
 | 
					use actix_identity::Identity;
 | 
				
			||||||
use std::future::{Ready, ready};
 | 
					use std::future::{ready, Ready};
 | 
				
			||||||
use std::rc::Rc;
 | 
					use std::rc::Rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
@@ -7,8 +7,8 @@ use crate::constants;
 | 
				
			|||||||
use actix_web::body::EitherBody;
 | 
					use actix_web::body::EitherBody;
 | 
				
			||||||
use actix_web::dev::Payload;
 | 
					use actix_web::dev::Payload;
 | 
				
			||||||
use actix_web::{
 | 
					use actix_web::{
 | 
				
			||||||
 | 
					    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
 | 
				
			||||||
    Error, FromRequest, HttpResponse,
 | 
					    Error, FromRequest, HttpResponse,
 | 
				
			||||||
    dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready},
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use futures_util::future::LocalBoxFuture;
 | 
					use futures_util::future::LocalBoxFuture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
use actix_web::HttpResponse;
 | 
					 | 
				
			||||||
use actix_web::body::BoxBody;
 | 
					use actix_web::body::BoxBody;
 | 
				
			||||||
use actix_web::http::StatusCode;
 | 
					use actix_web::http::StatusCode;
 | 
				
			||||||
 | 
					use actix_web::HttpResponse;
 | 
				
			||||||
use std::error::Error;
 | 
					use std::error::Error;
 | 
				
			||||||
use std::fmt::{Display, Formatter};
 | 
					use std::fmt::{Display, Formatter};
 | 
				
			||||||
 | 
					use std::io::ErrorKind;
 | 
				
			||||||
use zip::result::ZipError;
 | 
					use zip::result::ZipError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Custom error to ease controller writing
 | 
					/// Custom error to ease controller writing
 | 
				
			||||||
@@ -31,7 +32,7 @@ impl actix_web::error::ResponseError for HttpErr {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    fn error_response(&self) -> HttpResponse<BoxBody> {
 | 
					    fn error_response(&self) -> HttpResponse<BoxBody> {
 | 
				
			||||||
        log::error!("Error while processing request! {self}");
 | 
					        log::error!("Error while processing request! {}", self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        HttpResponse::InternalServerError().body("Failed to execute request!")
 | 
					        HttpResponse::InternalServerError().body("Failed to execute request!")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -51,7 +52,7 @@ impl From<serde_json::Error> for HttpErr {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl From<Box<dyn Error>> for HttpErr {
 | 
					impl From<Box<dyn Error>> for HttpErr {
 | 
				
			||||||
    fn from(value: Box<dyn Error>) -> Self {
 | 
					    fn from(value: Box<dyn Error>) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,43 +82,43 @@ impl From<reqwest::header::ToStrError> for HttpErr {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl From<actix_web::Error> for HttpErr {
 | 
					impl From<actix_web::Error> for HttpErr {
 | 
				
			||||||
    fn from(value: actix_web::Error) -> Self {
 | 
					    fn from(value: actix_web::Error) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<actix::MailboxError> for HttpErr {
 | 
					impl From<actix::MailboxError> for HttpErr {
 | 
				
			||||||
    fn from(value: actix::MailboxError) -> Self {
 | 
					    fn from(value: actix::MailboxError) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<actix_identity::error::GetIdentityError> for HttpErr {
 | 
					impl From<actix_identity::error::GetIdentityError> for HttpErr {
 | 
				
			||||||
    fn from(value: actix_identity::error::GetIdentityError) -> Self {
 | 
					    fn from(value: actix_identity::error::GetIdentityError) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<actix_identity::error::LoginError> for HttpErr {
 | 
					impl From<actix_identity::error::LoginError> for HttpErr {
 | 
				
			||||||
    fn from(value: actix_identity::error::LoginError) -> Self {
 | 
					    fn from(value: actix_identity::error::LoginError) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<openssl::error::ErrorStack> for HttpErr {
 | 
					impl From<openssl::error::ErrorStack> for HttpErr {
 | 
				
			||||||
    fn from(value: openssl::error::ErrorStack) -> Self {
 | 
					    fn from(value: openssl::error::ErrorStack) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<ZipError> for HttpErr {
 | 
					impl From<ZipError> for HttpErr {
 | 
				
			||||||
    fn from(value: ZipError) -> Self {
 | 
					    fn from(value: ZipError) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<walkdir::Error> for HttpErr {
 | 
					impl From<walkdir::Error> for HttpErr {
 | 
				
			||||||
    fn from(value: walkdir::Error) -> Self {
 | 
					    fn from(value: walkdir::Error) -> Self {
 | 
				
			||||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
use crate::logs::logs_manager;
 | 
					use crate::logs::logs_manager;
 | 
				
			||||||
use crate::logs::severity::LogSeverity;
 | 
					use crate::logs::severity::LogSeverity;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
					use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Deserialize, Clone)]
 | 
					#[derive(Debug, serde::Deserialize)]
 | 
				
			||||||
pub struct LogRequest {
 | 
					pub struct LogRequest {
 | 
				
			||||||
    severity: LogSeverity,
 | 
					    severity: LogSeverity,
 | 
				
			||||||
    message: String,
 | 
					    message: String,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
use crate::ota::ota_manager;
 | 
					use crate::ota::ota_manager;
 | 
				
			||||||
use crate::ota::ota_update::OTAPlatform;
 | 
					use crate::ota::ota_update::OTAPlatform;
 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct FirmwarePath {
 | 
					pub struct FirmwarePath {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,7 @@ pub struct JWTRequest {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl JWTRequest {
 | 
					impl JWTRequest {
 | 
				
			||||||
    pub async fn parse_jwt<E: DeserializeOwned + std::clone::Clone>(
 | 
					    pub async fn parse_jwt<E: DeserializeOwned>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        actor: WebEnergyActor,
 | 
					        actor: WebEnergyActor,
 | 
				
			||||||
    ) -> anyhow::Result<(Device, E)> {
 | 
					    ) -> anyhow::Result<(Device, E)> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,10 +4,10 @@ use crate::energy::energy_actor;
 | 
				
			|||||||
use crate::energy::energy_actor::RelaySyncStatus;
 | 
					use crate::energy::energy_actor::RelaySyncStatus;
 | 
				
			||||||
use crate::ota::ota_manager;
 | 
					use crate::ota::ota_manager;
 | 
				
			||||||
use crate::ota::ota_update::OTAPlatform;
 | 
					use crate::ota::ota_update::OTAPlatform;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
					use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
use openssl::nid::Nid;
 | 
					use openssl::nid::Nid;
 | 
				
			||||||
use openssl::x509::X509Req;
 | 
					use openssl::x509::X509Req;
 | 
				
			||||||
use std::str::FromStr;
 | 
					use std::str::FromStr;
 | 
				
			||||||
@@ -130,7 +130,7 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
 | 
				
			|||||||
        .body(cert))
 | 
					        .body(cert))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
struct Claims {
 | 
					struct Claims {
 | 
				
			||||||
    info: DeviceInfo,
 | 
					    info: DeviceInfo,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -155,11 +155,12 @@ pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) ->
 | 
				
			|||||||
    let mut available_update = None;
 | 
					    let mut available_update = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check if the version is available
 | 
					    // Check if the version is available
 | 
				
			||||||
    if let Some(desired) = device.desired_version
 | 
					    if let Some(desired) = device.desired_version {
 | 
				
			||||||
        && claims.info.version < desired
 | 
					        if claims.info.version < desired
 | 
				
			||||||
        && ota_manager::update_exists(OTAPlatform::from_str(&claims.info.reference)?, &desired)?
 | 
					            && ota_manager::update_exists(OTAPlatform::from_str(&claims.info.reference)?, &desired)?
 | 
				
			||||||
    {
 | 
					        {
 | 
				
			||||||
        available_update = Some(desired);
 | 
					            available_update = Some(desired);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(SyncResult {
 | 
					    Ok(HttpResponse::Ok().json(SyncResult {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,14 +10,14 @@ use crate::server::unsecure_server::*;
 | 
				
			|||||||
use crate::server::web_api::*;
 | 
					use crate::server::web_api::*;
 | 
				
			||||||
use crate::server::web_app_controller;
 | 
					use crate::server::web_app_controller;
 | 
				
			||||||
use actix_cors::Cors;
 | 
					use actix_cors::Cors;
 | 
				
			||||||
 | 
					use actix_identity::config::LogoutBehaviour;
 | 
				
			||||||
use actix_identity::IdentityMiddleware;
 | 
					use actix_identity::IdentityMiddleware;
 | 
				
			||||||
use actix_identity::config::LogoutBehavior;
 | 
					 | 
				
			||||||
use actix_remote_ip::RemoteIPConfig;
 | 
					use actix_remote_ip::RemoteIPConfig;
 | 
				
			||||||
use actix_session::SessionMiddleware;
 | 
					 | 
				
			||||||
use actix_session::storage::CookieSessionStore;
 | 
					use actix_session::storage::CookieSessionStore;
 | 
				
			||||||
 | 
					use actix_session::SessionMiddleware;
 | 
				
			||||||
use actix_web::cookie::{Key, SameSite};
 | 
					use actix_web::cookie::{Key, SameSite};
 | 
				
			||||||
use actix_web::middleware::Logger;
 | 
					use actix_web::middleware::Logger;
 | 
				
			||||||
use actix_web::{App, HttpServer, web};
 | 
					use actix_web::{web, App, HttpServer};
 | 
				
			||||||
use openssl::ssl::{SslAcceptor, SslMethod};
 | 
					use openssl::ssl::{SslAcceptor, SslMethod};
 | 
				
			||||||
use std::time::Duration;
 | 
					use std::time::Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,7 +84,7 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
				
			|||||||
        .build();
 | 
					        .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let identity_middleware = IdentityMiddleware::builder()
 | 
					        let identity_middleware = IdentityMiddleware::builder()
 | 
				
			||||||
            .logout_behavior(LogoutBehavior::PurgeSession)
 | 
					            .logout_behaviour(LogoutBehaviour::PurgeSession)
 | 
				
			||||||
            .visit_deadline(Some(Duration::from_secs(
 | 
					            .visit_deadline(Some(Duration::from_secs(
 | 
				
			||||||
                constants::MAX_INACTIVITY_DURATION,
 | 
					                constants::MAX_INACTIVITY_DURATION,
 | 
				
			||||||
            )))
 | 
					            )))
 | 
				
			||||||
@@ -231,10 +231,6 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
				
			|||||||
                "/web_api/relay/{id}",
 | 
					                "/web_api/relay/{id}",
 | 
				
			||||||
                web::put().to(relays_controller::update),
 | 
					                web::put().to(relays_controller::update),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .route(
 | 
					 | 
				
			||||||
                "/web_api/relay/{id}/forced_state",
 | 
					 | 
				
			||||||
                web::put().to(relays_controller::set_forced_state),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
                "/web_api/relay/{id}",
 | 
					                "/web_api/relay/{id}",
 | 
				
			||||||
                web::delete().to(relays_controller::delete),
 | 
					                web::delete().to(relays_controller::delete),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct ServeCRLPath {
 | 
					pub struct ServeCRLPath {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
use crate::devices::device::DeviceRelayID;
 | 
					use crate::devices::device::DeviceRelayID;
 | 
				
			||||||
use crate::energy::{energy_actor, relay_state_history};
 | 
					use crate::energy::{energy_actor, relay_state_history};
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct LegacyStateRelay {
 | 
					pub struct LegacyStateRelay {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ use crate::app_config::AppConfig;
 | 
				
			|||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_identity::Identity;
 | 
					use actix_identity::Identity;
 | 
				
			||||||
use actix_remote_ip::RemoteIP;
 | 
					use actix_remote_ip::RemoteIP;
 | 
				
			||||||
use actix_web::{HttpMessage, HttpRequest, HttpResponse, web};
 | 
					use actix_web::{web, HttpMessage, HttpRequest, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct AuthRequest {
 | 
					pub struct AuthRequest {
 | 
				
			||||||
@@ -17,11 +17,11 @@ pub async fn password_auth(
 | 
				
			|||||||
    remote_ip: RemoteIP,
 | 
					    remote_ip: RemoteIP,
 | 
				
			||||||
) -> HttpResult {
 | 
					) -> HttpResult {
 | 
				
			||||||
    if r.user != AppConfig::get().admin_username || r.password != AppConfig::get().admin_password {
 | 
					    if r.user != AppConfig::get().admin_username || r.password != AppConfig::get().admin_password {
 | 
				
			||||||
        log::error!("Failed login attempt from {}!", remote_ip.0);
 | 
					        log::error!("Failed login attempt from {}!", remote_ip.0.to_string());
 | 
				
			||||||
        return Ok(HttpResponse::Unauthorized().json("Invalid credentials!"));
 | 
					        return Ok(HttpResponse::Unauthorized().json("Invalid credentials!"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log::info!("Successful login attempt from {}!", remote_ip.0);
 | 
					    log::info!("Successful login attempt from {}!", remote_ip.0.to_string());
 | 
				
			||||||
    Identity::login(&request.extensions(), r.user.to_string())?;
 | 
					    Identity::login(&request.extensions(), r.user.to_string())?;
 | 
				
			||||||
    Ok(HttpResponse::Ok().finish())
 | 
					    Ok(HttpResponse::Ok().finish())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
use crate::devices::device::{DeviceGeneralInfo, DeviceId};
 | 
					use crate::devices::device::{DeviceGeneralInfo, DeviceId};
 | 
				
			||||||
use crate::energy::energy_actor;
 | 
					use crate::energy::energy_actor;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get the list of pending (not accepted yet) devices
 | 
					/// Get the list of pending (not accepted yet) devices
 | 
				
			||||||
pub async fn list_pending(actor: WebEnergyActor) -> HttpResult {
 | 
					pub async fn list_pending(actor: WebEnergyActor) -> HttpResult {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,8 @@ use crate::app_config::ConsumptionHistoryType;
 | 
				
			|||||||
use crate::energy::consumption::EnergyConsumption;
 | 
					use crate::energy::consumption::EnergyConsumption;
 | 
				
			||||||
use crate::energy::consumption_history_file::ConsumptionHistoryFile;
 | 
					use crate::energy::consumption_history_file::ConsumptionHistoryFile;
 | 
				
			||||||
use crate::energy::{consumption, energy_actor};
 | 
					use crate::energy::{consumption, energy_actor};
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
use crate::utils::time_utils::time_secs;
 | 
					use crate::utils::time_utils::time_secs;
 | 
				
			||||||
use actix_web::HttpResponse;
 | 
					use actix_web::HttpResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ use crate::logs::logs_manager;
 | 
				
			|||||||
use crate::logs::severity::LogSeverity;
 | 
					use crate::logs::severity::LogSeverity;
 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use crate::utils::time_utils::curr_day_number;
 | 
					use crate::utils::time_utils::curr_day_number;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct LogRequest {
 | 
					pub struct LogRequest {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,11 @@ use crate::devices::device::DeviceId;
 | 
				
			|||||||
use crate::energy::energy_actor;
 | 
					use crate::energy::energy_actor;
 | 
				
			||||||
use crate::ota::ota_manager;
 | 
					use crate::ota::ota_manager;
 | 
				
			||||||
use crate::ota::ota_update::OTAPlatform;
 | 
					use crate::ota::ota_update::OTAPlatform;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_multipart::form::MultipartForm;
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
use actix_multipart::form::tempfile::TempFile;
 | 
					use actix_multipart::form::tempfile::TempFile;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use actix_multipart::form::MultipartForm;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn supported_platforms() -> HttpResult {
 | 
					pub async fn supported_platforms() -> HttpResult {
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(OTAPlatform::supported_platforms()))
 | 
					    Ok(HttpResponse::Ok().json(OTAPlatform::supported_platforms()))
 | 
				
			||||||
@@ -123,17 +123,17 @@ pub async fn set_desired_version(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for d in devices {
 | 
					    for d in devices {
 | 
				
			||||||
        // Filter per platform
 | 
					        // Filter per platform
 | 
				
			||||||
        if let Some(p) = body.platform
 | 
					        if let Some(p) = body.platform {
 | 
				
			||||||
            && d.info.reference != p.to_string()
 | 
					            if d.info.reference != p.to_string() {
 | 
				
			||||||
        {
 | 
					                continue;
 | 
				
			||||||
            continue;
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Filter per device
 | 
					        // Filter per device
 | 
				
			||||||
        if let Some(ids) = &body.devices
 | 
					        if let Some(ids) = &body.devices {
 | 
				
			||||||
            && !ids.contains(&d.id)
 | 
					            if !ids.contains(&d.id) {
 | 
				
			||||||
        {
 | 
					                continue;
 | 
				
			||||||
            continue;
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        actor
 | 
					        actor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
use crate::devices::device::{DeviceId, DeviceRelay, DeviceRelayID};
 | 
					use crate::devices::device::{DeviceId, DeviceRelay, DeviceRelayID};
 | 
				
			||||||
use crate::energy::energy_actor;
 | 
					use crate::energy::energy_actor;
 | 
				
			||||||
use crate::energy::engine::SetRelayForcedStateReq;
 | 
					 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
use actix_web::{HttpResponse, web};
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get the full list of relays
 | 
					/// Get the full list of relays
 | 
				
			||||||
pub async fn get_list(actor: WebEnergyActor) -> HttpResult {
 | 
					pub async fn get_list(actor: WebEnergyActor) -> HttpResult {
 | 
				
			||||||
@@ -86,29 +85,6 @@ pub async fn update(
 | 
				
			|||||||
    Ok(HttpResponse::Accepted().finish())
 | 
					    Ok(HttpResponse::Accepted().finish())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Set relay forced status
 | 
					 | 
				
			||||||
pub async fn set_forced_state(
 | 
					 | 
				
			||||||
    actor: WebEnergyActor,
 | 
					 | 
				
			||||||
    req: web::Json<SetRelayForcedStateReq>,
 | 
					 | 
				
			||||||
    path: web::Path<RelayIDInPath>,
 | 
					 | 
				
			||||||
) -> HttpResult {
 | 
					 | 
				
			||||||
    // Check if relay exists first
 | 
					 | 
				
			||||||
    let list = actor.send(energy_actor::GetAllRelaysState).await?;
 | 
					 | 
				
			||||||
    if !list.into_iter().any(|r| r.id == path.id) {
 | 
					 | 
				
			||||||
        return Ok(HttpResponse::NotFound().json("Relay not found!"));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Update relay forced state
 | 
					 | 
				
			||||||
    actor
 | 
					 | 
				
			||||||
        .send(energy_actor::SetRelayForcedState(
 | 
					 | 
				
			||||||
            path.id,
 | 
					 | 
				
			||||||
            req.to_forced_state(),
 | 
					 | 
				
			||||||
        ))
 | 
					 | 
				
			||||||
        .await??;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(HttpResponse::Accepted().finish())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Delete an existing relay
 | 
					/// Delete an existing relay
 | 
				
			||||||
pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult {
 | 
					pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult {
 | 
				
			||||||
    actor
 | 
					    actor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ mod serve_static_debug {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[cfg(not(debug_assertions))]
 | 
					#[cfg(not(debug_assertions))]
 | 
				
			||||||
mod serve_static_release {
 | 
					mod serve_static_release {
 | 
				
			||||||
    use actix_web::{HttpResponse, Responder, web};
 | 
					    use actix_web::{web, HttpResponse, Responder};
 | 
				
			||||||
    use rust_embed::RustEmbed;
 | 
					    use rust_embed::RustEmbed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[derive(RustEmbed)]
 | 
					    #[derive(RustEmbed)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
import js from '@eslint/js'
 | 
					 | 
				
			||||||
import globals from 'globals'
 | 
					 | 
				
			||||||
import reactHooks from 'eslint-plugin-react-hooks'
 | 
					 | 
				
			||||||
import reactRefresh from 'eslint-plugin-react-refresh'
 | 
					 | 
				
			||||||
import tseslint from 'typescript-eslint'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default tseslint.config(
 | 
					 | 
				
			||||||
  { ignores: ['dist'] },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
 | 
					 | 
				
			||||||
    files: ['**/*.{ts,tsx}'],
 | 
					 | 
				
			||||||
    languageOptions: {
 | 
					 | 
				
			||||||
      ecmaVersion: 2020,
 | 
					 | 
				
			||||||
      globals: globals.browser,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    plugins: {
 | 
					 | 
				
			||||||
      'react-hooks': reactHooks,
 | 
					 | 
				
			||||||
      'react-refresh': reactRefresh,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    rules: {
 | 
					 | 
				
			||||||
      ...reactHooks.configs.recommended.rules,
 | 
					 | 
				
			||||||
      'react-refresh/only-export-components': [
 | 
					 | 
				
			||||||
        'warn',
 | 
					 | 
				
			||||||
        { allowConstantExport: true },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
							
								
								
									
										2846
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2846
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -6,40 +6,38 @@
 | 
				
			|||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "vite",
 | 
					    "dev": "vite",
 | 
				
			||||||
    "build": "tsc -b && vite build",
 | 
					    "build": "tsc -b && vite build",
 | 
				
			||||||
    "lint": "eslint .",
 | 
					    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
 | 
				
			||||||
    "preview": "vite preview"
 | 
					    "preview": "vite preview"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@emotion/react": "^11.14.0",
 | 
					    "@emotion/react": "^11.14.0",
 | 
				
			||||||
    "@emotion/styled": "^11.14.1",
 | 
					    "@emotion/styled": "^11.13.0",
 | 
				
			||||||
    "@fontsource/roboto": "^5.2.8",
 | 
					    "@fontsource/roboto": "^5.1.1",
 | 
				
			||||||
    "@mdi/js": "^7.4.47",
 | 
					    "@mdi/js": "^7.4.47",
 | 
				
			||||||
    "@mdi/react": "^1.6.1",
 | 
					    "@mdi/react": "^1.6.1",
 | 
				
			||||||
    "@mui/icons-material": "^7.3.4",
 | 
					    "@mui/icons-material": "^6.3.0",
 | 
				
			||||||
    "@mui/material": "^7.3.4",
 | 
					    "@mui/material": "^6.3.0",
 | 
				
			||||||
    "@mui/x-charts": "^8.16.0",
 | 
					    "@mui/x-charts": "^7.21.0",
 | 
				
			||||||
    "@mui/x-date-pickers": "^8.16.0",
 | 
					    "@mui/x-date-pickers": "^7.23.3",
 | 
				
			||||||
    "date-and-time": "^4.1.0",
 | 
					    "@types/semver": "^7.5.8",
 | 
				
			||||||
    "dayjs": "^1.11.19",
 | 
					    "date-and-time": "^3.6.0",
 | 
				
			||||||
    "filesize": "^11.0.13",
 | 
					    "dayjs": "^1.11.13",
 | 
				
			||||||
    "react": "^19.2.0",
 | 
					    "filesize": "^10.1.6",
 | 
				
			||||||
    "react-dom": "^19.2.0",
 | 
					    "react": "^19.0.0",
 | 
				
			||||||
    "react-router-dom": "^7.9.5",
 | 
					    "react-dom": "^19.0.0",
 | 
				
			||||||
    "semver": "^7.7.3"
 | 
					    "react-router-dom": "^7.1.1",
 | 
				
			||||||
 | 
					    "semver": "^7.6.3"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/react": "^19.2.2",
 | 
					    "@types/react": "^19.0.0",
 | 
				
			||||||
    "@types/react-dom": "^19.2.2",
 | 
					    "@types/react-dom": "^19.0.0",
 | 
				
			||||||
    "@types/semver": "^7.7.1",
 | 
					    "@typescript-eslint/eslint-plugin": "^8.18.2",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^8.46.2",
 | 
					    "@typescript-eslint/parser": "^8.18.2",
 | 
				
			||||||
    "@typescript-eslint/parser": "^8.46.2",
 | 
					    "@vitejs/plugin-react": "^4.3.2",
 | 
				
			||||||
    "@vitejs/plugin-react": "^5.1.0",
 | 
					    "eslint": "^9.17.0",
 | 
				
			||||||
    "eslint": "^9.38.0",
 | 
					    "eslint-plugin-react-hooks": "^5.0.0",
 | 
				
			||||||
    "eslint-plugin-react-hooks": "^7.0.1",
 | 
					    "eslint-plugin-react-refresh": "^0.4.12",
 | 
				
			||||||
    "eslint-plugin-react-refresh": "^0.4.24",
 | 
					    "typescript": "^5.6.3",
 | 
				
			||||||
    "globals": "^16.4.0",
 | 
					    "vite": "^6.0.6"
 | 
				
			||||||
    "typescript": "^5.9.3",
 | 
					 | 
				
			||||||
    "typescript-eslint": "^8.46.2",
 | 
					 | 
				
			||||||
    "vite": "^7.1.12"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,10 @@
 | 
				
			|||||||
import { APIClient } from "./ApiClient";
 | 
					import { APIClient } from "./ApiClient";
 | 
				
			||||||
import { Device, DeviceRelay } from "./DeviceApi";
 | 
					import { Device, DeviceRelay } from "./DeviceApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type RelayForcedState =
 | 
					 | 
				
			||||||
  | { type: "None" }
 | 
					 | 
				
			||||||
  | { type: "Off" | "On"; until: number };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type SetRelayForcedState =
 | 
					 | 
				
			||||||
  | { type: "None" }
 | 
					 | 
				
			||||||
  | { type: "Off" | "On"; for_secs: number };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface RelayStatus {
 | 
					export interface RelayStatus {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  on: boolean;
 | 
					  on: boolean;
 | 
				
			||||||
  for: number;
 | 
					  for: number;
 | 
				
			||||||
  forced_state: RelayForcedState;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type RelaysStatus = Map<string, RelayStatus>;
 | 
					export type RelaysStatus = Map<string, RelayStatus>;
 | 
				
			||||||
@@ -57,20 +48,6 @@ export class RelayApi {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Set relay forced state
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  static async SetForcedState(
 | 
					 | 
				
			||||||
    relay: DeviceRelay,
 | 
					 | 
				
			||||||
    forced: SetRelayForcedState
 | 
					 | 
				
			||||||
  ): Promise<void> {
 | 
					 | 
				
			||||||
    await APIClient.exec({
 | 
					 | 
				
			||||||
      method: "PUT",
 | 
					 | 
				
			||||||
      uri: `/relay/${relay.id}/forced_state`,
 | 
					 | 
				
			||||||
      jsonData: forced,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Delete a relay configuration
 | 
					   * Delete a relay configuration
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,7 +84,7 @@ export function DeployOTAUpdateDialogProvider(p: {
 | 
				
			|||||||
            </DialogContentText>
 | 
					            </DialogContentText>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <FormControl>
 | 
					            <FormControl>
 | 
				
			||||||
              <FormLabel>Deployment target</FormLabel>
 | 
					              <FormLabel>Gender</FormLabel>
 | 
				
			||||||
              <RadioGroup
 | 
					              <RadioGroup
 | 
				
			||||||
                name="radio-buttons-group"
 | 
					                name="radio-buttons-group"
 | 
				
			||||||
                value={allDevices}
 | 
					                value={allDevices}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ import {
 | 
				
			|||||||
  DialogTitle,
 | 
					  DialogTitle,
 | 
				
			||||||
  Typography,
 | 
					  Typography,
 | 
				
			||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
import Grid from "@mui/material/Grid";
 | 
					import Grid from "@mui/material/Grid2";
 | 
				
			||||||
import { TimePicker } from "@mui/x-date-pickers";
 | 
					import { TimePicker } from "@mui/x-date-pickers";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { Device, DeviceRelay } from "../api/DeviceApi";
 | 
					import { Device, DeviceRelay } from "../api/DeviceApi";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,53 +0,0 @@
 | 
				
			|||||||
import {
 | 
					 | 
				
			||||||
  Button,
 | 
					 | 
				
			||||||
  Dialog,
 | 
					 | 
				
			||||||
  DialogActions,
 | 
					 | 
				
			||||||
  DialogContent,
 | 
					 | 
				
			||||||
  DialogContentText,
 | 
					 | 
				
			||||||
  DialogTitle,
 | 
					 | 
				
			||||||
  TextField,
 | 
					 | 
				
			||||||
} from "@mui/material";
 | 
					 | 
				
			||||||
import { DeviceRelay } from "../api/DeviceApi";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function SelectForcedStateDurationDialog(p: {
 | 
					 | 
				
			||||||
  relay: DeviceRelay;
 | 
					 | 
				
			||||||
  forcedState: string;
 | 
					 | 
				
			||||||
  onCancel: () => void;
 | 
					 | 
				
			||||||
  onSubmit: (duration: number) => void;
 | 
					 | 
				
			||||||
}): React.ReactElement {
 | 
					 | 
				
			||||||
  const [duration, setDuration] = React.useState(60);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Dialog open onClose={p.onCancel}>
 | 
					 | 
				
			||||||
      <DialogTitle>Set forced relay state</DialogTitle>
 | 
					 | 
				
			||||||
      <DialogContent>
 | 
					 | 
				
			||||||
        <DialogContentText>
 | 
					 | 
				
			||||||
          Please specify the number of minutes the relay <i>{p.relay.name}</i>{" "}
 | 
					 | 
				
			||||||
          will remain in forced state <i>{p.forcedState}</i>:
 | 
					 | 
				
			||||||
        </DialogContentText>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <TextField
 | 
					 | 
				
			||||||
          label="Duration (min)"
 | 
					 | 
				
			||||||
          variant="standard"
 | 
					 | 
				
			||||||
          value={Math.floor(duration / 60)}
 | 
					 | 
				
			||||||
          onChange={(e) => {
 | 
					 | 
				
			||||||
            const val = Number.parseInt(e.target.value);
 | 
					 | 
				
			||||||
            setDuration((Number.isNaN(val) ? 1 : val) * 60);
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
          fullWidth
 | 
					 | 
				
			||||||
          style={{ marginTop: "5px" }}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <p>Equivalent in seconds: {duration} secs</p>
 | 
					 | 
				
			||||||
        <p>Equivalent in hours: {duration / 3600} hours</p>
 | 
					 | 
				
			||||||
      </DialogContent>
 | 
					 | 
				
			||||||
      <DialogActions>
 | 
					 | 
				
			||||||
        <Button onClick={p.onCancel}>Cancel</Button>
 | 
					 | 
				
			||||||
        <Button onClick={() => p.onSubmit(duration)} autoFocus>
 | 
					 | 
				
			||||||
          Start timer
 | 
					 | 
				
			||||||
        </Button>
 | 
					 | 
				
			||||||
      </DialogActions>
 | 
					 | 
				
			||||||
    </Dialog>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -10,16 +10,16 @@ import {
 | 
				
			|||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { Device, DeviceRelay } from "../../api/DeviceApi";
 | 
					import { Device, DeviceRelay } from "../../api/DeviceApi";
 | 
				
			||||||
import { RelayApi, RelayStatus } from "../../api/RelayApi";
 | 
					 | 
				
			||||||
import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog";
 | 
					import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog";
 | 
				
			||||||
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
 | 
					import { DeviceRouteCard } from "./DeviceRouteCard";
 | 
				
			||||||
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider";
 | 
					import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider";
 | 
				
			||||||
 | 
					import { RelayApi, RelayStatus } from "../../api/RelayApi";
 | 
				
			||||||
import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
				
			||||||
import { BoolText } from "../../widgets/BoolText";
 | 
					 | 
				
			||||||
import { TimeWidget } from "../../widgets/TimeWidget";
 | 
					import { TimeWidget } from "../../widgets/TimeWidget";
 | 
				
			||||||
import { DeviceRouteCard } from "./DeviceRouteCard";
 | 
					import { BoolText } from "../../widgets/BoolText";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DeviceRelays(p: {
 | 
					export function DeviceRelays(p: {
 | 
				
			||||||
  device: Device;
 | 
					  device: Device;
 | 
				
			||||||
@@ -145,8 +145,7 @@ function RelayEntryStatus(
 | 
				
			|||||||
      errMsg="Failed to load relay status!"
 | 
					      errMsg="Failed to load relay status!"
 | 
				
			||||||
      build={() => (
 | 
					      build={() => (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
          <BoolText val={state!.on} positive="ON" negative="OFF" />{" "}
 | 
					          <BoolText val={state!.on} positive="ON" negative="OFF" /> for{" "}
 | 
				
			||||||
          {state?.forced_state.type !== "None" && <b>Forced</b>} for{" "}
 | 
					 | 
				
			||||||
          <TimeWidget diff time={state!.for} />
 | 
					          <TimeWidget diff time={state!.for} />
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
import RefreshIcon from "@mui/icons-material/Refresh";
 | 
					import RefreshIcon from "@mui/icons-material/Refresh";
 | 
				
			||||||
import { IconButton, Tooltip } from "@mui/material";
 | 
					import { IconButton, Tooltip } from "@mui/material";
 | 
				
			||||||
import Grid from "@mui/material/Grid";
 | 
					import Grid from "@mui/material/Grid2";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { useNavigate, useParams } from "react-router-dom";
 | 
					import { useNavigate, useParams } from "react-router-dom";
 | 
				
			||||||
import { Device, DeviceApi } from "../../api/DeviceApi";
 | 
					import { Device, DeviceApi } from "../../api/DeviceApi";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Typography } from "@mui/material";
 | 
					import { Typography } from "@mui/material";
 | 
				
			||||||
import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget";
 | 
					import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget";
 | 
				
			||||||
import Grid from "@mui/material/Grid";
 | 
					import Grid from "@mui/material/Grid2";
 | 
				
			||||||
import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget";
 | 
					import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget";
 | 
				
			||||||
import { RelayConsumptionWidget } from "./HomeRoute/RelayConsumptionWidget";
 | 
					import { RelayConsumptionWidget } from "./HomeRoute/RelayConsumptionWidget";
 | 
				
			||||||
import { RelaysListRoute } from "./RelaysListRoute";
 | 
					import { RelaysListRoute } from "./RelaysListRoute";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ import Typography from "@mui/material/Typography";
 | 
				
			|||||||
import * as React from "react";
 | 
					import * as React from "react";
 | 
				
			||||||
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
 | 
					import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
 | 
				
			||||||
import { AuthApi } from "../api/AuthApi";
 | 
					import { AuthApi } from "../api/AuthApi";
 | 
				
			||||||
import Grid from "@mui/material/Grid";
 | 
					import Grid from "@mui/material/Grid2";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Copyright(props: any) {
 | 
					function Copyright(props: any) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,13 +16,12 @@ import React from "react";
 | 
				
			|||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi";
 | 
					import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi";
 | 
				
			||||||
import { RelayApi, RelaysStatus } from "../api/RelayApi";
 | 
					import { RelayApi, RelaysStatus } from "../api/RelayApi";
 | 
				
			||||||
import { ServerApi } from "../api/ServerApi";
 | 
					 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { BoolText } from "../widgets/BoolText";
 | 
					import { BoolText } from "../widgets/BoolText";
 | 
				
			||||||
import { CopyToClipboard } from "../widgets/CopyToClipboard";
 | 
					 | 
				
			||||||
import { RelayForcedState } from "../widgets/RelayForcedState";
 | 
					 | 
				
			||||||
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
 | 
					import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
 | 
				
			||||||
import { TimeWidget } from "../widgets/TimeWidget";
 | 
					import { TimeWidget } from "../widgets/TimeWidget";
 | 
				
			||||||
 | 
					import { CopyToClipboard } from "../widgets/CopyToClipboard";
 | 
				
			||||||
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RelaysListRoute(p: {
 | 
					export function RelaysListRoute(p: {
 | 
				
			||||||
  homeWidget?: boolean;
 | 
					  homeWidget?: boolean;
 | 
				
			||||||
@@ -105,7 +104,6 @@ function RelaysList(p: {
 | 
				
			|||||||
            <TableCell>Priority</TableCell>
 | 
					            <TableCell>Priority</TableCell>
 | 
				
			||||||
            <TableCell>Consumption</TableCell>
 | 
					            <TableCell>Consumption</TableCell>
 | 
				
			||||||
            <TableCell>Status</TableCell>
 | 
					            <TableCell>Status</TableCell>
 | 
				
			||||||
            <TableCell>Forced state</TableCell>
 | 
					 | 
				
			||||||
            <TableCell></TableCell>
 | 
					            <TableCell></TableCell>
 | 
				
			||||||
          </TableRow>
 | 
					          </TableRow>
 | 
				
			||||||
        </TableHead>
 | 
					        </TableHead>
 | 
				
			||||||
@@ -131,13 +129,6 @@ function RelaysList(p: {
 | 
				
			|||||||
                />{" "}
 | 
					                />{" "}
 | 
				
			||||||
                for <TimeWidget diff time={p.status.get(row.id)!.for} />
 | 
					                for <TimeWidget diff time={p.status.get(row.id)!.for} />
 | 
				
			||||||
              </TableCell>
 | 
					              </TableCell>
 | 
				
			||||||
              <TableCell>
 | 
					 | 
				
			||||||
                <RelayForcedState
 | 
					 | 
				
			||||||
                  relay={row}
 | 
					 | 
				
			||||||
                  state={p.status.get(row.id)!}
 | 
					 | 
				
			||||||
                  onUpdated={p.onReload}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
              </TableCell>
 | 
					 | 
				
			||||||
              <TableCell>
 | 
					              <TableCell>
 | 
				
			||||||
                <Tooltip title="Copy legacy api status">
 | 
					                <Tooltip title="Copy legacy api status">
 | 
				
			||||||
                  <CopyToClipboard
 | 
					                  <CopyToClipboard
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
import { MenuItem, Select, SelectChangeEvent } from "@mui/material";
 | 
					 | 
				
			||||||
import { DeviceRelay } from "../api/DeviceApi";
 | 
					 | 
				
			||||||
import { RelayApi, RelayStatus, SetRelayForcedState } from "../api/RelayApi";
 | 
					 | 
				
			||||||
import { TimeWidget } from "./TimeWidget";
 | 
					 | 
				
			||||||
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
 | 
					 | 
				
			||||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
					 | 
				
			||||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import { SelectForcedStateDurationDialog } from "../dialogs/SelectForcedStateDurationDialog";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RelayForcedState(p: {
 | 
					 | 
				
			||||||
  relay: DeviceRelay;
 | 
					 | 
				
			||||||
  state: RelayStatus;
 | 
					 | 
				
			||||||
  onUpdated: () => void;
 | 
					 | 
				
			||||||
}): React.ReactElement {
 | 
					 | 
				
			||||||
  const loadingMessage = useLoadingMessage();
 | 
					 | 
				
			||||||
  const alert = useAlert();
 | 
					 | 
				
			||||||
  const snackbar = useSnackbar();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [futureStateType, setFutureStateType] = React.useState<
 | 
					 | 
				
			||||||
    string | undefined
 | 
					 | 
				
			||||||
  >();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleChange = (event: SelectChangeEvent) => {
 | 
					 | 
				
			||||||
    if (event.target.value == "None") {
 | 
					 | 
				
			||||||
      submitChange({ type: "None" });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      setFutureStateType(event.target.value);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const submitChange = async (state: SetRelayForcedState) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      loadingMessage.show("Setting forced state...");
 | 
					 | 
				
			||||||
      await RelayApi.SetForcedState(p.relay, state);
 | 
					 | 
				
			||||||
      p.onUpdated();
 | 
					 | 
				
			||||||
      snackbar("Forced state successfully updated!");
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      console.error(`Failed to set relay forced state! ${e}`);
 | 
					 | 
				
			||||||
      alert(`Failed to set loading state for relay! ${e}`);
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      loadingMessage.hide();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <Select
 | 
					 | 
				
			||||||
        value={p.state.forced_state.type}
 | 
					 | 
				
			||||||
        onChange={handleChange}
 | 
					 | 
				
			||||||
        size="small"
 | 
					 | 
				
			||||||
        variant="standard"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <MenuItem value={"None"}>None</MenuItem>
 | 
					 | 
				
			||||||
        <MenuItem value={"Off"}>Off</MenuItem>
 | 
					 | 
				
			||||||
        <MenuItem value={"On"}>On</MenuItem>
 | 
					 | 
				
			||||||
      </Select>
 | 
					 | 
				
			||||||
      {p.state.forced_state.type !== "None" && (
 | 
					 | 
				
			||||||
        <>
 | 
					 | 
				
			||||||
          <TimeWidget future time={p.state.forced_state.until} /> left
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      {futureStateType !== undefined && (
 | 
					 | 
				
			||||||
        <SelectForcedStateDurationDialog
 | 
					 | 
				
			||||||
          {...p}
 | 
					 | 
				
			||||||
          forcedState={futureStateType}
 | 
					 | 
				
			||||||
          onCancel={() => setFutureStateType(undefined)}
 | 
					 | 
				
			||||||
          onSubmit={(d) =>
 | 
					 | 
				
			||||||
            submitChange({
 | 
					 | 
				
			||||||
              type: futureStateType as any,
 | 
					 | 
				
			||||||
              for_secs: d,
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -106,7 +106,7 @@ export default function StatCard({
 | 
				
			|||||||
          <Box sx={{ width: "100%", height: 100 }}>
 | 
					          <Box sx={{ width: "100%", height: 100 }}>
 | 
				
			||||||
            {data && interval && (
 | 
					            {data && interval && (
 | 
				
			||||||
              <SparkLineChart
 | 
					              <SparkLineChart
 | 
				
			||||||
                color={chartColor}
 | 
					                colors={[chartColor]}
 | 
				
			||||||
                data={data}
 | 
					                data={data}
 | 
				
			||||||
                area
 | 
					                area
 | 
				
			||||||
                showHighlight
 | 
					                showHighlight
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { Tooltip } from "@mui/material";
 | 
					import { Tooltip } from "@mui/material";
 | 
				
			||||||
import { format } from "date-and-time";
 | 
					import date from "date-and-time";
 | 
				
			||||||
import { time } from "../utils/DateUtils";
 | 
					import { time } from "../utils/DateUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function formatDate(time: number): string {
 | 
					export function formatDate(time: number): string {
 | 
				
			||||||
  const t = new Date();
 | 
					  const t = new Date();
 | 
				
			||||||
  t.setTime(1000 * time);
 | 
					  t.setTime(1000 * time);
 | 
				
			||||||
  return format(t, "DD/MM/YYYY HH:mm:ss");
 | 
					  return date.format(t, "DD/MM/YYYY HH:mm:ss");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function timeDiff(a: number, b: number): string {
 | 
					export function timeDiff(a: number, b: number): string {
 | 
				
			||||||
@@ -51,14 +51,13 @@ export function timeDiff(a: number, b: number): string {
 | 
				
			|||||||
  return `${diffYears} years`;
 | 
					  return `${diffYears} years`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function timeDiffFromNow(t: number, future?: boolean): string {
 | 
					export function timeDiffFromNow(t: number): string {
 | 
				
			||||||
  return future ? timeDiff(time(), t) : timeDiff(t, time());
 | 
					  return timeDiff(t, time());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function TimeWidget(p: {
 | 
					export function TimeWidget(p: {
 | 
				
			||||||
  time?: number;
 | 
					  time?: number;
 | 
				
			||||||
  diff?: boolean;
 | 
					  diff?: boolean;
 | 
				
			||||||
  future?: boolean;
 | 
					 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  if (!p.time) return <></>;
 | 
					  if (!p.time) return <></>;
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -66,9 +65,7 @@ export function TimeWidget(p: {
 | 
				
			|||||||
      title={formatDate(p.diff ? new Date().getTime() / 1000 - p.time : p.time)}
 | 
					      title={formatDate(p.diff ? new Date().getTime() / 1000 - p.time : p.time)}
 | 
				
			||||||
      arrow
 | 
					      arrow
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <span>
 | 
					      <span>{p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time)}</span>
 | 
				
			||||||
        {p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time, p.future)}
 | 
					 | 
				
			||||||
      </span>
 | 
					 | 
				
			||||||
    </Tooltip>
 | 
					    </Tooltip>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "composite": true,
 | 
				
			||||||
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
 | 
					    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
 | 
				
			||||||
    "target": "ES2020",
 | 
					    "target": "ES2020",
 | 
				
			||||||
    "useDefineForClassFields": true,
 | 
					    "useDefineForClassFields": true,
 | 
				
			||||||
@@ -10,6 +11,7 @@
 | 
				
			|||||||
    /* Bundler mode */
 | 
					    /* Bundler mode */
 | 
				
			||||||
    "moduleResolution": "bundler",
 | 
					    "moduleResolution": "bundler",
 | 
				
			||||||
    "allowImportingTsExtensions": true,
 | 
					    "allowImportingTsExtensions": true,
 | 
				
			||||||
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
    "isolatedModules": true,
 | 
					    "isolatedModules": true,
 | 
				
			||||||
    "moduleDetection": "force",
 | 
					    "moduleDetection": "force",
 | 
				
			||||||
    "noEmit": true,
 | 
					    "noEmit": true,
 | 
				
			||||||
@@ -19,8 +21,7 @@
 | 
				
			|||||||
    "strict": true,
 | 
					    "strict": true,
 | 
				
			||||||
    "noUnusedLocals": true,
 | 
					    "noUnusedLocals": true,
 | 
				
			||||||
    "noUnusedParameters": true,
 | 
					    "noUnusedParameters": true,
 | 
				
			||||||
    "noFallthroughCasesInSwitch": true,
 | 
					    "noFallthroughCasesInSwitch": true
 | 
				
			||||||
    "noUncheckedSideEffectImports": true
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "include": ["src"]
 | 
					  "include": ["src"]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "files": [],
 | 
					  "files": [],
 | 
				
			||||||
  "references": [
 | 
					  "references": [
 | 
				
			||||||
    { "path": "./tsconfig.app.json" },
 | 
					    {
 | 
				
			||||||
    { "path": "./tsconfig.node.json" }
 | 
					      "path": "./tsconfig.app.json"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "path": "./tsconfig.node.json"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,13 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "composite": true,
 | 
				
			||||||
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
 | 
					    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
 | 
				
			||||||
    "target": "ES2022",
 | 
					 | 
				
			||||||
    "lib": ["ES2023"],
 | 
					 | 
				
			||||||
    "module": "ESNext",
 | 
					 | 
				
			||||||
    "skipLibCheck": true,
 | 
					    "skipLibCheck": true,
 | 
				
			||||||
 | 
					    "module": "ESNext",
 | 
				
			||||||
    /* Bundler mode */
 | 
					 | 
				
			||||||
    "moduleResolution": "bundler",
 | 
					    "moduleResolution": "bundler",
 | 
				
			||||||
    "allowImportingTsExtensions": true,
 | 
					    "allowSyntheticDefaultImports": true,
 | 
				
			||||||
    "isolatedModules": true,
 | 
					 | 
				
			||||||
    "moduleDetection": "force",
 | 
					 | 
				
			||||||
    "noEmit": true,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* Linting */
 | 
					 | 
				
			||||||
    "strict": true,
 | 
					    "strict": true,
 | 
				
			||||||
    "noUnusedLocals": true,
 | 
					    "noEmit": true
 | 
				
			||||||
    "noUnusedParameters": true,
 | 
					 | 
				
			||||||
    "noFallthroughCasesInSwitch": true,
 | 
					 | 
				
			||||||
    "noUncheckedSideEffectImports": true
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "include": ["vite.config.ts"]
 | 
					  "include": ["vite.config.ts"]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { defineConfig } from 'vite'
 | 
					import { defineConfig } from 'vite'
 | 
				
			||||||
import react from '@vitejs/plugin-react'
 | 
					import react from '@vitejs/plugin-react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://vite.dev/config/
 | 
					// https://vitejs.dev/config/
 | 
				
			||||||
export default defineConfig({
 | 
					export default defineConfig({
 | 
				
			||||||
  plugins: [react()],
 | 
					  plugins: [react()],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1855
									
								
								custom_consumption/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1855
									
								
								custom_consumption/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,12 +1,12 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "custom_consumption"
 | 
					name = "custom_consumption"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
edition = "2024"
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
env_logger = "0.11.8"
 | 
					env_logger = "0.11.5"
 | 
				
			||||||
log = "0.4.28"
 | 
					log = "0.4.22"
 | 
				
			||||||
clap = { version = "4.5.51", features = ["derive", "env"] }
 | 
					clap = { version = "4.5.18", features = ["derive", "env"] }
 | 
				
			||||||
egui = "0.32.3"
 | 
					egui = "0.29.1"
 | 
				
			||||||
eframe = "0.32.3"
 | 
					eframe = "0.29.1"
 | 
				
			||||||
lazy_static = "1.5.0"
 | 
					lazy_static = "1.5.0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					#define CONFIG_ETH_USE_ESP32_EMAC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esp_eth.h"
 | 
					#include "esp_eth.h"
 | 
				
			||||||
#include "esp_eth_mac.h"
 | 
					#include "esp_eth_mac.h"
 | 
				
			||||||
#include "esp_eth_com.h"
 | 
					#include "esp_eth_com.h"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
1.0.3
 | 
					1.0.2
 | 
				
			||||||
@@ -1,3 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "extends": ["local>renovate/presets"]
 | 
					  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
 | 
				
			||||||
 | 
					  "packageRules": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "matchUpdateTypes": ["major", "minor", "patch"],
 | 
				
			||||||
 | 
					      "automerge": true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user