Updated backend to provide support for the overview tab
Updated frontend to parse chunks
This commit is contained in:
parent
e3e9817f2b
commit
abe69193e2
|
@ -137,6 +137,72 @@ public class Controller : ControllerBase
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet(nameof(GetCsvTimestampsForInstallation))]
|
||||||
|
public ActionResult<IEnumerable<CsvTimestamp>> GetCsvTimestampsForInstallation(Int64 id, Int32 start, Int32 end, Token authToken)
|
||||||
|
{
|
||||||
|
var user = Db.GetSession(authToken)?.User;
|
||||||
|
if (user == null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var installation = Db.GetInstallationById(id);
|
||||||
|
|
||||||
|
if (installation is null || !user.HasAccessTo(installation))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var sampleSize = 100;
|
||||||
|
var allTimestamps = new List<CsvTimestamp>();
|
||||||
|
|
||||||
|
if (start != 0 && end != 0)
|
||||||
|
{
|
||||||
|
allTimestamps = Db.CsvTimestamps
|
||||||
|
.Where(csvTimestamp => csvTimestamp.InstallationId == id && csvTimestamp.Timestamp > start && csvTimestamp.Timestamp < end)
|
||||||
|
.OrderBy(csvTimestamp => csvTimestamp.Timestamp).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allTimestamps = Db.CsvTimestamps
|
||||||
|
.Where(csvTimestamp => csvTimestamp.InstallationId == id)
|
||||||
|
.OrderBy(csvTimestamp => csvTimestamp.Timestamp).ToList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalRecords = allTimestamps.Count;
|
||||||
|
if (totalRecords <= sampleSize)
|
||||||
|
{
|
||||||
|
// If the total records are less than or equal to the sample size, return all records
|
||||||
|
Console.WriteLine("Start timestamp = "+start +" end timestamp = "+end);
|
||||||
|
Console.WriteLine("SampledTimestamps = " + allTimestamps.Count);
|
||||||
|
return allTimestamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int interval = totalRecords / sampleSize;
|
||||||
|
var sampledTimestamps = new List<CsvTimestamp>();
|
||||||
|
|
||||||
|
for (int i = 0; i < totalRecords; i += interval)
|
||||||
|
{
|
||||||
|
sampledTimestamps.Add(allTimestamps[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we haven't picked enough records (due to rounding), add the latest record to ensure completeness
|
||||||
|
if (sampledTimestamps.Count < sampleSize)
|
||||||
|
{
|
||||||
|
sampledTimestamps.Add(allTimestamps.Last());
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Start timestamp = "+start +" end timestamp = "+end);
|
||||||
|
Console.WriteLine("TotalRecords = "+totalRecords + " interval = "+ interval);
|
||||||
|
Console.WriteLine("SampledTimestamps = " + sampledTimestamps.Count);
|
||||||
|
|
||||||
|
return sampledTimestamps;
|
||||||
|
|
||||||
|
// return Db.CsvTimestamps
|
||||||
|
// .Where(csvTimestamp => csvTimestamp.InstallationId == id)
|
||||||
|
// .OrderByDescending(csvTimestamp => csvTimestamp.Timestamp)
|
||||||
|
// .ToList();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet(nameof(GetUserById))]
|
[HttpGet(nameof(GetUserById))]
|
||||||
public ActionResult<User> GetUserById(Int64 id, Token authToken)
|
public ActionResult<User> GetUserById(Int64 id, Token authToken)
|
||||||
{
|
{
|
||||||
|
@ -556,6 +622,7 @@ public class Controller : ControllerBase
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
var installationToUpdate = Db.GetInstallationById(installationId);
|
var installationToUpdate = Db.GetInstallationById(installationId);
|
||||||
|
|
||||||
|
|
||||||
if (installationToUpdate != null)
|
if (installationToUpdate != null)
|
||||||
{
|
{
|
||||||
_ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode,version);
|
_ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode,version);
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.Backend.DataTypes;
|
||||||
|
|
||||||
|
public class CsvTimestamp{
|
||||||
|
[PrimaryKey, AutoIncrement]
|
||||||
|
public Int64 Id { get; set; }
|
||||||
|
public Int64 InstallationId { get; set; }
|
||||||
|
public Int32 Timestamp { get; set; }
|
||||||
|
|
||||||
|
}
|
|
@ -32,6 +32,11 @@ public static partial class Db
|
||||||
return Insert(warning);
|
return Insert(warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Boolean Create(CsvTimestamp csvTimestamp)
|
||||||
|
{
|
||||||
|
return Insert(csvTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
public static Boolean Create(Folder folder)
|
public static Boolean Create(Folder folder)
|
||||||
{
|
{
|
||||||
return Insert(folder);
|
return Insert(folder);
|
||||||
|
@ -144,4 +149,35 @@ public static partial class Db
|
||||||
Create(newWarning);
|
Create(newWarning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddCsvTimestamp(CsvTimestamp newCsvTimestamp,int installationId)
|
||||||
|
{
|
||||||
|
var maxCSvPerInstallation = 2 * 60 * 24;
|
||||||
|
//Find the total number of warnings for this installation
|
||||||
|
var totalCsvNames = CsvTimestamps.Count(csvTimestamp => csvTimestamp.InstallationId == installationId);
|
||||||
|
|
||||||
|
//If there are 100 warnings, remove the one with the oldest timestamp
|
||||||
|
if (totalCsvNames == maxCSvPerInstallation)
|
||||||
|
{
|
||||||
|
var oldestCSvTimestamp =
|
||||||
|
CsvTimestamps.Where(csvTimestamp => csvTimestamp.InstallationId == installationId)
|
||||||
|
.OrderBy(csvTimestamp => csvTimestamp.Timestamp)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
//Remove the old error
|
||||||
|
Delete(oldestCSvTimestamp);
|
||||||
|
|
||||||
|
//Add the new error
|
||||||
|
Create(newCsvTimestamp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("---------------Added the new Csv Timestamp to the database-----------------");
|
||||||
|
Create(newCsvTimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -37,6 +37,7 @@ public static partial class Db
|
||||||
fileConnection.CreateTable<Error>();
|
fileConnection.CreateTable<Error>();
|
||||||
fileConnection.CreateTable<Warning>();
|
fileConnection.CreateTable<Warning>();
|
||||||
fileConnection.CreateTable<UserAction>();
|
fileConnection.CreateTable<UserAction>();
|
||||||
|
fileConnection.CreateTable<CsvTimestamp>();
|
||||||
|
|
||||||
return fileConnection;
|
return fileConnection;
|
||||||
//return CopyDbToMemory(fileConnection);
|
//return CopyDbToMemory(fileConnection);
|
||||||
|
@ -57,6 +58,7 @@ public static partial class Db
|
||||||
memoryConnection.CreateTable<Error>();
|
memoryConnection.CreateTable<Error>();
|
||||||
memoryConnection.CreateTable<Warning>();
|
memoryConnection.CreateTable<Warning>();
|
||||||
fileConnection.CreateTable<UserAction>();
|
fileConnection.CreateTable<UserAction>();
|
||||||
|
fileConnection.CreateTable<CsvTimestamp>();
|
||||||
|
|
||||||
//Copy all the existing tables from the disk to main memory
|
//Copy all the existing tables from the disk to main memory
|
||||||
fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
|
fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
|
||||||
|
@ -69,6 +71,7 @@ public static partial class Db
|
||||||
fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
|
fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
|
||||||
fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
|
fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
|
||||||
fileConnection.Table<UserAction>().ForEach(memoryConnection.Insert);
|
fileConnection.Table<UserAction>().ForEach(memoryConnection.Insert);
|
||||||
|
fileConnection.Table<CsvTimestamp>().ForEach(memoryConnection.Insert);
|
||||||
|
|
||||||
return memoryConnection;
|
return memoryConnection;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +92,7 @@ public static partial class Db
|
||||||
public static TableQuery<Error> Errors => Connection.Table<Error>();
|
public static TableQuery<Error> Errors => Connection.Table<Error>();
|
||||||
public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
|
public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
|
||||||
public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
|
public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
|
||||||
|
public static TableQuery<CsvTimestamp> CsvTimestamps => Connection.Table<CsvTimestamp>();
|
||||||
|
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
|
@ -111,6 +115,7 @@ public static partial class Db
|
||||||
Connection.CreateTable<Error>();
|
Connection.CreateTable<Error>();
|
||||||
Connection.CreateTable<Warning>();
|
Connection.CreateTable<Warning>();
|
||||||
Connection.CreateTable<UserAction>();
|
Connection.CreateTable<UserAction>();
|
||||||
|
Connection.CreateTable<CsvTimestamp>();
|
||||||
});
|
});
|
||||||
|
|
||||||
//UpdateKeys();
|
//UpdateKeys();
|
||||||
|
|
|
@ -78,6 +78,20 @@ public static partial class Db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Boolean Delete(CsvTimestamp csvTimestampToDelete)
|
||||||
|
{
|
||||||
|
var deleteSuccess = RunTransaction(DeleteCsvTimestampToDelete);
|
||||||
|
if (deleteSuccess)
|
||||||
|
BackupDatabase();
|
||||||
|
return deleteSuccess;
|
||||||
|
|
||||||
|
|
||||||
|
Boolean DeleteCsvTimestampToDelete()
|
||||||
|
{
|
||||||
|
return CsvTimestamps.Delete(csvTimestamp => csvTimestamp.Id == csvTimestampToDelete.Id) >0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Boolean Delete(Installation installation)
|
public static Boolean Delete(Installation installation)
|
||||||
{
|
{
|
||||||
var deleteSuccess = RunTransaction(DeleteInstallationAndItsDependencies);
|
var deleteSuccess = RunTransaction(DeleteInstallationAndItsDependencies);
|
||||||
|
|
|
@ -68,7 +68,18 @@ public static class RabbitMqManager
|
||||||
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
|
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
|
||||||
if (receivedStatusMessage.Type == MessageType.Heartbit)
|
if (receivedStatusMessage.Type == MessageType.Heartbit)
|
||||||
{
|
{
|
||||||
Console.WriteLine("This is a heartbit message from installation: " + installationId);
|
Console.WriteLine("This is a heartbit message from installation: " + installationId + " Name of the file is "+ receivedStatusMessage.Timestamp);
|
||||||
|
|
||||||
|
if (receivedStatusMessage.Timestamp != 0)
|
||||||
|
{
|
||||||
|
CsvTimestamp newCsvTimestamp = new CsvTimestamp
|
||||||
|
{
|
||||||
|
InstallationId = installationId,
|
||||||
|
Timestamp = receivedStatusMessage.Timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
Db.AddCsvTimestamp(newCsvTimestamp, installationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -183,51 +194,4 @@ public static class RabbitMqManager
|
||||||
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
|
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InformInstallationsToSubscribeToRabbitMq()
|
|
||||||
{
|
|
||||||
var installationIps = Db.Installations.Select(inst => inst.VpnIp).ToList();
|
|
||||||
Console.WriteLine("Count is "+installationIps.Count);
|
|
||||||
var maxRetransmissions = 2;
|
|
||||||
|
|
||||||
UdpClient udpClient = new UdpClient();
|
|
||||||
udpClient.Client.ReceiveTimeout = 2000;
|
|
||||||
int port = 9000;
|
|
||||||
//Send a message to each installation and tell it to subscribe to the queue
|
|
||||||
using (udpClient)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < installationIps.Count; i++)
|
|
||||||
{
|
|
||||||
if(installationIps[i]==""){continue;}
|
|
||||||
Console.WriteLine("-----------------------------------------------------------");
|
|
||||||
Console.WriteLine("Trying to reach installation with IP: " + installationIps[i]);
|
|
||||||
//Try at most MAX_RETRANSMISSIONS times to reach an installation.
|
|
||||||
for (int j = 0; j < maxRetransmissions; j++)
|
|
||||||
{
|
|
||||||
string message = "This is a message from RabbitMQ server, you can subscribe to the RabbitMQ queue";
|
|
||||||
byte[] data = Encoding.UTF8.GetBytes(message);
|
|
||||||
udpClient.Send(data, data.Length, installationIps[i], port);
|
|
||||||
|
|
||||||
Console.WriteLine($"Sent UDP message to {installationIps[i]}:{port}: {message}");
|
|
||||||
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(installationIps[i]), port);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
byte[] replyData = udpClient.Receive(ref remoteEndPoint);
|
|
||||||
string replyMessage = Encoding.UTF8.GetString(replyData);
|
|
||||||
Console.WriteLine("Received " + replyMessage + " from installation " + installationIps[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (SocketException ex)
|
|
||||||
{
|
|
||||||
if (ex.SocketErrorCode == SocketError.TimedOut){Console.WriteLine("Timed out waiting for a response. Retry...");}
|
|
||||||
else{Console.WriteLine("Error: " + ex.Message);}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Start RabbitMQ Consumer");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@ public class StatusMessage
|
||||||
public required MessageType Type { get; set; }
|
public required MessageType Type { get; set; }
|
||||||
public List<AlarmOrWarning>? Warnings { get; set; }
|
public List<AlarmOrWarning>? Warnings { get; set; }
|
||||||
public List<AlarmOrWarning>? Alarms { get; set; }
|
public List<AlarmOrWarning>? Alarms { get; set; }
|
||||||
|
public Int32 Timestamp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageType
|
public enum MessageType
|
||||||
|
|
|
@ -16,9 +16,9 @@ dotnet publish \
|
||||||
-r linux-x64
|
-r linux-x64
|
||||||
|
|
||||||
echo -e "\n============================ Deploy ============================\n"
|
echo -e "\n============================ Deploy ============================\n"
|
||||||
#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29")
|
#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.211")
|
||||||
#ip_addresses=("10.2.4.154" "10.2.4.29")
|
#ip_addresses=("10.2.4.154" "10.2.4.29")
|
||||||
ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.29")
|
ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ public class StatusMessage
|
||||||
public required MessageType Type { get; set; }
|
public required MessageType Type { get; set; }
|
||||||
public List<AlarmOrWarning>? Warnings { get; set; }
|
public List<AlarmOrWarning>? Warnings { get; set; }
|
||||||
public List<AlarmOrWarning>? Alarms { get; set; }
|
public List<AlarmOrWarning>? Alarms { get; set; }
|
||||||
|
public Int32 Timestamp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageType
|
public enum MessageType
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#undef Amax
|
#define Amax
|
||||||
#undef GridLimit
|
#undef GridLimit
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
@ -58,9 +58,10 @@ internal static class Program
|
||||||
private static Boolean _subscribedToQueue = false;
|
private static Boolean _subscribedToQueue = false;
|
||||||
private static Boolean _subscribeToQueueForTheFirstTime = false;
|
private static Boolean _subscribeToQueueForTheFirstTime = false;
|
||||||
private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green;
|
private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green;
|
||||||
private static Int32 _heartBitInterval = 0;
|
//private static Int32 _heartBitInterval = 0;
|
||||||
private const UInt16 NbrOfFileToConcatenate = 15;
|
private const UInt16 NbrOfFileToConcatenate = 15;
|
||||||
private static UInt16 _counterOfFile = 0;
|
private static UInt16 _counterOfFile = 0;
|
||||||
|
private static SalimaxAlarmState _salimaxAlarmState = SalimaxAlarmState.Green;
|
||||||
|
|
||||||
static Program()
|
static Program()
|
||||||
{
|
{
|
||||||
|
@ -185,7 +186,6 @@ internal static class Program
|
||||||
LoadOnAcGrid = gridBusLoad,
|
LoadOnAcGrid = gridBusLoad,
|
||||||
LoadOnAcIsland = loadOnAcIsland,
|
LoadOnAcIsland = loadOnAcIsland,
|
||||||
LoadOnDc = dcLoad,
|
LoadOnDc = dcLoad,
|
||||||
|
|
||||||
StateMachine = StateMachine.Default,
|
StateMachine = StateMachine.Default,
|
||||||
EssControl = EssControl.Default,
|
EssControl = EssControl.Default,
|
||||||
Log = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff
|
Log = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff
|
||||||
|
@ -269,7 +269,7 @@ internal static class Program
|
||||||
var subscribedNow = false;
|
var subscribedNow = false;
|
||||||
|
|
||||||
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
|
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
|
||||||
_heartBitInterval++;
|
//_heartBitInterval++;
|
||||||
|
|
||||||
//When the controller boots, it tries to subscribe to the queue
|
//When the controller boots, it tries to subscribe to the queue
|
||||||
if (_subscribeToQueueForTheFirstTime == false)
|
if (_subscribeToQueueForTheFirstTime == false)
|
||||||
|
@ -287,16 +287,16 @@ internal static class Program
|
||||||
if (s3Bucket != null)
|
if (s3Bucket != null)
|
||||||
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||||
}
|
}
|
||||||
else if (_subscribedToQueue && _heartBitInterval >= 30)
|
// else if (_subscribedToQueue && _heartBitInterval >= 30)
|
||||||
{
|
// {
|
||||||
//Send a heartbit to the backend
|
// //Send a heartbit to the backend
|
||||||
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
// Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
||||||
_heartBitInterval = 0;
|
// _heartBitInterval = 0;
|
||||||
currentSalimaxState.Type = MessageType.Heartbit;
|
// currentSalimaxState.Type = MessageType.Heartbit;
|
||||||
|
//
|
||||||
if (s3Bucket != null)
|
// if (s3Bucket != null)
|
||||||
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
// RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||||
}
|
// }
|
||||||
|
|
||||||
//If there is an available message from the RabbitMQ Broker, apply the configuration file
|
//If there is an available message from the RabbitMQ Broker, apply the configuration file
|
||||||
Configuration? config = SetConfigurationFile();
|
Configuration? config = SetConfigurationFile();
|
||||||
|
@ -441,13 +441,13 @@ internal static class Program
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var salimaxAlarmsState = warningList.Any()
|
_salimaxAlarmState = warningList.Any()
|
||||||
? SalimaxAlarmState.Orange
|
? SalimaxAlarmState.Orange
|
||||||
: SalimaxAlarmState.Green; // this will be replaced by LedState
|
: SalimaxAlarmState.Green; // this will be replaced by LedState
|
||||||
|
|
||||||
salimaxAlarmsState = alarmList.Any()
|
_salimaxAlarmState = alarmList.Any()
|
||||||
? SalimaxAlarmState.Red
|
? SalimaxAlarmState.Red
|
||||||
: salimaxAlarmsState; // this will be replaced by LedState
|
: _salimaxAlarmState; // this will be replaced by LedState
|
||||||
|
|
||||||
int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
|
int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
|
||||||
|
|
||||||
|
@ -455,7 +455,7 @@ internal static class Program
|
||||||
{
|
{
|
||||||
InstallationId = installationId,
|
InstallationId = installationId,
|
||||||
Product = 0,
|
Product = 0,
|
||||||
Status = salimaxAlarmsState,
|
Status = _salimaxAlarmState,
|
||||||
Type = MessageType.AlarmOrWarning,
|
Type = MessageType.AlarmOrWarning,
|
||||||
Alarms = alarmList,
|
Alarms = alarmList,
|
||||||
Warnings = warningList
|
Warnings = warningList
|
||||||
|
@ -683,6 +683,7 @@ internal static class Program
|
||||||
|
|
||||||
private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
|
private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
|
||||||
{
|
{
|
||||||
|
|
||||||
var csv = status.ToCsv().LogInfo();
|
var csv = status.ToCsv().LogInfo();
|
||||||
|
|
||||||
await RestApiSavingfile(csv);
|
await RestApiSavingfile(csv);
|
||||||
|
@ -730,6 +731,26 @@ internal static class Program
|
||||||
Console.WriteLine(error);
|
Console.WriteLine(error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
||||||
|
|
||||||
|
var s3Bucket = Config.Load().S3?.Bucket;
|
||||||
|
int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
|
||||||
|
int.TryParse(timeStamp.ToUnixTime().ToString(), out var nameOfCsvFile);
|
||||||
|
|
||||||
|
var returnedStatus = new StatusMessage
|
||||||
|
{
|
||||||
|
InstallationId = installationId,
|
||||||
|
Product = 0,
|
||||||
|
Status = _salimaxAlarmState,
|
||||||
|
Type = MessageType.Heartbit,
|
||||||
|
Timestamp = nameOfCsvFile
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (s3Bucket != null)
|
||||||
|
RabbitMqManager.InformMiddleware(returnedStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
_counterOfFile++;
|
_counterOfFile++;
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,10 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
<Route
|
<Route
|
||||||
path={routes.mainstats + '*'}
|
path={routes.mainstats + '*'}
|
||||||
element={
|
element={
|
||||||
<MainStats s3Credentials={props.s3Credentials}></MainStats>
|
<MainStats
|
||||||
|
s3Credentials={props.s3Credentials}
|
||||||
|
id={props.installationId}
|
||||||
|
></MainStats>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{props.values.batteryView.map((battery) => (
|
{props.values.batteryView.map((battery) => (
|
||||||
|
|
|
@ -28,6 +28,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
|
||||||
interface MainStatsProps {
|
interface MainStatsProps {
|
||||||
s3Credentials: I_S3Credentials;
|
s3Credentials: I_S3Credentials;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MainStats(props: MainStatsProps) {
|
function MainStats(props: MainStatsProps) {
|
||||||
|
@ -92,11 +93,7 @@ function MainStats(props: MainStatsProps) {
|
||||||
const resultPromise: Promise<{
|
const resultPromise: Promise<{
|
||||||
chartData: BatteryDataInterface;
|
chartData: BatteryDataInterface;
|
||||||
chartOverview: BatteryOverviewInterface;
|
chartOverview: BatteryOverviewInterface;
|
||||||
}> = transformInputToBatteryViewData(
|
}> = transformInputToBatteryViewData(props.s3Credentials, props.id);
|
||||||
props.s3Credentials,
|
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
|
|
||||||
UnixTime.now()
|
|
||||||
);
|
|
||||||
|
|
||||||
resultPromise
|
resultPromise
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
@ -186,6 +183,7 @@ function MainStats(props: MainStatsProps) {
|
||||||
chartOverview: BatteryOverviewInterface;
|
chartOverview: BatteryOverviewInterface;
|
||||||
}> = transformInputToBatteryViewData(
|
}> = transformInputToBatteryViewData(
|
||||||
props.s3Credentials,
|
props.s3Credentials,
|
||||||
|
props.id,
|
||||||
UnixTime.fromTicks(startDate.unix()),
|
UnixTime.fromTicks(startDate.unix()),
|
||||||
UnixTime.fromTicks(endDate.unix())
|
UnixTime.fromTicks(endDate.unix())
|
||||||
);
|
);
|
||||||
|
@ -246,6 +244,7 @@ function MainStats(props: MainStatsProps) {
|
||||||
chartOverview: BatteryOverviewInterface;
|
chartOverview: BatteryOverviewInterface;
|
||||||
}> = transformInputToBatteryViewData(
|
}> = transformInputToBatteryViewData(
|
||||||
props.s3Credentials,
|
props.s3Credentials,
|
||||||
|
props.id,
|
||||||
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
||||||
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
|
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
|
||||||
);
|
);
|
||||||
|
|
|
@ -136,7 +136,7 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
label="Select Action Date"
|
label="Select Action Date"
|
||||||
name="timestamp"
|
name="timestamp"
|
||||||
value={actionDate}
|
value={actionDate}
|
||||||
onChange={(newDate) => handleDateChange(newDate.toDate())}
|
onChange={(newDate) => handleDateChange(newDate)}
|
||||||
sx={{
|
sx={{
|
||||||
width: 450,
|
width: 450,
|
||||||
marginTop: 2
|
marginTop: 2
|
||||||
|
|
|
@ -4,9 +4,13 @@ import {
|
||||||
CardContent,
|
CardContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Container,
|
Container,
|
||||||
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
|
@ -42,6 +46,8 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
||||||
useState(false);
|
useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const DeviceTypes = ['Cerbo', 'Venus'];
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
const {
|
const {
|
||||||
updateInstallation,
|
updateInstallation,
|
||||||
|
@ -259,6 +265,61 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage id="vrmLink" defaultMessage="vrmLink" />
|
||||||
|
}
|
||||||
|
name="vrmLink"
|
||||||
|
value={formValues.vrmLink}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
marginLeft: 1,
|
||||||
|
marginTop: 1,
|
||||||
|
marginBottom: 1,
|
||||||
|
width: 390
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="DeviceType"
|
||||||
|
defaultMessage="Device Type"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="device"
|
||||||
|
value={
|
||||||
|
DeviceTypes.indexOf(
|
||||||
|
DeviceTypes[formValues.device - 1]
|
||||||
|
) + 1
|
||||||
|
}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{DeviceTypes.map((type) => (
|
||||||
|
<MenuItem
|
||||||
|
key={type}
|
||||||
|
value={DeviceTypes.indexOf(type) + 1}
|
||||||
|
>
|
||||||
|
{type}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={
|
||||||
|
@ -275,19 +336,6 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*<div>*/}
|
|
||||||
{/* <TextField*/}
|
|
||||||
{/* label={*/}
|
|
||||||
{/* <FormattedMessage id="vrmLink" defaultMessage="vrmLink" />*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/* name="vrmLink"*/}
|
|
||||||
{/* value={formValues.vrmLink}*/}
|
|
||||||
{/* onChange={handleChange}*/}
|
|
||||||
{/* variant="outlined"*/}
|
|
||||||
{/* fullWidth*/}
|
|
||||||
{/* />*/}
|
|
||||||
{/*</div>*/}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="S3 Bucket Name"
|
label="S3 Bucket Name"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
@ -35,16 +35,13 @@ function Installation(props: singleInstallationProps) {
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
const location = useLocation().pathname;
|
const location = useLocation().pathname;
|
||||||
|
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
||||||
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
|
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
|
||||||
const webSocketsContext = useContext(WebSocketContext);
|
const webSocketsContext = useContext(WebSocketContext);
|
||||||
const { getStatus } = webSocketsContext;
|
const { getStatus } = webSocketsContext;
|
||||||
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||||
const [values, setValues] = useState<TopologyValues | null>(null);
|
const [values, setValues] = useState<TopologyValues | null>(null);
|
||||||
const status = getStatus(props.current_installation.id);
|
const status = getStatus(props.current_installation.id);
|
||||||
const [
|
|
||||||
failedToCommunicateWithInstallation,
|
|
||||||
setFailedToCommunicateWithInstallation
|
|
||||||
] = useState(0);
|
|
||||||
const [connected, setConnected] = useState(true);
|
const [connected, setConnected] = useState(true);
|
||||||
|
|
||||||
if (props.current_installation == undefined) {
|
if (props.current_installation == undefined) {
|
||||||
|
@ -68,51 +65,122 @@ function Installation(props: singleInstallationProps) {
|
||||||
|
|
||||||
const s3Credentials = { s3Bucket, ...S3data };
|
const s3Credentials = { s3Bucket, ...S3data };
|
||||||
|
|
||||||
const fetchDataPeriodically = async () => {
|
function timeout(delay: number) {
|
||||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
return new Promise((res) => setTimeout(res, delay));
|
||||||
|
}
|
||||||
|
|
||||||
|
const continueFetching = useRef(false);
|
||||||
|
|
||||||
|
const fetchDataForOneTime = async () => {
|
||||||
|
var timeperiodToSearch = 80;
|
||||||
|
let res;
|
||||||
|
let timestampToFetch;
|
||||||
|
|
||||||
|
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||||
|
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||||
try {
|
try {
|
||||||
const res = await fetchData(now, s3Credentials);
|
res = await fetchData(timestampToFetch, s3Credentials);
|
||||||
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
if (i <= 0) {
|
||||||
setFailedToCommunicateWithInstallation(0);
|
setConnected(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
setConnected(true);
|
setConnected(true);
|
||||||
|
|
||||||
|
const timestamp = Object.keys(res)[Object.keys(res).length - 1];
|
||||||
|
|
||||||
setValues(
|
setValues(
|
||||||
extractValues({
|
extractValues({
|
||||||
time: now,
|
time: UnixTime.fromTicks(parseInt(timestamp, 10)),
|
||||||
value: res
|
value: res[timestamp]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
};
|
||||||
setFailedToCommunicateWithInstallation((prevCount) => {
|
|
||||||
if (prevCount + 1 >= 3) {
|
const fetchDataPeriodically = async () => {
|
||||||
setConnected(false);
|
var timeperiodToSearch = 80;
|
||||||
}
|
let res;
|
||||||
return prevCount + 1;
|
let timestampToFetch;
|
||||||
});
|
|
||||||
}
|
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||||
} catch (err) {
|
if (!continueFetching.current) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||||
|
|
||||||
const fetchDataOnlyOneTime = async () => {
|
try {
|
||||||
let success = false;
|
res = await fetchData(timestampToFetch, s3Credentials);
|
||||||
const max_retransmissions = 3;
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
|
|
||||||
for (let i = 0; i < max_retransmissions; i++) {
|
|
||||||
success = await fetchDataPeriodically();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
if (success) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i <= 0) {
|
||||||
|
setConnected(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setConnected(true);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
// Set values asynchronously with delay
|
||||||
|
setValues(
|
||||||
|
extractValues({
|
||||||
|
time: UnixTime.fromTicks(parseInt(timestamp, 10)),
|
||||||
|
value: res[timestamp]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// Wait for 2 seconds before processing next timestamp
|
||||||
|
await timeout(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(30));
|
||||||
|
console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Trying to fetch timestamp ' + timestampToFetch);
|
||||||
|
res = await fetchData(timestampToFetch, s3Credentials);
|
||||||
|
if (
|
||||||
|
res !== FetchResult.notAvailable &&
|
||||||
|
res !== FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let path = location.split('/');
|
let path = location.split('/');
|
||||||
|
|
||||||
setCurrentTab(path[path.length - 1]);
|
setCurrentTab(path[path.length - 1]);
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
|
@ -129,30 +197,28 @@ function Installation(props: singleInstallationProps) {
|
||||||
currentTab == 'configuration' ||
|
currentTab == 'configuration' ||
|
||||||
location.includes('batteryview')
|
location.includes('batteryview')
|
||||||
) {
|
) {
|
||||||
let interval;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentTab == 'live' ||
|
currentTab == 'live' ||
|
||||||
(location.includes('batteryview') && !location.includes('mainstats')) ||
|
(location.includes('batteryview') && !location.includes('mainstats')) ||
|
||||||
currentTab == 'pvview'
|
currentTab == 'pvview'
|
||||||
) {
|
) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
continueFetching.current = true;
|
||||||
|
if (!fetchFunctionCalled) {
|
||||||
|
setFetchFunctionCalled(true);
|
||||||
fetchDataPeriodically();
|
fetchDataPeriodically();
|
||||||
interval = setInterval(fetchDataPeriodically, 2000);
|
|
||||||
}
|
}
|
||||||
if (currentTab == 'configuration' || location.includes('mainstats')) {
|
}
|
||||||
fetchDataOnlyOneTime();
|
}
|
||||||
|
if (currentTab == 'configuration') {
|
||||||
|
fetchDataForOneTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup function to cancel interval
|
|
||||||
return () => {
|
return () => {
|
||||||
if (
|
continueFetching.current = false;
|
||||||
currentTab == 'live' ||
|
|
||||||
currentTab == 'pvview' ||
|
|
||||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
|
||||||
) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
continueFetching.current = false;
|
||||||
}
|
}
|
||||||
}, [currentTab, location]);
|
}, [currentTab, location]);
|
||||||
|
|
||||||
|
@ -324,7 +390,12 @@ function Installation(props: singleInstallationProps) {
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.overview}
|
path={routes.overview}
|
||||||
element={<Overview s3Credentials={s3Credentials}></Overview>}
|
element={
|
||||||
|
<Overview
|
||||||
|
s3Credentials={s3Credentials}
|
||||||
|
id={props.current_installation.id}
|
||||||
|
></Overview>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { I_S3Credentials } from 'src/interfaces/S3Types';
|
||||||
import { FetchResult } from 'src/dataCache/dataCache';
|
import { FetchResult } from 'src/dataCache/dataCache';
|
||||||
import { DataRecord } from 'src/dataCache/data';
|
import { DataRecord } from 'src/dataCache/data';
|
||||||
import { S3Access } from 'src/dataCache/S3/S3Access';
|
import { S3Access } from 'src/dataCache/S3/S3Access';
|
||||||
import { parseCsv } from '../Log/graph.util';
|
import { parseChunk, parseCsv } from '../Log/graph.util';
|
||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
export const fetchDailyData = (
|
export const fetchAggregatedData = (
|
||||||
date: string,
|
date: string,
|
||||||
s3Credentials?: I_S3Credentials
|
s3Credentials?: I_S3Credentials
|
||||||
): Promise<FetchResult<DataRecord>> => {
|
): Promise<FetchResult<DataRecord>> => {
|
||||||
|
@ -25,10 +25,7 @@ export const fetchDailyData = (
|
||||||
if (r.status === 404) {
|
if (r.status === 404) {
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
} else if (r.status === 200) {
|
} else if (r.status === 200) {
|
||||||
// const text = await r.text();
|
|
||||||
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
|
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
|
||||||
|
|
||||||
//const response = await fetch(url); // Fetch the resource from the server
|
|
||||||
const contentEncoding = r.headers.get('content-type');
|
const contentEncoding = r.headers.get('content-type');
|
||||||
|
|
||||||
if (contentEncoding != 'application/base64; charset=utf-8') {
|
if (contentEncoding != 'application/base64; charset=utf-8') {
|
||||||
|
@ -43,11 +40,7 @@ export const fetchDailyData = (
|
||||||
const zip = await JSZip.loadAsync(byteArray);
|
const zip = await JSZip.loadAsync(byteArray);
|
||||||
// Assuming the CSV file is named "data.csv" inside the ZIP archive
|
// Assuming the CSV file is named "data.csv" inside the ZIP archive
|
||||||
const csvContent = await zip.file('data.csv').async('text');
|
const csvContent = await zip.file('data.csv').async('text');
|
||||||
|
|
||||||
return parseCsv(csvContent);
|
return parseCsv(csvContent);
|
||||||
|
|
||||||
//console.log(parseCsv(text));
|
|
||||||
//return parseCsv(text);
|
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +54,7 @@ export const fetchDailyData = (
|
||||||
export const fetchData = (
|
export const fetchData = (
|
||||||
timestamp: UnixTime,
|
timestamp: UnixTime,
|
||||||
s3Credentials?: I_S3Credentials
|
s3Credentials?: I_S3Credentials
|
||||||
): Promise<FetchResult<DataRecord>> => {
|
): Promise<FetchResult<Record<string, DataRecord>>> => {
|
||||||
const s3Path = `${timestamp.ticks}.csv`;
|
const s3Path = `${timestamp.ticks}.csv`;
|
||||||
if (s3Credentials && s3Credentials.s3Bucket) {
|
if (s3Credentials && s3Credentials.s3Bucket) {
|
||||||
const s3Access = new S3Access(
|
const s3Access = new S3Access(
|
||||||
|
@ -79,12 +72,10 @@ export const fetchData = (
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
} else if (r.status === 200) {
|
} else if (r.status === 200) {
|
||||||
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
|
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
|
||||||
|
|
||||||
//const response = await fetch(url); // Fetch the resource from the server
|
|
||||||
const contentEncoding = r.headers.get('content-type');
|
const contentEncoding = r.headers.get('content-type');
|
||||||
|
|
||||||
if (contentEncoding != 'application/base64; charset=utf-8') {
|
if (contentEncoding != 'application/base64; charset=utf-8') {
|
||||||
return parseCsv(csvtext);
|
return parseChunk(csvtext);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteArray = Uint8Array.from(atob(csvtext), (c) =>
|
const byteArray = Uint8Array.from(atob(csvtext), (c) =>
|
||||||
|
@ -96,7 +87,7 @@ export const fetchData = (
|
||||||
// Assuming the CSV file is named "data.csv" inside the ZIP archive
|
// Assuming the CSV file is named "data.csv" inside the ZIP archive
|
||||||
const csvContent = await zip.file('data.csv').async('text');
|
const csvContent = await zip.file('data.csv').async('text');
|
||||||
|
|
||||||
return parseCsv(csvContent);
|
return parseChunk(csvContent);
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,35 @@ export const parseCsv = (text: string): DataRecord => {
|
||||||
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseChunk = (text: string): Record<string, DataRecord> => {
|
||||||
|
const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
|
||||||
|
|
||||||
|
let result: Record<string, DataRecord> = {};
|
||||||
|
let currentTimestamp = null;
|
||||||
|
|
||||||
|
lines.forEach((line) => {
|
||||||
|
const fields = line.split(';');
|
||||||
|
if (fields[0] === 'Timestamp') {
|
||||||
|
currentTimestamp = fields[1];
|
||||||
|
result[currentTimestamp] = {};
|
||||||
|
} else if (currentTimestamp) {
|
||||||
|
let key = fields[0];
|
||||||
|
let value = fields[1];
|
||||||
|
let unit = fields[2];
|
||||||
|
|
||||||
|
if (isNaN(Number(value)) || value === '') {
|
||||||
|
result[currentTimestamp][key] = { value: value, unit: unit };
|
||||||
|
} else {
|
||||||
|
result[currentTimestamp][key] = {
|
||||||
|
value: parseFloat(value),
|
||||||
|
unit: unit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
export interface I_BoxDataValue {
|
export interface I_BoxDataValue {
|
||||||
unit: string;
|
unit: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
|
|
|
@ -13,15 +13,16 @@ import {
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
|
||||||
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
|
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
|
||||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
|
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||||
|
|
||||||
interface OverviewProps {
|
interface OverviewProps {
|
||||||
s3Credentials: I_S3Credentials;
|
s3Credentials: I_S3Credentials;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeLast7Days = (): string[] => {
|
const computeLast7Days = (): string[] => {
|
||||||
|
@ -43,7 +44,6 @@ const computeLast7Days = (): string[] => {
|
||||||
function Overview(props: OverviewProps) {
|
function Overview(props: OverviewProps) {
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
|
|
||||||
const [dailyData, setDailyData] = useState(true);
|
const [dailyData, setDailyData] = useState(true);
|
||||||
const [aggregatedData, setAggregatedData] = useState(false);
|
const [aggregatedData, setAggregatedData] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
@ -85,11 +85,7 @@ function Overview(props: OverviewProps) {
|
||||||
const resultPromise: Promise<{
|
const resultPromise: Promise<{
|
||||||
chartData: chartDataInterface;
|
chartData: chartDataInterface;
|
||||||
chartOverview: overviewInterface;
|
chartOverview: overviewInterface;
|
||||||
}> = transformInputToDailyData(
|
}> = transformInputToDailyData(props.s3Credentials, props.id);
|
||||||
props.s3Credentials,
|
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
|
|
||||||
UnixTime.now()
|
|
||||||
);
|
|
||||||
|
|
||||||
resultPromise
|
resultPromise
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
@ -119,6 +115,7 @@ function Overview(props: OverviewProps) {
|
||||||
chartOverview: overviewInterface;
|
chartOverview: overviewInterface;
|
||||||
}> = transformInputToDailyData(
|
}> = transformInputToDailyData(
|
||||||
props.s3Credentials,
|
props.s3Credentials,
|
||||||
|
props.id,
|
||||||
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
||||||
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
|
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
|
||||||
);
|
);
|
||||||
|
@ -174,7 +171,6 @@ function Overview(props: OverviewProps) {
|
||||||
chartOverview: overviewInterface;
|
chartOverview: overviewInterface;
|
||||||
}> = transformInputToAggregatedData(
|
}> = transformInputToAggregatedData(
|
||||||
props.s3Credentials,
|
props.s3Credentials,
|
||||||
|
|
||||||
dayjs().subtract(1, 'week'),
|
dayjs().subtract(1, 'week'),
|
||||||
dayjs()
|
dayjs()
|
||||||
);
|
);
|
||||||
|
@ -246,6 +242,7 @@ function Overview(props: OverviewProps) {
|
||||||
chartOverview: overviewInterface;
|
chartOverview: overviewInterface;
|
||||||
}> = transformInputToDailyData(
|
}> = transformInputToDailyData(
|
||||||
props.s3Credentials,
|
props.s3Credentials,
|
||||||
|
props.id,
|
||||||
UnixTime.fromTicks(startDate.unix()),
|
UnixTime.fromTicks(startDate.unix()),
|
||||||
UnixTime.fromTicks(endDate.unix())
|
UnixTime.fromTicks(endDate.unix())
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
@ -56,93 +56,110 @@ function Installation(props: singleInstallationProps) {
|
||||||
'-' +
|
'-' +
|
||||||
'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
|
'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
|
||||||
|
|
||||||
|
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
||||||
const s3Credentials = { s3Bucket, ...S3data };
|
const s3Credentials = { s3Bucket, ...S3data };
|
||||||
|
|
||||||
const fetchDataOnlyOneTime = async () => {
|
function timeout(delay: number) {
|
||||||
var timeperiodToSearch = 70;
|
return new Promise((res) => setTimeout(res, delay));
|
||||||
|
|
||||||
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
|
||||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
|
||||||
|
|
||||||
const res = await fetchData(now, s3Credentials);
|
|
||||||
|
|
||||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
|
||||||
setConnected(true);
|
|
||||||
setFailedToCommunicateWithInstallation(0);
|
|
||||||
setValues(
|
|
||||||
extractValues({
|
|
||||||
time: now,
|
|
||||||
value: res
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return now;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
const continueFetching = useRef(false);
|
||||||
|
|
||||||
const fetchDataPeriodically = async () => {
|
const fetchDataPeriodically = async () => {
|
||||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
var timeperiodToSearch = 80;
|
||||||
|
let res;
|
||||||
|
let timestampToFetch;
|
||||||
|
|
||||||
try {
|
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||||
const res = await fetchData(now, s3Credentials);
|
if (!continueFetching.current) {
|
||||||
|
|
||||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
|
||||||
setConnected(true);
|
|
||||||
setFailedToCommunicateWithInstallation(0);
|
|
||||||
setValues(
|
|
||||||
extractValues({
|
|
||||||
time: now,
|
|
||||||
value: res
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
setFailedToCommunicateWithInstallation((prevCount) => {
|
|
||||||
if (prevCount + 1 >= 20) {
|
|
||||||
setConnected(false);
|
|
||||||
}
|
|
||||||
return prevCount + 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await fetchData(timestampToFetch, s3Credentials);
|
||||||
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i <= 0) {
|
||||||
|
setConnected(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setConnected(true);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
// Set values asynchronously with delay
|
||||||
|
setValues(
|
||||||
|
extractValues({
|
||||||
|
time: UnixTime.fromTicks(parseInt(timestamp, 10)),
|
||||||
|
value: res[timestamp]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// Wait for 2 seconds before processing next timestamp
|
||||||
|
await timeout(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(30));
|
||||||
|
console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Trying to fetch timestamp ' + timestampToFetch);
|
||||||
|
res = await fetchData(timestampToFetch, s3Credentials);
|
||||||
|
if (
|
||||||
|
res !== FetchResult.notAvailable &&
|
||||||
|
res !== FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let path = location.split('/');
|
let path = location.split('/');
|
||||||
|
|
||||||
setCurrentTab(path[path.length - 1]);
|
setCurrentTab(path[path.length - 1]);
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (location.includes('batteryview')) {
|
||||||
currentTab == 'live' ||
|
if (location.includes('batteryview') && !location.includes('mainstats')) {
|
||||||
currentTab == 'configuration' ||
|
if (!continueFetching.current) {
|
||||||
location.includes('batteryview')
|
continueFetching.current = true;
|
||||||
) {
|
if (!fetchFunctionCalled) {
|
||||||
let interval;
|
setFetchFunctionCalled(true);
|
||||||
|
fetchDataPeriodically();
|
||||||
if (
|
}
|
||||||
currentTab == 'live' ||
|
|
||||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
|
||||||
) {
|
|
||||||
fetchDataOnlyOneTime();
|
|
||||||
interval = setInterval(fetchDataPeriodically, 2000);
|
|
||||||
}
|
}
|
||||||
if (currentTab == 'configuration' || location.includes('mainstats')) {
|
|
||||||
fetchDataOnlyOneTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup function to cancel interval and update isMounted when unmounted
|
|
||||||
return () => {
|
return () => {
|
||||||
if (
|
continueFetching.current = false;
|
||||||
currentTab == 'live' ||
|
|
||||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
|
||||||
) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
continueFetching.current = false;
|
||||||
}
|
}
|
||||||
}, [currentTab, location]);
|
}, [currentTab, location]);
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,12 @@ import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
FormControl,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
@ -37,6 +41,8 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
|
||||||
'vpnIp',
|
'vpnIp',
|
||||||
'vrmLink'
|
'vrmLink'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const DeviceTypes = ['Cerbo', 'Venus'];
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
const { createInstallation, loading, setLoading, error, setError } =
|
const { createInstallation, loading, setLoading, error, setError } =
|
||||||
installationContext;
|
installationContext;
|
||||||
|
@ -128,6 +134,7 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
|
||||||
value={formValues.region}
|
value={formValues.region}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
|
error={formValues.region === ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -180,6 +187,36 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
sx={{ marginTop: 1, marginBottom: 1, width: 390 }}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="DeviceType"
|
||||||
|
defaultMessage="Device Type"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="device"
|
||||||
|
value={DeviceTypes[formValues.device]}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{DeviceTypes.map((type) => (
|
||||||
|
<MenuItem key={type} value={DeviceTypes.indexOf(type) + 1}>
|
||||||
|
{type}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import {
|
import {
|
||||||
fetchDailyData,
|
fetchAggregatedData,
|
||||||
fetchData
|
fetchData
|
||||||
} from '../content/dashboards/Installations/fetchData';
|
} from '../content/dashboards/Installations/fetchData';
|
||||||
import { FetchResult } from '../dataCache/dataCache';
|
import { FetchResult } from '../dataCache/dataCache';
|
||||||
import { I_S3Credentials } from './S3Types';
|
import { CsvTimestamp, I_S3Credentials } from './S3Types';
|
||||||
import { UnixTime } from '../dataCache/time';
|
import { TimeSpan, UnixTime } from '../dataCache/time';
|
||||||
|
import { DataRecord } from '../dataCache/data';
|
||||||
|
import axiosConfig from '../Resources/axiosConfig';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
export interface chartInfoInterface {
|
export interface chartInfoInterface {
|
||||||
magnitude: number;
|
magnitude: number;
|
||||||
|
@ -74,8 +77,9 @@ export interface BatteryOverviewInterface {
|
||||||
|
|
||||||
export const transformInputToBatteryViewData = async (
|
export const transformInputToBatteryViewData = async (
|
||||||
s3Credentials: I_S3Credentials,
|
s3Credentials: I_S3Credentials,
|
||||||
startTimestamp: UnixTime,
|
id: number,
|
||||||
endTimestamp: UnixTime
|
start_time?: UnixTime,
|
||||||
|
end_time?: UnixTime
|
||||||
): Promise<{
|
): Promise<{
|
||||||
chartData: BatteryDataInterface;
|
chartData: BatteryDataInterface;
|
||||||
chartOverview: BatteryOverviewInterface;
|
chartOverview: BatteryOverviewInterface;
|
||||||
|
@ -124,42 +128,69 @@ export const transformInputToBatteryViewData = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
let initialiation = true;
|
let initialiation = true;
|
||||||
|
|
||||||
|
//console.log(start_time);
|
||||||
|
//console.log(end_time);
|
||||||
|
|
||||||
|
let timestampArray: CsvTimestamp[] = [];
|
||||||
let adjustedTimestampArray = [];
|
let adjustedTimestampArray = [];
|
||||||
|
|
||||||
let startTimestampToNum = Number(startTimestamp);
|
|
||||||
if (startTimestampToNum % 2 != 0) {
|
|
||||||
startTimestampToNum += 1;
|
|
||||||
}
|
|
||||||
let startUnixTime = UnixTime.fromTicks(startTimestampToNum);
|
|
||||||
let diff = endTimestamp.ticks - startUnixTime.ticks;
|
|
||||||
|
|
||||||
const timestampPromises = [];
|
const timestampPromises = [];
|
||||||
|
|
||||||
while (startUnixTime < endTimestamp) {
|
if (start_time && end_time) {
|
||||||
timestampPromises.push(fetchData(startUnixTime, s3Credentials));
|
await axiosConfig
|
||||||
|
.get(
|
||||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100);
|
`/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
|
||||||
if (startUnixTime.ticks % 2 !== 0) {
|
)
|
||||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1);
|
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||||
|
timestampArray = res.data;
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
if (err.response && err.response.status == 401) {
|
||||||
|
//removeToken();
|
||||||
|
//navigate(routes.login);
|
||||||
}
|
}
|
||||||
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000);
|
});
|
||||||
|
} else {
|
||||||
|
await axiosConfig
|
||||||
|
.get(`/GetCsvTimestampsForInstallation?id=${id}&start=${0}&end=${0}`)
|
||||||
|
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||||
|
timestampArray = res.data;
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
if (err.response && err.response.status == 401) {
|
||||||
|
//removeToken();
|
||||||
|
//navigate(routes.login);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < timestampArray.length; i++) {
|
||||||
|
timestampPromises.push(
|
||||||
|
fetchDataForOneTime(
|
||||||
|
UnixTime.fromTicks(timestampArray[i].timestamp),
|
||||||
|
s3Credentials
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const adjustedTimestamp = new Date(timestampArray[i].timestamp * 1000);
|
||||||
|
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
||||||
adjustedTimestamp.setHours(
|
adjustedTimestamp.setHours(
|
||||||
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
|
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
|
||||||
);
|
);
|
||||||
adjustedTimestampArray.push(adjustedTimestamp);
|
adjustedTimestampArray.push(adjustedTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Wait until fetching all the data
|
const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
|
||||||
const results = await Promise.all(timestampPromises);
|
await Promise.all(timestampPromises);
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
const result = results[i];
|
if (results[i] == null) {
|
||||||
if (
|
|
||||||
result === FetchResult.notAvailable ||
|
|
||||||
result === FetchResult.tryLater
|
|
||||||
) {
|
|
||||||
// Handle not available or try later case
|
// Handle not available or try later case
|
||||||
} else {
|
} else {
|
||||||
|
const timestamp = Object.keys(results[i])[
|
||||||
|
Object.keys(results[i]).length - 1
|
||||||
|
];
|
||||||
|
const result = results[i][timestamp];
|
||||||
const battery_nodes = result['/Config/Devices/BatteryNodes'].value
|
const battery_nodes = result['/Config/Devices/BatteryNodes'].value
|
||||||
.toString()
|
.toString()
|
||||||
.split(',');
|
.split(',');
|
||||||
|
@ -252,14 +283,42 @@ export const transformInputToBatteryViewData = async (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchDataForOneTime = async (
|
||||||
|
startUnixTime: UnixTime,
|
||||||
|
s3Credentials: I_S3Credentials
|
||||||
|
): Promise<FetchResult<Record<string, DataRecord>>> => {
|
||||||
|
var timeperiodToSearch = 2;
|
||||||
|
let res;
|
||||||
|
let timestampToFetch;
|
||||||
|
|
||||||
|
for (var i = 0; i < timeperiodToSearch; i++) {
|
||||||
|
timestampToFetch = startUnixTime.later(TimeSpan.fromSeconds(i));
|
||||||
|
try {
|
||||||
|
res = await fetchData(timestampToFetch, s3Credentials);
|
||||||
|
|
||||||
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
|
//console.log('Successfully fetched ' + timestampToFetch);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const transformInputToDailyData = async (
|
export const transformInputToDailyData = async (
|
||||||
s3Credentials: I_S3Credentials,
|
s3Credentials: I_S3Credentials,
|
||||||
startTimestamp: UnixTime,
|
id: number,
|
||||||
endTimestamp: UnixTime
|
start_time?: UnixTime,
|
||||||
|
end_time?: UnixTime
|
||||||
): Promise<{
|
): Promise<{
|
||||||
chartData: chartDataInterface;
|
chartData: chartDataInterface;
|
||||||
chartOverview: overviewInterface;
|
chartOverview: overviewInterface;
|
||||||
}> => {
|
}> => {
|
||||||
|
//const navigate = useNavigate();
|
||||||
|
//const tokencontext = useContext(TokenContext);
|
||||||
|
//const { removeToken } = tokencontext;
|
||||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
const MAX_NUMBER = 9999999;
|
const MAX_NUMBER = 9999999;
|
||||||
const pathsToSearch = [
|
const pathsToSearch = [
|
||||||
|
@ -309,45 +368,71 @@ export const transformInputToDailyData = async (
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//console.log(start_time);
|
||||||
|
//console.log(end_time);
|
||||||
|
|
||||||
|
let timestampArray: CsvTimestamp[] = [];
|
||||||
let adjustedTimestampArray = [];
|
let adjustedTimestampArray = [];
|
||||||
|
|
||||||
let startTimestampToNum = Number(startTimestamp);
|
|
||||||
if (startTimestampToNum % 2 != 0) {
|
|
||||||
startTimestampToNum += 1;
|
|
||||||
}
|
|
||||||
let startUnixTime = UnixTime.fromTicks(startTimestampToNum);
|
|
||||||
let diff = endTimestamp.ticks - startUnixTime.ticks;
|
|
||||||
|
|
||||||
const timestampPromises = [];
|
const timestampPromises = [];
|
||||||
|
|
||||||
while (startUnixTime < endTimestamp) {
|
if (start_time && end_time) {
|
||||||
timestampPromises.push(fetchData(startUnixTime, s3Credentials));
|
await axiosConfig
|
||||||
|
.get(
|
||||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100);
|
`/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
|
||||||
if (startUnixTime.ticks % 2 !== 0) {
|
)
|
||||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1);
|
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||||
|
timestampArray = res.data;
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
if (err.response && err.response.status == 401) {
|
||||||
|
//removeToken();
|
||||||
|
//navigate(routes.login);
|
||||||
}
|
}
|
||||||
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000);
|
});
|
||||||
|
} else {
|
||||||
|
await axiosConfig
|
||||||
|
.get(`/GetCsvTimestampsForInstallation?id=${id}&start=${0}&end=${0}`)
|
||||||
|
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||||
|
timestampArray = res.data;
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
if (err.response && err.response.status == 401) {
|
||||||
|
//removeToken();
|
||||||
|
//navigate(routes.login);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//while (startUnixTime < endTimestamp) {
|
||||||
|
for (var i = 0; i < timestampArray.length; i++) {
|
||||||
|
timestampPromises.push(
|
||||||
|
fetchDataForOneTime(
|
||||||
|
UnixTime.fromTicks(timestampArray[i].timestamp),
|
||||||
|
s3Credentials
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const adjustedTimestamp = new Date(timestampArray[i].timestamp * 1000);
|
||||||
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
||||||
adjustedTimestamp.setHours(
|
adjustedTimestamp.setHours(
|
||||||
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
|
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
|
||||||
);
|
);
|
||||||
|
|
||||||
adjustedTimestampArray.push(adjustedTimestamp);
|
adjustedTimestampArray.push(adjustedTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all(timestampPromises);
|
const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
|
||||||
|
await Promise.all(timestampPromises);
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
const result = results[i];
|
if (results[i] == null) {
|
||||||
if (
|
|
||||||
result === FetchResult.notAvailable ||
|
|
||||||
result === FetchResult.tryLater
|
|
||||||
) {
|
|
||||||
// Handle not available or try later case
|
// Handle not available or try later case
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
const timestamp = Object.keys(results[i])[
|
||||||
|
Object.keys(results[i]).length - 1
|
||||||
|
];
|
||||||
|
const result = results[i][timestamp];
|
||||||
let category_index = 0;
|
let category_index = 0;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
if (result[path]) {
|
if (result[path]) {
|
||||||
const value = result[path];
|
const value = result[path];
|
||||||
|
@ -370,6 +455,7 @@ export const transformInputToDailyData = async (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
let value = Math.max(
|
let value = Math.max(
|
||||||
Math.abs(chartOverview[category].max),
|
Math.abs(chartOverview[category].max),
|
||||||
|
@ -500,7 +586,7 @@ export const transformInputToAggregatedData = async (
|
||||||
|
|
||||||
while (currentDay.isBefore(end_date)) {
|
while (currentDay.isBefore(end_date)) {
|
||||||
timestampPromises.push(
|
timestampPromises.push(
|
||||||
fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials)
|
fetchAggregatedData(currentDay.format('YYYY-MM-DD'), s3Credentials)
|
||||||
);
|
);
|
||||||
currentDay = currentDay.add(1, 'day');
|
currentDay = currentDay.add(1, 'day');
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,12 @@ export interface ErrorMessage {
|
||||||
seen: boolean;
|
seen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CsvTimestamp {
|
||||||
|
id: number;
|
||||||
|
installationId: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
id: number;
|
id: number;
|
||||||
userName: string;
|
userName: string;
|
||||||
|
|
Loading…
Reference in New Issue