From 1155e1bc4da65c2de7c79c8da7997979891557da Mon Sep 17 00:00:00 2001 From: Noe Date: Mon, 13 Nov 2023 17:51:36 +0100 Subject: [PATCH] Update backend and frontend with middleware functionality --- csharp/App/Backend/Controller.cs | 2 + csharp/App/Backend/DataTypes/Installation.cs | 4 +- .../App/Backend/DataTypes/Methods/ExoCmd.cs | 12 +- .../App/Backend/DataTypes/Methods/Session.cs | 7 +- csharp/App/Backend/Program.cs | 15 ++ .../Backend/Websockets/WebsockerManager.cs | 163 ++++++------ csharp/App/SaliMax/HostList.txt | 18 +- csharp/App/SaliMax/resources/ConfigReadme.txt | 110 -------- csharp/App/VrmGrabber/Controller.cs | 3 +- typescript/frontend-marios2/src/App.tsx | 17 +- .../src/Resources/axiosConfig.tsx | 8 +- .../Installations/FlatInstallationView.tsx | 7 +- .../dashboards/Installations/Installation.tsx | 236 ++++++++++-------- .../Installations/InstallationSearch.tsx | 6 +- .../dashboards/Installations/index.tsx | 21 +- .../dashboards/ManageAccess/Access.tsx | 4 +- .../content/dashboards/Overview/overview.tsx | 2 +- .../dashboards/Tree/CustomTreeItem.tsx | 6 +- .../content/dashboards/Users/UsersSearch.tsx | 4 +- .../src/content/dashboards/Users/index.tsx | 6 +- .../src/contexts/AccessContextProvider.tsx | 15 ++ .../src/contexts/LogContextProvider.tsx | 86 ------- .../src/contexts/UsersContextProvider.tsx | 114 --------- .../src/interfaces/InstallationTypes.tsx | 1 + .../SidebarLayout/Header/Userbox/index.tsx | 4 + 25 files changed, 318 insertions(+), 553 deletions(-) delete mode 100644 csharp/App/SaliMax/resources/ConfigReadme.txt delete mode 100644 typescript/frontend-marios2/src/contexts/LogContextProvider.tsx delete mode 100644 typescript/frontend-marios2/src/contexts/UsersContextProvider.tsx diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index ee4d50ba1..9feff6d72 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -61,6 +61,7 @@ public class Controller : ControllerBase if (session is null) { + Console.WriteLine("------------------------------------Unauthorized user----------------------------------------------"); HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; HttpContext.Abort(); return; @@ -68,6 +69,7 @@ public class Controller : ControllerBase if (!HttpContext.WebSockets.IsWebSocketRequest) { + Console.WriteLine("------------------------------------Not a websocket request ----------------------------------------------"); HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; HttpContext.Abort(); return; diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index dbcd527fb..ada9c68dd 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -8,7 +8,8 @@ public class Installation : TreeNode public String Region { get; set; } = ""; public String Country { get; set; } = ""; public String InstallationName { get; set; } = ""; - + public String VpnIp { get; set; } = ""; + // TODO: make relation //public IReadOnlyList OrderNumbers { get; set; } = Array.Empty(); // public String? OrderNumbers { get; set; } = ""; @@ -25,6 +26,7 @@ public class Installation : TreeNode public String ReadRoleId { get; set; } = ""; public String WriteRoleId { get; set; } = ""; + [Ignore] public String OrderNumbers { get; set; } diff --git a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs index d1e8f3c57..8f2836cd5 100644 --- a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs +++ b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs @@ -6,9 +6,7 @@ using System.Net; using System.Net.Http.Headers; using System.Text; using System.Text.Json.Nodes; -using Amazon.S3.Model; using InnovEnergy.App.Backend.Database; -using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.Backend.DataTypes.Methods; @@ -244,17 +242,11 @@ public static class ExoCmd return id; } - + public static async Task CreateBucket(this Installation installation) { - var cors = new CORSConfiguration(); - cors.Rules.Add(new CORSRule()); - cors.Rules[0].AllowedHeaders = new List { "*" }; - cors.Rules[0].AllowedOrigins = new List { "*" }; - cors.Rules[0].AllowedMethods = new List { "Get", "Head" }; var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Creds!); - var a = await s3Region.PutBucket(installation.BucketName()); - return a != null && await a.PutCors(cors); + return await s3Region.PutBucket(installation.BucketName()) != null; } public static async Task SendConfig(this Installation installation, String config) diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 9e162fd6f..2db8e0ff4 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -99,11 +99,10 @@ public static class SessionMethods && user.HasWriteAccess && user.HasAccessToParentOf(installation) && Db.Create(installation) // TODO: these two in a transaction - && installation.SetOrderNumbers() + && installation.SetOrderNumbers() && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }) - && await installation.CreateBucket() - && await installation.RenewS3Credentials(); - // generation of access _after_ generation of + && await installation.CreateBucket() + && await installation.RenewS3Credentials(); // generation of access _after_ generation of // bucket to prevent "zombie" access-rights. // This might ** us over if the creation of access rights fails, // as bucket-names are unique and bound to the installation id... -K diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 09ebc3c0a..89d449465 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -1,3 +1,7 @@ +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using System.Threading.Channels; using Hellang.Middleware.ProblemDetails; using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Websockets; @@ -5,6 +9,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; using InnovEnergy.Lib.Utils; +using RabbitMQ.Client; namespace InnovEnergy.App.Backend; @@ -16,6 +21,16 @@ public static class Program Db.Init(); var builder = WebApplication.CreateBuilder(args); + string vpnServerIp = "194.182.190.208"; + //string vpnServerIp = "127.0.0.1"; + WebsocketManager.Factory = new ConnectionFactory { HostName = vpnServerIp}; + WebsocketManager.Connection = WebsocketManager.Factory.CreateConnection(); + WebsocketManager.Channel = WebsocketManager.Connection.CreateModel(); + Console.WriteLine("Middleware subscribed to RabbitMQ queue, ready for receiving messages"); + WebsocketManager.Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null); + + WebsocketManager.StartRabbitMqConsumer(); + Console.WriteLine("Queue declared"); WebsocketManager.InformInstallationsToSubscribeToRabbitMq(); builder.Services.AddControllers(); diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index 591bb1abf..00ef1b1e0 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -4,6 +4,7 @@ using System.Net.Sockets; using System.Net.WebSockets; using System.Text; using System.Text.Json; +using InnovEnergy.App.Backend.Database; using RabbitMQ.Client; using RabbitMQ.Client.Events; @@ -11,27 +12,30 @@ namespace InnovEnergy.App.Backend.Websockets; public static class WebsocketManager { - public static readonly Dictionary InstallationConnections = new Dictionary(); - private static ConnectionFactory _factory = null!; - private static IConnection _connection = null!; - private static IModel _channel = null!; + public static Dictionary InstallationConnections = new Dictionary(); + public static ConnectionFactory Factory = null!; + public static IConnection Connection = null!; + public static IModel Channel = null!; public static void InformInstallationsToSubscribeToRabbitMq() { - var installationsIds = new List { 1 }; - var installationIps = new List { "10.2.3.115" }; + //var installationIps = new List { "10.2.3.115" }; + var installationIps = Db.Installations.Select(inst => inst.VpnIp).ToList(); + Console.WriteLine("Count is "+installationIps.Count); var maxRetransmissions = 2; - StartRabbitMqConsumer(); - + 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 - for (int i = 0; i < installationsIds.Count; i++) + using (udpClient) { - 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++) { @@ -46,7 +50,7 @@ public static class WebsocketManager { byte[] replyData = udpClient.Receive(ref remoteEndPoint); string replyMessage = Encoding.UTF8.GetString(replyData); - Console.WriteLine("Received "+replyMessage +" from installation " + installationsIds[i]); + Console.WriteLine("Received " + replyMessage + " from installation " + installationIps[i]); break; } catch (SocketException ex) @@ -63,83 +67,79 @@ public static class WebsocketManager } } } + + Console.WriteLine("Start RabbitMQ Consumer"); + } - public static void StartRabbitMqConsumer() + public static async Task StartRabbitMqConsumer() { - string vpnServerIp = "194.182.190.208"; - //string vpnServerIp = "127.0.0.1"; - _factory = new ConnectionFactory { HostName = vpnServerIp}; - _connection = _factory.CreateConnection(); - _channel = _connection.CreateModel(); - Console.WriteLine("Middleware subscribed to RabbitMQ queue, ready for receiving messages"); - _channel.QueueDeclare(queue: "statusQueue", durable: false, exclusive: false, autoDelete: false, arguments: null); - - var consumer = new EventingBasicConsumer(_channel); - consumer.Received += (_, ea) => CallbackReceiveMessageFromQueue(ea); - _channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer); - } - - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - private static void CallbackReceiveMessageFromQueue(BasicDeliverEventArgs ea) - { - var body = ea.Body.ToArray(); - var message = Encoding.UTF8.GetString(body); - StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize(message); - - lock (InstallationConnections) + var consumer = new EventingBasicConsumer(Channel); + consumer.Received += (_, ea) => { - // Process the received message - if (receivedStatusMessage != null) + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize(message); + + lock (InstallationConnections) { - Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status); - Console.WriteLine("----------------------------------------------"); - Console.WriteLine("Update installation connection table"); - var installationId = receivedStatusMessage.InstallationId; - - if (!InstallationConnections.ContainsKey(installationId)) + // Process the received message + if (receivedStatusMessage != null) { - Console.WriteLine("Create new empty list for installation: " + installationId); - InstallationConnections[installationId] = new InstallationInfo + Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status); + Console.WriteLine("----------------------------------------------"); + Console.WriteLine("Update installation connection table"); + var installationId = receivedStatusMessage.InstallationId; + + if (!InstallationConnections.ContainsKey(installationId)) { - Status = receivedStatusMessage.Status - }; - } - - Console.WriteLine("----------------------------------------------"); - - foreach (var installationConnection in InstallationConnections) - { - if (installationConnection.Key == installationId && installationConnection.Value.Connections.Count > 0) - { - Console.WriteLine("Update all the connected websockets for installation " + installationId); - installationConnection.Value.Status = receivedStatusMessage.Status; - - var jsonObject = new + Console.WriteLine("Create new empty list for installation: " + installationId); + InstallationConnections[installationId] = new InstallationInfo { - id = installationId, - status = receivedStatusMessage.Status + Status = receivedStatusMessage.Status }; + } + else + { + InstallationConnections[installationId].Status = receivedStatusMessage.Status; + } - string jsonString = JsonSerializer.Serialize(jsonObject); - byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString); + Console.WriteLine("----------------------------------------------"); - foreach (var connection in installationConnection.Value.Connections) + foreach (var installationConnection in InstallationConnections) + { + if (installationConnection.Key == installationId && installationConnection.Value.Connections.Count > 0) { - connection.SendAsync( - new ArraySegment(dataToSend, 0, dataToSend.Length), - WebSocketMessageType.Text, - true, // Indicates that this is the end of the message - CancellationToken.None - ); + Console.WriteLine("Update all the connected websockets for installation " + installationId); + installationConnection.Value.Status = receivedStatusMessage.Status; + + var jsonObject = new + { + id = installationId, + status = receivedStatusMessage.Status + }; + + string jsonString = JsonSerializer.Serialize(jsonObject); + byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString); + + foreach (var connection in installationConnection.Value.Connections) + { + connection.SendAsync( + new ArraySegment(dataToSend, 0, dataToSend.Length), + WebSocketMessageType.Text, + true, // Indicates that this is the end of the message + CancellationToken.None + ); + } } } } } - } + }; + Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer); } + - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public static async Task HandleWebSocketConnection(WebSocket currentWebSocket) { var buffer = new byte[4096]; @@ -149,13 +149,34 @@ public static class WebsocketManager { //Listen for incoming messages on this WebSocket var result = await currentWebSocket.ReceiveAsync(buffer, CancellationToken.None); - Console.WriteLine("Received a new message from websocket"); + if (result.MessageType != WebSocketMessageType.Text) continue; var message = Encoding.UTF8.GetString(buffer, 0, result.Count); var installationIds = JsonSerializer.Deserialize(message); + //This is a ping message to keep the connection alive, reply with a pong + if (installationIds[0] == -1) + { + var jsonObject = new + { + id = -1, + status = -1 + }; + + var jsonString = JsonSerializer.Serialize(jsonObject); + var dataToSend = Encoding.UTF8.GetBytes(jsonString); + currentWebSocket.SendAsync(dataToSend, + WebSocketMessageType.Text, + true, + CancellationToken.None + ); + + continue; + } + + Console.WriteLine("Received a new message from websocket"); lock (InstallationConnections) { //Each front-end will send the list of the installations it wants to access diff --git a/csharp/App/SaliMax/HostList.txt b/csharp/App/SaliMax/HostList.txt index fa9ccc44a..d02c4d176 100755 --- a/csharp/App/SaliMax/HostList.txt +++ b/csharp/App/SaliMax/HostList.txt @@ -1,11 +1,11 @@ -Prototype ie-entwicklung@10.2.3.115 -Salimax0001 ie-entwicklung@10.2.3.104 -Salimax0002 ie-entwicklung@10.2.4.29 -Salimax0003 ie-entwicklung@10.2.4.33 -Salimax0004 ie-entwicklung@10.2.4.32 +Prototype ie-entwicklung@10.2.3.115 Prototype +Salimax0001 ie-entwicklung@10.2.3.104 Marti Technik (Bern) +Salimax0002 ie-entwicklung@10.2.4.29 Weidmann Oberwil (ZG) +Salimax0003 ie-entwicklung@10.2.4.33 Elektrotechnik Stefan GmbH +Salimax0004 ie-entwicklung@10.2.4.32 Biohof Gubelmann (Walde) Salimax0004A ie-entwicklung@10.2.4.153 -Salimax0005 ie-entwicklung@10.2.4.36 -Salimax0006 ie-entwicklung@10.2.4.35 -Salimax0007 ie-entwicklung@10.2.4.154 -Salimax0008 ie-entwicklung@10.2.4.113 \ No newline at end of file +Salimax0005 ie-entwicklung@10.2.4.36 Schreinerei Schönthal (Thun) +Salimax0006 ie-entwicklung@10.2.4.35 Steakhouse Mettmenstetten +Salimax0007 ie-entwicklung@10.2.4.154 LerchenhofHerr Twannberg +Salimax0008 ie-entwicklung@10.2.4.113 Wittmann Kottingbrunn diff --git a/csharp/App/SaliMax/resources/ConfigReadme.txt b/csharp/App/SaliMax/resources/ConfigReadme.txt deleted file mode 100644 index a47ab3469..000000000 --- a/csharp/App/SaliMax/resources/ConfigReadme.txt +++ /dev/null @@ -1,110 +0,0 @@ -"MinSoc": Number, 0 - 100 this is the minimum State of Charge that the batteries must not go below, - "ForceCalibrationCharge": Boolean (true or false), A flag to force a calibration charge, - "DisplayIndividualBatteries": Boolean (true or false), To display the indvidual batteries - "PConstant": Number 0 - 1, P value of our controller. - "GridSetPoint": Number in Watts, The set point of our controller. - "BatterySelfDischargePower": Number, 200, this a physical measurement of the self discharging power. - "HoldSocZone": Number, 1, This is magic number for the soft landing factor. - "IslandMode": { // Dc Link Voltage in Island mode - "AcDc": { - "MaxDcLinkVoltage": Number, 810, Max Dc Link Voltage, - "MinDcLinkVoltage": Number, 690, Min Dc Link Voltage, - "ReferenceDcLinkVoltage": Number, 750, Reference Dc Link - }, - "DcDc": { - "LowerDcLinkVoltage": Number, 50, Lower Dc Link Window , - "ReferenceDcLinkVoltage": 750, reference Dc Link - "UpperDcLinkVoltage": Number, 50, Upper Dc Link Window , - } - }, - "GridTie": {// Dc Link Voltage in GrieTie mode - "AcDc": { - "MaxDcLinkVoltage":Number, 780, Max Dc Link Voltage, - "MinDcLinkVoltage": Number, 690, Min Dc Link Voltage, - "ReferenceDcLinkVoltage": Number, 750, Reference Dc Link - }, - "DcDc": { - "LowerDcLinkVoltage": Number, 20, Lower Dc Link Window , - "ReferenceDcLinkVoltage": 750, reference Dc Link - "UpperDcLinkVoltage": Number, 20, Upper Dc Link Window , - } - }, - "MaxBatteryChargingCurrent":Number, 0 - 210, Max Charging current by DcDc - "MaxBatteryDischargingCurrent":Number, 0 - 210, Max Discharging current by DcDc - "MaxDcPower": Number, 0 - 10000, Max Power exported/imported by DcDc (10000 is the maximum) - "MaxChargeBatteryVoltage": Number, 57, Max Charging battery Voltage - "MinDischargeBatteryVoltage": Number, 0, Min Charging Battery Voltage - "Devices": { This is All Salimax devices (including offline ones) - "RelaysIp": { - "DeviceState": 1, // 0: is not present, 1: Present and Can be mesured, 2: Present but must be computed/calculted - "Host": "10.0.1.1", // Ip @ of the device in the local network - "Port": 502 // port - }, - "GridMeterIp": { - "DeviceState": 1, - "Host": "10.0.4.1", - "Port": 502 - }, - "PvOnAcGrid": { - "DeviceState": 0, // If a device is not present - "Host": "false", // this is not important - "Port": 0 // this is not important - }, - "LoadOnAcGrid": { - "DeviceState": 2, // this is a computed device - "Host": "true", - "Port": 0 - }, - "PvOnAcIsland": { - "DeviceState": 0, - "Host": "false", - "Port": 0 - }, - "IslandBusLoadMeterIp": { - "DeviceState": 1, - "Host": "10.0.4.2", - "Port": 502 - }, - "TruConvertAcIp": { - "DeviceState": 1, - "Host": "10.0.2.1", - "Port": 502 - }, - "PvOnDc": { - "DeviceState": 1, - "Host": "10.0.5.1", - "Port": 502 - }, - "LoadOnDc": { - "DeviceState": 0, - "Host": "false", - "Port": 0 - }, - "TruConvertDcIp": { - "DeviceState": 1, - "Host": "10.0.3.1", - "Port": 502 - }, - "BatteryIp": { - "DeviceState": 1, - "Host": "localhost", - "Port": 6855 - }, - "BatteryNodes": [ // this is a list of battery nodes - 2, - 3, - 4, - 5, - 6 - ] - }, - "S3": { // this is parameters of S3 Buckets and co - "Bucket": "8-3e5b3069-214a-43ee-8d85-57d72000c19d", - "Region": "sos-ch-dk-2", - "Provider": "exo.io", - "Key": "EXO502627299197f83e8b090f63", - "Secret": "jUNYJL6B23WjndJnJlgJj4rc1i7uh981u5Aba5xdA5s", - "ContentType": "text/plain; charset=utf-8", - "Host": "8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io", - "Url": "https://8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io" - } diff --git a/csharp/App/VrmGrabber/Controller.cs b/csharp/App/VrmGrabber/Controller.cs index 17589cf59..46dddd884 100644 --- a/csharp/App/VrmGrabber/Controller.cs +++ b/csharp/App/VrmGrabber/Controller.cs @@ -26,7 +26,8 @@ public record InstallationToHtmlInterface( [Controller] public class Controller : ControllerBase { - //Todo change me for updates + + //Todo automatically grab newest version? private const String FirmwareVersion = "AF09"; diff --git a/typescript/frontend-marios2/src/App.tsx b/typescript/frontend-marios2/src/App.tsx index 817713db7..df14a337a 100644 --- a/typescript/frontend-marios2/src/App.tsx +++ b/typescript/frontend-marios2/src/App.tsx @@ -17,8 +17,9 @@ import routes from 'src/Resources/routes.json'; import './App.css'; import ForgotPassword from './components/ForgotPassword'; import { axiosConfigWithoutToken } from './Resources/axiosConfig'; -import UsersContextProvider from './contexts/UsersContextProvider'; import InstallationsContextProvider from './contexts/InstallationsContextProvider'; +import AccessContextProvider from './contexts/AccessContextProvider'; +import WebSocketContextProvider from './contexts/WebSocketContextProvider'; function App() { const context = useContext(UserContext); @@ -148,20 +149,22 @@ function App() { + + + } > + - + } /> diff --git a/typescript/frontend-marios2/src/Resources/axiosConfig.tsx b/typescript/frontend-marios2/src/Resources/axiosConfig.tsx index 7fcd77f7b..b9d24d440 100644 --- a/typescript/frontend-marios2/src/Resources/axiosConfig.tsx +++ b/typescript/frontend-marios2/src/Resources/axiosConfig.tsx @@ -1,13 +1,13 @@ import axios from 'axios'; export const axiosConfigWithoutToken = axios.create({ - //baseURL: 'https://monitor.innov.energy/api' - baseURL: 'https://stage.innov.energy/api' + baseURL: 'https://monitor.innov.energy/api' + // baseURL: 'http://127.0.0.1:7087/api' }); const axiosConfig = axios.create({ - //baseURL: 'https://monitor.innov.energy/api' - baseURL: 'https://stage.innov.energy/api' + baseURL: 'https://monitor.innov.energy/api' + //baseURL: 'http://127.0.0.1:7087/api' }); axiosConfig.defaults.params = {}; diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx index 8ea7c68cb..58445c1ab 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx @@ -15,7 +15,7 @@ import { import { I_Installation } from 'src/interfaces/InstallationTypes'; import Installation from './Installation'; import CancelIcon from '@mui/icons-material/Cancel'; -import { LogContext } from 'src/contexts/LogContextProvider'; +import { WebSocketContext } from 'src/contexts/WebSocketContextProvider'; import { FormattedMessage } from 'react-intl'; import { useNavigate } from 'react-router-dom'; @@ -25,8 +25,8 @@ interface FlatInstallationViewProps { const FlatInstallationView = (props: FlatInstallationViewProps) => { const [isRowHovered, setHoveredRow] = useState(-1); - const logContext = useContext(LogContext); - const { getStatus } = logContext; + const webSocketContext = useContext(WebSocketContext); + const { getStatus } = webSocketContext; const navigate = useNavigate(); const searchParams = new URLSearchParams(location.search); const installationId = parseInt(searchParams.get('installation')); @@ -95,7 +95,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { installation.id === selectedInstallation; const status = getStatus(installation.id); - const rowStyles = isRowHovered === installation.id ? { diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index 73412541a..d4311ff40 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -29,12 +29,13 @@ import { TopologyValues } from 'src/content/dashboards/Log/graph.util'; import { Notification } from 'src/interfaces/S3Types'; -import { LogContext } from 'src/contexts/LogContextProvider'; +import { WebSocketContext } from 'src/contexts/WebSocketContextProvider'; import Topology from '../Topology/Topology'; import { FormattedMessage } from 'react-intl'; import Overview from '../Overview/overview'; import Configuration from '../Configuration/Configuration'; import { fetchData } from 'src/content/dashboards/Installations/fetchData'; +import CancelIcon from '@mui/icons-material/Cancel'; interface singleInstallationProps { current_installation?: I_Installation; @@ -64,16 +65,17 @@ function Installation(props: singleInstallationProps) { const [warnings, setWarnings] = useState([]); const [errors, setErrors] = useState([]); const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false); - const logContext = useContext(LogContext); - const { installationStatus, handleLogWarningOrError, getStatus } = logContext; + const webSocketsContext = useContext(WebSocketContext); + const { getStatus } = webSocketsContext; const searchParams = new URLSearchParams(location.search); const installationId = parseInt(searchParams.get('installation')); const currentTab = searchParams.get('tab'); - const [values, setValues] = useState(null); const [openModalDeleteInstallation, setOpenModalDeleteInstallation] = useState(false); + const status = getStatus(props.current_installation.id); + if (formValues == undefined) { return null; } @@ -131,126 +133,67 @@ function Installation(props: singleInstallationProps) { const s3Credentials = { s3Bucket, ...S3data }; useEffect(() => { - let isMounted = true; - setFormValues(props.current_installation); - setErrorLoadingS3Data(false); - let disconnectedStatusResult = []; + if (installationId == props.current_installation.id) { + let isMounted = true; + setFormValues(props.current_installation); + setErrorLoadingS3Data(false); + let disconnectedStatusResult = []; - const fetchDataPeriodically = async () => { - const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20)); - const date = now.toDate(); + const fetchDataPeriodically = async () => { + const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20)); + const date = now.toDate(); - try { - const res = await fetchData(now, s3Credentials); + try { + const res = await fetchData(now, s3Credentials); - if (!isMounted) { - return; - } - - const newWarnings: Notification[] = []; - const newErrors: Notification[] = []; - - if (res === FetchResult.notAvailable || res === FetchResult.tryLater) { - handleLogWarningOrError(props.current_installation.id, -1); - - disconnectedStatusResult.unshift(-1); - disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); - - let i = 0; - //If at least one status value shows an error, then show error - for (i; i < disconnectedStatusResult.length; i++) { - if (disconnectedStatusResult[i] != -1) { - break; - } + if (!isMounted) { + return; } - if (i === disconnectedStatusResult.length) { - setErrorLoadingS3Data(true); - } - } else { - setErrorLoadingS3Data(false); - setValues( - extractValues({ - time: now, - value: res - }) - ); + const newWarnings: Notification[] = []; + const newErrors: Notification[] = []; - for (const key in res) { - if ( - (res.hasOwnProperty(key) && - key.includes('/Alarms') && - res[key].value !== '') || - (key.includes('/Warnings') && res[key].value !== '') - ) { - if (key.includes('/Warnings')) { - newWarnings.push({ - device: key.substring(1, key.lastIndexOf('/')), - description: res[key].value.toString(), - date: - date.getFullYear() + - '-' + - date.getMonth() + - '-' + - date.getDay(), - time: - date.getHours() + - ':' + - date.getMinutes() + - ':' + - date.getSeconds() - }); - } else if (key.includes('/Alarms')) { - newErrors.push({ - device: key.substring(1, key.lastIndexOf('/')), - description: res[key].value.toString(), - date: - date.getFullYear() + - '-' + - date.getMonth() + - '-' + - date.getDay(), - time: - date.getHours() + - ':' + - date.getMinutes() + - ':' + - date.getSeconds() - }); + if ( + res === FetchResult.notAvailable || + res === FetchResult.tryLater + ) { + disconnectedStatusResult.unshift(-1); + disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); + + let i = 0; + //If at least one status value shows an error, then show error + for (i; i < disconnectedStatusResult.length; i++) { + if (disconnectedStatusResult[i] != -1) { + break; } } - } - setWarnings(newWarnings); - setErrors(newErrors); - - if (newErrors.length > 0) { - handleLogWarningOrError(props.current_installation.id, 2); - disconnectedStatusResult.unshift(2); - disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); - } else if (newWarnings.length > 0) { - disconnectedStatusResult.unshift(1); - disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); - handleLogWarningOrError(props.current_installation.id, 1); + if (i === disconnectedStatusResult.length) { + setErrorLoadingS3Data(true); + } } else { - disconnectedStatusResult.unshift(0); - disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); - handleLogWarningOrError(props.current_installation.id, 0); + setErrorLoadingS3Data(false); + setValues( + extractValues({ + time: now, + value: res + }) + ); } + } catch (err) { + setErrorLoadingS3Data(true); } - } catch (err) { - setErrorLoadingS3Data(true); - } - }; + }; - const interval = setInterval(fetchDataPeriodically, 2000); + const interval = setInterval(fetchDataPeriodically, 2000); - // Cleanup function to cancel interval and update isMounted when unmounted - return () => { - isMounted = false; - clearInterval(interval); - }; - }, []); + // Cleanup function to cancel interval and update isMounted when unmounted + return () => { + isMounted = false; + clearInterval(interval); + }; + } + }, [installationId]); if (installationId == props.current_installation.id) { return ( @@ -354,6 +297,69 @@ function Installation(props: singleInstallationProps) { {props.current_installation.name} +
+ + Status: + +
+ {status === -1 ? ( + + ) : ( + '' + )} + + {status === -2 ? ( + + ) : ( + '' + )} + +
+
+
+
+ +
+
- - - + + ); } diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index fc33e30eb..cb1d4ea9f 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -17,8 +17,8 @@ import InstallationSearch from './InstallationSearch'; import { FormattedMessage } from 'react-intl'; import { UserContext } from '../../../contexts/userContext'; import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; -import LogContextProvider from '../../../contexts/LogContextProvider'; import Installation from './Installation'; +import { WebSocketContext } from '../../../contexts/WebSocketContextProvider'; function InstallationTabs() { const theme = useTheme(); @@ -34,6 +34,15 @@ function InstallationTabs() { const { installations, fetchAllInstallations } = useContext(InstallationsContext); + const webSocketsContext = useContext(WebSocketContext); + const { socket, openSocket } = webSocketsContext; + + useEffect(() => { + if (!socket && installations.length > 0) { + openSocket(installations); + } + }, [installations]); + useEffect(() => { if (installations.length === 0) { fetchAllInstallations(); @@ -315,12 +324,10 @@ function InstallationTabs() { element={ - - - + } diff --git a/typescript/frontend-marios2/src/content/dashboards/ManageAccess/Access.tsx b/typescript/frontend-marios2/src/content/dashboards/ManageAccess/Access.tsx index f6d2dceab..a3912a47c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/ManageAccess/Access.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/ManageAccess/Access.tsx @@ -25,7 +25,6 @@ import PersonRemoveIcon from '@mui/icons-material/PersonRemove'; import PersonIcon from '@mui/icons-material/Person'; import Button from '@mui/material/Button'; import { Close as CloseIcon } from '@mui/icons-material'; -import { UsersContext } from 'src/contexts/UsersContextProvider'; import { AccessContext } from 'src/contexts/AccessContextProvider'; import { FormattedMessage } from 'react-intl'; @@ -43,7 +42,6 @@ function Access(props: AccessProps) { const [isRowHovered, setHoveredRow] = useState(-1); const [directButtonPressed, setDirectButtonPressed] = useState(false); const [inheritedButtonPressed, setInheritedButtonPressed] = useState(false); - const { availableUsers, fetchAvailableUsers } = useContext(UsersContext); const [selectedUsers, setSelectedUsers] = useState([]); const [openFolder, setOpenFolder] = useState(false); const [openModal, setOpenModal] = useState(false); @@ -55,6 +53,8 @@ function Access(props: AccessProps) { const accessContext = useContext(AccessContext); const { + availableUsers, + fetchAvailableUsers, usersWithDirectAccess, fetchUsersWithDirectAccessForResource, usersWithInheritedAccess, diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index e535ae3b8..061986eab 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -125,7 +125,7 @@ function Overview(props: OverviewProps) { const timestamp = item.time.ticks * 1000; const adjustedTimestamp = new Date(timestamp); - adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 2); + adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1); if (csvContent[path]) { const value = csvContent[path]; diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx index a52bff156..4e9ddd922 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx @@ -7,7 +7,7 @@ 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 { LogContext } from 'src/contexts/LogContextProvider'; +import { WebSocketContext } from 'src/contexts/WebSocketContextProvider'; import routes from 'src/Resources/routes.json'; import { useNavigate } from 'react-router-dom'; @@ -38,8 +38,8 @@ const useTreeItemStyles = makeStyles((theme) => ({ function CustomTreeItem(props: CustomTreeItemProps) { const theme = useTheme(); const classes = useTreeItemStyles(); - const logContext = useContext(LogContext); - const { getStatus } = logContext; + const webSocketContext = useContext(WebSocketContext); + const { getStatus } = webSocketContext; const status = getStatus(props.node.id); const navigate = useNavigate(); const [selected, setSelected] = useState(false); diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx index a1d90cd69..d7ca160e5 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx @@ -8,7 +8,7 @@ import { } from '@mui/material'; import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone'; import FlatUsersView from './FlatUsersView'; -import { UsersContext } from '../../../contexts/UsersContextProvider'; +import { AccessContext } from '../../../contexts/AccessContextProvider'; import Button from '@mui/material/Button'; import UserForm from './userForm'; import { UserContext } from '../../../contexts/userContext'; @@ -17,7 +17,7 @@ import { FormattedMessage } from 'react-intl'; function UsersSearch() { const theme = useTheme(); const [searchTerm, setSearchTerm] = useState(''); - const { availableUsers, fetchAvailableUsers } = useContext(UsersContext); + const { availableUsers, fetchAvailableUsers } = useContext(AccessContext); const [filteredData, setFilteredData] = useState(availableUsers); const [openModal, setOpenModal] = useState(false); const context = useContext(UserContext); diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx index ed1c92cdd..1a3e95a48 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx @@ -1,15 +1,15 @@ import Footer from 'src/components/Footer'; import { Box, Container, Grid, useTheme } from '@mui/material'; import UsersSearch from './UsersSearch'; -import UsersContextProvider from 'src/contexts/UsersContextProvider'; import React from 'react'; +import AccessContextProvider from '../../../contexts/AccessContextProvider'; function Users() { const theme = useTheme(); return ( <> - + @@ -18,7 +18,7 @@ function Users() {