Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
73880f0737
|
|
@ -77,14 +77,18 @@ The script dynamically generates headers for the output CSV file based on the ke
|
||||||
extracted data, providing a clear and understandable format for subsequent analysis. The headers correspond to the keys used for data extraction, making
|
extracted data, providing a clear and understandable format for subsequent analysis. The headers correspond to the keys used for data extraction, making
|
||||||
it easy to identify and analyze the extracted data.
|
it easy to identify and analyze the extracted data.
|
||||||
|
|
||||||
|
|
||||||
4)Advanced Data Processing Capabilities:
|
4)Advanced Data Processing Capabilities:
|
||||||
|
|
||||||
Booleans as Numbers: The --booleans_as_numbers flag allows users to convert boolean values (True/False) into numeric representations (1/0). This feature
|
i) Booleans as Numbers: The --booleans_as_numbers flag allows users to convert boolean values (True/False) into numeric representations (1/0). This feature
|
||||||
is particularly useful for analytical tasks that require numerical data processing.
|
is particularly useful for analytical tasks that require numerical data processing.
|
||||||
|
|
||||||
|
ii) Sampling Stepsize: The --sampling_stepsize parameter enables users to define the granularity of the time range for data extraction. By specifying the number
|
||||||
|
of 1 minute intervals, users can adjust the sampling interval, allowing for flexible data retrieval based on time.
|
||||||
Example Command:
|
Example Command:
|
||||||
|
|
||||||
python3 extractS3data.py 1749062721 1749106001 --keys AcDc/SystemControl/ResetAlarmsAndWarnings,AcDc/Devices/1/Status/Ac/L1/Voltage --bucket-number 12 --product_name=SodistoreMax
|
python3 extractS3data.py 1749062721 1749106001 --keys AcDc/SystemControl/ResetAlarmsAndWarnings,AcDc/Devices/1/Status/Ac/L1/Voltage --bucket-number 12 --product_name=SodistoreMax --sampling_stepsize 2 --booleans_as_numbers
|
||||||
|
|
||||||
This command extracts data for AcDc/SystemControl/ResetAlarmsAndWarnings and AcDc/Devices/1/Status/Ac/L1/Voltage keys from bucket number 12, between the specified timestamps, with boolean values converted to numbers.
|
This command extracts data for AcDc/SystemControl/ResetAlarmsAndWarnings and AcDc/Devices/1/Status/Ac/L1/Voltage keys from bucket number 12, between the specified timestamps, with boolean values converted to numbers.
|
||||||
|
The script will fetch data in 2 minutes intervals
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,15 @@ def extract_timestamp(filename):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
def list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize,product_type,bucket_number):
|
def list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize, product_type, bucket_number):
|
||||||
if product_type == "Salimax" or product_type=="SodistoreMax":
|
if product_type in ["Salimax", "SodistoreMax"]:
|
||||||
hash = "3e5b3069-214a-43ee-8d85-57d72000c19d"
|
hash = "3e5b3069-214a-43ee-8d85-57d72000c19d"
|
||||||
elif product_type == "Salidomo":
|
elif product_type == "Salidomo":
|
||||||
hash = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
|
hash = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid product type option. Use Salimax or Salidomo or SodistoreMax")
|
raise ValueError("Invalid product type option.")
|
||||||
|
|
||||||
# Find common prefix
|
# Find common prefix
|
||||||
common_prefix = ""
|
common_prefix = ""
|
||||||
|
|
@ -43,20 +44,31 @@ def list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize,produc
|
||||||
output = subprocess.check_output(s3cmd_command, shell=True, text=True)
|
output = subprocess.check_output(s3cmd_command, shell=True, text=True)
|
||||||
files = [line.split()[-1] for line in output.strip().split("\n") if line.strip()]
|
files = [line.split()[-1] for line in output.strip().split("\n") if line.strip()]
|
||||||
filenames = []
|
filenames = []
|
||||||
|
count=0
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
name = f.split("/")[-1] # e.g., 1748802020.json
|
name = f.split("/")[-1]
|
||||||
timestamp_str = name.split(".")[0] # extract '1748802020'
|
timestamp_str = name.split(".")[0]
|
||||||
if timestamp_str.isdigit() and int(timestamp_str) <= int(end_timestamp):
|
|
||||||
filenames.append(name)
|
if timestamp_str.isdigit():
|
||||||
else:
|
timestamp = int(timestamp_str)
|
||||||
break
|
|
||||||
|
|
||||||
|
if start_timestamp <= timestamp <= end_timestamp :
|
||||||
|
if count % sampling_stepsize == 0:
|
||||||
|
filenames.append(name)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print(filenames)
|
print(filenames)
|
||||||
return filenames
|
return filenames
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print(f"No files found for prefix {common_prefix}")
|
print(f"No files found for prefix {common_prefix}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_nested_value(data, key_path):
|
def get_nested_value(data, key_path):
|
||||||
try:
|
try:
|
||||||
for key in key_path:
|
for key in key_path:
|
||||||
|
|
@ -151,7 +163,7 @@ def download_files(bucket_number, filenames_to_download, product_type):
|
||||||
print(f"Files with prefix '{filename}' downloaded successfully.")
|
print(f"Files with prefix '{filename}' downloaded successfully.")
|
||||||
decompress_file(os.path.join(output_directory, filename), output_directory)
|
decompress_file(os.path.join(output_directory, filename), output_directory)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# print(f"Error downloading files: {e}")
|
print(f"Error downloading files: {e}")
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
print(f"File '{filename}.json' already exists locally. Skipping download.")
|
print(f"File '{filename}.json' already exists locally. Skipping download.")
|
||||||
|
|
@ -187,7 +199,7 @@ def get_last_component(path):
|
||||||
path_without_slashes = path.replace('/', '')
|
path_without_slashes = path.replace('/', '')
|
||||||
return path_without_slashes
|
return path_without_slashes
|
||||||
|
|
||||||
def download_and_process_files(bucket_number, start_timestamp, end_timestamp, sampling_stepsize, keys, booleans_as_numbers, exact_match, product_type):
|
def download_and_process_files(bucket_number, start_timestamp, end_timestamp, sampling_stepsize, keys, booleans_as_numbers, product_type):
|
||||||
output_directory = f"S3cmdData_{bucket_number}"
|
output_directory = f"S3cmdData_{bucket_number}"
|
||||||
|
|
||||||
#if os.path.exists(output_directory):
|
#if os.path.exists(output_directory):
|
||||||
|
|
@ -200,7 +212,7 @@ def download_and_process_files(bucket_number, start_timestamp, end_timestamp, sa
|
||||||
filenames_to_check = list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize,product_type,bucket_number)
|
filenames_to_check = list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize,product_type,bucket_number)
|
||||||
existing_files = [filename for filename in filenames_to_check if os.path.exists(os.path.join(output_directory, f"{filename}.json"))]
|
existing_files = [filename for filename in filenames_to_check if os.path.exists(os.path.join(output_directory, f"{filename}.json"))]
|
||||||
files_to_download = set(filenames_to_check) - set(existing_files)
|
files_to_download = set(filenames_to_check) - set(existing_files)
|
||||||
print(files_to_download)
|
#print(files_to_download)
|
||||||
|
|
||||||
#if os.listdir(output_directory):
|
#if os.listdir(output_directory):
|
||||||
# print("Files already exist in the local folder. Skipping download.")
|
# print("Files already exist in the local folder. Skipping download.")
|
||||||
|
|
@ -231,9 +243,8 @@ def main():
|
||||||
parser.add_argument('end_timestamp', type=int, help='The end timestamp for the range (even number)')
|
parser.add_argument('end_timestamp', type=int, help='The end timestamp for the range (even number)')
|
||||||
parser.add_argument('--keys', type=parse_keys, required=True, help='The part to match from each CSV file, can be a single key or a comma-separated list of keys')
|
parser.add_argument('--keys', type=parse_keys, required=True, help='The part to match from each CSV file, can be a single key or a comma-separated list of keys')
|
||||||
parser.add_argument('--bucket-number', type=int, required=True, help='The number of the bucket to download from')
|
parser.add_argument('--bucket-number', type=int, required=True, help='The number of the bucket to download from')
|
||||||
parser.add_argument('--sampling_stepsize', type=int, required=False, default=1, help='The number of 2sec intervals, which define the length of the sampling interval in S3 file retrieval')
|
parser.add_argument('--sampling_stepsize', type=int, required=False, default=1, help='The number of 1 minute intervals, which define the length of the sampling interval in S3 file retrieval')
|
||||||
parser.add_argument('--booleans_as_numbers', action="store_true", required=False, help='If key used, then booleans are converted to numbers [0/1], if key not used, then booleans maintained as text [False/True]')
|
parser.add_argument('--booleans_as_numbers', action="store_true", required=False, help='If key used, then booleans are converted to numbers [0/1], if key not used, then booleans maintained as text [False/True]')
|
||||||
parser.add_argument('--exact_match', action="store_true", required=False, help='If key used, then key has to match exactly "=", else it is enough that key is found "in" text')
|
|
||||||
parser.add_argument('--product_name', required=True, help='Use Salimax, Salidomo or SodistoreMax')
|
parser.add_argument('--product_name', required=True, help='Use Salimax, Salidomo or SodistoreMax')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
@ -243,14 +254,13 @@ def main():
|
||||||
bucket_number = args.bucket_number
|
bucket_number = args.bucket_number
|
||||||
sampling_stepsize = args.sampling_stepsize
|
sampling_stepsize = args.sampling_stepsize
|
||||||
booleans_as_numbers = args.booleans_as_numbers
|
booleans_as_numbers = args.booleans_as_numbers
|
||||||
exact_match = args.exact_match
|
|
||||||
# new arg for product type
|
# new arg for product type
|
||||||
product_type = args.product_name
|
product_type = args.product_name
|
||||||
|
|
||||||
if start_timestamp >= end_timestamp:
|
if start_timestamp >= end_timestamp:
|
||||||
print("Error: start_timestamp must be smaller than end_timestamp.")
|
print("Error: start_timestamp must be smaller than end_timestamp.")
|
||||||
return
|
return
|
||||||
download_and_process_files(bucket_number, start_timestamp, end_timestamp, sampling_stepsize, keys, booleans_as_numbers, exact_match, product_type)
|
download_and_process_files(bucket_number, start_timestamp, end_timestamp, sampling_stepsize, keys, booleans_as_numbers, product_type)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,18 @@ public class Controller : ControllerBase
|
||||||
.Select(u => u.HidePassword())
|
.Select(u => u.HidePassword())
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet(nameof(GetInstallationsTheUserHasAccess))]
|
||||||
|
public ActionResult<IEnumerable<Object>> GetInstallationsTheUserHasAccess(Int64 userId, Token authToken)
|
||||||
|
{
|
||||||
|
var user = Db.GetUserById(userId);
|
||||||
|
if (user == null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return user.AccessibleInstallations().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
||||||
public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
|
public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
|
||||||
|
|
@ -926,10 +938,12 @@ public class Controller : ControllerBase
|
||||||
public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,Token authToken)
|
public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,Token authToken)
|
||||||
{
|
{
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
|
Console.WriteLine("CONFIG IS " + config.GetConfigurationString());
|
||||||
|
|
||||||
// Send configuration changes
|
// Send configuration changes
|
||||||
var success = await session.SendInstallationConfig(installationId, config);
|
var success = await session.SendInstallationConfig(installationId, config);
|
||||||
|
|
||||||
// Record configuration change
|
// Record configuration change
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
|
|
@ -940,6 +954,7 @@ public class Controller : ControllerBase
|
||||||
Timestamp = DateTime.Now,
|
Timestamp = DateTime.Now,
|
||||||
Description = config.GetConfigurationString()
|
Description = config.GetConfigurationString()
|
||||||
};
|
};
|
||||||
|
Console.WriteLine(action.Description);
|
||||||
|
|
||||||
var actionSuccess = await session.InsertUserAction(action);
|
var actionSuccess = await session.InsertUserAction(action);
|
||||||
return actionSuccess?Ok():Unauthorized();
|
return actionSuccess?Ok():Unauthorized();
|
||||||
|
|
@ -1020,7 +1035,7 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
Db.DeleteUserPassword(user);
|
Db.DeleteUserPassword(user);
|
||||||
|
|
||||||
return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
|
return Redirect($"https://monitor.inesco.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,8 @@ public static class ExoCmd
|
||||||
byte[] data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize<Configuration>(config));
|
byte[] data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize<Configuration>(config));
|
||||||
udpClient.Send(data, data.Length, installation.VpnIp, port);
|
udpClient.Send(data, data.Length, installation.VpnIp, port);
|
||||||
|
|
||||||
|
Console.WriteLine(config.GetConfigurationString());
|
||||||
|
|
||||||
//Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}: {config}");
|
//Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}: {config}");
|
||||||
Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}"+" GridSetPoint is "+config.GridSetPoint +" and MinimumSoC is "+config.MinimumSoC);
|
Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}"+" GridSetPoint is "+config.GridSetPoint +" and MinimumSoC is "+config.MinimumSoC);
|
||||||
|
|
||||||
|
|
@ -435,15 +437,6 @@ public static class ExoCmd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
//var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
|
|
||||||
//var url = s3Region.Bucket(installation.BucketName()).Path("config.json");
|
|
||||||
//return await url.PutObject(config);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,12 +7,9 @@ namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
||||||
|
|
||||||
public static class InstallationMethods
|
public static class InstallationMethods
|
||||||
{
|
{
|
||||||
private static readonly String BucketNameSalt =
|
private static readonly String BucketNameSalt = "3e5b3069-214a-43ee-8d85-57d72000c19d";
|
||||||
// Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == ""
|
|
||||||
// ? "stage" :"3e5b3069-214a-43ee-8d85-57d72000c19d";
|
|
||||||
"3e5b3069-214a-43ee-8d85-57d72000c19d";
|
|
||||||
|
|
||||||
private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e";
|
private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e";
|
||||||
|
private static readonly String SodioHomeBucketNameSalt = "e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa";
|
||||||
|
|
||||||
public static String BucketName(this Installation installation)
|
public static String BucketName(this Installation installation)
|
||||||
{
|
{
|
||||||
|
|
@ -20,6 +17,11 @@ public static class InstallationMethods
|
||||||
{
|
{
|
||||||
return $"{installation.S3BucketId}-{BucketNameSalt}";
|
return $"{installation.S3BucketId}-{BucketNameSalt}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (installation.Product == (int)ProductType.SodioHome)
|
||||||
|
{
|
||||||
|
return $"{installation.S3BucketId}-{SodioHomeBucketNameSalt}";
|
||||||
|
}
|
||||||
|
|
||||||
return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}";
|
return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ public static class SessionMethods
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installation.Product == (int)ProductType.SodiStoreMax)
|
if (installation.Product == (int)ProductType.SodiStoreMax || installation.Product == (int)ProductType.SodioHome)
|
||||||
{
|
{
|
||||||
return user is not null
|
return user is not null
|
||||||
&& user.UserType != 0
|
&& user.UserType != 0
|
||||||
|
|
@ -244,15 +244,15 @@ public static class SessionMethods
|
||||||
&& await installation.CreateBucket()
|
&& await installation.CreateBucket()
|
||||||
&& await installation.RenewS3Credentials();
|
&& await installation.RenewS3Credentials();
|
||||||
}
|
}
|
||||||
|
//
|
||||||
if (installation.Product == (int)ProductType.SodioHome)
|
// if (installation.Product == (int)ProductType.SodioHome)
|
||||||
{
|
// {
|
||||||
return user is not null
|
// return user is not null
|
||||||
&& user.UserType != 0
|
// && user.UserType != 0
|
||||||
&& user.HasAccessToParentOf(installation)
|
// && user.HasAccessToParentOf(installation)
|
||||||
&& Db.Create(installation);
|
// && Db.Create(installation);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,9 @@ public static class UserMethods
|
||||||
|
|
||||||
public static Task SendEmail(this User user, String subject, String body)
|
public static Task SendEmail(this User user, String subject, String body)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
Console.WriteLine(user.Name);
|
||||||
|
Console.WriteLine(subject);
|
||||||
return Mailer.Send(user.Name, user.Email, subject, body);
|
return Mailer.Send(user.Name, user.Email, subject, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,7 @@ public static partial class Db
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("return false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"SmtpServerUrl" : "mail.agenturserver.de",
|
"SmtpServerUrl" : "smtp.gmail.com",
|
||||||
"SmtpUsername" : "p518526p69",
|
"SmtpUsername" : "angelis@inesco.energy",
|
||||||
"SmtpPassword" : "i;b*xqm4iB5uhl",
|
"SmtpPassword" : "huvu pkqd kakz hqtm ",
|
||||||
"SmtpPort" : 587,
|
"SmtpPort" : 587,
|
||||||
"SenderName" : "InnovEnergy",
|
"SenderName" : "Inesco Energy",
|
||||||
"SenderAddress" : "noreply@innov.energy"
|
"SenderAddress" : "noreply@inesco.energy"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,8 @@ public static class Program
|
||||||
|
|
||||||
RabbitMqManager.InitializeEnvironment();
|
RabbitMqManager.InitializeEnvironment();
|
||||||
RabbitMqManager.StartRabbitMqConsumer().SupressAwaitWarning();
|
RabbitMqManager.StartRabbitMqConsumer().SupressAwaitWarning();
|
||||||
WebsocketManager.MonitorSalimaxInstallationTable().SupressAwaitWarning();
|
|
||||||
WebsocketManager.MonitorSalidomoInstallationTable().SupressAwaitWarning();
|
WebsocketManager.MonitorInstallationTable().SupressAwaitWarning();
|
||||||
WebsocketManager.MonitorSodistoreInstallationTable().SupressAwaitWarning();
|
|
||||||
|
|
||||||
|
|
||||||
// Task.Run(() => DeleteOldDataFromS3.DeleteOldData());
|
// Task.Run(() => DeleteOldDataFromS3.DeleteOldData());
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ public class Session : Relation<String, Int64>
|
||||||
AccessToSalidomo = user.AccessibleInstallations(product: (int)ProductType.Salidomo).ToList().Count > 0;
|
AccessToSalidomo = user.AccessibleInstallations(product: (int)ProductType.Salidomo).ToList().Count > 0;
|
||||||
AccessToSodistoreMax = user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count > 0;
|
AccessToSodistoreMax = user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count > 0;
|
||||||
AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count > 0;
|
AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count > 0;
|
||||||
|
|
||||||
|
Console.WriteLine("salimax" +user.AccessibleInstallations(product: (int)ProductType.Salimax).ToList().Count);
|
||||||
|
Console.WriteLine("AccessToSodistoreMax" +user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count);
|
||||||
|
Console.WriteLine("sodio" + user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String CreateToken()
|
private static String CreateToken()
|
||||||
|
|
|
||||||
|
|
@ -13,101 +13,41 @@ public static class WebsocketManager
|
||||||
{
|
{
|
||||||
public static Dictionary<Int64, InstallationInfo> InstallationConnections = new Dictionary<Int64, InstallationInfo>();
|
public static Dictionary<Int64, InstallationInfo> InstallationConnections = new Dictionary<Int64, InstallationInfo>();
|
||||||
|
|
||||||
//Every 1 minute, check the timestamp of the latest received message for every installation.
|
public static async Task MonitorInstallationTable()
|
||||||
//If the difference between the two timestamps is more than two minutes, we consider this Salimax installation unavailable.
|
|
||||||
public static async Task MonitorSalimaxInstallationTable()
|
|
||||||
{
|
{
|
||||||
while (true){
|
while (true)
|
||||||
lock (InstallationConnections){
|
{
|
||||||
// Console.WriteLine("MONITOR SALIMAX INSTALLATIONS\n");
|
lock (InstallationConnections)
|
||||||
foreach (var installationConnection in InstallationConnections){
|
{
|
||||||
|
Console.WriteLine("Monitoring installation table...");
|
||||||
if (installationConnection.Value.Product==(int)ProductType.Salimax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)){
|
foreach (var installationConnection in InstallationConnections)
|
||||||
|
{
|
||||||
// Console.WriteLine("Installation ID is "+installationConnection.Key);
|
Console.WriteLine("installationConnection ID is " + installationConnection.Key + "latest timestamp is" +installationConnection.Value.Timestamp + "product is "+ installationConnection.Value.Product
|
||||||
// Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
|
+ "and time diff is "+ (DateTime.Now - installationConnection.Value.Timestamp));
|
||||||
|
|
||||||
|
if ((installationConnection.Value.Product == (int)ProductType.Salimax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ||
|
||||||
|
(installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60)) ||
|
||||||
|
(installationConnection.Value.Product == (int)ProductType.SodioHome && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ||
|
||||||
|
(installationConnection.Value.Product == (int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
Console.WriteLine("Installation ID is " + installationConnection.Key);
|
||||||
|
Console.WriteLine("installationConnection.Value.Timestamp is " + installationConnection.Value.Timestamp);
|
||||||
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
|
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
|
||||||
|
|
||||||
installationConnection.Value.Status = (int)StatusType.Offline;
|
|
||||||
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.Salimax && f.Id == installationConnection.Key);
|
|
||||||
installation.Status = (int)StatusType.Offline;
|
|
||||||
installation.Apply(Db.Update);
|
|
||||||
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Console.WriteLine("FINISHED MONITORING SALIMAX INSTALLATIONS\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Every 1 minute, check the timestamp of the latest received message for every installation.
|
|
||||||
//If the difference between the two timestamps is more than 1 hour, we consider this Salidomo installation unavailable.
|
|
||||||
public static async Task MonitorSalidomoInstallationTable()
|
|
||||||
{
|
|
||||||
while (true){
|
|
||||||
//Console.WriteLine("TRY TO LOCK FOR MONITOR SALIDOMO INSTALLATIONS\n");
|
|
||||||
lock (InstallationConnections){
|
|
||||||
//Console.WriteLine("MONITOR SALIDOMO INSTALLATIONS\n");
|
|
||||||
foreach (var installationConnection in InstallationConnections)
|
|
||||||
{
|
|
||||||
//Console.WriteLine("Installation ID is "+installationConnection.Key);
|
|
||||||
// if (installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) < TimeSpan.FromMinutes(60)){
|
|
||||||
// Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (installationConnection.Value.Product==(int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60))
|
installationConnection.Value.Status = (int)StatusType.Offline;
|
||||||
{
|
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == installationConnection.Value.Product && f.Id == installationConnection.Key);
|
||||||
|
|
||||||
//Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
|
|
||||||
//Console.WriteLine("timestamp now is is "+(DateTime.Now));
|
|
||||||
|
|
||||||
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.Salidomo && f.Id == installationConnection.Key);
|
|
||||||
//Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
|
|
||||||
installation.Status = (int)StatusType.Offline;
|
installation.Status = (int)StatusType.Offline;
|
||||||
installation.Apply(Db.Update);
|
installation.Apply(Db.Update);
|
||||||
|
if (installationConnection.Value.Connections.Count > 0)
|
||||||
installationConnection.Value.Status = (int)StatusType.Offline;
|
{
|
||||||
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
|
InformWebsocketsForInstallation(installationConnection.Key);
|
||||||
//else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Console.WriteLine("FINISHED WITH UPDATING\n");
|
|
||||||
}
|
}
|
||||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Every 1 minute, check the timestamp of the latest received message for every installation.
|
|
||||||
//If the difference between the two timestamps is more than two minutes, we consider this Sodistore installation unavailable.
|
|
||||||
public static async Task MonitorSodistoreInstallationTable()
|
|
||||||
{
|
|
||||||
while (true){
|
|
||||||
//Console.WriteLine("TRY TO LOCK FOR MONITOR SODISTORE INSTALLATIONS\n");
|
|
||||||
lock (InstallationConnections){
|
|
||||||
//Console.WriteLine("MONITOR SODISTORE INSTALLATIONS\n");
|
|
||||||
foreach (var installationConnection in InstallationConnections)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (installationConnection.Value.Product==(int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2))
|
|
||||||
{
|
|
||||||
|
|
||||||
//Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
|
|
||||||
//Console.WriteLine("timestamp now is is "+(DateTime.Now));
|
|
||||||
|
|
||||||
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.SodiStoreMax && f.Id == installationConnection.Key);
|
|
||||||
//Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
|
|
||||||
installation.Status = (int)StatusType.Offline;
|
|
||||||
installation.Apply(Db.Update);
|
|
||||||
|
|
||||||
installationConnection.Value.Status = (int)StatusType.Offline;
|
|
||||||
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
|
|
||||||
//else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Console.WriteLine("FINISHED WITH UPDATING\n");
|
|
||||||
}
|
|
||||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +80,7 @@ public static class WebsocketManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static async Task HandleWebSocketConnection(WebSocket currentWebSocket)
|
public static async Task HandleWebSocketConnection(WebSocket currentWebSocket)
|
||||||
{
|
{
|
||||||
var buffer = new byte[4096];
|
var buffer = new byte[4096];
|
||||||
|
|
@ -155,6 +96,7 @@ public static class WebsocketManager
|
||||||
|
|
||||||
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||||
var installationIds = JsonSerializer.Deserialize<int[]>(message);
|
var installationIds = JsonSerializer.Deserialize<int[]>(message);
|
||||||
|
Console.WriteLine("Received Websocket message: " + message);
|
||||||
|
|
||||||
//This is a ping message to keep the connection alive, reply with a pong
|
//This is a ping message to keep the connection alive, reply with a pong
|
||||||
if (installationIds[0] == -1)
|
if (installationIds[0] == -1)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace DataCollectorWebApp;
|
||||||
|
|
||||||
|
public class LoginResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; }
|
||||||
|
public object User { get; set; } // or a User class if needed
|
||||||
|
public bool AccessToSalimax { get; set; }
|
||||||
|
public bool AccessToSalidomo { get; set; }
|
||||||
|
public bool AccessToSodiohome { get; set; }
|
||||||
|
public bool AccessToSodistoreMax { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Installation
|
||||||
|
{
|
||||||
|
//Each installation has 2 roles, a read role and a write role.
|
||||||
|
//There are 2 keys per role a public key and a secret
|
||||||
|
//Product can be 0 or 1, 0 for Salimax, 1 for Salidomo
|
||||||
|
public String Name { get; set; }
|
||||||
|
public String Location { get; set; }
|
||||||
|
public String Region { get; set; } = "";
|
||||||
|
public String Country { get; set; } = "";
|
||||||
|
public String VpnIp { get; set; } = "";
|
||||||
|
public String InstallationName { get; set; } = "";
|
||||||
|
|
||||||
|
public String S3Region { get; set; } = "sos-ch-dk-2";
|
||||||
|
public String S3Provider { get; set; } = "exo.io";
|
||||||
|
public String S3WriteKey { get; set; } = "";
|
||||||
|
public String S3Key { get; set; } = "";
|
||||||
|
public String S3WriteSecret { get; set; } = "";
|
||||||
|
public String S3Secret { get; set; } = "";
|
||||||
|
public int S3BucketId { get; set; } = 0;
|
||||||
|
public String ReadRoleId { get; set; } = "";
|
||||||
|
public String WriteRoleId { get; set; } = "";
|
||||||
|
public Boolean TestingMode { get; set; } = false;
|
||||||
|
public int Status { get; set; } = -1;
|
||||||
|
public int Product { get; set; } = 0;
|
||||||
|
public int Device { get; set; } = 0;
|
||||||
|
public string SerialNumber { get; set; } = "";
|
||||||
|
|
||||||
|
public String OrderNumbers { get; set; }
|
||||||
|
public String VrmLink { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[Controller]
|
||||||
|
public class InstallationsController : Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/Installations")]
|
||||||
|
[Produces("text/html")]
|
||||||
|
public async Task<IActionResult> Index()
|
||||||
|
{
|
||||||
|
const string HtmlHeader = @"
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Inesco Energy Installations Overview</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
margin: 2rem;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid rgb(200, 200, 200);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
thead th {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #EB9486;
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #0645AD;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-circle {
|
||||||
|
display: inline-block;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 50%;
|
||||||
|
}
|
||||||
|
.status-0 { background-color: #4CAF50; } /* Green */
|
||||||
|
.status-1 { background-color: #FF9800; } /* Orange */
|
||||||
|
.status-2 { background-color: #F44336; } /* Red */
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-symbol {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #F44336; /* Gray */
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.status-cell {
|
||||||
|
text-align: center; /* center horizontally */
|
||||||
|
vertical-align: middle; /* center vertically */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Installation Overview</h1>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class=""centered"">Name</th>
|
||||||
|
<th class=""centered"">Product</th>
|
||||||
|
<th class=""centered"">Location</th>
|
||||||
|
<th class=""centered"">VPN IP</th>
|
||||||
|
<th class=""centered"">Status</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
";
|
||||||
|
|
||||||
|
const string HtmlFooter = @"
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
string GetProductName(int productId)
|
||||||
|
{
|
||||||
|
return productId switch
|
||||||
|
{
|
||||||
|
0 => "Salimax",
|
||||||
|
1 => "Salidomo",
|
||||||
|
2 => "SodioHome",
|
||||||
|
3 => "SodistoreMax",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetStatusHtml(int status)
|
||||||
|
{
|
||||||
|
if (status == -1)
|
||||||
|
{
|
||||||
|
return "<span class='offline-symbol' title='Offline'>×</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var statusClass = $"status-{status}";
|
||||||
|
var title = status switch
|
||||||
|
{
|
||||||
|
0 => "Online",
|
||||||
|
1 => "Warning",
|
||||||
|
2 => "Error",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
return $"<span class='status-circle {statusClass}' title='{title}'></span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string BuildRowHtml(Installation i) => $@"
|
||||||
|
<tr>
|
||||||
|
<td>{i.Name}</td>
|
||||||
|
<td>{GetProductName(i.Product)}</td>
|
||||||
|
<td>{i.Location}</td>
|
||||||
|
<td>{i.VpnIp}</td>
|
||||||
|
<td>{GetStatusHtml(i.Status)}</td>
|
||||||
|
</tr>
|
||||||
|
";
|
||||||
|
|
||||||
|
var installations = await FetchInstallationsFromApi();
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(HtmlHeader);
|
||||||
|
|
||||||
|
foreach (var i in installations)
|
||||||
|
{
|
||||||
|
sb.Append(BuildRowHtml(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(HtmlFooter);
|
||||||
|
return Content(sb.ToString(), "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<List<Installation>?> FetchInstallationsFromApi()
|
||||||
|
{
|
||||||
|
|
||||||
|
var username = "baumgartner@innov.energy";
|
||||||
|
var password = "1234";
|
||||||
|
|
||||||
|
using var http = new HttpClient { BaseAddress = new Uri("https://monitor.inesco.energy/api/") };
|
||||||
|
|
||||||
|
// Step 1: Login
|
||||||
|
var loginResponse = await http.PostAsync($"Login?username={username}&password={password}", null);
|
||||||
|
if (!loginResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Login failed with status code {StatusCode}", loginResponse.StatusCode);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginData = await loginResponse.Content.ReadFromJsonAsync<LoginResponse>();
|
||||||
|
if (loginData?.Token is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Login succeeded but token was missing");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = loginData.Token;
|
||||||
|
Console.WriteLine($"Token: {token}");
|
||||||
|
var installations = new List<Installation>();
|
||||||
|
|
||||||
|
var getInstallationsRequestResponse = await http.GetAsync($"GetAllInstallationsFromProduct?product=0&authToken={token}");
|
||||||
|
|
||||||
|
var newInstallations= await getInstallationsRequestResponse.Content.ReadFromJsonAsync<List<Installation>>();
|
||||||
|
if (newInstallations != null)
|
||||||
|
{
|
||||||
|
installations.AddRange(newInstallations);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstallationsRequestResponse = await http.GetAsync($"GetAllInstallationsFromProduct?product=1&authToken={token}");
|
||||||
|
newInstallations= await getInstallationsRequestResponse.Content.ReadFromJsonAsync<List<Installation>>();
|
||||||
|
if (newInstallations != null)
|
||||||
|
{
|
||||||
|
installations.AddRange(newInstallations);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstallationsRequestResponse = await http.GetAsync($"GetAllInstallationsFromProduct?product=2&authToken={token}");
|
||||||
|
newInstallations= await getInstallationsRequestResponse.Content.ReadFromJsonAsync<List<Installation>>();
|
||||||
|
if (newInstallations != null)
|
||||||
|
{
|
||||||
|
installations.AddRange(newInstallations);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstallationsRequestResponse = await http.GetAsync($"GetAllInstallationsFromProduct?product=3&authToken={token}");
|
||||||
|
newInstallations= await getInstallationsRequestResponse.Content.ReadFromJsonAsync<List<Installation>>();
|
||||||
|
if (newInstallations != null)
|
||||||
|
{
|
||||||
|
installations.AddRange(newInstallations);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Console.WriteLine("Installations retrieved ",installations);
|
||||||
|
return installations;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.OpenApi" Version="1.6.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.DataCollectorWebApp;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Starting DataCollectorWebApp");
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
await app.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -103,6 +103,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrowattCommunication", "App
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WITGrowatt4-15K", "Lib\Devices\WITGrowatt4-15K\WITGrowatt4-15K.csproj", "{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WITGrowatt4-15K", "Lib\Devices\WITGrowatt4-15K\WITGrowatt4-15K.csproj", "{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataCollectorWebApp", "DataCollectorWebApp\DataCollectorWebApp.csproj", "{6069D487-DBAB-4253-BFA1-CF994B84BE49}"
|
||||||
|
EndProject
|
||||||
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
@ -274,6 +276,10 @@ Global
|
||||||
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}.Release|Any CPU.Build.0 = Release|Any CPU
|
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6069D487-DBAB-4253-BFA1-CF994B84BE49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6069D487-DBAB-4253-BFA1-CF994B84BE49}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6069D487-DBAB-4253-BFA1-CF994B84BE49}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6069D487-DBAB-4253-BFA1-CF994B84BE49}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
|
|
@ -321,5 +327,6 @@ Global
|
||||||
{39B83793-49DB-4940-9C25-A7F944607407} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
{39B83793-49DB-4940-9C25-A7F944607407} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
{DC0BE34A-368F-46DC-A081-70C9A1EFE9C0} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
{DC0BE34A-368F-46DC-A081-70C9A1EFE9C0} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
{44DD9E5E-2AD3-4579-A47D-7A40FD28D369} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||||
|
{6069D487-DBAB-4253-BFA1-CF994B84BE49} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -5191,7 +5191,7 @@
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"width": 0,
|
"width": 0,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"format": "<a href=\"https://monitor.innov.energy/salidomo_installations/list/\" target=\"_blank\" class=\"button\"> Battery Monitor </a>\n",
|
"format": "<a href=\"https://monitor.inesco.energy/salidomo_installations/list/\" target=\"_blank\" class=\"button\"> Battery Monitor </a>\n",
|
||||||
"storeOutMessages": true,
|
"storeOutMessages": true,
|
||||||
"fwdInMessages": true,
|
"fwdInMessages": true,
|
||||||
"resendOnRefresh": true,
|
"resendOnRefresh": true,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
npm run build && rsync -rv .* ubuntu@194.182.190.208:~/frontend/ && ssh ubuntu@194.182.190.208 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@194.182.190.208 'sudo npm install -g serve'
|
npm run build && rsync -rv .* ubuntu@194.182.190.208:~/frontend/ && ssh ubuntu@194.182.190.208 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.inesco.energy/html/' && ssh ubuntu@194.182.190.208 'sudo npm install -g serve'
|
||||||
|
|
||||||
|
|
||||||
#npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
#npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.inesco.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/stage.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/stage.inesco.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<link href="%PUBLIC_URL%/favicon.png" rel="shortcut icon"/>
|
<link href="%PUBLIC_URL%/Logo.svg" rel="shortcut icon"/>
|
||||||
<meta
|
<meta
|
||||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
name="viewport"
|
name="viewport"
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<title>InnovEnergy</title>
|
<title>Inesco Energy</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ function App() {
|
||||||
path={routes.sodiohome_installations + '*'}
|
path={routes.sodiohome_installations + '*'}
|
||||||
element={
|
element={
|
||||||
<AccessContextProvider>
|
<AccessContextProvider>
|
||||||
<SodioHomeInstallationTabs />
|
<SodioHomeInstallationTabs product={2} />
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,12 +1,12 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export const axiosConfigWithoutToken = axios.create({
|
export const axiosConfigWithoutToken = axios.create({
|
||||||
baseURL: 'https://monitor.innov.energy/api'
|
baseURL: 'https://monitor.inesco.energy/api'
|
||||||
//baseURL: 'http://127.0.0.1:7087/api'
|
//baseURL: 'http://127.0.0.1:7087/api'
|
||||||
});
|
});
|
||||||
|
|
||||||
const axiosConfig = axios.create({
|
const axiosConfig = axios.create({
|
||||||
baseURL: 'https://monitor.innov.energy/api'
|
baseURL: 'https://monitor.inesco.energy/api'
|
||||||
//baseURL: 'http://127.0.0.1:7087/api'
|
//baseURL: 'http://127.0.0.1:7087/api'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ function Footer() {
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
© 2024 - InnovEnergy AG
|
© 2025 - Inesco Energy AG
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
|
|
@ -29,11 +29,11 @@ function Footer() {
|
||||||
>
|
>
|
||||||
Crafted by{' '}
|
Crafted by{' '}
|
||||||
<Link
|
<Link
|
||||||
href="https://www.innov.energy/"
|
href="https://www.inesco.energy/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
InnovEnergy AG
|
Inesco Energy AG
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
|
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
|
@ -76,8 +77,8 @@ function ForgotPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inesco logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -100,7 +101,7 @@ function ForgotPassword() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -134,15 +135,15 @@ function ForgotPassword() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && <CircularProgress sx={{ color: '#ffc04d' }} />}
|
{loading && <CircularProgress sx={{ color: '#00b33c' }} />}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
mt: 3,
|
mt: 3,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -181,9 +182,9 @@ function ForgotPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setErrorModalOpen(false)}
|
onClick={() => setErrorModalOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
@ -221,9 +222,9 @@ function ForgotPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={handleReturn}
|
onClick={handleReturn}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
|
|
||||||
import axiosConfig from 'src/Resources/axiosConfig';
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
|
@ -73,8 +74,8 @@ function ResetPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inesco logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -97,7 +98,7 @@ function ResetPassword() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -137,7 +138,7 @@ function ResetPassword() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '170px' }} />
|
<CircularProgress sx={{ color: '#00b33c', marginLeft: '170px' }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{password != verifypassword && (
|
{password != verifypassword && (
|
||||||
|
|
@ -155,8 +156,8 @@ function ResetPassword() {
|
||||||
mt: 3,
|
mt: 3,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -195,9 +196,9 @@ function ResetPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
|
|
||||||
import axiosConfig from 'src/Resources/axiosConfig';
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
|
@ -74,8 +75,8 @@ function SetNewPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inesco logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -98,7 +99,7 @@ function SetNewPassword() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -138,7 +139,7 @@ function SetNewPassword() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '0px' }} />
|
<CircularProgress sx={{ color: '#00b33c', marginLeft: '0px' }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{password != verifypassword && (
|
{password != verifypassword && (
|
||||||
|
|
@ -156,8 +157,8 @@ function SetNewPassword() {
|
||||||
mt: 3,
|
mt: 3,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -196,9 +197,9 @@ function SetNewPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
import { axiosConfigWithoutToken } from 'src/Resources/axiosConfig';
|
import { axiosConfigWithoutToken } from 'src/Resources/axiosConfig';
|
||||||
import Cookies from 'universal-cookie';
|
import Cookies from 'universal-cookie';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
|
@ -93,6 +93,8 @@ function Login() {
|
||||||
navigate(routes.installations);
|
navigate(routes.installations);
|
||||||
} else if (response.data.accessToSalidomo) {
|
} else if (response.data.accessToSalidomo) {
|
||||||
navigate(routes.salidomo_installations);
|
navigate(routes.salidomo_installations);
|
||||||
|
} else if (response.data.accessToSodistoreMax) {
|
||||||
|
navigate(routes.sodistore_installations);
|
||||||
} else {
|
} else {
|
||||||
navigate(routes.sodiohome_installations);
|
navigate(routes.sodiohome_installations);
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +115,8 @@ function Login() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }} className="login">
|
<Container maxWidth="xl" sx={{ pt: 2 }} className="login">
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inescologo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -137,7 +139,7 @@ function Login() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -193,7 +195,7 @@ function Login() {
|
||||||
checked={rememberMe}
|
checked={rememberMe}
|
||||||
onChange={handleRememberMeChange}
|
onChange={handleRememberMeChange}
|
||||||
icon={<CheckBoxOutlineBlankIcon style={{ color: 'grey' }} />}
|
icon={<CheckBoxOutlineBlankIcon style={{ color: 'grey' }} />}
|
||||||
checkedIcon={<CheckBoxIcon style={{ color: '#ffc04d' }} />}
|
checkedIcon={<CheckBoxIcon style={{ color: '#00b33c' }} />}
|
||||||
style={{ marginLeft: -175 }}
|
style={{ marginLeft: -175 }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
@ -204,8 +206,8 @@ function Login() {
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -218,7 +220,7 @@ function Login() {
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
sx={{
|
sx={{
|
||||||
color: '#ffc04d',
|
color: '#009933',
|
||||||
marginLeft: '20px'
|
marginLeft: '20px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -253,9 +255,9 @@ function Login() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
|
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
|
||||||
const sortedBatteryView =
|
const sortedBatteryView =
|
||||||
props.values != null && props.values?.Battery?.Devices
|
props.values != null && props.values?.Battery?.Devices
|
||||||
? Object.entries(props.values.Battery.Devices)
|
? Object.entries(props.values.Battery.Devices)
|
||||||
|
|
@ -58,8 +60,6 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
navigate(routes.mainstats);
|
navigate(routes.mainstats);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { product, setProduct } = useContext(ProductIdContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sortedBatteryView.length == 0) {
|
if (sortedBatteryView.length == 0) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -232,7 +232,7 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
<TableCell align="center">Battery</TableCell>
|
<TableCell align="center">Battery</TableCell>
|
||||||
<TableCell align="center">Firmware</TableCell>
|
<TableCell align="center">Firmware</TableCell>
|
||||||
<TableCell align="center">Power</TableCell>
|
<TableCell align="center">Power</TableCell>
|
||||||
<TableCell align="center">Voltage</TableCell>
|
<TableCell align="center">Battery Voltage</TableCell>
|
||||||
<TableCell align="center">SoC</TableCell>
|
<TableCell align="center">SoC</TableCell>
|
||||||
<TableCell align="center">Temperature</TableCell>
|
<TableCell align="center">Temperature</TableCell>
|
||||||
{product === 0 ? (
|
{product === 0 ? (
|
||||||
|
|
@ -293,7 +293,7 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
sx={{
|
sx={{
|
||||||
width: '10%',
|
width: '14%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
import { JSONRecordData } from '../Log/graph.util';
|
||||||
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
interface BatteryViewSodioHomeProps {
|
||||||
|
values: JSONRecordData;
|
||||||
|
s3Credentials: I_S3Credentials;
|
||||||
|
installationId: number;
|
||||||
|
connected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
|
if (props.values === null && props.connected == true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLocation = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const sortedBatteryView =
|
||||||
|
props.values != null &&
|
||||||
|
props.values?.AcDcGrowatt?.BatteriesRecords?.Batteries
|
||||||
|
? Object.entries(props.values.AcDcGrowatt.BatteriesRecords.Batteries)
|
||||||
|
.map(([BatteryId, battery]) => {
|
||||||
|
return { BatteryId, battery }; // Here we return an object with the id and device
|
||||||
|
})
|
||||||
|
.sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
console.log('battery view', sortedBatteryView);
|
||||||
|
const [loading, setLoading] = useState(sortedBatteryView.length == 0);
|
||||||
|
const handleMainStatsButton = () => {
|
||||||
|
navigate(routes.mainstats);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sortedBatteryView.length == 0) {
|
||||||
|
setLoading(true);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [sortedBatteryView]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!props.connected && (
|
||||||
|
<Container
|
||||||
|
maxWidth="xl"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '70vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{ color: 'black', fontWeight: 'bold' }}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
Unable to communicate with the installation
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" style={{ color: 'black' }}>
|
||||||
|
Please wait or refresh the page
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
{loading && props.connected && (
|
||||||
|
<Container
|
||||||
|
maxWidth="xl"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '70vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{ color: 'black', fontWeight: 'bold' }}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
Battery service is not available at the moment
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" style={{ color: 'black' }}>
|
||||||
|
Please wait or refresh the page
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && props.connected && (
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid container>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={6}
|
||||||
|
md={6}
|
||||||
|
sx={{
|
||||||
|
display:
|
||||||
|
!currentLocation.pathname.includes('detailed_view') &&
|
||||||
|
!currentLocation.pathname.includes('mainstats')
|
||||||
|
? 'block'
|
||||||
|
: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: '#808080',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="battery_view"
|
||||||
|
defaultMessage="Battery View"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleMainStatsButton}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '20px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/*<Grid container>*/}
|
||||||
|
{/* <Routes>*/}
|
||||||
|
{/* <Route*/}
|
||||||
|
{/* path={routes.mainstats + '*'}*/}
|
||||||
|
{/* element={*/}
|
||||||
|
{/* <MainStats*/}
|
||||||
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
{/* id={props.installationId}*/}
|
||||||
|
{/* ></MainStats>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* {product === 0*/}
|
||||||
|
{/* ? Object.entries(props.values.Battery.Devices).map(*/}
|
||||||
|
{/* ([BatteryId, battery]) => (*/}
|
||||||
|
{/* <Route*/}
|
||||||
|
{/* key={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* path={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* element={*/}
|
||||||
|
{/* <DetailedBatteryView*/}
|
||||||
|
{/* batteryId={Number(BatteryId)}*/}
|
||||||
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
{/* batteryData={battery}*/}
|
||||||
|
{/* installationId={props.installationId}*/}
|
||||||
|
{/* productNum={product}*/}
|
||||||
|
{/* ></DetailedBatteryView>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* : Object.entries(props.values.Battery.Devices).map(*/}
|
||||||
|
{/* ([BatteryId, battery]) => (*/}
|
||||||
|
{/* <Route*/}
|
||||||
|
{/* key={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* path={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* element={*/}
|
||||||
|
{/* <DetailedBatteryViewSodistore*/}
|
||||||
|
{/* batteryId={Number(BatteryId)}*/}
|
||||||
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
{/* batteryData={battery}*/}
|
||||||
|
{/* installationId={props.installationId}*/}
|
||||||
|
{/* productNum={product}*/}
|
||||||
|
{/* ></DetailedBatteryViewSodistore>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* )}*/}
|
||||||
|
{/* </Routes>*/}
|
||||||
|
{/*</Grid>*/}
|
||||||
|
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
display:
|
||||||
|
!currentLocation.pathname.includes('detailed_view') &&
|
||||||
|
!currentLocation.pathname.includes('mainstats')
|
||||||
|
? 'block'
|
||||||
|
: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center">Battery</TableCell>
|
||||||
|
<TableCell align="center">Power</TableCell>
|
||||||
|
<TableCell align="center">Battery Voltage</TableCell>
|
||||||
|
<TableCell align="center">Current</TableCell>
|
||||||
|
<TableCell align="center">SoC</TableCell>
|
||||||
|
<TableCell align="center">SoH</TableCell>
|
||||||
|
<TableCell align="center">Daily Charge Energy</TableCell>
|
||||||
|
<TableCell align="center">Daily Discharge Energy</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{sortedBatteryView.map(({ BatteryId, battery }) => (
|
||||||
|
<TableRow
|
||||||
|
key={BatteryId}
|
||||||
|
style={{
|
||||||
|
height: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
component="th"
|
||||||
|
scope="row"
|
||||||
|
align="center"
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
style={{ color: 'black' }}
|
||||||
|
to={routes.detailed_view + BatteryId}
|
||||||
|
>
|
||||||
|
{'Battery Cluster ' + BatteryId}
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '13%',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Power + ' ' + 'W'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '13%',
|
||||||
|
textAlign: 'center',
|
||||||
|
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'black'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Voltage + ' ' + 'V'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '13%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Current + ' A'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '8%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Soc + ' %'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '8%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Soh + ' %'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '15%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.DailyChargeEnergy + ' Wh'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '15%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.DailyDischargeEnergy + ' Wh'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BatteryViewSodioHome;
|
||||||
|
|
@ -169,7 +169,7 @@ function DetailedBatteryViewSodistore(
|
||||||
align="left"
|
align="left"
|
||||||
sx={{ fontWeight: 'bold' }}
|
sx={{ fontWeight: 'bold' }}
|
||||||
>
|
>
|
||||||
Total Battery Voltage
|
Bus Voltage
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
align="right"
|
align="right"
|
||||||
|
|
@ -183,6 +183,29 @@ function DetailedBatteryViewSodistore(
|
||||||
' V'}
|
' V'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
component="th"
|
||||||
|
scope="row"
|
||||||
|
align="left"
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Battery Voltage
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="right"
|
||||||
|
sx={{
|
||||||
|
width: '6ch',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
paddingRight: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.batteryData.BatteryDeligreenDataRecord
|
||||||
|
.TotalBatteryVoltage + ' V'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
component="th"
|
component="th"
|
||||||
|
|
@ -349,25 +372,6 @@ function DetailedBatteryViewSodistore(
|
||||||
{props.batteryData.BatteryDeligreenDataRecord.Soh + ' %'}
|
{props.batteryData.BatteryDeligreenDataRecord.Soh + ' %'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
component="th"
|
|
||||||
scope="row"
|
|
||||||
align="left"
|
|
||||||
sx={{ fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Port Voltage
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
align="right"
|
|
||||||
sx={{
|
|
||||||
width: '6ch',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
paddingRight: '12px'
|
|
||||||
}}
|
|
||||||
></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,13 @@ import {
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
|
|
@ -29,6 +32,7 @@ import { UserContext } from '../../../contexts/userContext';
|
||||||
|
|
||||||
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
||||||
import { TimePicker } from '@mui/lab';
|
import { TimePicker } from '@mui/lab';
|
||||||
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
|
|
||||||
interface ConfigurationProps {
|
interface ConfigurationProps {
|
||||||
values: JSONRecordData;
|
values: JSONRecordData;
|
||||||
|
|
@ -40,6 +44,9 @@ function Configuration(props: ConfigurationProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//console.log(props.values.Config);
|
||||||
|
const [activeTab, setActiveTab] = useState<'charge' | 'discharge'>('charge');
|
||||||
|
|
||||||
const CalibrationChargeOptions = [
|
const CalibrationChargeOptions = [
|
||||||
'Repetitive Calibration',
|
'Repetitive Calibration',
|
||||||
'Additional Calibration',
|
'Additional Calibration',
|
||||||
|
|
@ -84,11 +91,12 @@ function Configuration(props: ConfigurationProps) {
|
||||||
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
|
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser, setUser } = context;
|
||||||
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
|
||||||
const [formValues, setFormValues] = useState<ConfigurationValues>({
|
const [formValues, setFormValues] = useState<ConfigurationValues>({
|
||||||
minimumSoC: props.values.Config.MinSoc,
|
minimumSoC: props.values.Config.MinSoc,
|
||||||
gridSetPoint: (props.values.Config.GridSetPoint as number) / 1000,
|
gridSetPoint: (props.values.Config.GridSetPoint as number) / 1000,
|
||||||
CalibrationChargeState: CalibrationChargeOptionsController.indexOf(
|
calibrationChargeState: CalibrationChargeOptionsController.indexOf(
|
||||||
props.values.Config.ForceCalibrationChargeState.toString()
|
props.values.Config.ForceCalibrationChargeState.toString()
|
||||||
),
|
),
|
||||||
calibrationChargeDate:
|
calibrationChargeDate:
|
||||||
|
|
@ -100,9 +108,27 @@ function Configuration(props: ConfigurationProps) {
|
||||||
.toDate()
|
.toDate()
|
||||||
: dayjs(props.values.Config.DayAndTimeForAdditionalCalibration)
|
: dayjs(props.values.Config.DayAndTimeForAdditionalCalibration)
|
||||||
// .add(localOffset, 'minute')
|
// .add(localOffset, 'minute')
|
||||||
.toDate()
|
.toDate(),
|
||||||
|
|
||||||
|
...(product === 3 && {
|
||||||
|
calibrationDischargeState: CalibrationChargeOptionsController.indexOf(
|
||||||
|
props.values.Config.ForceCalibrationDischargeState.toString()
|
||||||
|
),
|
||||||
|
calibrationDischargeDate:
|
||||||
|
CalibrationChargeOptionsController.indexOf(
|
||||||
|
props.values.Config.ForceCalibrationDischargeState.toString()
|
||||||
|
) == 0
|
||||||
|
? dayjs(
|
||||||
|
props.values.Config.DownDayAndTimeForRepetitiveCalibration
|
||||||
|
).toDate()
|
||||||
|
: dayjs(
|
||||||
|
props.values.Config.DownDayAndTimeForAdditionalCalibration
|
||||||
|
).toDate()
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// console.log(formValues);
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
if (
|
if (
|
||||||
CalibrationChargeOptionsController.indexOf(
|
CalibrationChargeOptionsController.indexOf(
|
||||||
|
|
@ -116,7 +142,7 @@ function Configuration(props: ConfigurationProps) {
|
||||||
setErrorDateModalOpen(true);
|
setErrorDateModalOpen(true);
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
formValues.CalibrationChargeState === 1 &&
|
formValues.calibrationChargeState === 1 &&
|
||||||
dayjs(formValues.calibrationChargeDate).isBefore(dayjs())
|
dayjs(formValues.calibrationChargeDate).isBefore(dayjs())
|
||||||
) {
|
) {
|
||||||
//console.log('asked for', dayjs(formValues.calibrationChargeDate));
|
//console.log('asked for', dayjs(formValues.calibrationChargeDate));
|
||||||
|
|
@ -128,13 +154,17 @@ function Configuration(props: ConfigurationProps) {
|
||||||
const configurationToSend: ConfigurationValues = {
|
const configurationToSend: ConfigurationValues = {
|
||||||
minimumSoC: formValues.minimumSoC,
|
minimumSoC: formValues.minimumSoC,
|
||||||
gridSetPoint: formValues.gridSetPoint,
|
gridSetPoint: formValues.gridSetPoint,
|
||||||
CalibrationChargeState: formValues.CalibrationChargeState,
|
calibrationChargeState: formValues.calibrationChargeState,
|
||||||
calibrationChargeDate: dayjs
|
calibrationChargeDate: dayjs
|
||||||
.utc(formValues.calibrationChargeDate)
|
.utc(formValues.calibrationChargeDate)
|
||||||
.add(localOffset, 'minute')
|
.add(localOffset, 'minute')
|
||||||
|
.toDate(),
|
||||||
|
calibrationDischargeState: formValues.calibrationDischargeState,
|
||||||
|
calibrationDischargeDate: dayjs
|
||||||
|
.utc(formValues.calibrationDischargeDate)
|
||||||
|
.add(localOffset, 'minute')
|
||||||
.toDate()
|
.toDate()
|
||||||
};
|
};
|
||||||
// console.log('will send ', dayjs(formValues.calibrationChargeDate));
|
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await axiosConfig
|
const res = await axiosConfig
|
||||||
|
|
@ -169,6 +199,15 @@ function Configuration(props: ConfigurationProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfirmDischarge = (newDate) => {
|
||||||
|
//console.log('non adapted day is ', newDate);
|
||||||
|
//console.log('adapted day is ', dayjs.utc(newDate).toDate());
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
['calibrationDischargeDate']: dayjs(newDate).toDate()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectedCalibrationChargeDay = (event) => {
|
const handleSelectedCalibrationChargeDay = (event) => {
|
||||||
const selectedDay = daysInWeek.indexOf(event.target.value);
|
const selectedDay = daysInWeek.indexOf(event.target.value);
|
||||||
const currentDate = dayjs();
|
const currentDate = dayjs();
|
||||||
|
|
@ -184,10 +223,25 @@ function Configuration(props: ConfigurationProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectedCalibrationDisChargeDay = (event) => {
|
||||||
|
const selectedDay = daysInWeek.indexOf(event.target.value);
|
||||||
|
const currentDate = dayjs();
|
||||||
|
let difference = selectedDay - currentDate.day();
|
||||||
|
if (difference < 0) {
|
||||||
|
difference += 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjustedDate = currentDate.add(difference, 'day');
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
['calibrationDischargeDate']: adjustedDate.toDate()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectedCalibrationChargeChange = (event) => {
|
const handleSelectedCalibrationChargeChange = (event) => {
|
||||||
setFormValues({
|
setFormValues({
|
||||||
...formValues,
|
...formValues,
|
||||||
['CalibrationChargeState']: CalibrationChargeOptions.indexOf(
|
['calibrationChargeState']: CalibrationChargeOptions.indexOf(
|
||||||
event.target.value
|
event.target.value
|
||||||
),
|
),
|
||||||
['calibrationChargeDate']:
|
['calibrationChargeDate']:
|
||||||
|
|
@ -201,6 +255,23 @@ function Configuration(props: ConfigurationProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectedCalibrationDisChargeChange = (event) => {
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
['calibrationDischargeState']: CalibrationChargeOptions.indexOf(
|
||||||
|
event.target.value
|
||||||
|
),
|
||||||
|
['calibrationDischargeDate']:
|
||||||
|
CalibrationChargeOptions.indexOf(event.target.value) == 0
|
||||||
|
? dayjs(props.values.Config.DownDayAndTimeForRepetitiveCalibration)
|
||||||
|
// .add(localOffset, 'minute')
|
||||||
|
.toDate()
|
||||||
|
: dayjs(props.values.Config.DownDayAndTimeForAdditionalCalibration)
|
||||||
|
// .add(localOffset, 'minute')
|
||||||
|
.toDate()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
|
@ -284,7 +355,84 @@ function Configuration(props: ConfigurationProps) {
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
mt: 2,
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(e, newValue) => setActiveTab(newValue)}
|
||||||
|
textColor="inherit"
|
||||||
|
TabIndicatorProps={{ style: { display: 'none' } }} // hide default underline
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#f5f5f5',
|
||||||
|
borderRadius: 2,
|
||||||
|
p: 0.5,
|
||||||
|
boxShadow: 1,
|
||||||
|
width: 500,
|
||||||
|
height: 47
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
value="charge"
|
||||||
|
label="Calibration Charge"
|
||||||
|
sx={(theme) => ({
|
||||||
|
flex: 2,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
color:
|
||||||
|
activeTab === 'charge'
|
||||||
|
? 'white'
|
||||||
|
: theme.palette.text.primary,
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'charge'
|
||||||
|
? theme.palette.primary.main
|
||||||
|
: 'transparent',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'charge'
|
||||||
|
? theme.palette.primary.dark
|
||||||
|
: '#eee'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{product === 3 && (
|
||||||
|
<Tab
|
||||||
|
value="discharge"
|
||||||
|
label="Calibration Discharge"
|
||||||
|
sx={(theme) => ({
|
||||||
|
flex: 2,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
color:
|
||||||
|
activeTab === 'discharge'
|
||||||
|
? 'white'
|
||||||
|
: theme.palette.text.primary,
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'discharge'
|
||||||
|
? theme.palette.primary.main
|
||||||
|
: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'discharge'
|
||||||
|
? theme.palette.primary.dark
|
||||||
|
: '#eee'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
|
|
@ -294,98 +442,34 @@ function Configuration(props: ConfigurationProps) {
|
||||||
noValidate
|
noValidate
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{activeTab === 'charge' && (
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="minimum_soc "
|
|
||||||
defaultMessage="Minimum SoC (%)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="minimumSoC"
|
|
||||||
value={formValues.minimumSoC}
|
|
||||||
onChange={handleChange}
|
|
||||||
helperText={
|
|
||||||
errors.minimumSoC ? (
|
|
||||||
<span style={{ color: 'red' }}>
|
|
||||||
Value should be between 0-100%
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
|
||||||
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
|
||||||
<InputLabel
|
|
||||||
sx={{
|
|
||||||
fontSize: 14,
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="forced_calibration_charge"
|
|
||||||
defaultMessage="Calibration Charge State"
|
|
||||||
/>
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
value={
|
|
||||||
CalibrationChargeOptions[
|
|
||||||
formValues.CalibrationChargeState
|
|
||||||
]
|
|
||||||
}
|
|
||||||
onChange={handleSelectedCalibrationChargeChange}
|
|
||||||
>
|
|
||||||
{CalibrationChargeOptions.map((option) => (
|
|
||||||
<MenuItem key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
{formValues.CalibrationChargeState == 1 && (
|
|
||||||
<div>
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
||||||
<DateTimePicker
|
|
||||||
label="Select Next Calibration Charge Date"
|
|
||||||
value={dayjs(formValues.calibrationChargeDate)}
|
|
||||||
onChange={handleConfirm}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
sx={{
|
|
||||||
marginTop: 2, // Apply styles here
|
|
||||||
width: '100%' // Optional: You can adjust the width or other styling here
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*<DateTimePicker*/}
|
|
||||||
{/* format="DD/MM/YYYY HH:mm"*/}
|
|
||||||
{/* ampm={false}*/}
|
|
||||||
{/* label="Select Next Calibration Charge Date"*/}
|
|
||||||
{/* value={dayjs(formValues.calibrationChargeDate)}*/}
|
|
||||||
{/* onChange={handleConfirm}*/}
|
|
||||||
{/* sx={{*/}
|
|
||||||
{/* marginTop: 2*/}
|
|
||||||
{/* }} // This should work with the correct imports*/}
|
|
||||||
{/*/>*/}
|
|
||||||
</LocalizationProvider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{formValues.CalibrationChargeState == 0 && (
|
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: '5px' }}>
|
<div style={{ marginBottom: '5px' }}>
|
||||||
<FormControl
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="minimum_soc "
|
||||||
|
defaultMessage="Minimum SoC (%)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="minimumSoC"
|
||||||
|
value={formValues.minimumSoC}
|
||||||
|
onChange={handleChange}
|
||||||
|
helperText={
|
||||||
|
errors.minimumSoC ? (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
Value should be between 0-100%
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ marginLeft: 1, width: 390, marginTop: 2 }}
|
/>
|
||||||
>
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -393,106 +477,283 @@ function Configuration(props: ConfigurationProps) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="calibration_charge_day"
|
id="forced_calibration_charge"
|
||||||
defaultMessage="Calibration Charge Day"
|
defaultMessage="Calibration Charge State"
|
||||||
/>
|
/>
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={
|
value={
|
||||||
daysInWeek[formValues.calibrationChargeDate.getDay()]
|
CalibrationChargeOptions[
|
||||||
|
formValues.calibrationChargeState
|
||||||
|
]
|
||||||
}
|
}
|
||||||
onChange={handleSelectedCalibrationChargeDay}
|
onChange={handleSelectedCalibrationChargeChange}
|
||||||
>
|
>
|
||||||
{daysInWeek.map((day) => (
|
{CalibrationChargeOptions.map((option) => (
|
||||||
<MenuItem key={day} value={day}>
|
<MenuItem key={option} value={option}>
|
||||||
{day}
|
{option}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
{formValues.calibrationChargeState == 1 && (
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<div>
|
||||||
<TimePicker
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
ampm={false}
|
<DateTimePicker
|
||||||
label="Calibration Charge Hour"
|
label="Select Next Calibration Charge Date"
|
||||||
value={dayjs(formValues.calibrationChargeDate)}
|
value={dayjs(formValues.calibrationChargeDate)}
|
||||||
onChange={(newTime) => handleConfirm(dayjs(newTime))}
|
onChange={handleConfirm}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2, // Apply styles here
|
||||||
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formValues.calibrationChargeState == 0 && (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
sx={{ marginLeft: 1, width: 390, marginTop: 2 }}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2, // Apply styles here
|
fontSize: 14,
|
||||||
width: '100%' // Optional: You can adjust the width or other styling here
|
backgroundColor: 'white'
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="calibration_charge_day"
|
||||||
|
defaultMessage="Calibration Charge Day"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
daysInWeek[
|
||||||
|
formValues.calibrationChargeDate.getDay()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={handleSelectedCalibrationChargeDay}
|
||||||
|
>
|
||||||
|
{daysInWeek.map((day) => (
|
||||||
|
<MenuItem key={day} value={day}>
|
||||||
|
{day}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
<TimePicker
|
||||||
|
ampm={false}
|
||||||
|
label="Calibration Charge Hour"
|
||||||
|
value={dayjs(formValues.calibrationChargeDate)}
|
||||||
|
onChange={(newTime) =>
|
||||||
|
handleConfirm(dayjs(newTime))
|
||||||
|
}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2, // Apply styles here
|
||||||
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
</LocalizationProvider>
|
||||||
/>
|
</div>
|
||||||
</LocalizationProvider>
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="grid_set_point"
|
||||||
|
defaultMessage="Grid Set Point (kW)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="gridSetPoint"
|
||||||
|
value={formValues.gridSetPoint}
|
||||||
|
onChange={handleChange}
|
||||||
|
helperText={
|
||||||
|
errors.gridSetPoint ? (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
Please provide a valid number
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Installed_Power_DC1010"
|
||||||
|
defaultMessage="Installed Power DC1010 (kW)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
(props.values.DcDc.SystemControl
|
||||||
|
.NumberOfConnectedSlaves as number) * 10
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{props.values.Config.MaxBatteryDischargingCurrent && (
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Maximum_Discharge_Power"
|
||||||
|
defaultMessage="Maximum Discharge Power (W)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
(props.values.Config
|
||||||
|
.MaxBatteryDischargingCurrent as number) *
|
||||||
|
48 *
|
||||||
|
(props.values.DcDc.SystemControl
|
||||||
|
.NumberOfConnectedSlaves as number)
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{product == 3 && activeTab === 'discharge' && (
|
||||||
<TextField
|
<>
|
||||||
label={
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
<FormattedMessage
|
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
||||||
id="grid_set_point"
|
<InputLabel
|
||||||
defaultMessage="Grid Set Point (kW)"
|
sx={{
|
||||||
/>
|
fontSize: 14,
|
||||||
}
|
backgroundColor: 'white'
|
||||||
name="gridSetPoint"
|
}}
|
||||||
value={formValues.gridSetPoint}
|
>
|
||||||
onChange={handleChange}
|
<FormattedMessage
|
||||||
helperText={
|
id="calibration_discharge_state"
|
||||||
errors.gridSetPoint ? (
|
defaultMessage="Calibration Discharge State"
|
||||||
<span style={{ color: 'red' }}>
|
/>
|
||||||
Please provide a valid number
|
</InputLabel>
|
||||||
</span>
|
<Select
|
||||||
) : (
|
value={
|
||||||
''
|
CalibrationChargeOptions[
|
||||||
)
|
formValues.calibrationDischargeState
|
||||||
}
|
]
|
||||||
fullWidth
|
}
|
||||||
/>
|
onChange={handleSelectedCalibrationDisChargeChange}
|
||||||
</div>
|
>
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{CalibrationChargeOptions.map((option) => (
|
||||||
<TextField
|
<MenuItem key={option} value={option}>
|
||||||
label={
|
{option}
|
||||||
<FormattedMessage
|
</MenuItem>
|
||||||
id="Installed_Power_DC1010"
|
))}
|
||||||
defaultMessage="Installed Power DC1010 (kW)"
|
</Select>
|
||||||
/>
|
</FormControl>
|
||||||
}
|
</div>
|
||||||
value={
|
|
||||||
(props.values.DcDc.SystemControl
|
|
||||||
.NumberOfConnectedSlaves as number) * 10
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{props.values.Config.MaxBatteryDischargingCurrent && (
|
{formValues.calibrationDischargeState == 1 && (
|
||||||
<div style={{ marginBottom: '5px' }}>
|
<div>
|
||||||
<TextField
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
label={
|
<DateTimePicker
|
||||||
<FormattedMessage
|
label="Select Next Calibration Discharge Date"
|
||||||
id="Maximum_Discharge_Power"
|
value={dayjs(formValues.calibrationDischargeDate)}
|
||||||
defaultMessage="Maximum Discharge Power (W)"
|
onChange={handleConfirmDischarge}
|
||||||
/>
|
renderInput={(params) => (
|
||||||
}
|
<TextField
|
||||||
value={
|
{...params}
|
||||||
(props.values.Config
|
sx={{
|
||||||
.MaxBatteryDischargingCurrent as number) *
|
marginTop: 2, // Apply styles here
|
||||||
48 *
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
(props.values.DcDc.SystemControl
|
}}
|
||||||
.NumberOfConnectedSlaves as number)
|
/>
|
||||||
}
|
)}
|
||||||
fullWidth
|
/>
|
||||||
/>
|
</LocalizationProvider>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formValues.calibrationDischargeState == 0 && (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
sx={{ marginLeft: 1, width: 390, marginTop: 2 }}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="calibration_charge_day"
|
||||||
|
defaultMessage="Calibration Discharge Day"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
daysInWeek[
|
||||||
|
formValues.calibrationDischargeDate.getDay()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={handleSelectedCalibrationDisChargeDay}
|
||||||
|
>
|
||||||
|
{daysInWeek.map((day) => (
|
||||||
|
<MenuItem key={day} value={day}>
|
||||||
|
{day}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
<TimePicker
|
||||||
|
ampm={false}
|
||||||
|
label="Calibration Discharge Hour"
|
||||||
|
value={dayjs(formValues.calibrationDischargeDate)}
|
||||||
|
onChange={(newTime) =>
|
||||||
|
handleConfirmDischarge(dayjs(newTime))
|
||||||
|
}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2, // Apply styles here
|
||||||
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/*<div>*/}
|
{/*<div>*/}
|
||||||
{/* <TextField*/}
|
{/* <TextField*/}
|
||||||
{/* label={*/}
|
{/* label={*/}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||||
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
|
|
||||||
interface InformationProps {
|
interface InformationProps {
|
||||||
values: I_Installation;
|
values: I_Installation;
|
||||||
|
|
@ -38,6 +39,7 @@ function Information(props: InformationProps) {
|
||||||
const [formValues, setFormValues] = useState(props.values);
|
const [formValues, setFormValues] = useState(props.values);
|
||||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
const requiredFields = ['name', 'region', 'location', 'country'];
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
const {
|
const {
|
||||||
updateInstallation,
|
updateInstallation,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -265,6 +267,30 @@ function Information(props: InformationProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="information"
|
||||||
|
defaultMessage="Information"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="information"
|
||||||
|
value={formValues.information}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
minRows={6} // 👈 Makes it visually bigger
|
||||||
|
maxRows={12} // 👈 Optional max height before scroll
|
||||||
|
inputProps={{
|
||||||
|
style: {
|
||||||
|
fontFamily: 'monospace' // optional: makes tabs/formatting more clear
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -282,8 +308,11 @@ function Information(props: InformationProps) {
|
||||||
label="S3 Bucket Name"
|
label="S3 Bucket Name"
|
||||||
name="s3bucketname"
|
name="s3bucketname"
|
||||||
value={
|
value={
|
||||||
formValues.s3BucketId +
|
product === 0 || product == 3
|
||||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
? formValues.s3BucketId +
|
||||||
|
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
||||||
|
: formValues.s3BucketId +
|
||||||
|
'-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa'
|
||||||
}
|
}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
|
||||||
|
|
@ -1,393 +0,0 @@
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
CardContent,
|
|
||||||
CircularProgress,
|
|
||||||
Container,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
|
||||||
import React, { useContext, useState } from 'react';
|
|
||||||
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
|
||||||
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
|
||||||
import routes from '../../../Resources/routes.json';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
|
||||||
|
|
||||||
interface InformationSodioHomeProps {
|
|
||||||
values: I_Installation;
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function InformationSodioHome(props: InformationSodioHomeProps) {
|
|
||||||
if (props.values === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = useContext(UserContext);
|
|
||||||
const { currentUser } = context;
|
|
||||||
const theme = useTheme();
|
|
||||||
const [formValues, setFormValues] = useState(props.values);
|
|
||||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
|
||||||
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
|
|
||||||
useState(false);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const DeviceTypes = ['Cerbo', 'Venus'];
|
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
|
||||||
const {
|
|
||||||
updateInstallation,
|
|
||||||
deleteInstallation,
|
|
||||||
loading,
|
|
||||||
setLoading,
|
|
||||||
error,
|
|
||||||
setError,
|
|
||||||
updated,
|
|
||||||
setUpdated
|
|
||||||
} = installationContext;
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setFormValues({
|
|
||||||
...formValues,
|
|
||||||
[name]: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleSubmit = () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
updateInstallation(formValues, props.type);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
setOpenModalDeleteInstallation(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteInstallationModalHandle = () => {
|
|
||||||
setOpenModalDeleteInstallation(false);
|
|
||||||
deleteInstallation(formValues, props.type);
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
navigate(routes.salidomo_installations + routes.list, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteInstallationModalHandleCancel = () => {
|
|
||||||
setOpenModalDeleteInstallation(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!formValues[field]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{openModalDeleteInstallation && (
|
|
||||||
<Modal
|
|
||||||
open={openModalDeleteInstallation}
|
|
||||||
onClose={deleteInstallationModalHandleCancel}
|
|
||||||
aria-labelledby="error-modal"
|
|
||||||
aria-describedby="error-modal-description"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 350,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Do you want to delete this installation?
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteInstallationModalHandle}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
marginLeft: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteInstallationModalHandleCancel}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Container maxWidth="xl">
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="stretch"
|
|
||||||
spacing={3}
|
|
||||||
>
|
|
||||||
<Grid item xs={12} md={12}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
sx={{
|
|
||||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="installation_name"
|
|
||||||
defaultMessage="Installation Name"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="name"
|
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="region" defaultMessage="Region" />
|
|
||||||
}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.region === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="location"
|
|
||||||
defaultMessage="Location"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.location === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="country" defaultMessage="Country" />
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.country === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="information"
|
|
||||||
defaultMessage="Information"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="information"
|
|
||||||
value={formValues.information}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="BitWatt Cloud Access Key"
|
|
||||||
name="s3WriteKey"
|
|
||||||
value={formValues.s3WriteKey}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="BitWatt Cloud Secret Key"
|
|
||||||
name="s3WriteSecret"
|
|
||||||
onChange={handleChange}
|
|
||||||
value={formValues.s3WriteSecret}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="applyChanges"
|
|
||||||
defaultMessage="Apply Changes"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleDelete}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="deleteInstallation"
|
|
||||||
defaultMessage="Delete Installation"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="errorOccured"
|
|
||||||
defaultMessage="An error has occurred"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setError(false)}
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{updated && (
|
|
||||||
<Alert
|
|
||||||
severity="success"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="successfullyUpdated"
|
|
||||||
defaultMessage="Successfully updated"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Container>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InformationSodioHome;
|
|
||||||
|
|
@ -135,7 +135,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSelectOneInstallation = (installationID: number): void => {
|
const handleSelectOneInstallation = (installationID: number): void => {
|
||||||
console.log('when selecting installation', product);
|
|
||||||
if (selectedInstallation != installationID) {
|
if (selectedInstallation != installationID) {
|
||||||
setSelectedInstallation(installationID);
|
setSelectedInstallation(installationID);
|
||||||
setSelectedInstallation(-1);
|
setSelectedInstallation(-1);
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,14 @@ interface installationFormProps {
|
||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
parentid: number;
|
parentid: number;
|
||||||
|
productToInsert: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function installationForm(props: installationFormProps) {
|
function installationForm(props: installationFormProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
|
||||||
|
console.log('productToInsert IS ', props.productToInsert);
|
||||||
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
||||||
installationName: '',
|
installationName: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -59,7 +62,7 @@ function installationForm(props: installationFormProps) {
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
formValues.parentId = props.parentid;
|
formValues.parentId = props.parentid;
|
||||||
formValues.product = 0;
|
formValues.product = props.productToInsert;
|
||||||
const responseData = await createInstallation(formValues);
|
const responseData = await createInstallation(formValues);
|
||||||
props.submit();
|
props.submit();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,20 @@ export interface Line {
|
||||||
Power: Power;
|
Power: Power;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SodioHomeBattery {
|
||||||
|
AccumulatedChargeEnergy: number;
|
||||||
|
AccumulatedDischargeEnergy: number;
|
||||||
|
Current: number;
|
||||||
|
DailyChargeEnergy: number;
|
||||||
|
DailyDischargeEnergy: number;
|
||||||
|
MaxAllowableChargePower: number;
|
||||||
|
MaxAllowableDischargePower: number;
|
||||||
|
Power: number;
|
||||||
|
Soc: number;
|
||||||
|
Soh: number;
|
||||||
|
Voltage: number;
|
||||||
|
}
|
||||||
|
|
||||||
// The interface for the Battery structure, with dynamic keys (Device IDs)
|
// The interface for the Battery structure, with dynamic keys (Device IDs)
|
||||||
export interface JSONRecordData {
|
export interface JSONRecordData {
|
||||||
Battery: {
|
Battery: {
|
||||||
|
|
@ -282,9 +296,12 @@ export interface JSONRecordData {
|
||||||
CurtailP: number;
|
CurtailP: number;
|
||||||
DayAndTimeForAdditionalCalibration: string;
|
DayAndTimeForAdditionalCalibration: string;
|
||||||
DayAndTimeForRepetitiveCalibration: string;
|
DayAndTimeForRepetitiveCalibration: string;
|
||||||
|
DownDayAndTimeForAdditionalCalibration: string;
|
||||||
|
DownDayAndTimeForRepetitiveCalibration: string;
|
||||||
DisplayIndividualBatteries: string;
|
DisplayIndividualBatteries: string;
|
||||||
MaxBatteryDischargingCurrent: number;
|
MaxBatteryDischargingCurrent: number;
|
||||||
ForceCalibrationChargeState: string;
|
ForceCalibrationChargeState: string;
|
||||||
|
ForceCalibrationDischargeState: string;
|
||||||
GridSetPoint: number;
|
GridSetPoint: number;
|
||||||
HoldSocZone: number;
|
HoldSocZone: number;
|
||||||
MinSoc: number;
|
MinSoc: number;
|
||||||
|
|
@ -392,17 +409,101 @@ export interface JSONRecordData {
|
||||||
LoadOnDc: { Power: number };
|
LoadOnDc: { Power: number };
|
||||||
|
|
||||||
PvOnDc: {
|
PvOnDc: {
|
||||||
DcWh: number;
|
[deviceId: string]: {
|
||||||
NbrOfStrings: number;
|
DcWh: number;
|
||||||
Dc: {
|
Dc: {
|
||||||
Voltage: number;
|
Voltage: number;
|
||||||
Current: number;
|
Current: number;
|
||||||
Power: number;
|
Power: number;
|
||||||
};
|
NbrOfStrings: number;
|
||||||
Strings: {
|
};
|
||||||
[PvId: string]: PvString;
|
Strings: {
|
||||||
|
[PvId: string]: PvString;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AcDcGrowatt: {
|
||||||
|
AcChargeEnable: number;
|
||||||
|
ActivePowerPercentDerating: number;
|
||||||
|
ActualChargeDischargePowerControlValue: number;
|
||||||
|
AlarmMainCode: number;
|
||||||
|
AlarmSubCode: number;
|
||||||
|
BatteriesRecords: {
|
||||||
|
AverageSoc: number;
|
||||||
|
AverageSoh: number;
|
||||||
|
Batteries: { [deviceId: string]: SodioHomeBattery };
|
||||||
|
LowestSoc: number;
|
||||||
|
Power: number;
|
||||||
|
TotalChargeEnergy: number;
|
||||||
|
TotalDischargeEnergy: number;
|
||||||
|
TotalMaxCharge: number;
|
||||||
|
TotalMaxDischarge: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
BatteryChargeCutoffVoltage: number;
|
||||||
|
BatteryClusterIndex: number;
|
||||||
|
BatteryDischargeCutoffVoltage: number;
|
||||||
|
BatteryMaxChargeCurrent: number;
|
||||||
|
BatteryMaxChargePower: number;
|
||||||
|
BatteryMaxDischargePower: number;
|
||||||
|
BatteryMaxdischargeCurrent: number;
|
||||||
|
BatteryOperatingMode: string;
|
||||||
|
BatteryType: number;
|
||||||
|
ChargeCutoffSoc: number;
|
||||||
|
ControlPermession: number;
|
||||||
|
DischargeCutoffSoc: number;
|
||||||
|
EmsCommunicationFailureTime: number;
|
||||||
|
EnableCommand: number;
|
||||||
|
EnableEmsCommunicationFailureTime: number;
|
||||||
|
EnableSyn: string;
|
||||||
|
EnergyToGrid: number;
|
||||||
|
EnergyToUser: number;
|
||||||
|
FaultMainCode: number;
|
||||||
|
FaultSubCode: number;
|
||||||
|
Frequency: number;
|
||||||
|
GridAbLineVoltage: number;
|
||||||
|
GridBcLineVoltage: number;
|
||||||
|
GridCaLineVoltage: number;
|
||||||
|
InverterActivePower: number;
|
||||||
|
InverterReactivePower: number;
|
||||||
|
InverterTemperature: number;
|
||||||
|
LoadPriorityDischargeCutoffSoc: number;
|
||||||
|
MaxActivePower: number;
|
||||||
|
MeterPower: number;
|
||||||
|
OffGridDischargeCutoffSoc: number;
|
||||||
|
OperatingPriority: string;
|
||||||
|
PhaseACurrent: number;
|
||||||
|
PhaseBCurrent: number;
|
||||||
|
PhaseCCurrent: number;
|
||||||
|
PowerFactor: number;
|
||||||
|
Pv1Current: number;
|
||||||
|
Pv1Voltage: number;
|
||||||
|
Pv2Current: number;
|
||||||
|
Pv2Voltage: number;
|
||||||
|
PvInputMaxPower: number;
|
||||||
|
RatedPower: number;
|
||||||
|
RemoteChargDischargePower: number;
|
||||||
|
RemotePowerControl: number;
|
||||||
|
SystemDateTime: string;
|
||||||
|
SystemOperatingMode: string;
|
||||||
|
TotalEnergyToGrid: number;
|
||||||
|
TotalEnergyToUser: number;
|
||||||
|
VppProtocolVerNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// DcWh: number;
|
||||||
|
// // NbrOfStrings: number;
|
||||||
|
// Dc: { [deviceId: string]: Dc };
|
||||||
|
// // Dc: {
|
||||||
|
// // Voltage: number;
|
||||||
|
// // Current: number;
|
||||||
|
// // Power: number;
|
||||||
|
// // };
|
||||||
|
// Strings: {
|
||||||
|
// [PvId: string]: PvString;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseChunkJson = (
|
export const parseChunkJson = (
|
||||||
|
|
@ -497,8 +598,10 @@ export interface I_BoxDataValue {
|
||||||
export type ConfigurationValues = {
|
export type ConfigurationValues = {
|
||||||
minimumSoC: string | number;
|
minimumSoC: string | number;
|
||||||
gridSetPoint: number;
|
gridSetPoint: number;
|
||||||
CalibrationChargeState: number;
|
calibrationChargeState: number;
|
||||||
calibrationChargeDate: Date | null;
|
calibrationChargeDate: Date | null;
|
||||||
|
calibrationDischargeState: number;
|
||||||
|
calibrationDischargeDate: Date | null;
|
||||||
};
|
};
|
||||||
//
|
//
|
||||||
// export interface Pv {
|
// export interface Pv {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
import React, {
|
||||||
|
Fragment,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Box,
|
||||||
|
Container,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
InputLabel,
|
||||||
|
ListItem,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import PersonIcon from '@mui/icons-material/Person';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
import { AccessContext } from 'src/contexts/AccessContextProvider';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { InnovEnergyUser, UserType } from '../../../interfaces/UserTypes';
|
||||||
|
import PersonRemoveIcon from '@mui/icons-material/PersonRemove';
|
||||||
|
import {
|
||||||
|
I_Folder,
|
||||||
|
I_Installation
|
||||||
|
} from '../../../interfaces/InstallationTypes';
|
||||||
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
|
|
||||||
|
interface UserAccessProps {
|
||||||
|
current_user: InnovEnergyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserAccess(props: UserAccessProps) {
|
||||||
|
if (props.current_user == undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const tokencontext = useContext(TokenContext);
|
||||||
|
const { removeToken } = tokencontext;
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const { currentUser, setUser } = context;
|
||||||
|
const [openFolder, setOpenFolder] = useState(false);
|
||||||
|
const [openInstallation, setOpenInstallation] = useState(false);
|
||||||
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
|
const [selectedFolderNames, setSelectedFolderNames] = useState<string[]>([]);
|
||||||
|
const [selectedInstallationNames, setSelectedInstallationNames] = useState<
|
||||||
|
string[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const [folders, setFolders] = useState<I_Folder[]>([]);
|
||||||
|
const [installations, setInstallations] = useState<I_Installation[]>([]);
|
||||||
|
const accessContext = useContext(AccessContext);
|
||||||
|
const {
|
||||||
|
fetchInstallationsForUser,
|
||||||
|
accessibleInstallationsForUser,
|
||||||
|
error,
|
||||||
|
setError,
|
||||||
|
updated,
|
||||||
|
setUpdated,
|
||||||
|
updatedmessage,
|
||||||
|
errormessage,
|
||||||
|
setErrorMessage,
|
||||||
|
setUpdatedMessage,
|
||||||
|
RevokeAccessFromResource
|
||||||
|
} = accessContext;
|
||||||
|
|
||||||
|
const fetchFolders = useCallback(async () => {
|
||||||
|
return axiosConfig
|
||||||
|
.get('/GetAllFolders')
|
||||||
|
.then((res) => {
|
||||||
|
setFolders(res.data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err.response && err.response.status == 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [setFolders]);
|
||||||
|
|
||||||
|
const fetchInstallations = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
// fetch product 0
|
||||||
|
const res0 = await axiosConfig.get(
|
||||||
|
`/GetAllInstallationsFromProduct?product=0`
|
||||||
|
);
|
||||||
|
const installations0 = res0.data;
|
||||||
|
|
||||||
|
// fetch product 1
|
||||||
|
const res1 = await axiosConfig.get(
|
||||||
|
`/GetAllInstallationsFromProduct?product=3`
|
||||||
|
);
|
||||||
|
const installations1 = res1.data;
|
||||||
|
|
||||||
|
// aggregate
|
||||||
|
const combined = [...installations0, ...installations1];
|
||||||
|
|
||||||
|
// update
|
||||||
|
setInstallations(combined);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response && err.response.status === 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}, [setInstallations]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
|
}, [props.current_user]);
|
||||||
|
|
||||||
|
const handleGrantAccess = () => {
|
||||||
|
fetchFolders();
|
||||||
|
fetchInstallations();
|
||||||
|
setOpenModal(true);
|
||||||
|
setSelectedFolderNames([]);
|
||||||
|
setSelectedInstallationNames([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFolderChange = (event) => {
|
||||||
|
setSelectedFolderNames(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInstallationChange = (event) => {
|
||||||
|
setSelectedInstallationNames(event.target.value);
|
||||||
|
};
|
||||||
|
const handleOpenFolder = () => {
|
||||||
|
setOpenFolder(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFolder = () => {
|
||||||
|
setOpenFolder(false);
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpenModal(false);
|
||||||
|
};
|
||||||
|
const handleOpenInstallation = () => {
|
||||||
|
setOpenInstallation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseInstallation = () => {
|
||||||
|
setOpenInstallation(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
for (const folderName of selectedFolderNames) {
|
||||||
|
const folder = folders.find((folder) => folder.name === folderName);
|
||||||
|
|
||||||
|
await axiosConfig
|
||||||
|
.post(
|
||||||
|
`/GrantUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folder.id}`
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setUpdatedMessage(
|
||||||
|
'Granted access to user ' + props.current_user.name
|
||||||
|
);
|
||||||
|
setUpdated(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setErrorMessage('An error has occured');
|
||||||
|
setError(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const installationName of selectedInstallationNames) {
|
||||||
|
const installation = installations.find(
|
||||||
|
(installation) => installation.name === installationName
|
||||||
|
);
|
||||||
|
|
||||||
|
await axiosConfig
|
||||||
|
.post(
|
||||||
|
`/GrantUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installation.id}`
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setUpdatedMessage(
|
||||||
|
'Granted access to user ' + props.current_user.name
|
||||||
|
);
|
||||||
|
setUpdated(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setErrorMessage('An error has occured');
|
||||||
|
setError(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenModal(false);
|
||||||
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
{updated && (
|
||||||
|
<Alert
|
||||||
|
severity="success"
|
||||||
|
sx={{
|
||||||
|
mt: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{updatedmessage}
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setUpdated(false)}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{errormessage}
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setError(false)}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={openModal}
|
||||||
|
onClose={() => {}}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '30%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 500,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<FormControl fullWidth sx={{ marginTop: 1, width: 390 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="grantAccessToFolders"
|
||||||
|
defaultMessage="Grant access to folders"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
value={selectedFolderNames}
|
||||||
|
onChange={handleFolderChange}
|
||||||
|
open={openFolder}
|
||||||
|
onClose={handleCloseFolder}
|
||||||
|
onOpen={handleOpenFolder}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<div>
|
||||||
|
{selected.map((folder) => (
|
||||||
|
<span key={folder}>{folder}, </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{folders.map((folder) => (
|
||||||
|
<MenuItem key={folder.id} value={folder.name}>
|
||||||
|
{folder.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginLeft: '150px',
|
||||||
|
marginTop: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleCloseFolder}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
|
</Button>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormControl fullWidth sx={{ marginTop: 2, width: 390 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="grantAccessToInstallations"
|
||||||
|
defaultMessage="Grant access to installations"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
value={selectedInstallationNames}
|
||||||
|
onChange={handleInstallationChange}
|
||||||
|
open={openInstallation}
|
||||||
|
onClose={handleCloseInstallation}
|
||||||
|
onOpen={handleOpenInstallation}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<div>
|
||||||
|
{selected.map((installation) => (
|
||||||
|
<span key={installation}>{installation}, </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{installations.map((installation) => (
|
||||||
|
<MenuItem
|
||||||
|
key={installation.id}
|
||||||
|
value={installation.name}
|
||||||
|
>
|
||||||
|
{installation.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginLeft: '150px',
|
||||||
|
marginTop: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleCloseInstallation}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
|
</Button>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleCancel}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleGrantAccess}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="grantAccess" defaultMessage="Grant Access" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
{accessibleInstallationsForUser.map((installation, index) => {
|
||||||
|
const isLast = index === accessibleInstallationsForUser.length - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={installation.name}>
|
||||||
|
<ListItem
|
||||||
|
sx={{
|
||||||
|
mb: isLast ? 4 : 0 // Apply margin-bottom to the last item only
|
||||||
|
}}
|
||||||
|
secondaryAction={
|
||||||
|
currentUser.userType === UserType.admin && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
RevokeAccessFromResource(
|
||||||
|
'ToInstallation',
|
||||||
|
props.current_user.id,
|
||||||
|
'InstallationId',
|
||||||
|
installation.id,
|
||||||
|
props.current_user.name
|
||||||
|
);
|
||||||
|
|
||||||
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
<PersonRemoveIcon />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<PersonIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText primary={installation.name} />
|
||||||
|
</ListItem>
|
||||||
|
<Divider />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{accessibleInstallationsForUser.length == 0 && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="theUserDoesNOtHaveAccessToAnyInstallation"
|
||||||
|
defaultMessage="The user does not have access to any installation "
|
||||||
|
/>
|
||||||
|
<IconButton color="inherit" size="small"></IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserAccess;
|
||||||
|
|
@ -24,34 +24,37 @@ function PvView(props: PvViewProps) {
|
||||||
if (props.values === null && props.connected == true) {
|
if (props.values === null && props.connected == true) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const sortedPvView =
|
// ✅ Flatten, sort, and assign unique displayId from 1-N
|
||||||
props.values != null && props.values.PvOnDc
|
const sortedPvView = props.values?.PvOnDc
|
||||||
? Object.entries(props.values.PvOnDc.Strings)
|
? Object.entries(props.values.PvOnDc)
|
||||||
.map(([pvId, pv]) => {
|
.flatMap(([deviceId, device]) =>
|
||||||
return { pvId, pv }; // Here we return an object with the id and device
|
Object.entries(device.Strings).map(([pvId, pv], index) => ({
|
||||||
})
|
pvId,
|
||||||
.sort((a, b) => parseInt(b.pvId) - parseInt(a.pvId))
|
pv,
|
||||||
: [];
|
deviceId,
|
||||||
|
displayId: `CU${deviceId} -> AMPT ${index + 1}`
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.deviceId === b.deviceId) {
|
||||||
|
return parseInt(a.pvId) - parseInt(b.pvId);
|
||||||
|
}
|
||||||
|
return a.deviceId.localeCompare(b.deviceId);
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
const [loading, setLoading] = useState(sortedPvView.length == 0);
|
const [loading, setLoading] = useState(sortedPvView.length === 0);
|
||||||
|
|
||||||
const handleMainStatsButton = () => {
|
const handleMainStatsButton = () => {
|
||||||
navigate(routes.mainstats);
|
navigate(routes.mainstats);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const findBatteryData = (batteryId: number) => {
|
|
||||||
// for (let i = 0; i < props.values.batteryView.length; i++) {
|
|
||||||
// if (props.values.batteryView[i].BatteryId == batteryId) {
|
|
||||||
// return props.values.batteryView[i];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sortedPvView.length == 0) {
|
if (sortedPvView.length === 0) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -84,6 +87,7 @@ function PvView(props: PvViewProps) {
|
||||||
</Typography>
|
</Typography>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loading && props.connected && (
|
{loading && props.connected && (
|
||||||
<Container
|
<Container
|
||||||
maxWidth="xl"
|
maxWidth="xl"
|
||||||
|
|
@ -111,80 +115,72 @@ function PvView(props: PvViewProps) {
|
||||||
|
|
||||||
{!loading && props.connected && (
|
{!loading && props.connected && (
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<TableContainer
|
{Object.entries(
|
||||||
component={Paper}
|
sortedPvView.reduce((acc, entry) => {
|
||||||
sx={{
|
if (!acc[entry.deviceId]) acc[entry.deviceId] = [];
|
||||||
marginTop: '20px',
|
acc[entry.deviceId].push(entry);
|
||||||
marginBottom: '20px'
|
return acc;
|
||||||
}}
|
}, {} as Record<string, typeof sortedPvView>)
|
||||||
>
|
).map(([deviceId, entries]) => (
|
||||||
<Table sx={{ minWidth: 250 }} aria-label="simple table">
|
<TableContainer
|
||||||
<TableHead>
|
key={deviceId}
|
||||||
<TableRow>
|
component={Paper}
|
||||||
<TableCell align="center">Pv</TableCell>
|
sx={{ marginTop: '30px', marginBottom: '40px', boxShadow: 3 }}
|
||||||
<TableCell align="center">Power</TableCell>
|
>
|
||||||
<TableCell align="center">Voltage</TableCell>
|
<Table sx={{ minWidth: 250 }} aria-label={`CU${deviceId} table`}>
|
||||||
<TableCell align="center">Current</TableCell>
|
<TableHead>
|
||||||
</TableRow>
|
<TableRow>
|
||||||
</TableHead>
|
<TableCell align="center">Pv</TableCell>
|
||||||
<TableBody>
|
<TableCell align="center">Power</TableCell>
|
||||||
{sortedPvView.map(({ pvId, pv }) => (
|
<TableCell align="center">Voltage</TableCell>
|
||||||
<TableRow
|
<TableCell align="center">Current</TableCell>
|
||||||
key={pvId}
|
|
||||||
style={{
|
|
||||||
height: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TableCell
|
|
||||||
component="th"
|
|
||||||
scope="row"
|
|
||||||
align="center"
|
|
||||||
sx={{ width: '10%', fontWeight: 'bold', color: 'black' }}
|
|
||||||
>
|
|
||||||
{'AMPT ' + pvId}
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
width: '10%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
backgroundColor:
|
|
||||||
pv.Current == 0 ? '#FF033E' : '#32CD32',
|
|
||||||
color: 'inherit'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pv.Power + ' W'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
width: '10%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
backgroundColor:
|
|
||||||
pv.Current == 0 ? '#FF033E' : '#32CD32',
|
|
||||||
color: 'inherit'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pv.Voltage + ' V'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
width: '10%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
backgroundColor:
|
|
||||||
pv.Current == 0 ? '#FF033E' : '#32CD32',
|
|
||||||
color: 'inherit'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pv.Current + ' A'}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHead>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{entries.map(({ displayId, pv }, index) => (
|
||||||
</TableContainer>
|
<TableRow key={index}>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{ fontWeight: 'bold', color: 'black' }}
|
||||||
|
>
|
||||||
|
{displayId}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor:
|
||||||
|
pv.Current === 0 ? '#FF033E' : '#32CD32'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pv.Power} W
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor:
|
||||||
|
pv.Current === 0 ? '#FF033E' : '#32CD32'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pv.Voltage} V
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor:
|
||||||
|
pv.Current === 0 ? '#FF033E' : '#32CD32'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pv.Current} A
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
routes.installation +
|
routes.installation +
|
||||||
`${installationID}` +
|
`${installationID}` +
|
||||||
'/' +
|
'/' +
|
||||||
routes.live,
|
routes.batteryview,
|
||||||
{
|
{
|
||||||
replace: true
|
replace: true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,11 @@ import HistoryOfActions from '../History/History';
|
||||||
import BuildIcon from '@mui/icons-material/Build';
|
import BuildIcon from '@mui/icons-material/Build';
|
||||||
import AccessContextProvider from '../../../contexts/AccessContextProvider';
|
import AccessContextProvider from '../../../contexts/AccessContextProvider';
|
||||||
import Access from '../ManageAccess/Access';
|
import Access from '../ManageAccess/Access';
|
||||||
import InformationSodioHome from '../Information/InformationSodioHome';
|
import Information from '../Information/Information';
|
||||||
import CryptoJS from 'crypto-js';
|
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||||
|
import { fetchDataJson } from '../Installations/fetchData';
|
||||||
|
import { FetchResult } from '../../../dataCache/dataCache';
|
||||||
|
import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome';
|
||||||
|
|
||||||
interface singleInstallationProps {
|
interface singleInstallationProps {
|
||||||
current_installation?: I_Installation;
|
current_installation?: I_Installation;
|
||||||
|
|
@ -31,6 +34,19 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
if (props.current_installation == undefined) {
|
if (props.current_installation == undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const S3data = {
|
||||||
|
s3Region: props.current_installation.s3Region,
|
||||||
|
s3Provider: props.current_installation.s3Provider,
|
||||||
|
s3Key: props.current_installation.s3Key,
|
||||||
|
s3Secret: props.current_installation.s3Secret,
|
||||||
|
s3BucketId: props.current_installation.s3BucketId
|
||||||
|
};
|
||||||
|
|
||||||
|
const s3Bucket =
|
||||||
|
props.current_installation.s3BucketId.toString() +
|
||||||
|
'-' +
|
||||||
|
'e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa';
|
||||||
|
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
const location = useLocation().pathname;
|
const location = useLocation().pathname;
|
||||||
|
|
@ -45,6 +61,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
const [connected, setConnected] = useState(true);
|
const [connected, setConnected] = useState(true);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
||||||
|
const s3Credentials = { s3Bucket, ...S3data };
|
||||||
|
|
||||||
//In React, useRef creates a mutable object that persists across renders without triggering re-renders when its value changes.
|
//In React, useRef creates a mutable object that persists across renders without triggering re-renders when its value changes.
|
||||||
//While fetching, we check the value of continueFetching, if its false, we break. This means that either the user changed tab or the object has been finished its execution (return)
|
//While fetching, we check the value of continueFetching, if its false, we break. This means that either the user changed tab or the object has been finished its execution (return)
|
||||||
|
|
@ -91,48 +108,75 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchDataPeriodically = async () => {
|
const fetchDataPeriodically = async () => {
|
||||||
while (continueFetching.current) {
|
var timeperiodToSearch = 200;
|
||||||
//Fetch data from Bitwatt cloud
|
let res;
|
||||||
console.log('Fetching from Bitwatt cloud');
|
let timestampToFetch;
|
||||||
|
|
||||||
console.log(props.current_installation.serialNumber);
|
for (var i = 0; i < timeperiodToSearch; i += 2) {
|
||||||
console.log(props.current_installation.s3WriteKey);
|
if (!continueFetching.current) {
|
||||||
console.log(props.current_installation.s3WriteSecret);
|
return false;
|
||||||
|
}
|
||||||
const timeStamp = Date.now().toString();
|
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||||
|
|
||||||
// Encrypt timestamp using AES-ECB with PKCS7 padding
|
|
||||||
const key = CryptoJS.enc.Utf8.parse(
|
|
||||||
props.current_installation.s3WriteSecret
|
|
||||||
);
|
|
||||||
const encrypted = CryptoJS.AES.encrypt(timeStamp, key, {
|
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7
|
|
||||||
}).toString();
|
|
||||||
|
|
||||||
// Set headers
|
|
||||||
const headers = {
|
|
||||||
'X-Signature': encrypted,
|
|
||||||
'X-AccessKey': props.current_installation.s3WriteKey,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
};
|
|
||||||
|
|
||||||
// API URL
|
|
||||||
const url = `https://www.biwattpower.com/gateway/admin/open/device/currentEnergyFlowData/${props.current_installation.serialNumber}`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { method: 'GET', headers });
|
res = await fetchDataJson(timestampToFetch, s3Credentials, false);
|
||||||
const result = await response.json();
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
console.log('API Response:', result);
|
break;
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Request failed:', error);
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= timeperiodToSearch) {
|
||||||
|
setConnected(false);
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setConnected(true);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
while (continueFetching.current) {
|
||||||
|
for (const timestamp of Object.keys(res)) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
setFetchFunctionCalled(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log(`Timestamp: ${timestamp}`);
|
||||||
|
console.log(res[timestamp]);
|
||||||
|
|
||||||
|
setValues(res[timestamp]);
|
||||||
|
await timeout(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for 2 seconds before fetching again
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(60));
|
||||||
await timeout(200000);
|
console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
|
||||||
console.log('ssssssssssssssssssssssssssssssssssssss');
|
|
||||||
|
for (i = 0; i < 30; i++) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Trying to fetch timestamp ' + timestampToFetch);
|
||||||
|
res = await fetchDataJson(timestampToFetch, s3Credentials, false);
|
||||||
|
if (
|
||||||
|
res !== FetchResult.notAvailable &&
|
||||||
|
res !== FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1));
|
||||||
|
}
|
||||||
|
if (i == 30) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setFetchFunctionCalled(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -147,11 +191,16 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(currentTab);
|
if (
|
||||||
if (currentTab == 'live' || location.includes('batteryview')) {
|
currentTab == 'live' ||
|
||||||
//Fetch periodically if the tab is live or batteryview
|
currentTab == 'pvview' ||
|
||||||
|
currentTab == 'configuration' ||
|
||||||
|
location.includes('batteryview')
|
||||||
|
) {
|
||||||
|
//Fetch periodically if the tab is live, pvview or batteryview
|
||||||
if (
|
if (
|
||||||
currentTab == 'live' ||
|
currentTab == 'live' ||
|
||||||
|
currentTab == 'pvview' ||
|
||||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
(location.includes('batteryview') && !location.includes('mainstats'))
|
||||||
) {
|
) {
|
||||||
if (!continueFetching.current) {
|
if (!continueFetching.current) {
|
||||||
|
|
@ -163,6 +212,10 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//Fetch only one time in configuration tab
|
||||||
|
// if (currentTab == 'configuration') {
|
||||||
|
// fetchDataForOneTime();
|
||||||
|
// }
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
continueFetching.current = false;
|
continueFetching.current = false;
|
||||||
|
|
@ -322,10 +375,11 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
<Route
|
<Route
|
||||||
path={routes.information}
|
path={routes.information}
|
||||||
element={
|
element={
|
||||||
<InformationSodioHome
|
<Information
|
||||||
values={props.current_installation}
|
values={props.current_installation}
|
||||||
|
s3Credentials={s3Credentials}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
></InformationSodioHome>
|
></Information>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -351,28 +405,17 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*<Route*/}
|
<Route
|
||||||
{/* path={routes.overview}*/}
|
path={routes.batteryview + '*'}
|
||||||
{/* element={*/}
|
element={
|
||||||
{/* <SalidomoOverview*/}
|
<BatteryViewSodioHome
|
||||||
{/* s3Credentials={s3Credentials}*/}
|
values={values}
|
||||||
{/* id={props.current_installation.id}*/}
|
s3Credentials={s3Credentials}
|
||||||
{/* ></SalidomoOverview>*/}
|
installationId={props.current_installation.id}
|
||||||
{/* }*/}
|
connected={connected}
|
||||||
{/*/>*/}
|
></BatteryViewSodioHome>
|
||||||
|
}
|
||||||
{/*<Route*/}
|
></Route>
|
||||||
{/* path={routes.batteryview + '*'}*/}
|
|
||||||
{/* element={*/}
|
|
||||||
{/* <BatteryViewSalidomo*/}
|
|
||||||
{/* values={values}*/}
|
|
||||||
{/* s3Credentials={s3Credentials}*/}
|
|
||||||
{/* installationId={props.current_installation.id}*/}
|
|
||||||
{/* productNum={props.current_installation.product}*/}
|
|
||||||
{/* connected={connected}*/}
|
|
||||||
{/* ></BatteryViewSalidomo>*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/*></Route>*/}
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
|
|
|
||||||
|
|
@ -1,286 +0,0 @@
|
||||||
import React, { useContext, useState } from 'react';
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
CircularProgress,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
TextField,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
interface SodiohomeInstallationFormProps {
|
|
||||||
cancel: () => void;
|
|
||||||
submit: () => void;
|
|
||||||
parentid: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SodiohomeInstallationForm(props: SodiohomeInstallationFormProps) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const [open, setOpen] = useState(true);
|
|
||||||
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
|
||||||
name: '',
|
|
||||||
region: '',
|
|
||||||
location: '',
|
|
||||||
country: '',
|
|
||||||
serialNumber: '',
|
|
||||||
s3WriteSecret: '',
|
|
||||||
s3WriteKey: ''
|
|
||||||
});
|
|
||||||
const requiredFields = ['name', 'location', 'country', 'serialNumber'];
|
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
|
||||||
const { createInstallation, loading, setLoading, error, setError } =
|
|
||||||
installationContext;
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
|
|
||||||
setFormValues({
|
|
||||||
...formValues,
|
|
||||||
[name]: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
formValues.parentId = props.parentid;
|
|
||||||
formValues.product = 2;
|
|
||||||
const responseData = await createInstallation(formValues);
|
|
||||||
props.submit();
|
|
||||||
};
|
|
||||||
const handleCancelSubmit = (e) => {
|
|
||||||
props.cancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!formValues[field]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
onClose={() => {}}
|
|
||||||
aria-labelledby="error-modal"
|
|
||||||
aria-describedby="error-modal-description"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: isMobile ? '50%' : '40%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 500,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center', // Center items horizontally
|
|
||||||
'& .MuiTextField-root': {
|
|
||||||
m: 1,
|
|
||||||
width: 390
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="installationName"
|
|
||||||
defaultMessage="Installation Name"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="name"
|
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={<FormattedMessage id="region" defaultMessage="Region" />}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.region === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="location" defaultMessage="Location" />
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.location === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="country" defaultMessage="Country" />
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.country === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="serialNumber"
|
|
||||||
defaultMessage="Serial Number"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="serialNumber"
|
|
||||||
value={formValues.serialNumber}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.serialNumber === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="s3WriteKey"
|
|
||||||
defaultMessage="BitWatt Cloud Access Key"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="s3WriteKey"
|
|
||||||
value={formValues.s3WriteKey}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.s3WriteKey === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="s3WriteSecret"
|
|
||||||
defaultMessage="BitWatt Cloud Secret Key"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="s3WriteSecret"
|
|
||||||
value={formValues.s3WriteSecret}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.s3WriteSecret === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="Information"
|
|
||||||
defaultMessage="Information"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="information"
|
|
||||||
value={formValues.information}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleCancelSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="errorOccured"
|
|
||||||
defaultMessage="An error has occured"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setError(false)}
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SodiohomeInstallationForm;
|
|
||||||
|
|
@ -14,12 +14,17 @@ import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
import SodioHomeInstallation from './Installation';
|
import SodioHomeInstallation from './Installation';
|
||||||
|
|
||||||
function SodioHomeInstallationTabs() {
|
interface SodioHomeInstallationTabsProps {
|
||||||
|
product: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
const tabList = [
|
const tabList = [
|
||||||
'live',
|
'live',
|
||||||
|
'batteryview',
|
||||||
'information',
|
'information',
|
||||||
'manage',
|
'manage',
|
||||||
'overview',
|
'overview',
|
||||||
|
|
@ -53,26 +58,14 @@ function SodioHomeInstallationTabs() {
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sodiohomeInstallations.length === 0 && fetchedInstallations === false) {
|
setProduct(props.product);
|
||||||
|
}, [props.product]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (product == props.product) {
|
||||||
fetchAllSodiohomeInstallations();
|
fetchAllSodiohomeInstallations();
|
||||||
setFetchedInstallations(true);
|
|
||||||
}
|
}
|
||||||
}, [sodiohomeInstallations]);
|
}, [product]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (sodiohomeInstallations && sodiohomeInstallations.length > 0) {
|
|
||||||
if (!socket) {
|
|
||||||
openSocket(2);
|
|
||||||
} else if (product != 2) {
|
|
||||||
closeSocket();
|
|
||||||
openSocket(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [sodiohomeInstallations]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setProduct(2);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
setCurrentTab(value);
|
setCurrentTab(value);
|
||||||
|
|
@ -101,14 +94,23 @@ function SodioHomeInstallationTabs() {
|
||||||
const singleInstallationTabs =
|
const singleInstallationTabs =
|
||||||
currentUser.userType == UserType.admin
|
currentUser.userType == UserType.admin
|
||||||
? [
|
? [
|
||||||
|
// {
|
||||||
|
// value: 'live',
|
||||||
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'live',
|
value: 'batteryview',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: (
|
||||||
},
|
<FormattedMessage
|
||||||
{
|
id="batteryview"
|
||||||
value: 'overview',
|
defaultMessage="Battery View"
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// value: 'overview',
|
||||||
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
|
@ -141,14 +143,14 @@ function SodioHomeInstallationTabs() {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
// {
|
||||||
value: 'live',
|
// value: 'live',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
value: 'overview',
|
// value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
},
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
|
|
@ -172,14 +174,23 @@ function SodioHomeInstallationTabs() {
|
||||||
value: 'tree',
|
value: 'tree',
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// value: 'live',
|
||||||
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'live',
|
value: 'batteryview',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: (
|
||||||
},
|
<FormattedMessage
|
||||||
{
|
id="batteryview"
|
||||||
value: 'overview',
|
defaultMessage="Battery View"
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// value: 'overview',
|
||||||
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
|
@ -224,10 +235,10 @@ function SodioHomeInstallationTabs() {
|
||||||
value: 'tree',
|
value: 'tree',
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
value: 'live',
|
// value: 'live',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'overview',
|
value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ function Topology(props: TopologyProps) {
|
||||||
|
|
||||||
const { product, setProduct } = useContext(ProductIdContext);
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
|
||||||
//console.log('product VALUE IS ', product);
|
|
||||||
const [showValues, setShowValues] = useState(false);
|
const [showValues, setShowValues] = useState(false);
|
||||||
|
|
||||||
const handleSwitch = () => () => {
|
const handleSwitch = () => () => {
|
||||||
|
|
@ -38,6 +37,13 @@ function Topology(props: TopologyProps) {
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
|
const totalPvPower = props.values?.PvOnDc
|
||||||
|
? Object.values(props.values.PvOnDc).reduce(
|
||||||
|
(sum, device) => sum + (device?.Dc?.Power || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
: 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
|
@ -408,7 +414,7 @@ function Topology(props: TopologyProps) {
|
||||||
data: props.values?.PvOnDc
|
data: props.values?.PvOnDc
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: props.values.PvOnDc.Dc.Power,
|
value: totalPvPower,
|
||||||
unit: 'W'
|
unit: 'W'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -421,15 +427,12 @@ function Topology(props: TopologyProps) {
|
||||||
position: 'top',
|
position: 'top',
|
||||||
data: props.values?.PvOnDc
|
data: props.values?.PvOnDc
|
||||||
? {
|
? {
|
||||||
value: props.values.PvOnDc.Dc.Power,
|
value: totalPvPower,
|
||||||
unit: 'W'
|
unit: 'W'
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
amount: props.values?.PvOnDc
|
amount: props.values?.PvOnDc
|
||||||
? getAmount(
|
? getAmount(highestConnectionValue, totalPvPower)
|
||||||
highestConnectionValue,
|
|
||||||
props.values.PvOnDc.Dc.Power
|
|
||||||
)
|
|
||||||
: 0,
|
: 0,
|
||||||
showValues: showValues
|
showValues: showValues
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import { InstallationsContext } from '../../../contexts/InstallationsContextProv
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
import InstallationForm from '../Installations/installationForm';
|
import InstallationForm from '../Installations/installationForm';
|
||||||
import SalidomoInstallationForm from '../SalidomoInstallations/SalidomoInstallationForm';
|
import SalidomoInstallationForm from '../SalidomoInstallations/SalidomoInstallationForm';
|
||||||
import SodiohomeInstallationForm from '../SodiohomeInstallations/SodiohomeInstallationForm';
|
|
||||||
|
|
||||||
interface TreeInformationProps {
|
interface TreeInformationProps {
|
||||||
folder: I_Folder;
|
folder: I_Folder;
|
||||||
|
|
@ -65,7 +64,7 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
// console.log('Selected Product:', e.target.value);
|
// console.log('Selected Product:', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProductTypes = ['Salimax', 'Salidomo', 'Sodiohome'];
|
const ProductTypes = ['Salimax', 'Salidomo', 'Sodiohome', 'SodistoreMax'];
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
|
|
@ -322,13 +321,17 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
{openModalInstallation && product == 'Salimax' && (
|
{openModalInstallation &&
|
||||||
<InstallationForm
|
(product == 'Salimax' ||
|
||||||
cancel={handleFormCancel}
|
product == 'Sodiohome' ||
|
||||||
submit={handleInstallationFormSubmit}
|
product == 'SodistoreMax') && (
|
||||||
parentid={props.folder.id}
|
<InstallationForm
|
||||||
/>
|
cancel={handleFormCancel}
|
||||||
)}
|
submit={handleInstallationFormSubmit}
|
||||||
|
parentid={props.folder.id}
|
||||||
|
productToInsert={ProductTypes.indexOf(product)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{openModalInstallation && product == 'Salidomo' && (
|
{openModalInstallation && product == 'Salidomo' && (
|
||||||
<SalidomoInstallationForm
|
<SalidomoInstallationForm
|
||||||
cancel={handleFormCancel}
|
cancel={handleFormCancel}
|
||||||
|
|
@ -336,13 +339,13 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
parentid={props.folder.id}
|
parentid={props.folder.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{openModalInstallation && product == 'Sodiohome' && (
|
{/*{openModalInstallation && product == 'Sodiohome' && (*/}
|
||||||
<SodiohomeInstallationForm
|
{/* <SodiohomeInstallationForm*/}
|
||||||
cancel={handleFormCancel}
|
{/* cancel={handleFormCancel}*/}
|
||||||
submit={handleInstallationFormSubmit}
|
{/* submit={handleInstallationFormSubmit}*/}
|
||||||
parentid={props.folder.id}
|
{/* parentid={props.folder.id}*/}
|
||||||
/>
|
{/* />*/}
|
||||||
)}
|
{/*)}*/}
|
||||||
|
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Grid
|
<Grid
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
||||||
<Grid item xs={12} md={isMobile ? 5 : 4}>
|
<Grid item xs={6} md={5}>
|
||||||
<Card>
|
<Card>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TableContainer sx={{ maxHeight: '520px', overflowY: 'auto' }}>
|
<TableContainer sx={{ maxHeight: '520px', overflowY: 'auto' }}>
|
||||||
|
|
@ -113,13 +113,14 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={6} md={7}>
|
||||||
{selectedUser && (
|
{selectedUser && (
|
||||||
<User
|
<User
|
||||||
current_user={findUser(selectedUser)}
|
current_user={findUser(selectedUser)}
|
||||||
fetchDataAgain={props.fetchDataAgain}
|
fetchDataAgain={props.fetchDataAgain}
|
||||||
></User>
|
></User>
|
||||||
)}
|
)}
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import UserAccess from '../ManageAccess/UserAccess';
|
||||||
|
|
||||||
interface singleUserProps {
|
interface singleUserProps {
|
||||||
current_user: InnovEnergyUser;
|
current_user: InnovEnergyUser;
|
||||||
|
|
@ -41,7 +42,10 @@ function User(props: singleUserProps) {
|
||||||
const [formValues, setFormValues] = useState(props.current_user);
|
const [formValues, setFormValues] = useState(props.current_user);
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { removeToken } = tokencontext;
|
const { removeToken } = tokencontext;
|
||||||
const tabs = [{ value: 'user', label: 'User' }];
|
const tabs = [
|
||||||
|
{ value: 'user', label: 'User' },
|
||||||
|
{ value: 'manage', label: 'Access Management' }
|
||||||
|
];
|
||||||
const [openModalDeleteUser, setOpenModalDeleteUser] = useState(false);
|
const [openModalDeleteUser, setOpenModalDeleteUser] = useState(false);
|
||||||
|
|
||||||
const UserTypes = ['Client', 'Partner', 'Admin'];
|
const UserTypes = ['Client', 'Partner', 'Admin'];
|
||||||
|
|
@ -226,7 +230,7 @@ function User(props: singleUserProps) {
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Grid item xs={12} md={isMobile ? 7 : 8}>
|
<Grid item xs={12} md={12}>
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
onChange={handleTabsChange}
|
onChange={handleTabsChange}
|
||||||
|
|
@ -301,7 +305,7 @@ function User(props: singleUserProps) {
|
||||||
<div>
|
<div>
|
||||||
<FormControl
|
<FormControl
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ marginLeft: 1, marginTop: 1, width: 390 }}
|
sx={{ marginLeft: 1, marginTop: 1, width: 445 }}
|
||||||
>
|
>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -413,6 +417,9 @@ function User(props: singleUserProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
{currentTab === 'manage' && (
|
||||||
|
<UserAccess current_user={props.current_user}></UserAccess>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -69,25 +69,38 @@ function userForm(props: userFormProps) {
|
||||||
|
|
||||||
const fetchInstallations = useCallback(async () => {
|
const fetchInstallations = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
return axiosConfig
|
|
||||||
.get('/GetAllInstallations')
|
try {
|
||||||
.then((res) => {
|
// fetch product 0
|
||||||
setInstallations(res.data);
|
const res0 = await axiosConfig.get(
|
||||||
setLoading(false);
|
`/GetAllInstallationsFromProduct?product=0`
|
||||||
})
|
);
|
||||||
.catch((err) => {
|
const installations0 = res0.data;
|
||||||
setLoading(false);
|
|
||||||
if (err.response && err.response.status == 401) {
|
// fetch product 1
|
||||||
removeToken();
|
const res1 = await axiosConfig.get(
|
||||||
}
|
`/GetAllInstallationsFromProduct?product=3`
|
||||||
});
|
);
|
||||||
|
const installations1 = res1.data;
|
||||||
|
|
||||||
|
// aggregate
|
||||||
|
const combined = [...installations0, ...installations1];
|
||||||
|
|
||||||
|
// update
|
||||||
|
setInstallations(combined);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response && err.response.status === 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}, [setInstallations]);
|
}, [setInstallations]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchFolders();
|
fetchFolders();
|
||||||
fetchInstallations();
|
fetchInstallations();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormValues({
|
setFormValues({
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,11 @@ import {
|
||||||
InnovEnergyUser
|
InnovEnergyUser
|
||||||
} from '../interfaces/UserTypes';
|
} from '../interfaces/UserTypes';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { I_Installation } from '../interfaces/InstallationTypes';
|
||||||
|
|
||||||
interface AccessContextProviderProps {
|
interface AccessContextProviderProps {
|
||||||
|
fetchInstallationsForUser: (userId: number) => void;
|
||||||
|
accessibleInstallationsForUser: I_Installation[];
|
||||||
availableUsers: InnovEnergyUser[];
|
availableUsers: InnovEnergyUser[];
|
||||||
fetchAvailableUsers: () => Promise<void>;
|
fetchAvailableUsers: () => Promise<void>;
|
||||||
usersWithDirectAccess: InnovEnergyUser[];
|
usersWithDirectAccess: InnovEnergyUser[];
|
||||||
|
|
@ -44,6 +47,8 @@ interface AccessContextProviderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccessContext = createContext<AccessContextProviderProps>({
|
export const AccessContext = createContext<AccessContextProviderProps>({
|
||||||
|
fetchInstallationsForUser: () => Promise.resolve(),
|
||||||
|
accessibleInstallationsForUser: [],
|
||||||
availableUsers: [],
|
availableUsers: [],
|
||||||
fetchAvailableUsers: () => {
|
fetchAvailableUsers: () => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
@ -74,6 +79,8 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
InnovEnergyUser[]
|
InnovEnergyUser[]
|
||||||
>([]);
|
>([]);
|
||||||
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
|
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
|
||||||
|
const [accessibleInstallationsForUser, setAccessibleInstallationsForUser] =
|
||||||
|
useState<I_Installation[]>([]);
|
||||||
|
|
||||||
const [usersWithInheritedAccess, setUsersWithInheritedAccess] = useState<
|
const [usersWithInheritedAccess, setUsersWithInheritedAccess] = useState<
|
||||||
I_UserWithInheritedAccess[]
|
I_UserWithInheritedAccess[]
|
||||||
|
|
@ -104,6 +111,26 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchInstallationsForUser = useCallback(async (userId: number) => {
|
||||||
|
axiosConfig
|
||||||
|
.get(`/GetInstallationsTheUserHasAccess?userId=${userId}`)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setAccessibleInstallationsForUser(response.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setError(true);
|
||||||
|
const message = (
|
||||||
|
<FormattedMessage
|
||||||
|
id="unableToLoadData"
|
||||||
|
defaultMessage="Unable to load data"
|
||||||
|
/>
|
||||||
|
).props.defaultMessage;
|
||||||
|
setErrorMessage(message);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const fetchUsersWithInheritedAccessForResource = useCallback(
|
const fetchUsersWithInheritedAccessForResource = useCallback(
|
||||||
async (tempresourceType: string, id: number) => {
|
async (tempresourceType: string, id: number) => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
|
|
@ -192,6 +219,8 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<AccessContext.Provider
|
<AccessContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
fetchInstallationsForUser,
|
||||||
|
accessibleInstallationsForUser,
|
||||||
availableUsers,
|
availableUsers,
|
||||||
fetchAvailableUsers,
|
fetchAvailableUsers,
|
||||||
usersWithDirectAccess,
|
usersWithDirectAccess,
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ const InstallationsContextProvider = ({
|
||||||
}
|
}
|
||||||
const tokenString = localStorage.getItem('token');
|
const tokenString = localStorage.getItem('token');
|
||||||
const token = tokenString !== null ? tokenString : '';
|
const token = tokenString !== null ? tokenString : '';
|
||||||
const urlWithToken = `wss://monitor.innov.energy/api/CreateWebSocket?authToken=${token}`;
|
const urlWithToken = `wss://monitor.inesco.energy/api/CreateWebSocket?authToken=${token}`;
|
||||||
|
|
||||||
const new_socket = new WebSocket(urlWithToken);
|
const new_socket = new WebSocket(urlWithToken);
|
||||||
|
|
||||||
|
|
@ -200,9 +200,10 @@ const InstallationsContextProvider = ({
|
||||||
const fetchAllSodiohomeInstallations = useCallback(async () => {
|
const fetchAllSodiohomeInstallations = useCallback(async () => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.get('/GetAllSodioHomeInstallations')
|
.get('/GetAllSodioHomeInstallations')
|
||||||
.then((res: AxiosResponse<I_Installation[]>) =>
|
.then((res: AxiosResponse<I_Installation[]>) => {
|
||||||
setSodiohomeInstallations(res.data)
|
setSodiohomeInstallations(res.data);
|
||||||
)
|
openSocket(res.data);
|
||||||
|
})
|
||||||
.catch((err: AxiosError) => {
|
.catch((err: AxiosError) => {
|
||||||
if (err.response?.status === 401) {
|
if (err.response?.status === 401) {
|
||||||
removeToken();
|
removeToken();
|
||||||
|
|
|
||||||
|
|
@ -55,112 +55,6 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// if (sortedInstallations) {
|
|
||||||
// const tokenString = localStorage.getItem('token');
|
|
||||||
// const token = tokenString !== null ? tokenString : '';
|
|
||||||
// const urlWithToken = `wss://monitor.innov.energy/api/CreateWebSocket?authToken=${token}`;
|
|
||||||
//
|
|
||||||
// const socket = new WebSocket(urlWithToken);
|
|
||||||
// // Connection opened
|
|
||||||
// socket.addEventListener('open', (event) => {
|
|
||||||
// socket.send(
|
|
||||||
// JSON.stringify(
|
|
||||||
// sortedInstallations.map((installation) => installation.id)
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // Periodically send ping messages to keep the connection alive
|
|
||||||
// const pingInterval = setInterval(() => {
|
|
||||||
// if (socket.readyState === WebSocket.OPEN) {
|
|
||||||
// socket.send(JSON.stringify([-1]));
|
|
||||||
// }
|
|
||||||
// }, 10000); // Send a ping every 10 seconds
|
|
||||||
//
|
|
||||||
// let messageBuffer = [];
|
|
||||||
// let isProcessing = false;
|
|
||||||
//
|
|
||||||
// socket.addEventListener('message', (event) => {
|
|
||||||
// const message = JSON.parse(event.data); // Parse the JSON data
|
|
||||||
//
|
|
||||||
// if (Array.isArray(message)) {
|
|
||||||
// message.forEach((item) => {
|
|
||||||
// console.log('status is ' + item.status);
|
|
||||||
// // Update status and testingMode for each installation received
|
|
||||||
// // updateInstallationStatus(item.id, item.status, item.testingMode);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// // } else if (message.id !== -1) {
|
|
||||||
// // // Handle individual messages for installations
|
|
||||||
// // updateInstallationStatus(
|
|
||||||
// // message.id,
|
|
||||||
// // message.status,
|
|
||||||
// // message.testingMode
|
|
||||||
// // );
|
|
||||||
// // }
|
|
||||||
//
|
|
||||||
// // if (Array.isArray(message)) {
|
|
||||||
// // // Existing code for handling arrays, if necessary
|
|
||||||
// // setInstallationMode((prevMode) => {
|
|
||||||
// // const newMode = new Map(prevMode);
|
|
||||||
// // message.forEach((item) => {
|
|
||||||
// // newMode.set(item.id, item.testingMode);
|
|
||||||
// // });
|
|
||||||
// // return newMode;
|
|
||||||
// // });
|
|
||||||
// //
|
|
||||||
// // setInstallationStatus((prevStatus) => {
|
|
||||||
// // const newStatus = new Map(prevStatus);
|
|
||||||
// // message.forEach((item) => {
|
|
||||||
// // newStatus.set(item.id, item.status);
|
|
||||||
// // });
|
|
||||||
// // return newStatus;
|
|
||||||
// // });
|
|
||||||
// // } else if (message.id != -1) {
|
|
||||||
// // // Accumulate messages in the buffer
|
|
||||||
// // messageBuffer.push(message);
|
|
||||||
// //
|
|
||||||
// // // Process the buffer if not already processing
|
|
||||||
// // if (!isProcessing) {
|
|
||||||
// // isProcessing = true;
|
|
||||||
// //
|
|
||||||
// // // Use setTimeout to process the buffer periodically
|
|
||||||
// // setTimeout(() => {
|
|
||||||
// // const newInstallationMode = new Map();
|
|
||||||
// // const newInstallationStatus = new Map();
|
|
||||||
// //
|
|
||||||
// // // Process all accumulated messages
|
|
||||||
// // messageBuffer.forEach((msg) => {
|
|
||||||
// // newInstallationMode.set(msg.id, msg.testingMode);
|
|
||||||
// // newInstallationStatus.set(msg.id, msg.status);
|
|
||||||
// // });
|
|
||||||
// //
|
|
||||||
// // // Update the state with the accumulated messages
|
|
||||||
// // setInstallationMode(
|
|
||||||
// // (prevMode) => new Map([...prevMode, ...newInstallationMode])
|
|
||||||
// // );
|
|
||||||
// // setInstallationStatus(
|
|
||||||
// // (prevStatus) =>
|
|
||||||
// // new Map([...prevStatus, ...newInstallationStatus])
|
|
||||||
// // );
|
|
||||||
// //
|
|
||||||
// // // Clear the buffer after processing
|
|
||||||
// // messageBuffer = [];
|
|
||||||
// // isProcessing = false; // Reset processing flag
|
|
||||||
// // }, 100); // Adjust the delay as needed to control processing frequency
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// setSocket(socket);
|
|
||||||
// }
|
|
||||||
}, [sortedInstallations]);
|
|
||||||
|
|
||||||
// const openSocket = (installations: I_Installation[]) => {
|
|
||||||
// setInstallations(installations);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const openSocket = (installations) => {
|
const openSocket = (installations) => {
|
||||||
// setSortedInstallations(installations.sort((a, b) => b.status - a.status)); // Sort installations by status
|
// setSortedInstallations(installations.sort((a, b) => b.status - a.status)); // Sort installations by status
|
||||||
};
|
};
|
||||||
|
|
@ -168,19 +62,7 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const closeSocket = () => {
|
const closeSocket = () => {
|
||||||
// socket.close();
|
// socket.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
// const getStatus = (installationId: number) => {
|
|
||||||
// return installationStatus.get(installationId);
|
|
||||||
// // if (installationStatus.has(installationId)) {
|
|
||||||
// // installationStatus.get(installationId);
|
|
||||||
// // } else {
|
|
||||||
// // return -2;
|
|
||||||
// // }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getTestingMode = (installationId: number) => {
|
|
||||||
// return installationMode.get(installationId);
|
|
||||||
// };
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WebSocketContext.Provider
|
<WebSocketContext.Provider
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ export const transformInputToDailyDataJson = async (
|
||||||
//'Battery.Dc.Power' for salimax,
|
//'Battery.Dc.Power' for salimax,
|
||||||
// 'Battery.Power',
|
// 'Battery.Power',
|
||||||
'GridMeter.Ac.Power.Active',
|
'GridMeter.Ac.Power.Active',
|
||||||
'PvOnDc.Dc.Power',
|
'PvOnDc',
|
||||||
'DcDc.Dc.Link.Voltage',
|
'DcDc.Dc.Link.Voltage',
|
||||||
'LoadOnAcGrid.Power.Active',
|
'LoadOnAcGrid.Power.Active',
|
||||||
'LoadOnDc.Power'
|
'LoadOnDc.Power'
|
||||||
|
|
@ -420,23 +420,35 @@ export const transformInputToDailyDataJson = async (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
if (get(result, path) !== undefined) {
|
if (get(result, path) !== undefined) {
|
||||||
const value = path
|
let value: number | undefined = undefined;
|
||||||
.split('.')
|
|
||||||
.reduce((o, key) => (o ? o[key] : undefined), result);
|
|
||||||
|
|
||||||
if (value < chartOverview[categories[category_index]].min) {
|
if (category_index === 4) {
|
||||||
chartOverview[categories[category_index]].min = value;
|
// Custom logic for 'PvOnDc.Dc.Power'
|
||||||
|
value = Object.values(
|
||||||
|
result.PvOnDc as Record<string, { Dc?: { Power?: number } }>
|
||||||
|
).reduce((sum, device) => sum + (device.Dc?.Power || 0), 0);
|
||||||
|
} else if (get(result, path) !== undefined) {
|
||||||
|
// Default path-based extraction
|
||||||
|
value = path
|
||||||
|
.split('.')
|
||||||
|
.reduce((o, key) => (o ? o[key] : undefined), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value > chartOverview[categories[category_index]].max) {
|
// Only push value if defined
|
||||||
chartOverview[categories[category_index]].max = value;
|
if (value !== undefined) {
|
||||||
|
if (value < chartOverview[categories[category_index]].min) {
|
||||||
|
chartOverview[categories[category_index]].min = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > chartOverview[categories[category_index]].max) {
|
||||||
|
chartOverview[categories[category_index]].max = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
chartData[categories[category_index]].data.push([
|
||||||
|
adjustedTimestampArray[i],
|
||||||
|
value
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
chartData[categories[category_index]].data.push([
|
|
||||||
adjustedTimestampArray[i],
|
|
||||||
value
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
//data[path].push([adjustedTimestamp, null]);
|
|
||||||
}
|
}
|
||||||
category_index++;
|
category_index++;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ function SidebarMenu() {
|
||||||
<Box sx={{ marginTop: '3px' }}>
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="sodistore"
|
id="sodistore"
|
||||||
defaultMessage="Sodistore"
|
defaultMessage="SodistoreMax"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import Scrollbar from 'src/components/Scrollbar';
|
import Scrollbar from 'src/components/Scrollbar';
|
||||||
import { SidebarContext } from 'src/contexts/SidebarContext';
|
import { SidebarContext } from 'src/contexts/SidebarContext';
|
||||||
import innovenergyLogo from 'src/Resources/images/innovenergy-Logo_Speichern-mit-Salz_R_color.svg';
|
import inescoLogo from 'src/Resources/images/inesco_logo.png';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -17,8 +18,8 @@ import SidebarMenu from './SidebarMenu';
|
||||||
|
|
||||||
const SidebarWrapper = styled(Box)(
|
const SidebarWrapper = styled(Box)(
|
||||||
({ theme }) => `
|
({ theme }) => `
|
||||||
width: ${theme.sidebar.width};
|
width: 280px; /* previously theme.sidebar.width */
|
||||||
min-width: ${theme.sidebar.width};
|
min-width: 280px;
|
||||||
color: ${theme.colors.alpha.trueWhite[70]};
|
color: ${theme.colors.alpha.trueWhite[70]};
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 7;
|
z-index: 7;
|
||||||
|
|
@ -54,16 +55,24 @@ function Sidebar() {
|
||||||
<Scrollbar>
|
<Scrollbar>
|
||||||
<Box mt={3}>
|
<Box mt={3}>
|
||||||
<Box
|
<Box
|
||||||
mx={2}
|
|
||||||
sx={{
|
sx={{
|
||||||
width: 52
|
px: 2, // Padding left & right
|
||||||
|
py: 1.5, // Optional: padding top & bottom
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
border: '2px solid white', // Optional: border around logo
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#fff', // Optional: white background
|
||||||
|
mx: 2 // Horizontal margin to avoid sticking to edge
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={innovenergyLogo}
|
src={inescoLogo}
|
||||||
alt="innovenergy logo"
|
alt="inesco logo"
|
||||||
style={{
|
style={{
|
||||||
width: '150px' // Width of the image
|
width: '160px',
|
||||||
|
objectFit: 'contain'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -105,8 +114,8 @@ function Sidebar() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={innovenergyLogo}
|
src={inescoLogo}
|
||||||
alt="innovenergy logo"
|
alt="inesco logo"
|
||||||
style={{
|
style={{
|
||||||
width: '150px' // Width of the image
|
width: '150px' // Width of the image
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const SidebarLayout = (props: SidebarLayoutProps) => {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
pt: `${theme.header.height}`,
|
pt: `${theme.header.height}`,
|
||||||
[theme.breakpoints.up('lg')]: {
|
[theme.breakpoints.up('lg')]: {
|
||||||
ml: `${theme.sidebar.width}`
|
ml: '260px'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { alpha, createTheme, lighten, darken } from '@mui/material';
|
import { alpha, createTheme, darken, lighten } from '@mui/material';
|
||||||
import '@mui/lab/themeAugmentation';
|
import '@mui/lab/themeAugmentation';
|
||||||
|
|
||||||
const themeColors = {
|
const themeColors = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue