Update logos in frontend

Fixed delete installation method (delete read/write keys, read/write roles, buckets)
Fixed bug in not-connected installation in frontend
This commit is contained in:
Noe 2024-11-28 14:43:47 +01:00
parent e342cb18b1
commit f03325a6a2
50 changed files with 812 additions and 818 deletions

View File

@ -24,12 +24,15 @@ public class JobStatus
[Controller]
[Route("api/")]
//All the http requests from the frontend that contain "/api" will be forwarded to this controller from the nginx reverse proxy.
public class Controller : ControllerBase
{
[HttpPost(nameof(Login))]
public ActionResult<Session> Login(String username, String? password)
{
//Find the user to the database, verify its password and create a new session.
//Store the new session to the database and return it to the frontend.
//If the user log out, the session will be deleted. Each session is valid for 24 hours. The db deletes all invalid/expired sessions every 30 minutes.
var user = Db.GetUserByEmail(username);
if (user is null)
@ -37,18 +40,11 @@ public class Controller : ControllerBase
if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword) && !user.VerifyPassword(password))
{
//return Unauthorized("No Password set");
throw new Exceptions(401, "Wrong Password Exception", "Please try again.", Request.Path.Value!);
}
var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
//TODO The Frontend should check for the MustResetPassword Flag
return Db.Create(session)
? session
: throw new Exceptions(401,"Session Creation Exception", "Not allowed to log in.", Request.Path.Value!);
@ -58,6 +54,7 @@ public class Controller : ControllerBase
[HttpPost(nameof(Logout))]
public ActionResult Logout(Token authToken)
{
//Find the session and delete it from the database.
var session = Db.GetSession(authToken);
return session.Logout()

View File

@ -188,12 +188,83 @@ public static class ExoCmd
return id;
}
public static async Task<Boolean> RevokeReadKey(String S3Key)
public static async Task<bool> RemoveReadRole(this Installation installation)
{
var roleId = installation.ReadRoleId;
var url = $"https://api-ch-dk-2.exoscale.com/v2/iam-role/{roleId}";
var method = $"iam-role/{roleId}";
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" + BuildSignature("DELETE", method, unixtime);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
try
{
var response = await client.DeleteAsync(url);
if (response.IsSuccessStatusCode)
{
Console.WriteLine($"Successfully deleted read role with ID {roleId}.");
return true;
}
Console.WriteLine($"Failed to delete read role. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred while deleting read role: {ex.Message}");
return false;
}
}
public static async Task<bool> RemoveWriteRole(this Installation installation)
{
var roleId = installation.WriteRoleId;
var url = $"https://api-ch-dk-2.exoscale.com/v2/iam-role/{roleId}";
var method = $"iam-role/{roleId}";
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" + BuildSignature("DELETE", method, unixtime);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
try
{
var response = await client.DeleteAsync(url);
if (response.IsSuccessStatusCode)
{
Console.WriteLine($"Successfully deleted write role with ID {roleId}.");
return true;
}
Console.WriteLine($"Failed to delete write role. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred while deleting write role: {ex.Message}");
return false;
}
}
public static async Task<Boolean> RevokeReadKey(this Installation installation)
{
//Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{S3Key}";
var method = $"access-key/{S3Key}";
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3Key}";
var method = $"access-key/{installation.S3Key}";
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
@ -208,12 +279,12 @@ public static class ExoCmd
return response.IsSuccessStatusCode;
}
public static async Task<Boolean> RevokeReadKey(this Installation installation)
public static async Task<Boolean> RevokeWriteKey(this Installation installation)
{
//Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3Key}";
var method = $"access-key/{installation.S3Key}";
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3WriteKey}";
var method = $"access-key/{installation.S3WriteKey}";
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
@ -296,6 +367,12 @@ public static class ExoCmd
return await s3Region.PutBucket(installation.BucketName()) != null;
}
public static async Task<Boolean> DeleteBucket(this Installation installation)
{
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
return await s3Region.DeleteBucket(installation.BucketName()) ;
}
public static async Task<Boolean> SendConfig(this Installation installation, Configuration config)
{

View File

@ -47,12 +47,6 @@ public static class InstallationMethods
}
public static async Task<Boolean> DeleteBucket(this Installation installation)
{
// TODO We dont do this here
return true;
}
public static IEnumerable<User> UsersWithAccess(this Installation installation)
{
return installation

View File

@ -280,8 +280,11 @@ public static class SessionMethods
&& user.UserType != 0
&& user.HasAccessTo(installation)
&& Db.Delete(installation)
&& await installation.DeleteBucket();
&& await installation.RevokeReadKey()
&& await installation.RevokeWriteKey()
&& await installation.DeleteBucket()
&& await installation.RemoveReadRole()
&& await installation.RemoveWriteRole();
}

View File

@ -212,7 +212,7 @@ public static class UserMethods
public static Task SendPasswordResetEmail(this User user, String token)
{
const String subject = "Reset the password of your InnovEnergy-Account";
const String subject = "Reset the password of your Inesco Energy Account";
const String resetLink = "https://monitor.innov.energy/api/ResetPassword"; // TODO: move to settings file
var encodedToken = HttpUtility.UrlEncode(token);
@ -225,13 +225,13 @@ public static class UserMethods
public static Task SendNewUserWelcomeMessage(this User user)
{
const String subject = "Your new InnovEnergy-Account";
const String subject = "Your new Inesco Energy Account";
var resetLink = $"https://monitor.innov.energy/?username={user.Email}"; // TODO: move to settings file
var body = $"Dear {user.Name}\n" +
$"To set your password and log in to your " +
$"Innovenergy-Account open this link:{resetLink}";
$"Inesco Energy Account open this link:{resetLink}";
return user.SendEmail(subject, body);
}

View File

@ -1,15 +1,16 @@
using SQLite;
namespace InnovEnergy.App.Backend.DataTypes;
public abstract partial class TreeNode
{
//This is the parent class of each relation. It has an autoincrement Id, name, information, parent Id and Type.
//Ignore means: "Do not map this property to a database column."
[PrimaryKey, AutoIncrement]
public Int64 Id { get; set; }
public virtual String Name { get; set; } = ""; // overridden by User (unique)
public String Information { get; set; } = ""; // unstructured random info
[Indexed] // parent/child relation
[Indexed]
public Int64 ParentId { get; set; }
[Ignore]

View File

@ -9,7 +9,6 @@ public class User : TreeNode
public String Email { get; set; } = null!;
public int UserType { get; set; } = 0;
public Boolean MustResetPassword { get; set; } = false;
public String Language { get; set; } = null!;
public String? Password { get; set; } = null!;
[Unique]

View File

@ -7,12 +7,54 @@ using InnovEnergy.Lib.Utils;
using SQLite;
using SQLiteConnection = SQLite.SQLiteConnection;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
private static SQLiteConnection Connection { get; } = InitConnection();
public static TableQuery<Session> Sessions => Connection.Table<Session>();
public static TableQuery<Folder> Folders => Connection.Table<Folder>();
public static TableQuery<Installation> Installations => Connection.Table<Installation>();
public static TableQuery<User> Users => Connection.Table<User>();
public static TableQuery<FolderAccess> FolderAccess => Connection.Table<FolderAccess>();
public static TableQuery<InstallationAccess> InstallationAccess => Connection.Table<InstallationAccess>();
public static TableQuery<OrderNumber2Installation> OrderNumber2Installation => Connection.Table<OrderNumber2Installation>();
public static TableQuery<Error> Errors => Connection.Table<Error>();
public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
public static void Init()
{
//Used to force static constructor
//Since this class is static, we call Init method from the Program.cs to initialize all the fields of the class
//When a class is loaded, the fields are initialized before the constructor's code is executed.
//The TableQuery fields are lazy meaning that they will be initialized when they get accessed
//The connection searches for the latest backup and it binds all the tables to it.
}
//This is the constructor of the class
static Db()
{
Connection.RunInTransaction(() =>
{
Connection.CreateTable<User>();
Connection.CreateTable<Installation>();
Connection.CreateTable<Folder>();
Connection.CreateTable<FolderAccess>();
Connection.CreateTable<InstallationAccess>();
Connection.CreateTable<Session>();
Connection.CreateTable<OrderNumber2Installation>();
Connection.CreateTable<Error>();
Connection.CreateTable<Warning>();
Connection.CreateTable<UserAction>();
});
//UpdateKeys();
CleanupSessions().SupressAwaitWarning();
DeleteSnapshots().SupressAwaitWarning();
}
private static SQLiteConnection InitConnection()
{
@ -42,89 +84,59 @@ public static partial class Db
//return CopyDbToMemory(fileConnection);
}
private static SQLiteConnection CopyDbToMemory(SQLiteConnection fileConnection)
{
var memoryConnection = new SQLiteConnection(":memory:");
//Create a table if it does not exist in main memory
memoryConnection.CreateTable<User>();
memoryConnection.CreateTable<Installation>();
memoryConnection.CreateTable<Folder>();
memoryConnection.CreateTable<FolderAccess>();
memoryConnection.CreateTable<InstallationAccess>();
memoryConnection.CreateTable<Session>();
memoryConnection.CreateTable<OrderNumber2Installation>();
memoryConnection.CreateTable<Error>();
memoryConnection.CreateTable<Warning>();
fileConnection.CreateTable<UserAction>();
//Copy all the existing tables from the disk to main memory
fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
fileConnection.Table<Folder>().ForEach(memoryConnection.Insert);
fileConnection.Table<Installation>().ForEach(memoryConnection.Insert);
fileConnection.Table<User>().ForEach(memoryConnection.Insert);
fileConnection.Table<FolderAccess>().ForEach(memoryConnection.Insert);
fileConnection.Table<InstallationAccess>().ForEach(memoryConnection.Insert);
fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
fileConnection.Table<UserAction>().ForEach(memoryConnection.Insert);
return memoryConnection;
}
public static void BackupDatabase()
{
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
Connection.Backup("DbBackups/" + filename);
}
public static TableQuery<Session> Sessions => Connection.Table<Session>();
public static TableQuery<Folder> Folders => Connection.Table<Folder>();
public static TableQuery<Installation> Installations => Connection.Table<Installation>();
public static TableQuery<User> Users => Connection.Table<User>();
public static TableQuery<FolderAccess> FolderAccess => Connection.Table<FolderAccess>();
public static TableQuery<InstallationAccess> InstallationAccess => Connection.Table<InstallationAccess>();
public static TableQuery<OrderNumber2Installation> OrderNumber2Installation => Connection.Table<OrderNumber2Installation>();
public static TableQuery<Error> Errors => Connection.Table<Error>();
public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
public static void Init()
//Delete all by 10 snapshots every 24 hours.
private static async Task DeleteSnapshots()
{
// used to force static constructor
//Since this class is static, we call Init method from the Program.cs to initialize all the fields of the class
while (true)
{
try
{
var files = new DirectoryInfo("DbBackups")
.GetFiles()
.OrderByDescending(f => f.LastWriteTime);
var filesToDelete = files.Skip(10);
foreach (var file in filesToDelete)
{
Console.WriteLine("File to delete is " + file.Name);
file.Delete();
}
//This is the constructor of the class
static Db()
}
catch(Exception e)
{
Connection.RunInTransaction(() =>
{
Connection.CreateTable<User>();
Connection.CreateTable<Installation>();
Connection.CreateTable<Folder>();
Connection.CreateTable<FolderAccess>();
Connection.CreateTable<InstallationAccess>();
Connection.CreateTable<Session>();
Connection.CreateTable<OrderNumber2Installation>();
Connection.CreateTable<Error>();
Connection.CreateTable<Warning>();
Connection.CreateTable<UserAction>();
});
//UpdateKeys();
CleanupSessions().SupressAwaitWarning();
Console.WriteLine("An error has occured when cleaning database snapshots, exception is:\n"+e);
}
await Task.Delay(TimeSpan.FromHours(24));
}
}
//Delete all expired sessions every half an hour. An expired session is a session remained for more than 1 day.
private static async Task CleanupSessions()
{
while (true)
{
try
{
DeleteStaleSessions();
var deadline = DateTime.Now.AddDays(-Session.MaxAge.Days);
foreach (var session in Sessions)
{
if (session.LastSeen < deadline)
{
Console.WriteLine("Need to remove session of user id " + session.User.Name + "last time is "+session.LastSeen);
}
}
Sessions.Delete(s => s.LastSeen < deadline);
}
catch(Exception e)
{
@ -232,12 +244,6 @@ public static partial class Db
}
private static void DeleteStaleSessions()
{
var deadline = DateTime.Now.AddDays((-1) * Session.MaxAge.Days);
Sessions.Delete(s => s.LastSeen < deadline);
}
private static async Task UpdateS3Urls()
{
var regions = Installations

View File

@ -1,101 +0,0 @@
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
public static void CreateFakeRelations()
{
Connection.RunInTransaction(() =>
{
CreateFakeUserTree();
CreateFakeFolderTree();
LinkFakeInstallationsToFolders();
GiveFakeUsersAccessToFolders();
GiveFakeUsersAccessToInstallations();
});
}
private static void CreateFakeUserTree()
{
foreach (var userId in Enumerable.Range(1, Users.Count()))
{
var user = GetUserById(userId);
if (user is null)
continue;
user.ParentId = userId > 1
? Random.Shared.Next(userId - 1) + 1
: 0; // root has parentId 0
Update(user);
}
}
private static void CreateFakeFolderTree()
{
foreach (var folderId in Enumerable.Range(1, Folders.Count()))
{
var folder = GetFolderById(folderId);
if (folder is null)
continue;
folder.ParentId = folderId > 1
? Random.Shared.Next(folderId - 1) + 1
: 0; // root has parentId 0
Update(folder);
}
}
private static void LinkFakeInstallationsToFolders()
{
var nFolders = Folders.Count();
foreach (var installation in Installations)
{
installation.ParentId = Random.Shared.Next(nFolders) + 1;
Update(installation);
}
}
private static void GiveFakeUsersAccessToFolders()
{
foreach (var uf in FolderAccess) // remove existing relations
Connection.Delete(uf);
var nFolders = Folders.Count();
var nUsers = Users.Count();
foreach (var user in Users)
while (Random.Shared.Next((Int32)(nUsers - user.Id + 1)) != 0)
{
var relation = new FolderAccess
{
UserId = user.Id,
FolderId = Random.Shared.Next(nFolders) + 1
};
Connection.Insert(relation);
}
}
private static void GiveFakeUsersAccessToInstallations()
{
foreach (var ui in InstallationAccess) // remove existing relations
Connection.Delete(ui);
var nbInstallations = Installations.Count();
foreach (var user in Users)
while (Random.Shared.Next(5) != 0)
{
var relation = new InstallationAccess
{
UserId = user.Id,
InstallationId = Random.Shared.Next(nbInstallations) + 1
};
Connection.Insert(relation);
}
}
//TODO fake OrderNumbers
}

View File

@ -1,12 +1,11 @@
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.Relations;
namespace InnovEnergy.App.Backend.Database;
public static partial class Db
{
//In this file, we provide all the methods that can be used in order to retrieve information from the database (read)
public static Folder? GetFolderById(Int64? id)
{
return Folders
@ -39,14 +38,15 @@ public static partial class Db
public static Session? GetSession(String token)
{
//This method is called in almost every controller function.
//After logging in, the frontend receives a session object which contains a token. For all the future REST API calls, this token is used for session authentication.
var session = Sessions
.FirstOrDefault(s => s.Token == token);
// cannot use session.Valid in the DB query above.
// It does not exist in the db (IgnoreAttribute)
if (session is null)
{
return null;
}
if (!session.Valid)
{

View File

@ -28,11 +28,6 @@ public static partial class Db
return Update(obj: warning);
}
// public static Boolean Update(UserAction action)
// {
// return Update(obj: action);
// }
public static Boolean Update(Installation installation)
{
return Update(obj: installation);

View File

@ -7,7 +7,6 @@ using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
using InnovEnergy.Lib.Utils;
using Org.BouncyCastle.Math.EC;
namespace InnovEnergy.App.Backend;
@ -15,6 +14,13 @@ public static class Program
{
public static async Task Main(String[] args)
{
//First, we initialize the database. This is an empty constructor of the Db class that will be called.
//In addition, we initialize WatchDog in order to restart the backend service in case of failure.
//Finally, we start all the backend services. We call the InitializeEnvironment function of RabbitMqManager to create the queue (factory/connection)
//Then, we generate a consumer that binds to the queue. This is a separate async Task so it must not be awaited (it acts as a separate thread).
//Finally, we call the MonitorSalimaxInstallationTable and MonitorSalidomoInstallationTable from the WebsocketManager class.
//Those methods will build in-memory data structures to track the connected frontends and update them regarding the offline installations.
Watchdog.NotifyReady();
Db.Init();
var builder = WebApplication.CreateBuilder(args);
@ -80,7 +86,7 @@ public static class Program
private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
{
Title = "InnovEnergy Backend API",
Title = "Innesco Backend API",
Version = "v1"
};

View File

@ -7,41 +7,48 @@ namespace InnovEnergy.App.Backend.Relations;
public class Session : Relation<String, Int64>
{
public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(7);
public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(1);
[Unique ] public String Token { get => Left ; init => Left = value;}
[Indexed] public Int64 UserId { get => Right; init => Right = value;}
[Indexed] public DateTime LastSeen { get; set; }
public Boolean AccessToSalimax { get; set; } = false;
public Boolean AccessToSalidomo { get; set; } = false;
[Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge
&& (User) is not null;
[Ignore] public User User => _User ??= Db.GetUserById(UserId)!;
[Ignore] public Boolean Valid => DateTime.Now - LastSeen <=MaxAge ;
// Private backing field
private User? _User;
[Ignore] public User User
{
get => _User ??= Db.GetUserById(UserId)!;
set => _User =value;
}
[Obsolete("To be used only by deserializer")]
public Session()
{}
//We need to return a session object to the frontend. Only the public fields can be included.
//For this reason, we use the public User User. It is a public field but ignored, so it can be included to the object returned
//to the frontend but it will not get inserted to the database.
//When we initialize it like that: User = Db.GetUserById(user.Id)!, the set will be called and the private member will be initialized as well.
//What if the getSession method is called from another function of the controller?
//GetSession will retrieve a session object from the database, but this does not have the metadata included (the private fields and the ignored public fields)
//Thus, the get will be called and the private field _User will be initialized on the fly.
public Session(User user)
{
_User = user;
User = Db.GetUserById(user.Id)!;
Token = CreateToken();
UserId = user.Id;
LastSeen = DateTime.Now;
AccessToSalimax = user.AccessibleInstallations(product: 0).ToList().Count > 0;
AccessToSalidomo = user.AccessibleInstallations(product: 1).ToList().Count > 0;
}
private static String CreateToken()
{
//var token = new Byte[24];
//Random.Shared.NextBytes(token);
//return Convert.ToBase64String(token).Replace("/","");
return Guid.NewGuid().ToString("N");
}

View File

@ -5,6 +5,7 @@ using System.Text;
using System.Text.Json;
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.Websockets;
@ -21,6 +22,9 @@ public static class WebsocketManager
foreach (var installationConnection in InstallationConnections){
if (installationConnection.Value.Product==0 && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(1)){
installationConnection.Value.Status = -1;
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == 0 && f.S3BucketId == installationConnection.Key);
installation.Status = -1;
installation.Apply(Db.Update);
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
}
}
@ -40,12 +44,16 @@ public static class WebsocketManager
// Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == 1 && f.S3BucketId == installationConnection.Key);
installation.Status = -1;
installation.Apply(Db.Update);
installationConnection.Value.Status = -1;
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
}
}
}
await Task.Delay(TimeSpan.FromMinutes(1));
await Task.Delay(TimeSpan.FromMinutes(10));
}
}

View File

@ -185,21 +185,6 @@ public static class S3
}
}
// public static async Task<S3Bucket?> PutBucket(this S3Region region, String name)
// {
// var request = new PutBucketRequest { BucketName = name };
//
// var response = await region
// .GetS3Client()
// .PutBucketAsync(request);
//
// return response.HttpStatusCode switch
// {
// HttpStatusCode.OK => region.Bucket(name),
// _ => null
// };
// }
public static async Task<Boolean> PutCors(this S3Bucket bucket, CORSConfiguration corsConfiguration)
{
@ -216,14 +201,15 @@ public static class S3
return response.HttpStatusCode == HttpStatusCode.OK;
}
public static async Task<Boolean> DeleteBucket(this S3Bucket bucket)
public static async Task<Boolean> DeleteBucket(this S3Region region, String bucketName)
{
var request = new DeleteBucketRequest { BucketName = bucket.Name };
var response = await bucket
var request = new DeleteBucketRequest { BucketName = bucketName };
var response = await region
.GetS3Client()
.DeleteBucketAsync(request);
return response.HttpStatusCode == HttpStatusCode.OK;
return response.HttpStatusCode == HttpStatusCode.NoContent;
}
private static AmazonS3Client GetS3Client(this S3Url url ) => url.Bucket.GetS3Client();

View File

@ -13,9 +13,9 @@ DEVICE_INSTANCE = 1
SERVICE_NAME_PREFIX = 'com.victronenergy.battery.'
#s3 configuration
S3BUCKET = "637-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXOe9a2f9b47c34cf9f1b615b09"
S3SECRET = "S8MuM7k3KGAVw2iPiociaCfYVrJ5RXvozL1wY_f_i90"
S3BUCKET = "673-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXO270612dc3f57a61870220eea"
S3SECRET = "4fPVVN8JGnD9IY1k5RrrNUzo2L1IpR6gdSuGRB9pMWg"
# driver configuration

View File

@ -54,6 +54,6 @@ INNOVENERGY_PROTOCOL_VERSION = '48TL200V3'
# S3 Credentials
S3BUCKET = "425-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXO3fb358076b23f6daebe779ac"
S3SECRET = "HnspAdjwfRjtB_6vm0aH2BUYPsPOvZW6Hya_OU0gSLU"
S3BUCKET = "140-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXOa947c7fc5990a7a6f6c40860"
S3SECRET = "J1yOTLbYEO6cMxQ2wgIwe__ru9-_RH5BBtKzx_2JJHk"

View File

@ -549,6 +549,7 @@ def create_update_task(modbus, service, batteries):
elapsed_time = time.time() - start_time
create_csv_files(csv_signals, statuses, node_numbers, alarms_number_list, warnings_number_list)
print("11111111111111111111111111111111111111111111 elapsed time is ",elapsed_time)
# keep at most 1900 files at CSV_DIR for logging and aggregation
manage_csv_files(CSV_DIR, 1900)
@ -561,7 +562,7 @@ def create_update_task(modbus, service, batteries):
upload_status_to_innovenergy(_socket, statuses)
logging.debug('finished update cycle\n')
# logging.debug('finished update cycleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\n')
alive = True

View File

@ -15,7 +15,7 @@ echo -e "\n============================ Deploy ============================\n"
# Steiger, Rheinau 10.2.0.188 failed with ssh
ip_addresses=("10.2.1.115" "10.2.0.238" "10.2.0.115" "10.2.0.160" "10.2.0.149")
ip_addresses=("10.2.0.219")
#
#ip_addresses=(
#"10.2.1.70"

View File

@ -1,11 +1,11 @@
{
"name": "InnovEnergy",
"name": "Inesco Energy",
"version": "2.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "InnovEnergy",
"name": "Inesco Energy",
"version": "2.0.0",
"dependencies": {
"@emotion/react": "11.9.0",

View File

@ -1,7 +1,7 @@
{
"name": "InnovEnergy",
"name": "c",
"version": "2.0.0",
"title": "InnovEnergy",
"title": "Inesco Energy",
"private": false,
"dependencies": {
"@emotion/react": "11.9.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -13,7 +13,7 @@
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
rel="stylesheet"
/>
<title>InnovEnergy</title>
<title>Inesco Energy</title>
</head>
<body>

View File

@ -18,7 +18,6 @@ import ForgotPassword from './components/ForgotPassword';
import { axiosConfigWithoutToken } from './Resources/axiosConfig';
import InstallationsContextProvider from './contexts/InstallationsContextProvider';
import AccessContextProvider from './contexts/AccessContextProvider';
import WebSocketContextProvider from './contexts/WebSocketContextProvider';
import SalidomoInstallationTabs from './content/dashboards/SalidomoInstallations';
import { ProductIdContext } from './contexts/ProductIdContextProvider';
@ -125,7 +124,7 @@ function App() {
locale={language}
defaultLocale="en"
>
<WebSocketContextProvider>
<InstallationsContextProvider>
<CssBaseline />
<Routes>
<Route
@ -149,9 +148,7 @@ function App() {
path={routes.installations + '*'}
element={
<AccessContextProvider>
<InstallationsContextProvider>
<InstallationTabs />
</InstallationsContextProvider>
</AccessContextProvider>
}
/>
@ -160,9 +157,7 @@ function App() {
path={routes.salidomo_installations + '*'}
element={
<AccessContextProvider>
<InstallationsContextProvider>
<SalidomoInstallationTabs />
</InstallationsContextProvider>
</AccessContextProvider>
}
/>
@ -175,7 +170,7 @@ function App() {
<Route path="ResetPassword" element={<ResetPassword />}></Route>
</Route>
</Routes>
</WebSocketContextProvider>
</InstallationsContextProvider>
</IntlProvider>
</ThemeProvider>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 841.89 595.28" style="enable-background:new 0 0 841.89 595.28;" xml:space="preserve">
<style type="text/css">
.st0{fill:#646363;}
.st1{fill:#F39200;}
</style>
<g>
<path class="st0" d="M114.05,430.69c2.55,1.07,6.32,1.94,10.5,1.94c6.47,0,12.13-2.7,12.13-9.69c0-7.03-4.99-9.02-11.06-11.01
c-4.79-1.53-7.55-2.91-7.55-7.04c0-3.82,2.55-5.91,7.9-5.91c2.45,0,5.15,0.61,7.54,1.38l0.76-3.11c-2.24-0.87-5.71-1.53-8.56-1.53
c-6.98,0-11.42,3.21-11.42,9.23c0,5.71,3.52,8.21,9.53,10.14c5.45,1.68,9.07,3.11,9.07,7.85c0,4.38-3.26,6.42-8.46,6.42
c-3.98,0-7.29-1.07-9.68-1.94L114.05,430.69L114.05,430.69z M165.59,418.56c0-7.54-3.98-12.85-11.98-12.85
c-2.86,0-6.42,0.71-9.64,2.39v33.34h3.57v-10.14c1.38,0.66,3.26,1.27,5.86,1.27C161.76,432.58,165.59,426.72,165.59,418.56
L165.59,418.56z M147.54,428.09V410.3c1.53-0.87,3.57-1.48,5.91-1.48c5.91,0,8.41,4.38,8.41,9.79c0,6.98-2.96,10.91-8.82,10.91
C150.6,429.52,148.92,428.81,147.54,428.09L147.54,428.09z M192.96,427.84c-2.4,1.02-5.1,1.68-7.7,1.68
c-5.81,0-9.48-3.11-9.79-9.63h18.35c0.1-0.92,0.15-1.89,0.15-3.01c0-6.22-3.62-11.16-10.4-11.16c-7.34,0-11.77,5.61-11.77,13.41
c0,8.77,5.2,13.51,13.2,13.51c3.21,0,6.12-0.61,8.61-1.78L192.96,427.84L192.96,427.84z M183.58,408.77c4.89,0,6.88,3.72,6.88,7.6
c0,0.41,0,0.61-0.05,0.97h-14.88C175.99,412.24,178.79,408.77,183.58,408.77L183.58,408.77z M205.55,406.33h-3.57v25.69h3.57
V406.33L205.55,406.33z M203.77,400.98c1.53,0,2.4-1.07,2.4-2.45c0-1.33-0.87-2.35-2.4-2.35c-1.53,0-2.4,1.02-2.4,2.35
C201.37,399.9,202.24,400.98,203.77,400.98L203.77,400.98z M225.89,405.72c-7.85,0-12.29,5.51-12.29,13.66
c0,8.41,4.64,13.25,12.9,13.25c2.55,0,5.15-0.66,6.98-1.63l-0.71-2.96c-1.89,0.92-4.03,1.48-6.12,1.48
c-6.17,0-9.38-3.57-9.38-10.19c0-6.37,3.01-10.5,8.82-10.5c2.14,0,4.43,0.56,6.37,1.32l0.76-2.9
C231.4,406.38,228.8,405.72,225.89,405.72L225.89,405.72z M244.24,432.02v-21.26c2.04-1.22,4.38-1.94,7.14-1.94
c4.54,0,6.73,2.5,6.73,6.52v16.67h3.57V415.1c0-5.81-3.57-9.38-9.94-9.38c-3.21,0-5.45,0.82-7.49,1.89v-12.49l-3.57,0.25v36.65
H244.24L244.24,432.02z M290.42,427.84c-2.4,1.02-5.1,1.68-7.7,1.68c-5.81,0-9.48-3.11-9.79-9.63h18.35
c0.1-0.92,0.15-1.89,0.15-3.01c0-6.22-3.62-11.16-10.4-11.16c-7.34,0-11.78,5.61-11.78,13.41c0,8.77,5.2,13.51,13.2,13.51
c3.21,0,6.12-0.61,8.61-1.78L290.42,427.84L290.42,427.84z M281.05,408.77c4.89,0,6.88,3.72,6.88,7.6c0,0.41,0,0.61-0.05,0.97
h-14.88C273.45,412.24,276.25,408.77,281.05,408.77L281.05,408.77z M299.45,408.32v23.7h3.57v-21.46c1.78-1.02,4.23-1.73,7.34-1.73
c0.61,0,1.48,0.05,2.14,0.25l0.51-3.11c-0.87-0.15-1.73-0.25-2.85-0.25C306.02,405.72,302.15,406.79,299.45,408.32L299.45,408.32z
M322.44,432.02v-21.46c2.19-1.17,4.69-1.73,7.24-1.73c4.59,0,6.63,2.55,6.63,6.47v16.72h3.57V415.1c0-5.81-3.31-9.38-10.2-9.38
c-3.98,0-7.7,1.02-10.81,2.6v23.7H322.44L322.44,432.02z M402.21,432.02V415.1c0-5.56-3.11-9.38-9.89-9.38
c-3.62,0-6.63,1.17-9.12,2.65c-1.63-1.68-4.03-2.65-7.29-2.65c-3.82,0-7.39,1.02-10.45,2.6v23.7h3.57v-21.46
c2.09-1.17,4.43-1.73,6.78-1.73c4.64,0,6.27,2.75,6.27,6.47v16.72h3.52v-16.46c0-1.68-0.26-3.16-0.71-4.49
c1.89-1.27,4.64-2.24,7.34-2.24c4.59,0,6.42,2.75,6.42,6.47v16.72H402.21L402.21,432.02z M415.21,406.33h-3.57v25.69h3.57V406.33
L415.21,406.33z M413.42,400.98c1.53,0,2.4-1.07,2.4-2.45c0-1.33-0.87-2.35-2.4-2.35c-1.53,0-2.4,1.02-2.4,2.35
C411.03,399.9,411.89,400.98,413.42,400.98L413.42,400.98z M436.67,432.12l-0.26-3.01c-0.87,0.25-2.09,0.41-3.11,0.41
c-2.75,0-4.38-1.27-4.38-5.05v-15.14h6.93v-3.01h-6.93v-6.32l-3.57,0.26v6.07h-3.87v3.01h3.87v15.14c0,5.96,3.01,8.16,7.49,8.16
C434.32,432.63,435.49,432.43,436.67,432.12L436.67,432.12z M457.31,430.69c2.55,1.07,6.32,1.94,10.5,1.94
c6.47,0,12.13-2.7,12.13-9.69c0-7.03-5-9.02-11.06-11.01c-4.79-1.53-7.54-2.91-7.54-7.04c0-3.82,2.55-5.91,7.9-5.91
c2.45,0,5.15,0.61,7.54,1.38l0.76-3.11c-2.24-0.87-5.71-1.53-8.56-1.53c-6.98,0-11.42,3.21-11.42,9.23c0,5.71,3.52,8.21,9.53,10.14
c5.45,1.68,9.07,3.11,9.07,7.85c0,4.38-3.26,6.42-8.46,6.42c-3.98,0-7.29-1.07-9.69-1.94L457.31,430.69L457.31,430.69z
M495.28,429.78c-3.98,0-6.37-1.58-6.37-4.79c0-3.82,2.85-5.1,6.68-5.1c2.29,0,4.13,0.36,5.81,0.71v7.75
C500.28,429.06,498.34,429.78,495.28,429.78L495.28,429.78z M504.92,430.24V415.2c0-6.12-3.31-9.48-9.94-9.48
c-2.9,0-5.96,0.61-7.95,1.48l0.82,2.96c1.89-0.81,4.38-1.32,6.88-1.32c4.64,0,6.68,2.29,6.68,6.22V418
c-1.84-0.41-4.08-0.71-6.17-0.71c-5.45,0.05-9.89,2.29-9.89,7.8c0,4.43,3.31,7.55,9.63,7.55
C499.51,432.63,502.67,431.56,504.92,430.24L504.92,430.24z M517.66,395.11l-3.57,0.25v29.41c0,5.15,2.09,7.85,6.47,7.85
c0.71,0,1.63-0.15,2.29-0.31l-0.31-2.91c-0.36,0.05-0.87,0.15-1.48,0.15c-2.6,0-3.41-1.63-3.41-4.84V395.11L517.66,395.11z
M527.4,406.33v3.01h14.53c-3.62,6.47-9.18,13.41-15.39,20.14v2.55h20.14v-3.01h-15.65c6.07-6.63,11.01-13.15,14.89-20.14v-2.55
H527.4L527.4,406.33z M558.08,396.34h-4.03v12.13c0,3.31,0.2,7.39,0.61,11.98h2.8c0.41-4.64,0.61-8.61,0.61-11.93V396.34
L558.08,396.34z M558.44,430.03c0-1.33-0.87-2.29-2.29-2.29c-1.53,0-2.4,0.97-2.4,2.29s0.87,2.35,2.4,2.35
C557.57,432.38,558.44,431.36,558.44,430.03L558.44,430.03z"/>
<path class="st0" d="M305.72,269.82v-65.28c5.44-2.72,12-4.16,18.72-4.16c12.16,0,18.08,6.72,18.08,18.4v51.04h16.16v-52.16
c0-19.52-11.84-30.88-34.24-30.88c-12.32,0-24.96,2.88-34.88,7.52v75.52H305.72L305.72,269.82z M399,269.82v-65.28
c5.44-2.72,12-4.16,18.72-4.16c12.16,0,18.08,6.72,18.08,18.4v51.04h16.16v-52.16c0-19.52-11.84-30.88-34.24-30.88
c-12.32,0-24.96,2.88-34.88,7.52v75.52H399L399,269.82z M547.32,229.18c0-25.28-14.08-42.4-38.08-42.4
c-23.68,0-37.92,17.12-37.92,42.4c0,25.44,14.24,42.56,37.92,42.56C533.24,271.74,547.32,254.62,547.32,229.18L547.32,229.18z
M487.8,229.18c0-17.44,7.36-28.8,21.44-28.8c14.56,0,21.44,11.36,21.44,28.8c0,17.6-6.88,28.96-21.44,28.96
C495.16,258.14,487.8,246.78,487.8,229.18L487.8,229.18z M575.8,188.7h-16.32c3.2,31.2,11.84,53.12,28.16,81.12h17.92
c16-28,24.32-49.92,28.16-81.12h-16.16c-2.56,27.36-9.92,47.2-20.96,67.68C585.88,235.9,578.68,216.06,575.8,188.7L575.8,188.7z"/>
<path class="st1" d="M307.68,360.51c-7.2,3.04-15.36,4.96-23.36,4.96c-15.68,0-25.44-7.84-27.04-24.96h54.56
c0.32-3.04,0.64-6.56,0.64-10.72c0-20.32-12.48-35.52-33.6-35.52c-23.36,0-38.24,17.44-38.24,42.4c0,27.04,16.8,42.56,42.24,42.56
c10.4,0,20-2.08,27.52-5.6L307.68,360.51L307.68,360.51z M278.88,307.55c12.32,0,17.92,9.28,17.92,20c0,0.96,0,1.6-0.16,2.56h-39.2
C258.88,316.35,266.4,307.55,278.88,307.55L278.88,307.55z M348.07,377.31v-65.28c5.44-2.72,12-4.16,18.72-4.16
c12.16,0,18.08,6.72,18.08,18.4v51.04h16.16v-52.16c0-19.52-11.84-30.88-34.24-30.88c-12.32,0-24.96,2.88-34.88,7.52v75.52H348.07
L348.07,377.31z M487.43,360.51c-7.2,3.04-15.36,4.96-23.36,4.96c-15.68,0-25.44-7.84-27.04-24.96h54.56
c0.32-3.04,0.64-6.56,0.64-10.72c0-20.32-12.48-35.52-33.6-35.52c-23.36,0-38.24,17.44-38.24,42.4c0,27.04,16.8,42.56,42.24,42.56
c10.4,0,20-2.08,27.52-5.6L487.43,360.51L487.43,360.51z M458.63,307.55c12.32,0,17.92,9.28,17.92,20c0,0.96,0,1.6-0.16,2.56h-39.2
C438.63,316.35,446.15,307.55,458.63,307.55L458.63,307.55z M512.71,301.95v75.36h16.16v-65.28c4.48-2.4,10.56-4.16,18.56-4.16
c2.56,0,5.6,0.32,8,1.12l2.24-13.6c-2.88-0.64-6.88-1.12-11.36-1.12C533.99,294.27,521.03,297.31,512.71,301.95L512.71,301.95z
M636.05,375.07v-74.4c-7.04-3.36-17.92-6.4-29.92-6.4c-24.8,0-41.12,15.84-41.12,41.44c0,24.32,14.4,38.24,37.28,38.24
c6.72,0,12.64-1.6,17.6-4.16v6.72c0,11.04-7.52,17.6-21.76,17.6c-8.48,0-15.36-1.28-22.4-4l-3.36,13.12
c8.16,3.2,15.2,4.64,26.72,4.64C621.81,407.87,636.05,396.35,636.05,375.07L636.05,375.07z M581.49,334.43
c0-16.48,9.44-26.56,24.64-26.56c5.12,0,10.24,1.28,13.76,2.56v45.6c-4,2.56-8.96,4.16-15.36,4.16
C589.97,360.19,581.49,350.43,581.49,334.43L581.49,334.43z M700.69,372.83c12.8-22.88,22.56-47.68,25.76-76.64h-16.16
c-2.4,27.36-8.48,45.12-18.56,65.28c-11.04-18.56-19.36-37.92-21.92-65.28h-16.32c3.36,30.72,12.64,53.44,29.76,77.76
c-6.24,10.4-16.96,16.96-27.36,19.04l2.72,13.92C674.45,403.71,688.53,394.11,700.69,372.83L700.69,372.83z"/>
<polygon class="st0" points="263.6,188.59 247.44,188.59 247.44,269.71 263.6,269.71 263.6,188.59 "/>
<path class="st1" d="M255.44,174.51c6.24,0,9.92-4,9.92-9.92c0-5.6-3.68-9.6-9.92-9.6c-6.4,0-10.08,4-10.08,9.6
C245.36,170.51,249.04,174.51,255.44,174.51L255.44,174.51z"/>
<path class="st0" d="M666.38,255.96c0,8.32-4.87,12.94-12.92,12.94c-8.12,0-12.85-4.62-12.85-12.94c0-8.25,4.73-12.94,12.85-12.94
C661.51,243.03,666.38,247.71,666.38,255.96L666.38,255.96z M637.36,255.96c0,9.51,6.29,15.78,16.1,15.78
c9.61,0,16.17-6.27,16.17-15.78s-6.49-15.78-16.17-15.78C643.72,240.19,637.36,246.46,637.36,255.96L637.36,255.96z M647.78,263.69
h3.11v-5.81h2.37c1.02,1.98,2.23,3.96,3.79,5.81h3.79c-1.83-2.18-3.31-4.23-4.4-6.27c2.23-0.73,3.25-2.38,3.25-4.62
c0-3.1-2.03-5.15-6.49-5.15h-5.41V263.69L647.78,263.69z M653.19,250.15c2.3,0,3.52,0.79,3.52,2.64c0,1.85-1.22,2.64-3.52,2.64
h-2.3v-5.28H653.19L653.19,250.15z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -18,7 +18,7 @@ function Footer() {
>
<Box>
<Typography variant="subtitle1">
&copy; 2024 - InnovEnergy AG
&copy; 2024 - Inesco Energy Solutions AG
</Typography>
</Box>
<Typography
@ -33,7 +33,7 @@ function Footer() {
target="_blank"
rel="noopener noreferrer"
>
InnovEnergy AG
Inesco Energy Solutions AG
</Link>
</Typography>
</Box>

View File

@ -3,14 +3,11 @@ import {
Box,
Button,
CircularProgress,
Container,
Grid,
Modal,
TextField,
Typography,
useTheme
} from '@mui/material';
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
import { UserContext } from 'src/contexts/userContext';
import { TokenContext } from 'src/contexts/tokenContext';
import Avatar from '@mui/material/Avatar';
@ -18,6 +15,7 @@ import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import axiosConfig from 'src/Resources/axiosConfig';
import { useNavigate } from 'react-router-dom';
import routes from 'src/Resources/routes.json';
import inescologo from '../Resources/images/Logo.svg';
interface ForgotPasswordPromps {
resetPassword: () => void;
@ -73,16 +71,6 @@ function ForgotPassword() {
return (
<>
<Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid container>
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
<a href="https://monitor.innov.energy/">
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
</a>
</Grid>
</Grid>
</Container>
<Box
sx={{
marginTop: 8,
@ -100,7 +88,12 @@ function ForgotPassword() {
transform: 'translate(-50%, -50%)'
}}
>
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
<a href="https://monitor.innov.energy/">
<img src={inescologo} alt="inescologo" height="100" />
</a>
</Box>
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
@ -134,15 +127,15 @@ function ForgotPassword() {
}}
/>
{loading && <CircularProgress sx={{ color: '#ffc04d' }} />}
{loading && <CircularProgress sx={{ color: '#00b33c' }} />}
<Button
sx={{
mt: 3,
mb: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
'&:hover': { bgcolor: '#f7b34d' }
bgcolor: '#00b33c',
'&:hover': { bgcolor: '#009933' }
}}
variant="contained"
fullWidth={true}
@ -181,9 +174,9 @@ function ForgotPassword() {
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
bgcolor: '#00b33c',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
'&:hover': { bgcolor: '#009933' }
}}
onClick={() => setErrorModalOpen(false)}
>
@ -221,9 +214,9 @@ function ForgotPassword() {
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
bgcolor: '#00b33c',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
'&:hover': { bgcolor: '#009933' }
}}
onClick={handleReturn}
>

View File

@ -3,20 +3,18 @@ import {
Box,
Button,
CircularProgress,
Container,
Grid,
Modal,
TextField,
Typography,
useTheme
} from '@mui/material';
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
import axiosConfig from 'src/Resources/axiosConfig';
import { UserContext } from 'src/contexts/userContext';
import { TokenContext } from 'src/contexts/tokenContext';
import { useNavigate } from 'react-router-dom';
import Avatar from '@mui/material/Avatar';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import inescologo from '../Resources/images/Logo.svg';
function ResetPassword() {
const [username, setUsername] = useState('');
@ -70,16 +68,6 @@ function ResetPassword() {
return (
<>
<Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid container>
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
<a href="https://monitor.innov.energy/">
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
</a>
</Grid>
</Grid>
</Container>
<Box
sx={{
marginTop: 8,
@ -97,7 +85,12 @@ function ResetPassword() {
transform: 'translate(-50%, -50%)'
}}
>
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
<a href="https://monitor.innov.energy/">
<img src={inescologo} alt="inescologo" height="100" />
</a>
</Box>
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
@ -137,7 +130,7 @@ function ResetPassword() {
/>
{loading && (
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '170px' }} />
<CircularProgress sx={{ color: '#00b33c', marginLeft: '170px' }} />
)}
{password != verifypassword && (
@ -155,8 +148,8 @@ function ResetPassword() {
mt: 3,
mb: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
'&:hover': { bgcolor: '#f7b34d' }
bgcolor: '#00b33c',
'&:hover': { bgcolor: '#009933' }
}}
variant="contained"
fullWidth={true}
@ -195,9 +188,9 @@ function ResetPassword() {
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
bgcolor: '#00b33c',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
'&:hover': { bgcolor: '#009933' }
}}
onClick={() => setOpen(false)}
>

View File

@ -3,20 +3,18 @@ import {
Box,
Button,
CircularProgress,
Container,
Grid,
Modal,
TextField,
Typography,
useTheme
} from '@mui/material';
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
import axiosConfig from 'src/Resources/axiosConfig';
import { UserContext } from 'src/contexts/userContext';
import { TokenContext } from 'src/contexts/tokenContext';
import { useNavigate } from 'react-router-dom';
import Avatar from '@mui/material/Avatar';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import inescologo from '../Resources/images/Logo.svg';
function SetNewPassword() {
const [username, setUsername] = useState('');
@ -71,16 +69,6 @@ function SetNewPassword() {
return (
<>
<Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid container>
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
<a href="https://monitor.innov.energy/">
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
</a>
</Grid>
</Grid>
</Container>
<Box
sx={{
marginTop: 8,
@ -98,7 +86,12 @@ function SetNewPassword() {
transform: 'translate(-50%, -50%)'
}}
>
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
<a href="https://monitor.innov.energy/">
<img src={inescologo} alt="inescologo" height="100" />
</a>
</Box>
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
@ -138,7 +131,7 @@ function SetNewPassword() {
/>
{loading && (
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '0px' }} />
<CircularProgress sx={{ color: '#00b33c', marginLeft: '0px' }} />
)}
{password != verifypassword && (
@ -156,8 +149,8 @@ function SetNewPassword() {
mt: 3,
mb: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
'&:hover': { bgcolor: '#f7b34d' }
bgcolor: '#00b33c',
'&:hover': { bgcolor: '#009933' }
}}
variant="contained"
fullWidth={true}
@ -196,9 +189,9 @@ function SetNewPassword() {
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
bgcolor: '#00b33c',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
'&:hover': { bgcolor: '#009933' }
}}
onClick={() => setOpen(false)}
>

View File

@ -4,7 +4,6 @@ import {
Button,
Checkbox,
CircularProgress,
Container,
FormControlLabel,
Grid,
Modal,
@ -15,7 +14,7 @@ import {
import Avatar from '@mui/material/Avatar';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Link from '@mui/material/Link';
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
import inescologo from 'src/Resources/images/Logo.svg';
import { axiosConfigWithoutToken } from 'src/Resources/axiosConfig';
import Cookies from 'universal-cookie';
import { UserContext } from 'src/contexts/userContext';
@ -102,16 +101,6 @@ function Login() {
return (
<div style={{ userSelect: 'none' }}>
<Container maxWidth="xl" sx={{ pt: 2 }} className="login">
<Grid container>
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
<a href="https://monitor.innov.energy/">
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
</a>
</Grid>
</Grid>
</Container>
<Box
sx={{
marginTop: 8,
@ -129,7 +118,12 @@ function Login() {
transform: 'translate(-50%, -50%)'
}}
>
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
<a href="https://monitor.innov.energy/">
<img src={inescologo} alt="inescologo" height="100" />
</a>
</Box>
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
@ -185,7 +179,7 @@ function Login() {
checked={rememberMe}
onChange={handleRememberMeChange}
icon={<CheckBoxOutlineBlankIcon style={{ color: 'grey' }} />}
checkedIcon={<CheckBoxIcon style={{ color: '#ffc04d' }} />}
checkedIcon={<CheckBoxIcon style={{ color: '#00b33c' }} />}
style={{ marginLeft: -175 }}
/>
}
@ -196,8 +190,8 @@ function Login() {
sx={{
mb: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
'&:hover': { bgcolor: '#f7b34d' }
bgcolor: '#00b33c',
'&:hover': { bgcolor: '#009933' }
}}
variant="contained"
fullWidth={true}
@ -210,7 +204,7 @@ function Login() {
{loading && (
<CircularProgress
sx={{
color: '#ffc04d',
color: '#009933',
marginLeft: '20px'
}}
/>
@ -245,9 +239,9 @@ function Login() {
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
bgcolor: '#00b33c',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
'&:hover': { bgcolor: '#009933' }
}}
onClick={() => setOpen(false)}
>

View File

@ -200,7 +200,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
defaultMessage="Installation Name"
/>
}
name="installationName"
name="name"
value={formValues.name}
onChange={handleChange}
variant="outlined"

View File

@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react';
import React, { useState } from 'react';
import {
Card,
CircularProgress,
@ -15,7 +15,6 @@ import {
import { I_Installation } from 'src/interfaces/InstallationTypes';
import CancelIcon from '@mui/icons-material/Cancel';
import BuildIcon from '@mui/icons-material/Build';
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import { FormattedMessage } from 'react-intl';
import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
@ -26,8 +25,6 @@ interface FlatInstallationViewProps {
const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [isRowHovered, setHoveredRow] = useState(-1);
const webSocketContext = useContext(WebSocketContext);
const { getStatus, getTestingMode } = webSocketContext;
const navigate = useNavigate();
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
const currentLocation = useLocation();
@ -35,8 +32,8 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const sortedInstallations = [...props.installations].sort((a, b) => {
// Compare the status field of each installation and sort them based on the status.
//Installations with alarms go first
let a_status = getStatus(a.id);
let b_status = getStatus(b.id);
let a_status = a.status;
let b_status = b.status;
if (a_status > b_status) {
return -1;
@ -120,7 +117,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const isInstallationSelected =
installation.s3BucketId === selectedInstallation;
const status = getStatus(installation.id);
const status = installation.status;
const rowStyles =
isRowHovered === installation.s3BucketId
? {
@ -244,7 +241,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
}}
/>
{getTestingMode(installation.id) && (
{installation.testingMode && (
<BuildIcon
style={{
width: '23px',

View File

@ -17,7 +17,6 @@ import {
extractValues,
TopologyValues
} from 'src/content/dashboards/Log/graph.util';
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import Topology from '../Topology/Topology';
import { FormattedMessage } from 'react-intl';
import Overview from '../Overview/overview';
@ -44,11 +43,9 @@ function Installation(props: singleInstallationProps) {
const location = useLocation().pathname;
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
const webSocketsContext = useContext(WebSocketContext);
const { getStatus, getTestingMode } = webSocketsContext;
const [currentTab, setCurrentTab] = useState<string>(undefined);
const [values, setValues] = useState<TopologyValues | null>(null);
const status = getStatus(props.current_installation.id);
const status = props.current_installation.status;
const [connected, setConnected] = useState(true);
const [loading, setLoading] = useState(true);
@ -138,14 +135,13 @@ function Installation(props: singleInstallationProps) {
}
}
if (i <= 0) {
if (i >= timeperiodToSearch) {
setConnected(false);
setLoading(false);
return false;
}
setConnected(true);
setLoading(false);
console.log('NUMBER OF FILES=' + Object.keys(res).length);
while (continueFetching.current) {
for (const timestamp of Object.keys(res)) {
@ -360,7 +356,7 @@ function Installation(props: singleInstallationProps) {
}}
/>
{getTestingMode(props.current_installation.id) && (
{props.current_installation.testingMode && (
<BuildIcon
style={{
width: '23px',
@ -456,7 +452,11 @@ function Installation(props: singleInstallationProps) {
<Route
path={routes.live}
element={
<Topology values={values} connected={connected}></Topology>
<Topology
values={values}
connected={connected}
loading={loading}
></Topology>
}
/>

View File

@ -12,7 +12,6 @@ import { FormattedMessage } from 'react-intl';
import { UserContext } from '../../../contexts/userContext';
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
import Installation from './Installation';
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
import { UserType } from '../../../interfaces/UserTypes';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
@ -33,14 +32,16 @@ function InstallationTabs() {
];
const [currentTab, setCurrentTab] = useState<string>(undefined);
const { salimaxInstallations, fetchAllInstallations } =
useContext(InstallationsContext);
const {
salimaxInstallations,
fetchAllInstallations,
currentProduct,
socket,
openSocket,
closeSocket
} = useContext(InstallationsContext);
const { product, setProduct } = useContext(ProductIdContext);
const webSocketsContext = useContext(WebSocketContext);
const { socket, openSocket, closeSocket } = webSocketsContext;
useEffect(() => {
let path = location.pathname.split('/');
@ -54,15 +55,15 @@ function InstallationTabs() {
}
}, [location]);
useEffect(() => {
if (salimaxInstallations && salimaxInstallations.length > 0) {
if (socket) {
closeSocket();
}
openSocket(salimaxInstallations);
}
}, [salimaxInstallations]);
// useEffect(() => {
// if (salimaxInstallations && salimaxInstallations.length > 0) {
// if (socket) {
// closeSocket();
// }
//
// openSocket(salimaxInstallations);
// }
// }, [salimaxInstallations]);
useEffect(() => {
if (salimaxInstallations.length === 0) {
@ -70,6 +71,17 @@ function InstallationTabs() {
}
}, [salimaxInstallations]);
useEffect(() => {
if (salimaxInstallations && salimaxInstallations.length > 0) {
if (!socket) {
openSocket(0);
} else if (currentProduct == 1) {
closeSocket();
openSocket(0);
}
}
}, [salimaxInstallations, currentProduct]);
useEffect(() => {
setProduct(0);
}, []);
@ -378,7 +390,11 @@ function InstallationTabs() {
return salimaxInstallations.length > 1 ? (
<>
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<Container
maxWidth="xl"
sx={{ marginLeft: '40px', marginTop: '20px' }}
className="mainframe"
>
<TabsContainerWrapper>
<Tabs
onChange={handleTabsChange}
@ -440,7 +456,11 @@ function InstallationTabs() {
</>
) : salimaxInstallations.length === 1 ? (
<>
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<Container
maxWidth="xl"
sx={{ marginLeft: '40px', marginTop: '20px' }}
className="mainframe"
>
<TabsContainerWrapper>
<Tabs
onChange={handleTabsChange}

View File

@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react';
import React, { useState } from 'react';
import {
Card,
CircularProgress,
@ -14,7 +14,6 @@ import {
useTheme
} from '@mui/material';
import { I_Installation } from 'src/interfaces/InstallationTypes';
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import { FormattedMessage } from 'react-intl';
import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
@ -26,17 +25,17 @@ interface FlatInstallationViewProps {
}
const FlatInstallationView = (props: FlatInstallationViewProps) => {
const webSocketContext = useContext(WebSocketContext);
const { getStatus, getTestingMode } = webSocketContext;
// const webSocketContext = useContext(WebSocketContext);
// const { getSortedInstallations } = webSocketContext;
const navigate = useNavigate();
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
const currentLocation = useLocation();
//
const sortedInstallations = [...props.installations].sort((a, b) => {
// Compare the status field of each installation and sort them based on the status.
//Installations with alarms go first
let a_status = getStatus(a.id);
let b_status = getStatus(b.id);
let a_status = a.status;
let b_status = b.status;
if (a_status > b_status) {
return -1;
@ -47,6 +46,16 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
return 0;
});
// const sortedInstallations = useMemo(() => {
// return [...props.installations].sort((a, b) => {
// const a_status = getStatus(a.id) || 0;
// const b_status = getStatus(b.id) || 0;
// return b_status - a_status;
// });
// }, [props.installations, getStatus]);
// const sortedInstallations = getSortedInstallations();
const handleSelectOneInstallation = (installationID: number): void => {
if (selectedInstallation != installationID) {
setSelectedInstallation(installationID);
@ -124,7 +133,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const isInstallationSelected =
installation.s3BucketId === selectedInstallation;
const status = getStatus(installation.id);
const status = installation.status;
return (
<HoverableTableRow
@ -306,7 +315,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
: 'green'
}}
/>
{getTestingMode(installation.id) && (
{installation.testingMode && (
<BuildIcon
style={{
width: '23px',
@ -315,7 +324,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
borderRadius: '50%',
position: 'relative',
zIndex: 1,
marginLeft: '15px'
marginLeft: status != -1 ? '25px' : '0px'
}}
/>
)}

View File

@ -14,7 +14,6 @@ import {
extractValues,
TopologyValues
} from 'src/content/dashboards/Log/graph.util';
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import { FormattedMessage } from 'react-intl';
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
@ -40,11 +39,9 @@ function SalidomoInstallation(props: singleInstallationProps) {
const { currentUser } = context;
const location = useLocation().pathname;
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
const webSocketsContext = useContext(WebSocketContext);
const { getStatus, getTestingMode } = webSocketsContext;
const [currentTab, setCurrentTab] = useState<string>(undefined);
const [values, setValues] = useState<TopologyValues | null>(null);
const status = getStatus(props.current_installation.id);
const status = props.current_installation.status;
const [
failedToCommunicateWithInstallation,
setFailedToCommunicateWithInstallation
@ -181,7 +178,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
}, [currentTab, location]);
useEffect(() => {
if (status === -1) {
if (status === null) {
setConnected(false);
}
}, [status]);
@ -281,7 +278,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
}}
/>
{getTestingMode(props.current_installation.id) && (
{props.current_installation.testingMode && (
<BuildIcon
style={{
width: '23px',

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import FlatInstallationView from './FlatInstallationView';
@ -14,17 +14,25 @@ interface installationSearchProps {
function InstallationSearch(props: installationSearchProps) {
const [searchTerm, setSearchTerm] = useState('');
const currentLocation = useLocation();
const [filteredData, setFilteredData] = useState(props.installations);
// const [filteredData, setFilteredData] = useState(props.installations);
useEffect(() => {
const filtered = props.installations.filter(
const indexedData = useMemo(() => {
return props.installations.map((item) => ({
...item,
nameLower: item.name.toLowerCase(),
locationLower: item.location.toLowerCase(),
regionLower: item.region.toLowerCase()
}));
}, [props.installations]);
const filteredData = useMemo(() => {
return indexedData.filter(
(item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.location.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.region.toLowerCase().includes(searchTerm.toLowerCase())
item.nameLower.includes(searchTerm.toLowerCase()) ||
item.locationLower.includes(searchTerm.toLowerCase()) ||
item.regionLower.includes(searchTerm.toLowerCase())
);
setFilteredData(filtered);
}, [searchTerm, props.installations]);
}, [searchTerm, indexedData]);
return (
<>

View File

@ -7,7 +7,6 @@ import InstallationSearch from './InstallationSearch';
import { FormattedMessage } from 'react-intl';
import { UserContext } from '../../../contexts/userContext';
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
import ListIcon from '@mui/icons-material/List';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import TreeView from '../Tree/treeView';
@ -31,13 +30,18 @@ function SalidomoInstallationTabs() {
const [currentTab, setCurrentTab] = useState<string>(undefined);
const [fetchedInstallations, setFetchedInstallations] =
useState<boolean>(false);
const { salidomoInstallations, fetchAllSalidomoInstallations } =
useContext(InstallationsContext);
const {
salidomoInstallations,
fetchAllSalidomoInstallations,
currentProduct,
socket,
openSocket,
closeSocket
} = useContext(InstallationsContext);
const { product, setProduct } = useContext(ProductIdContext);
const webSocketsContext = useContext(WebSocketContext);
const { socket, openSocket, closeSocket } = webSocketsContext;
// const webSocketsContext = useContext(WebSocketContext);
// const { socket, openSocket, closeSocket } = webSocketsContext;
useEffect(() => {
let path = location.pathname.split('/');
@ -61,10 +65,12 @@ function SalidomoInstallationTabs() {
useEffect(() => {
if (salidomoInstallations && salidomoInstallations.length > 0) {
if (socket) {
if (!socket) {
openSocket(1);
} else if (currentProduct == 0) {
closeSocket();
openSocket(1);
}
openSocket(salidomoInstallations);
}
}, [salidomoInstallations]);
@ -270,7 +276,11 @@ function SalidomoInstallationTabs() {
return salidomoInstallations.length > 1 ? (
<>
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<Container
maxWidth="xl"
sx={{ marginLeft: '40px', marginTop: '20px' }}
className="mainframe"
>
<TabsContainerWrapper>
<Tabs
onChange={handleTabsChange}
@ -336,7 +346,11 @@ function SalidomoInstallationTabs() {
) : salidomoInstallations.length === 1 ? (
<>
{' '}
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<Container
maxWidth="xl"
sx={{ marginLeft: '40px', marginTop: '20px' }}
className="mainframe"
>
<TabsContainerWrapper>
<Tabs
onChange={handleTabsChange}

View File

@ -16,6 +16,7 @@ import {
interface TopologyProps {
values: TopologyValues;
connected: boolean;
loading: boolean;
}
function Topology(props: TopologyProps) {
@ -35,7 +36,7 @@ function Topology(props: TopologyProps) {
return (
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
<Grid container>
{!props.connected && (
{!props.connected && !props.loading && (
<Container
maxWidth="xl"
sx={{

View File

@ -7,7 +7,6 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
import Typography from '@mui/material/Typography';
import { makeStyles } from '@mui/styles';
import CancelIcon from '@mui/icons-material/Cancel';
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import routes from 'src/Resources/routes.json';
import { useLocation, useNavigate } from 'react-router-dom';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
@ -39,9 +38,7 @@ const useTreeItemStyles = makeStyles((theme) => ({
function CustomTreeItem(props: CustomTreeItemProps) {
const theme = useTheme();
const classes = useTreeItemStyles();
const webSocketContext = useContext(WebSocketContext);
const { getStatus } = webSocketContext;
const status = getStatus(props.node.id);
const status = props.node.status;
const navigate = useNavigate();
const [selected, setSelected] = useState(false);
const currentLocation = useLocation();

View File

@ -8,7 +8,6 @@ import Installation from '../Installations/Installation';
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
import { Route, Routes } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
import SalidomoInstallation from '../SalidomoInstallations/Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import Folder from './Folder';
@ -16,12 +15,8 @@ import Folder from './Folder';
function InstallationTree() {
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
useContext(InstallationsContext);
const { product, setProduct } = useContext(ProductIdContext);
const webSocketContext = useContext(WebSocketContext);
const { getStatus } = webSocketContext;
const sortedInstallations = [...foldersAndInstallations].sort((a, b) => {
// Compare the status field of each installation and sort them based on the status.
//Installations with alarms go first
@ -29,8 +24,8 @@ function InstallationTree() {
if (a.type == 'Folder') {
return -1;
}
let a_status = getStatus(a.id);
let b_status = getStatus(b.id);
let a_status = a.status;
let b_status = b.status;
if (a_status > b_status) {
return -1;

View File

@ -4,6 +4,9 @@ import {
ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import axiosConfig from 'src/Resources/axiosConfig';
@ -12,48 +15,7 @@ import { TokenContext } from './tokenContext';
import routes from '../Resources/routes.json';
import { useNavigate } from 'react-router-dom';
interface I_InstallationContextProviderProps {
salimaxInstallations: I_Installation[];
salidomoInstallations: I_Installation[];
foldersAndInstallations: I_Installation[];
fetchAllInstallations: () => Promise<void>;
fetchAllSalidomoInstallations: () => Promise<void>;
fetchAllFoldersAndInstallations: (product: number) => Promise<void>;
createInstallation: (value: Partial<I_Installation>) => Promise<void>;
updateInstallation: (value: I_Installation, view: string) => Promise<void>;
loading: boolean;
setLoading: (value: boolean) => void;
error: boolean;
setError: (value: boolean) => void;
updated: boolean;
setUpdated: (value: boolean) => void;
deleteInstallation: (value: I_Installation, view: string) => Promise<void>;
createFolder: (value: Partial<I_Folder>, product: number) => Promise<void>;
updateFolder: (value: I_Folder, product: number) => Promise<void>;
deleteFolder: (value: I_Folder, product: number) => Promise<void>;
}
export const InstallationsContext =
createContext<I_InstallationContextProviderProps>({
salimaxInstallations: [],
salidomoInstallations: [],
foldersAndInstallations: [],
fetchAllInstallations: () => Promise.resolve(),
fetchAllSalidomoInstallations: () => Promise.resolve(),
fetchAllFoldersAndInstallations: (product: number) => Promise.resolve(),
createInstallation: () => Promise.resolve(),
updateInstallation: () => Promise.resolve(),
loading: false,
setLoading: () => {},
error: false,
setError: () => {},
updated: false,
setUpdated: () => {},
deleteInstallation: () => Promise.resolve(),
createFolder: () => Promise.resolve(),
updateFolder: () => Promise.resolve(),
deleteFolder: () => Promise.resolve()
});
export const InstallationsContext = createContext(null);
const InstallationsContextProvider = ({
children
@ -73,224 +35,286 @@ const InstallationsContextProvider = ({
const navigate = useNavigate();
const tokencontext = useContext(TokenContext);
const { removeToken } = tokencontext;
const [socket, setSocket] = useState<WebSocket>(null);
const [currentProduct, setcurrentProduct] = useState<number>(0);
const pendingUpdates = useRef<
Record<number, { status: number; testingMode: boolean }>
>({}); // To store pending updates
const updateInstallationStatus = useCallback(
(product, id, status, testingMode) => {
// Buffer updates instead of applying them immediately
pendingUpdates.current[id] = { status: Number(status), testingMode }; // Ensure status is a number
},
[]
);
const applyBatchUpdates = useCallback(() => {
if (Object.keys(pendingUpdates.current).length === 0) return; // No updates to apply
const updatedSalidomo = salidomoInstallations.map((installation) => {
const update = pendingUpdates.current[installation.id];
return update
? {
...installation,
status: update.status,
testingMode: update.testingMode
}
: installation;
});
const updatedSalimax = salimaxInstallations.map((installation) => {
const update = pendingUpdates.current[installation.id];
return update
? {
...installation,
status: update.status,
testingMode: update.testingMode
}
: installation;
});
setSalidomoInstallations(updatedSalidomo);
setSalimaxInstallations(updatedSalimax);
// Clear the pending updates after applying
pendingUpdates.current = {};
}, [salidomoInstallations, salimaxInstallations]);
useEffect(() => {
const timer = setInterval(() => {
applyBatchUpdates();
}, 60000);
return () => clearInterval(timer); // Cleanup timer on component unmount
}, [applyBatchUpdates]);
const openSocket = (product) => {
setcurrentProduct(product);
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', () => {
socket.send(
JSON.stringify(
product === 1
? salidomoInstallations.map((installation) => installation.id)
: salimaxInstallations.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);
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data); // Parse the JSON data
if (message.id !== -1) {
updateInstallationStatus(
product,
message.id,
message.status,
message.testingMode
);
}
});
socket.addEventListener('close', () => {
clearInterval(pingInterval); // Cleanup ping interval on socket close
setSocket(null);
});
setSocket(socket);
};
const closeSocket = () => socket?.close();
const fetchAllInstallations = useCallback(async () => {
let isMounted = true;
axiosConfig
.get('/GetAllInstallations', {})
.then((res: AxiosResponse<I_Installation[]>) => {
setSalimaxInstallations(res.data);
})
.get('/GetAllInstallations')
.then((res: AxiosResponse<I_Installation[]>) =>
setSalimaxInstallations(res.data)
)
.catch((err: AxiosError) => {
if (err.response && err.response.status == 401) {
if (err.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
}, []);
}, [navigate, removeToken]);
const fetchAllSalidomoInstallations = useCallback(async () => {
let isMounted = true;
axiosConfig
.get('/GetAllSalidomoInstallations', {})
.then((res: AxiosResponse<I_Installation[]>) => {
setSalidomoInstallations(res.data);
})
.get('/GetAllSalidomoInstallations')
.then((res: AxiosResponse<I_Installation[]>) =>
setSalidomoInstallations(res.data)
)
.catch((err: AxiosError) => {
if (err.response && err.response.status == 401) {
if (err.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
}, []);
}, [navigate, removeToken]);
const fetchAllFoldersAndInstallations = useCallback(
async (product: number) => {
return axiosConfig
axiosConfig
.get(`/GetAllFoldersAndInstallations?productId=${product}`)
.then((res) => {
setFoldersAndInstallations(res.data);
})
.then((res) => setFoldersAndInstallations(res.data))
.catch((err) => {
if (err.response && err.response.status == 401) {
if (err.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[navigate, removeToken]
);
const createInstallation = useCallback(
async (formValues: Partial<I_Installation>) => {
axiosConfig
.post('/CreateInstallation', formValues)
.then((res) => {
setLoading(false);
fetchAllFoldersAndInstallations(formValues.product);
// if (formValues.product == 0) {
// fetchAllFoldersAndInstallations();
// } else {
// fetchAllSalidomoInstallations();
// }
})
.then(() => fetchAllFoldersAndInstallations(formValues.product))
.catch((error) => {
setLoading(false);
setError(true);
if (error.response && error.response.status == 401) {
if (error.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[fetchAllFoldersAndInstallations, navigate, removeToken]
);
const updateInstallation = useCallback(
async (formValues: I_Installation, view: string) => {
axiosConfig
.put('/UpdateInstallation', formValues)
.then((response) => {
if (response) {
setLoading(false);
.then(() => {
setUpdated(true);
if (formValues.product == 0 && view == 'installation') {
if (formValues.product === 0 && view === 'installation')
fetchAllInstallations();
} else if (formValues.product == 1 && view == 'installation') {
else if (formValues.product === 1 && view === 'installation')
fetchAllSalidomoInstallations();
} else {
fetchAllFoldersAndInstallations(formValues.product);
}
setTimeout(() => {
setUpdated(false);
}, 3000);
}
else fetchAllFoldersAndInstallations(formValues.product);
setTimeout(() => setUpdated(false), 3000);
})
.catch((error) => {
setLoading(false);
setError(true);
if (error.response && error.response.status == 401) {
if (error.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[
fetchAllFoldersAndInstallations,
fetchAllInstallations,
fetchAllSalidomoInstallations,
navigate,
removeToken
]
);
const deleteInstallation = useCallback(
async (formValues: I_Installation, view: string) => {
axiosConfig
.delete(`/DeleteInstallation?installationId=${formValues.id}`)
.then((response) => {
if (response) {
setLoading(false);
.then(() => {
setUpdated(true);
if (formValues.product == 0 && view == 'installation') {
if (formValues.product === 0 && view === 'installation')
fetchAllInstallations();
} else if (formValues.product == 1 && view == 'installation') {
else if (formValues.product === 1 && view === 'installation')
fetchAllSalidomoInstallations();
} else {
fetchAllFoldersAndInstallations(formValues.product);
}
setTimeout(() => {
setUpdated(false);
}, 3000);
}
else fetchAllFoldersAndInstallations(formValues.product);
setTimeout(() => setUpdated(false), 3000);
})
.catch((error) => {
setLoading(false);
setError(true);
if (error.response && error.response.status == 401) {
if (error.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[
fetchAllFoldersAndInstallations,
fetchAllInstallations,
fetchAllSalidomoInstallations,
navigate,
removeToken
]
);
const createFolder = useCallback(
async (formValues: Partial<I_Folder>, product: number) => {
axiosConfig
.post('/CreateFolder', formValues)
.then((res) => {
setLoading(false);
fetchAllFoldersAndInstallations(product);
})
.then(() => fetchAllFoldersAndInstallations(product))
.catch((error) => {
setLoading(false);
setError(true);
if (error.response && error.response.status == 401) {
if (error.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[fetchAllFoldersAndInstallations, navigate, removeToken]
);
const updateFolder = useCallback(
async (formValues: I_Folder, product: number) => {
axiosConfig
.put('/UpdateFolder', formValues)
.then((response) => {
if (response) {
setLoading(false);
.then(() => {
setUpdated(true);
fetchAllFoldersAndInstallations(product);
setTimeout(() => {
setUpdated(false);
}, 3000);
}
setTimeout(() => setUpdated(false), 3000);
})
.catch((error) => {
setLoading(false);
setError(true);
if (error.response && error.response.status == 401) {
if (error.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[fetchAllFoldersAndInstallations, navigate, removeToken]
);
const deleteFolder = useCallback(
async (formValues: I_Folder, product: number) => {
axiosConfig
.delete(`/DeleteFolder?folderId=${formValues.id}`)
.then((response) => {
if (response) {
setLoading(false);
.then(() => {
setUpdated(true);
fetchAllFoldersAndInstallations(product);
setTimeout(() => {
setUpdated(false);
}, 3000);
}
setTimeout(() => setUpdated(false), 3000);
})
.catch((error) => {
setLoading(false);
setError(true);
if (error.response && error.response.status == 401) {
if (error.response?.status === 401) {
removeToken();
navigate(routes.login);
}
});
},
[]
[fetchAllFoldersAndInstallations, navigate, removeToken]
);
return (
<InstallationsContext.Provider
value={{
const contextValue = useMemo(
() => ({
salimaxInstallations,
salidomoInstallations,
foldersAndInstallations,
@ -308,9 +332,26 @@ const InstallationsContextProvider = ({
deleteInstallation,
createFolder,
updateFolder,
deleteFolder
}}
>
deleteFolder,
currentProduct,
socket,
openSocket,
closeSocket
}),
[
salimaxInstallations,
salidomoInstallations,
foldersAndInstallations,
loading,
error,
updated,
socket,
currentProduct
]
);
return (
<InstallationsContext.Provider value={contextValue}>
{children}
</InstallationsContext.Provider>
);

View File

@ -1,4 +1,10 @@
import { createContext, ReactNode, useEffect, useState } from 'react';
import {
createContext,
ReactNode,
useCallback,
useEffect,
useState
} from 'react';
import { I_Installation } from '../interfaces/InstallationTypes';
// Define the shape of the context
@ -6,8 +12,7 @@ interface WebSocketContextProviderProps {
socket: WebSocket;
openSocket: (installations: I_Installation[]) => void;
closeSocket: () => void;
getStatus: (installationId: number) => number;
getTestingMode: (installationId: number) => boolean;
getSortedInstallations: () => I_Installation[];
}
// Create the context.
@ -17,118 +22,165 @@ export const WebSocketContext = createContext<
const WebSocketContextProvider = ({ children }: { children: ReactNode }) => {
const [socket, setSocket] = useState<WebSocket>(null);
const [installations, setInstallations] = useState<I_Installation[]>(null);
const [installationStatus, setInstallationStatus] = useState(new Map());
const [installationMode, setInstallationMode] = useState(new Map());
// const [installations, setInstallations] = useState<I_Installation[]>(null);
const [sortedInstallations, setSortedInstallations] = useState<
I_Installation[]
>([]);
// const [installationStatus, setInstallationStatus] = useState(new Map());
// const [installationMode, setInstallationMode] = useState(new Map());
const BUFFER_LENGTH = 5;
const updateInstallationStatus = useCallback(
(id, newStatus, newTestingMode) => {
setSortedInstallations((prevInstallations) => {
const installationIndex = prevInstallations.findIndex(
(inst) => inst.id === id
);
if (installationIndex === -1) return prevInstallations; // Installation not found
const updatedInstallation = {
...prevInstallations[installationIndex],
status: newStatus,
testingMode: newTestingMode
};
const newInstallations = [...prevInstallations];
newInstallations[installationIndex] = updatedInstallation;
// Sort the installations based on the new status
return newInstallations.sort((a, b) => b.status - a.status);
});
},
[]
);
useEffect(() => {
if (installations) {
const tokenString = localStorage.getItem('token');
const token = tokenString !== null ? tokenString : '';
// 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 urlWithToken = `ws://localhost:7087/api/CreateWebSocket?authToken=${token}`;
const urlWithToken = `wss://monitor.innov.energy/api/CreateWebSocket?authToken=${token}`;
//const socket = new WebSocket('wss://monitor.innov.energy/websocket');
// const openSocket = (installations: I_Installation[]) => {
// setInstallations(installations);
// };
const socket = new WebSocket(urlWithToken);
// Connection opened
socket.addEventListener('open', (event) => {
socket.send(
JSON.stringify(installations.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)) {
// 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);
}
}, [installations]);
const openSocket = (installations: I_Installation[]) => {
setInstallations(installations);
const openSocket = (installations) => {
// setSortedInstallations(installations.sort((a, b) => b.status - a.status)); // Sort installations by status
};
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 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);
};
// const getTestingMode = (installationId: number) => {
// return installationMode.get(installationId);
// };
return (
<WebSocketContext.Provider
@ -136,8 +188,7 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => {
socket: socket,
openSocket: openSocket,
closeSocket: closeSocket,
getStatus: getStatus,
getTestingMode: getTestingMode
getSortedInstallations: () => sortedInstallations
}}
>
{children}

View File

@ -18,7 +18,8 @@ export interface I_Installation extends I_S3Credentials {
s3WriteSecret: string;
product: number;
device: number;
testingMode: boolean;
testingMode?: boolean;
status?: number;
}
export interface I_Folder {
@ -28,5 +29,6 @@ export interface I_Folder {
parentId: number;
type: string;
s3BucketId: number;
status?: number;
children?: (I_Installation | I_Folder)[];
}

View File

@ -18,8 +18,9 @@ import { FormattedMessage } from 'react-intl';
import { TokenContext } from 'src/contexts/tokenContext';
import { useNavigate } from 'react-router-dom';
import routes from 'src/Resources/routes.json';
import { WebSocketContext } from '../../../../contexts/WebSocketContextProvider';
import '../../../../App.css';
import { InstallationsContext } from '../../../../contexts/InstallationsContextProvider';
const UserBoxButton = styled(Button)(
({ theme }) => `
@ -65,8 +66,7 @@ function HeaderUserbox() {
const tokencontext = useContext(TokenContext);
const { token, setNewToken, removeToken } = tokencontext;
const navigate = useNavigate();
const webSocketsContext = useContext(WebSocketContext);
const { closeSocket } = webSocketsContext;
const { closeSocket } = useContext(InstallationsContext);
const handleSubmit = () => {
closeSocket();

View File

@ -1,7 +1,6 @@
import { useContext } from 'react';
import Scrollbar from 'src/components/Scrollbar';
import { SidebarContext } from 'src/contexts/SidebarContext';
import innovenergyLogo from 'src/Resources/images/innovenergy-Logo_Speichern-mit-Salz_R_color.svg';
import {
alpha,
Box,
@ -14,10 +13,11 @@ import {
} from '@mui/material';
import SidebarMenu from './SidebarMenu';
import Logo from 'src/Resources/images/Logo_for_dark_bg.svg';
const SidebarWrapper = styled(Box)(
({ theme }) => `
width: ${theme.sidebar.width};
width : 250px;
min-width: ${theme.sidebar.width};
color: ${theme.colors.alpha.trueWhite[70]};
position: relative;
@ -60,10 +60,11 @@ function Sidebar() {
}}
>
<img
src={innovenergyLogo}
alt="innovenergy logo"
src={Logo}
alt="inesco logo"
style={{
width: '150px' // Width of the image
marginLeft: '30px',
width: '150px'
}}
/>
</Box>
@ -105,8 +106,8 @@ function Sidebar() {
}}
>
<img
src={innovenergyLogo}
alt="innovenergy logo"
src={Logo}
alt="innesco logo"
style={{
width: '150px' // Width of the image
}}