Update backend and frontend with middleware functionality

This commit is contained in:
Noe 2023-11-13 17:51:36 +01:00
parent 71b4a1d2bd
commit 1155e1bc4d
25 changed files with 318 additions and 553 deletions

View File

@ -61,6 +61,7 @@ public class Controller : ControllerBase
if (session is null) if (session is null)
{ {
Console.WriteLine("------------------------------------Unauthorized user----------------------------------------------");
HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
HttpContext.Abort(); HttpContext.Abort();
return; return;
@ -68,6 +69,7 @@ public class Controller : ControllerBase
if (!HttpContext.WebSockets.IsWebSocketRequest) if (!HttpContext.WebSockets.IsWebSocketRequest)
{ {
Console.WriteLine("------------------------------------Not a websocket request ----------------------------------------------");
HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
HttpContext.Abort(); HttpContext.Abort();
return; return;

View File

@ -8,7 +8,8 @@ public class Installation : TreeNode
public String Region { get; set; } = ""; public String Region { get; set; } = "";
public String Country { get; set; } = ""; public String Country { get; set; } = "";
public String InstallationName { get; set; } = ""; public String InstallationName { get; set; } = "";
public String VpnIp { get; set; } = "";
// TODO: make relation // TODO: make relation
//public IReadOnlyList<String> OrderNumbers { get; set; } = Array.Empty<String>(); //public IReadOnlyList<String> OrderNumbers { get; set; } = Array.Empty<String>();
// public String? OrderNumbers { get; set; } = ""; // public String? OrderNumbers { get; set; } = "";
@ -25,6 +26,7 @@ public class Installation : TreeNode
public String ReadRoleId { get; set; } = ""; public String ReadRoleId { get; set; } = "";
public String WriteRoleId { get; set; } = ""; public String WriteRoleId { get; set; } = "";
[Ignore] [Ignore]
public String OrderNumbers { get; set; } public String OrderNumbers { get; set; }

View File

@ -6,9 +6,7 @@ using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Amazon.S3.Model;
using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Database;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.DataTypes.Methods; namespace InnovEnergy.App.Backend.DataTypes.Methods;
@ -244,17 +242,11 @@ public static class ExoCmd
return id; return id;
} }
public static async Task<Boolean> CreateBucket(this Installation installation) public static async Task<Boolean> CreateBucket(this Installation installation)
{ {
var cors = new CORSConfiguration();
cors.Rules.Add(new CORSRule());
cors.Rules[0].AllowedHeaders = new List<string> { "*" };
cors.Rules[0].AllowedOrigins = new List<string> { "*" };
cors.Rules[0].AllowedMethods = new List<string> { "Get", "Head" };
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Creds!); var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Creds!);
var a = await s3Region.PutBucket(installation.BucketName()); return await s3Region.PutBucket(installation.BucketName()) != null;
return a != null && await a.PutCors(cors);
} }
public static async Task<Boolean> SendConfig(this Installation installation, String config) public static async Task<Boolean> SendConfig(this Installation installation, String config)

View File

@ -99,11 +99,10 @@ public static class SessionMethods
&& user.HasWriteAccess && user.HasWriteAccess
&& user.HasAccessToParentOf(installation) && user.HasAccessToParentOf(installation)
&& Db.Create(installation) // TODO: these two in a transaction && Db.Create(installation) // TODO: these two in a transaction
&& installation.SetOrderNumbers() && installation.SetOrderNumbers()
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }) && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
&& await installation.CreateBucket() && await installation.CreateBucket()
&& await installation.RenewS3Credentials(); && await installation.RenewS3Credentials(); // generation of access _after_ generation of
// generation of access _after_ generation of
// bucket to prevent "zombie" access-rights. // bucket to prevent "zombie" access-rights.
// This might ** us over if the creation of access rights fails, // This might ** us over if the creation of access rights fails,
// as bucket-names are unique and bound to the installation id... -K // as bucket-names are unique and bound to the installation id... -K

View File

@ -1,3 +1,7 @@
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading.Channels;
using Hellang.Middleware.ProblemDetails; using Hellang.Middleware.ProblemDetails;
using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.Websockets; using InnovEnergy.App.Backend.Websockets;
@ -5,6 +9,7 @@ using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
using RabbitMQ.Client;
namespace InnovEnergy.App.Backend; namespace InnovEnergy.App.Backend;
@ -16,6 +21,16 @@ public static class Program
Db.Init(); Db.Init();
var builder = WebApplication.CreateBuilder(args); 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(); WebsocketManager.InformInstallationsToSubscribeToRabbitMq();
builder.Services.AddControllers(); builder.Services.AddControllers();

View File

@ -4,6 +4,7 @@ using System.Net.Sockets;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using InnovEnergy.App.Backend.Database;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
@ -11,27 +12,30 @@ namespace InnovEnergy.App.Backend.Websockets;
public static class WebsocketManager public static class WebsocketManager
{ {
public static readonly Dictionary<int, InstallationInfo> InstallationConnections = new Dictionary<int, InstallationInfo>(); public static Dictionary<int, InstallationInfo> InstallationConnections = new Dictionary<int, InstallationInfo>();
private static ConnectionFactory _factory = null!; public static ConnectionFactory Factory = null!;
private static IConnection _connection = null!; public static IConnection Connection = null!;
private static IModel _channel = null!; public static IModel Channel = null!;
public static void InformInstallationsToSubscribeToRabbitMq() public static void InformInstallationsToSubscribeToRabbitMq()
{ {
var installationsIds = new List<int> { 1 }; //var installationIps = new List<string> { "10.2.3.115" };
var installationIps = new List<string> { "10.2.3.115" }; var installationIps = Db.Installations.Select(inst => inst.VpnIp).ToList();
Console.WriteLine("Count is "+installationIps.Count);
var maxRetransmissions = 2; var maxRetransmissions = 2;
StartRabbitMqConsumer();
UdpClient udpClient = new UdpClient(); UdpClient udpClient = new UdpClient();
udpClient.Client.ReceiveTimeout = 2000; udpClient.Client.ReceiveTimeout = 2000;
int port = 9000; int port = 9000;
//Send a message to each installation and tell it to subscribe to the queue //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. //Try at most MAX_RETRANSMISSIONS times to reach an installation.
for (int j = 0; j < maxRetransmissions; j++) for (int j = 0; j < maxRetransmissions; j++)
{ {
@ -46,7 +50,7 @@ public static class WebsocketManager
{ {
byte[] replyData = udpClient.Receive(ref remoteEndPoint); byte[] replyData = udpClient.Receive(ref remoteEndPoint);
string replyMessage = Encoding.UTF8.GetString(replyData); string replyMessage = Encoding.UTF8.GetString(replyData);
Console.WriteLine("Received "+replyMessage +" from installation " + installationsIds[i]); Console.WriteLine("Received " + replyMessage + " from installation " + installationIps[i]);
break; break;
} }
catch (SocketException ex) 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"; var consumer = new EventingBasicConsumer(Channel);
//string vpnServerIp = "127.0.0.1"; consumer.Received += (_, ea) =>
_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 = "<Pending>")]
private static void CallbackReceiveMessageFromQueue(BasicDeliverEventArgs ea)
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
lock (InstallationConnections)
{ {
// Process the received message var body = ea.Body.ToArray();
if (receivedStatusMessage != null) var message = Encoding.UTF8.GetString(body);
StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
lock (InstallationConnections)
{ {
Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status); // Process the received message
Console.WriteLine("----------------------------------------------"); if (receivedStatusMessage != null)
Console.WriteLine("Update installation connection table");
var installationId = receivedStatusMessage.InstallationId;
if (!InstallationConnections.ContainsKey(installationId))
{ {
Console.WriteLine("Create new empty list for installation: " + installationId); Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status);
InstallationConnections[installationId] = new InstallationInfo Console.WriteLine("----------------------------------------------");
Console.WriteLine("Update installation connection table");
var installationId = receivedStatusMessage.InstallationId;
if (!InstallationConnections.ContainsKey(installationId))
{ {
Status = receivedStatusMessage.Status Console.WriteLine("Create new empty list for installation: " + installationId);
}; InstallationConnections[installationId] = new InstallationInfo
}
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
{ {
id = installationId, Status = receivedStatusMessage.Status
status = receivedStatusMessage.Status
}; };
}
else
{
InstallationConnections[installationId].Status = receivedStatusMessage.Status;
}
string jsonString = JsonSerializer.Serialize(jsonObject); Console.WriteLine("----------------------------------------------");
byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString);
foreach (var connection in installationConnection.Value.Connections) foreach (var installationConnection in InstallationConnections)
{
if (installationConnection.Key == installationId && installationConnection.Value.Connections.Count > 0)
{ {
connection.SendAsync( Console.WriteLine("Update all the connected websockets for installation " + installationId);
new ArraySegment<byte>(dataToSend, 0, dataToSend.Length), installationConnection.Value.Status = receivedStatusMessage.Status;
WebSocketMessageType.Text,
true, // Indicates that this is the end of the message var jsonObject = new
CancellationToken.None {
); 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<byte>(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 = "<Pending>")]
public static async Task HandleWebSocketConnection(WebSocket currentWebSocket) public static async Task HandleWebSocketConnection(WebSocket currentWebSocket)
{ {
var buffer = new byte[4096]; var buffer = new byte[4096];
@ -149,13 +149,34 @@ public static class WebsocketManager
{ {
//Listen for incoming messages on this WebSocket //Listen for incoming messages on this WebSocket
var result = await currentWebSocket.ReceiveAsync(buffer, CancellationToken.None); var result = await currentWebSocket.ReceiveAsync(buffer, CancellationToken.None);
Console.WriteLine("Received a new message from websocket");
if (result.MessageType != WebSocketMessageType.Text) if (result.MessageType != WebSocketMessageType.Text)
continue; continue;
var message = Encoding.UTF8.GetString(buffer, 0, result.Count); var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
var installationIds = JsonSerializer.Deserialize<int[]>(message); var installationIds = JsonSerializer.Deserialize<int[]>(message);
//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) lock (InstallationConnections)
{ {
//Each front-end will send the list of the installations it wants to access //Each front-end will send the list of the installations it wants to access

View File

@ -1,11 +1,11 @@
Prototype ie-entwicklung@10.2.3.115 Prototype ie-entwicklung@10.2.3.115 Prototype
Salimax0001 ie-entwicklung@10.2.3.104 Salimax0001 ie-entwicklung@10.2.3.104 Marti Technik (Bern)
Salimax0002 ie-entwicklung@10.2.4.29 Salimax0002 ie-entwicklung@10.2.4.29 Weidmann Oberwil (ZG)
Salimax0003 ie-entwicklung@10.2.4.33 Salimax0003 ie-entwicklung@10.2.4.33 Elektrotechnik Stefan GmbH
Salimax0004 ie-entwicklung@10.2.4.32 Salimax0004 ie-entwicklung@10.2.4.32 Biohof Gubelmann (Walde)
Salimax0004A ie-entwicklung@10.2.4.153 Salimax0004A ie-entwicklung@10.2.4.153
Salimax0005 ie-entwicklung@10.2.4.36 Salimax0005 ie-entwicklung@10.2.4.36 Schreinerei Schönthal (Thun)
Salimax0006 ie-entwicklung@10.2.4.35 Salimax0006 ie-entwicklung@10.2.4.35 Steakhouse Mettmenstetten
Salimax0007 ie-entwicklung@10.2.4.154 Salimax0007 ie-entwicklung@10.2.4.154 LerchenhofHerr Twannberg
Salimax0008 ie-entwicklung@10.2.4.113 Salimax0008 ie-entwicklung@10.2.4.113 Wittmann Kottingbrunn

View File

@ -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"
}

View File

@ -26,7 +26,8 @@ public record InstallationToHtmlInterface(
[Controller] [Controller]
public class Controller : ControllerBase public class Controller : ControllerBase
{ {
//Todo change me for updates
//Todo automatically grab newest version?
private const String FirmwareVersion = "AF09"; private const String FirmwareVersion = "AF09";

View File

@ -17,8 +17,9 @@ import routes from 'src/Resources/routes.json';
import './App.css'; import './App.css';
import ForgotPassword from './components/ForgotPassword'; import ForgotPassword from './components/ForgotPassword';
import { axiosConfigWithoutToken } from './Resources/axiosConfig'; import { axiosConfigWithoutToken } from './Resources/axiosConfig';
import UsersContextProvider from './contexts/UsersContextProvider';
import InstallationsContextProvider from './contexts/InstallationsContextProvider'; import InstallationsContextProvider from './contexts/InstallationsContextProvider';
import AccessContextProvider from './contexts/AccessContextProvider';
import WebSocketContextProvider from './contexts/WebSocketContextProvider';
function App() { function App() {
const context = useContext(UserContext); const context = useContext(UserContext);
@ -148,20 +149,22 @@ function App() {
<Route <Route
path="/" path="/"
element={ element={
<SidebarLayout <WebSocketContextProvider>
language={language} <SidebarLayout
onSelectLanguage={setLanguage} language={language}
/> onSelectLanguage={setLanguage}
/>
</WebSocketContextProvider>
} }
> >
<Route <Route
path={routes.installations + '*'} path={routes.installations + '*'}
element={ element={
<UsersContextProvider> <AccessContextProvider>
<InstallationsContextProvider> <InstallationsContextProvider>
<InstallationTabs /> <InstallationTabs />
</InstallationsContextProvider> </InstallationsContextProvider>
</UsersContextProvider> </AccessContextProvider>
} }
/> />

View File

@ -1,13 +1,13 @@
import axios from 'axios'; import axios from 'axios';
export const axiosConfigWithoutToken = axios.create({ export const axiosConfigWithoutToken = axios.create({
//baseURL: 'https://monitor.innov.energy/api' baseURL: 'https://monitor.innov.energy/api'
baseURL: 'https://stage.innov.energy/api' // baseURL: 'http://127.0.0.1:7087/api'
}); });
const axiosConfig = axios.create({ const axiosConfig = axios.create({
//baseURL: 'https://monitor.innov.energy/api' baseURL: 'https://monitor.innov.energy/api'
baseURL: 'https://stage.innov.energy/api' //baseURL: 'http://127.0.0.1:7087/api'
}); });
axiosConfig.defaults.params = {}; axiosConfig.defaults.params = {};

View File

@ -15,7 +15,7 @@ import {
import { I_Installation } from 'src/interfaces/InstallationTypes'; import { I_Installation } from 'src/interfaces/InstallationTypes';
import Installation from './Installation'; import Installation from './Installation';
import CancelIcon from '@mui/icons-material/Cancel'; 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 { FormattedMessage } from 'react-intl';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -25,8 +25,8 @@ interface FlatInstallationViewProps {
const FlatInstallationView = (props: FlatInstallationViewProps) => { const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [isRowHovered, setHoveredRow] = useState(-1); const [isRowHovered, setHoveredRow] = useState(-1);
const logContext = useContext(LogContext); const webSocketContext = useContext(WebSocketContext);
const { getStatus } = logContext; const { getStatus } = webSocketContext;
const navigate = useNavigate(); const navigate = useNavigate();
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
const installationId = parseInt(searchParams.get('installation')); const installationId = parseInt(searchParams.get('installation'));
@ -95,7 +95,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
installation.id === selectedInstallation; installation.id === selectedInstallation;
const status = getStatus(installation.id); const status = getStatus(installation.id);
const rowStyles = const rowStyles =
isRowHovered === installation.id isRowHovered === installation.id
? { ? {

View File

@ -29,12 +29,13 @@ import {
TopologyValues TopologyValues
} from 'src/content/dashboards/Log/graph.util'; } from 'src/content/dashboards/Log/graph.util';
import { Notification } from 'src/interfaces/S3Types'; import { Notification } from 'src/interfaces/S3Types';
import { LogContext } from 'src/contexts/LogContextProvider'; import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import Topology from '../Topology/Topology'; import Topology from '../Topology/Topology';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Overview from '../Overview/overview'; import Overview from '../Overview/overview';
import Configuration from '../Configuration/Configuration'; import Configuration from '../Configuration/Configuration';
import { fetchData } from 'src/content/dashboards/Installations/fetchData'; import { fetchData } from 'src/content/dashboards/Installations/fetchData';
import CancelIcon from '@mui/icons-material/Cancel';
interface singleInstallationProps { interface singleInstallationProps {
current_installation?: I_Installation; current_installation?: I_Installation;
@ -64,16 +65,17 @@ function Installation(props: singleInstallationProps) {
const [warnings, setWarnings] = useState<Notification[]>([]); const [warnings, setWarnings] = useState<Notification[]>([]);
const [errors, setErrors] = useState<Notification[]>([]); const [errors, setErrors] = useState<Notification[]>([]);
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false); const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
const logContext = useContext(LogContext); const webSocketsContext = useContext(WebSocketContext);
const { installationStatus, handleLogWarningOrError, getStatus } = logContext; const { getStatus } = webSocketsContext;
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
const installationId = parseInt(searchParams.get('installation')); const installationId = parseInt(searchParams.get('installation'));
const currentTab = searchParams.get('tab'); const currentTab = searchParams.get('tab');
const [values, setValues] = useState<TopologyValues | null>(null); const [values, setValues] = useState<TopologyValues | null>(null);
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] = const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
useState(false); useState(false);
const status = getStatus(props.current_installation.id);
if (formValues == undefined) { if (formValues == undefined) {
return null; return null;
} }
@ -131,126 +133,67 @@ function Installation(props: singleInstallationProps) {
const s3Credentials = { s3Bucket, ...S3data }; const s3Credentials = { s3Bucket, ...S3data };
useEffect(() => { useEffect(() => {
let isMounted = true; if (installationId == props.current_installation.id) {
setFormValues(props.current_installation); let isMounted = true;
setErrorLoadingS3Data(false); setFormValues(props.current_installation);
let disconnectedStatusResult = []; setErrorLoadingS3Data(false);
let disconnectedStatusResult = [];
const fetchDataPeriodically = async () => { const fetchDataPeriodically = async () => {
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20)); const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
const date = now.toDate(); const date = now.toDate();
try { try {
const res = await fetchData(now, s3Credentials); const res = await fetchData(now, s3Credentials);
if (!isMounted) { if (!isMounted) {
return; 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 (i === disconnectedStatusResult.length) { const newWarnings: Notification[] = [];
setErrorLoadingS3Data(true); const newErrors: Notification[] = [];
}
} else {
setErrorLoadingS3Data(false);
setValues(
extractValues({
time: now,
value: res
})
);
for (const key in res) { if (
if ( res === FetchResult.notAvailable ||
(res.hasOwnProperty(key) && res === FetchResult.tryLater
key.includes('/Alarms') && ) {
res[key].value !== '') || disconnectedStatusResult.unshift(-1);
(key.includes('/Warnings') && res[key].value !== '') disconnectedStatusResult = disconnectedStatusResult.slice(0, 5);
) {
if (key.includes('/Warnings')) { let i = 0;
newWarnings.push({ //If at least one status value shows an error, then show error
device: key.substring(1, key.lastIndexOf('/')), for (i; i < disconnectedStatusResult.length; i++) {
description: res[key].value.toString(), if (disconnectedStatusResult[i] != -1) {
date: break;
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()
});
} }
} }
}
setWarnings(newWarnings); if (i === disconnectedStatusResult.length) {
setErrors(newErrors); setErrorLoadingS3Data(true);
}
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);
} else { } else {
disconnectedStatusResult.unshift(0); setErrorLoadingS3Data(false);
disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); setValues(
handleLogWarningOrError(props.current_installation.id, 0); 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 // Cleanup function to cancel interval and update isMounted when unmounted
return () => { return () => {
isMounted = false; isMounted = false;
clearInterval(interval); clearInterval(interval);
}; };
}, []); }
}, [installationId]);
if (installationId == props.current_installation.id) { if (installationId == props.current_installation.id) {
return ( return (
@ -354,6 +297,69 @@ function Installation(props: singleInstallationProps) {
{props.current_installation.name} {props.current_installation.name}
</Typography> </Typography>
</div> </div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Typography
fontWeight="bold"
color="text.primary"
noWrap
sx={{
marginTop: '0px',
marginBottom: '10px',
fontSize: '14px'
}}
>
Status:
</Typography>
<div
style={{
display: 'flex',
alignItems: 'center',
marginLeft: '80px',
marginTop: '-10px'
}}
>
{status === -1 ? (
<CancelIcon
style={{
width: '23px',
height: '23px',
color: 'red',
borderRadius: '50%'
}}
/>
) : (
''
)}
{status === -2 ? (
<CircularProgress
size={20}
sx={{
color: '#f7b34d'
}}
/>
) : (
''
)}
<div
style={{
width: '20px',
height: '20px',
marginLeft: '2px',
borderRadius: '50%',
backgroundColor:
status === 2
? 'red'
: status === 1
? 'orange'
: status === -1 || status === -2
? 'transparent'
: 'green'
}}
/>
</div>
</div>
<Card variant="outlined"> <Card variant="outlined">
<Grid <Grid
@ -482,6 +488,16 @@ function Installation(props: singleInstallationProps) {
{currentUser.hasWriteAccess && ( {currentUser.hasWriteAccess && (
<> <>
<div>
<TextField
label="Vpn IP"
name="VpnIp"
value={formValues.vpnIp}
variant="outlined"
fullWidth
/>
</div>
<div> <div>
<TextField <TextField
label="S3 Write Key" label="S3 Write Key"

View File

@ -8,7 +8,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone'; import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView'; import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
import LogContextProvider from '../../../contexts/LogContextProvider';
import { I_Installation } from '../../../interfaces/InstallationTypes'; import { I_Installation } from '../../../interfaces/InstallationTypes';
interface installationSearchProps { interface installationSearchProps {
@ -59,9 +58,8 @@ function InstallationSearch(props: installationSearchProps) {
</FormControl> </FormControl>
</Grid> </Grid>
</Grid> </Grid>
<LogContextProvider>
<FlatInstallationView installations={filteredData} /> <FlatInstallationView installations={filteredData} />
</LogContextProvider>
</> </>
); );
} }

View File

@ -17,8 +17,8 @@ import InstallationSearch from './InstallationSearch';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { UserContext } from '../../../contexts/userContext'; import { UserContext } from '../../../contexts/userContext';
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
import LogContextProvider from '../../../contexts/LogContextProvider';
import Installation from './Installation'; import Installation from './Installation';
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
function InstallationTabs() { function InstallationTabs() {
const theme = useTheme(); const theme = useTheme();
@ -34,6 +34,15 @@ function InstallationTabs() {
const { installations, fetchAllInstallations } = const { installations, fetchAllInstallations } =
useContext(InstallationsContext); useContext(InstallationsContext);
const webSocketsContext = useContext(WebSocketContext);
const { socket, openSocket } = webSocketsContext;
useEffect(() => {
if (!socket && installations.length > 0) {
openSocket(installations);
}
}, [installations]);
useEffect(() => { useEffect(() => {
if (installations.length === 0) { if (installations.length === 0) {
fetchAllInstallations(); fetchAllInstallations();
@ -315,12 +324,10 @@ function InstallationTabs() {
element={ element={
<Grid item xs={12}> <Grid item xs={12}>
<Box p={4}> <Box p={4}>
<LogContextProvider> <Installation
<Installation current_installation={installations[0]}
current_installation={installations[0]} type="installation"
type="installation" ></Installation>
></Installation>
</LogContextProvider>
</Box> </Box>
</Grid> </Grid>
} }

View File

@ -25,7 +25,6 @@ import PersonRemoveIcon from '@mui/icons-material/PersonRemove';
import PersonIcon from '@mui/icons-material/Person'; import PersonIcon from '@mui/icons-material/Person';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { Close as CloseIcon } from '@mui/icons-material'; import { Close as CloseIcon } from '@mui/icons-material';
import { UsersContext } from 'src/contexts/UsersContextProvider';
import { AccessContext } from 'src/contexts/AccessContextProvider'; import { AccessContext } from 'src/contexts/AccessContextProvider';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -43,7 +42,6 @@ function Access(props: AccessProps) {
const [isRowHovered, setHoveredRow] = useState(-1); const [isRowHovered, setHoveredRow] = useState(-1);
const [directButtonPressed, setDirectButtonPressed] = useState(false); const [directButtonPressed, setDirectButtonPressed] = useState(false);
const [inheritedButtonPressed, setInheritedButtonPressed] = useState(false); const [inheritedButtonPressed, setInheritedButtonPressed] = useState(false);
const { availableUsers, fetchAvailableUsers } = useContext(UsersContext);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]); const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [openFolder, setOpenFolder] = useState(false); const [openFolder, setOpenFolder] = useState(false);
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
@ -55,6 +53,8 @@ function Access(props: AccessProps) {
const accessContext = useContext(AccessContext); const accessContext = useContext(AccessContext);
const { const {
availableUsers,
fetchAvailableUsers,
usersWithDirectAccess, usersWithDirectAccess,
fetchUsersWithDirectAccessForResource, fetchUsersWithDirectAccessForResource,
usersWithInheritedAccess, usersWithInheritedAccess,

View File

@ -125,7 +125,7 @@ function Overview(props: OverviewProps) {
const timestamp = item.time.ticks * 1000; const timestamp = item.time.ticks * 1000;
const adjustedTimestamp = new Date(timestamp); const adjustedTimestamp = new Date(timestamp);
adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 2); adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1);
if (csvContent[path]) { if (csvContent[path]) {
const value = csvContent[path]; const value = csvContent[path];

View File

@ -7,7 +7,7 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { makeStyles } from '@mui/styles'; import { makeStyles } from '@mui/styles';
import CancelIcon from '@mui/icons-material/Cancel'; 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 routes from 'src/Resources/routes.json';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -38,8 +38,8 @@ const useTreeItemStyles = makeStyles((theme) => ({
function CustomTreeItem(props: CustomTreeItemProps) { function CustomTreeItem(props: CustomTreeItemProps) {
const theme = useTheme(); const theme = useTheme();
const classes = useTreeItemStyles(); const classes = useTreeItemStyles();
const logContext = useContext(LogContext); const webSocketContext = useContext(WebSocketContext);
const { getStatus } = logContext; const { getStatus } = webSocketContext;
const status = getStatus(props.node.id); const status = getStatus(props.node.id);
const navigate = useNavigate(); const navigate = useNavigate();
const [selected, setSelected] = useState(false); const [selected, setSelected] = useState(false);

View File

@ -8,7 +8,7 @@ import {
} from '@mui/material'; } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone'; import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import FlatUsersView from './FlatUsersView'; import FlatUsersView from './FlatUsersView';
import { UsersContext } from '../../../contexts/UsersContextProvider'; import { AccessContext } from '../../../contexts/AccessContextProvider';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import UserForm from './userForm'; import UserForm from './userForm';
import { UserContext } from '../../../contexts/userContext'; import { UserContext } from '../../../contexts/userContext';
@ -17,7 +17,7 @@ import { FormattedMessage } from 'react-intl';
function UsersSearch() { function UsersSearch() {
const theme = useTheme(); const theme = useTheme();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const { availableUsers, fetchAvailableUsers } = useContext(UsersContext); const { availableUsers, fetchAvailableUsers } = useContext(AccessContext);
const [filteredData, setFilteredData] = useState(availableUsers); const [filteredData, setFilteredData] = useState(availableUsers);
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const context = useContext(UserContext); const context = useContext(UserContext);

View File

@ -1,15 +1,15 @@
import Footer from 'src/components/Footer'; import Footer from 'src/components/Footer';
import { Box, Container, Grid, useTheme } from '@mui/material'; import { Box, Container, Grid, useTheme } from '@mui/material';
import UsersSearch from './UsersSearch'; import UsersSearch from './UsersSearch';
import UsersContextProvider from 'src/contexts/UsersContextProvider';
import React from 'react'; import React from 'react';
import AccessContextProvider from '../../../contexts/AccessContextProvider';
function Users() { function Users() {
const theme = useTheme(); const theme = useTheme();
return ( return (
<> <>
<UsersContextProvider> <AccessContextProvider>
<Container maxWidth="xl" sx={{ marginTop: '20px' }}> <Container maxWidth="xl" sx={{ marginTop: '20px' }}>
<Grid item xs={12}> <Grid item xs={12}>
<Box p={4}> <Box p={4}>
@ -18,7 +18,7 @@ function Users() {
</Grid> </Grid>
</Container> </Container>
<Footer /> <Footer />
</UsersContextProvider> </AccessContextProvider>
</> </>
); );
} }

View File

@ -14,6 +14,8 @@ import {
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
interface AccessContextProviderProps { interface AccessContextProviderProps {
availableUsers: InnovEnergyUser[];
fetchAvailableUsers: () => Promise<void>;
usersWithDirectAccess: InnovEnergyUser[]; usersWithDirectAccess: InnovEnergyUser[];
fetchUsersWithDirectAccessForResource: ( fetchUsersWithDirectAccessForResource: (
tempresourceType: string, tempresourceType: string,
@ -42,6 +44,10 @@ interface AccessContextProviderProps {
} }
export const AccessContext = createContext<AccessContextProviderProps>({ export const AccessContext = createContext<AccessContextProviderProps>({
availableUsers: [],
fetchAvailableUsers: () => {
return Promise.resolve();
},
usersWithDirectAccess: [], usersWithDirectAccess: [],
fetchUsersWithDirectAccessForResource: () => Promise.resolve(), fetchUsersWithDirectAccessForResource: () => Promise.resolve(),
usersWithInheritedAccess: [], usersWithInheritedAccess: [],
@ -67,6 +73,7 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
const [usersWithDirectAccess, setUsersWithDirectAccess] = useState< const [usersWithDirectAccess, setUsersWithDirectAccess] = useState<
InnovEnergyUser[] InnovEnergyUser[]
>([]); >([]);
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
const [usersWithInheritedAccess, setUsersWithInheritedAccess] = useState< const [usersWithInheritedAccess, setUsersWithInheritedAccess] = useState<
I_UserWithInheritedAccess[] I_UserWithInheritedAccess[]
@ -120,6 +127,12 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
[] []
); );
const fetchAvailableUsers = async (): Promise<void> => {
return axiosConfig.get('/GetAllChildUsers').then((res) => {
setAvailableUsers(res.data);
});
};
const RevokeAccessFromResource = useCallback( const RevokeAccessFromResource = useCallback(
async ( async (
resourceType: string, resourceType: string,
@ -179,6 +192,8 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
return ( return (
<AccessContext.Provider <AccessContext.Provider
value={{ value={{
availableUsers,
fetchAvailableUsers,
usersWithDirectAccess, usersWithDirectAccess,
fetchUsersWithDirectAccessForResource, fetchUsersWithDirectAccessForResource,
usersWithInheritedAccess, usersWithInheritedAccess,

View File

@ -1,86 +0,0 @@
import { createContext, ReactNode, useState } from 'react';
interface LogContextProviderProps {
installationStatus: Record<number, number[]>;
handleLogWarningOrError: (installation_id: number, value: number) => void;
getStatus: (installationId: number) => number;
}
export const LogContext = createContext<LogContextProviderProps | undefined>(
undefined
);
export const LogContextProvider = ({ children }: { children: ReactNode }) => {
const [installationStatus, setInstallationStatus] = useState<
Record<number, number[]>
>({});
const BUFFER_LENGTH = 5;
const handleLogWarningOrError = (installation_id: number, value: number) => {
setInstallationStatus((prevStatus) => {
// Create a new object by spreading the previous state
const newStatus = { ...prevStatus };
if (!newStatus.hasOwnProperty(installation_id)) {
newStatus[installation_id] = [];
}
newStatus[installation_id].unshift(value);
newStatus[installation_id] = newStatus[installation_id].slice(
0,
BUFFER_LENGTH
);
return newStatus;
});
};
const getStatus = (installationId: number) => {
let status;
if (!installationStatus.hasOwnProperty(installationId)) {
status = -2;
} else {
let i = 0;
//If at least one status value shows an error, then show error
for (i; i < installationStatus[installationId].length; i++) {
if (installationStatus[installationId][i] === 2) {
status = 2;
return status;
}
if (installationStatus[installationId][i] === 1) {
status = 1;
return status;
}
}
if (installationStatus[installationId][0] == -1) {
let i = 0;
for (i; i < installationStatus[installationId].length; i++) {
if (installationStatus[installationId][i] != -1) {
break;
}
}
if (i === installationStatus[installationId].length) {
status = -1;
}
} else {
status = installationStatus[installationId][0];
}
}
return status;
};
return (
<LogContext.Provider
value={{
installationStatus,
handleLogWarningOrError,
getStatus
}}
>
{children}
</LogContext.Provider>
);
};
export default LogContextProvider;

View File

@ -1,114 +0,0 @@
import { createContext, ReactNode, useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import axiosConfig from 'src/Resources/axiosConfig';
import {
I_UserWithInheritedAccess,
InnovEnergyUser
} from 'src/interfaces/UserTypes';
interface I_UsersContextProviderProps {
directAccessUsers: InnovEnergyUser[];
setDirectAccessUsers: (value: InnovEnergyUser[]) => void;
inheritedAccessUsers: I_UserWithInheritedAccess[];
setInheritedAccessUsers: (value: I_UserWithInheritedAccess[]) => void;
availableUsers: InnovEnergyUser[];
setAvailableUsers: (value: InnovEnergyUser[]) => void;
getAvailableUsersForResource: () => InnovEnergyUser[];
fetchUsersWithInheritedAccessForResource: () => Promise<void>;
fetchUsersWithDirectAccessForResource: () => Promise<void>;
fetchAvailableUsers: () => Promise<void>;
}
export const UsersContext = createContext<I_UsersContextProviderProps>({
directAccessUsers: [],
setDirectAccessUsers: () => {},
inheritedAccessUsers: [],
setInheritedAccessUsers: () => {},
availableUsers: [],
setAvailableUsers: () => {},
getAvailableUsersForResource: () => {
return [];
},
fetchUsersWithInheritedAccessForResource: () => {
return Promise.resolve();
},
fetchUsersWithDirectAccessForResource: () => {
return Promise.resolve();
},
fetchAvailableUsers: () => {
return Promise.resolve();
}
});
const UsersContextProvider = ({ children }: { children: ReactNode }) => {
const [directAccessUsers, setDirectAccessUsers] = useState<InnovEnergyUser[]>(
[]
);
const [inheritedAccessUsers, setInheritedAccessUsers] = useState<
I_UserWithInheritedAccess[]
>([]);
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
const currentType = 13;
const { id } = useParams();
const fetchUsersWithAccess = useCallback(
async (route: string, setState: (value: any) => void) => {
axiosConfig.get(route + currentType, { params: { id } }).then((res) => {
setState(res.data);
});
},
[currentType, id]
);
const fetchUsersWithInheritedAccessForResource = useCallback(async () => {
fetchUsersWithAccess(
'/GetUsersWithInheritedAccessTo',
setInheritedAccessUsers
);
}, [fetchUsersWithAccess]);
const fetchUsersWithDirectAccessForResource = useCallback(async () => {
fetchUsersWithAccess('/GetUsersWithDirectAccessTo', setDirectAccessUsers);
}, [fetchUsersWithAccess]);
const getAvailableUsersForResource = () => {
const inheritedUsers = inheritedAccessUsers.map(
(inheritedAccessUser) => inheritedAccessUser.user
);
const allUsersWithAccess = [...inheritedUsers, ...directAccessUsers];
return availableUsers.filter(
(availableUser) =>
!allUsersWithAccess.find(
(userWithAccess) => availableUser.id === userWithAccess.id
)
);
};
const fetchAvailableUsers = async (): Promise<void> => {
return axiosConfig.get('/GetAllChildUsers').then((res) => {
setAvailableUsers(res.data);
});
};
return (
<UsersContext.Provider
value={{
directAccessUsers,
setDirectAccessUsers,
inheritedAccessUsers,
setInheritedAccessUsers,
availableUsers,
setAvailableUsers,
getAvailableUsersForResource,
fetchUsersWithInheritedAccessForResource,
fetchUsersWithDirectAccessForResource,
fetchAvailableUsers
}}
>
{children}
</UsersContext.Provider>
);
};
export default UsersContextProvider;

View File

@ -10,6 +10,7 @@ export interface I_Installation extends I_S3Credentials {
region: string; region: string;
country: string; country: string;
installationName: string; installationName: string;
vpnIp: string;
orderNumbers: string[] | string; orderNumbers: string[] | string;
lat: number; lat: number;
long: number; long: number;

View File

@ -18,6 +18,7 @@ import { FormattedMessage } from 'react-intl';
import { TokenContext } from 'src/contexts/tokenContext'; import { TokenContext } from 'src/contexts/tokenContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import routes from 'src/Resources/routes.json'; import routes from 'src/Resources/routes.json';
import { WebSocketContext } from '../../../../contexts/WebSocketContextProvider';
const UserBoxButton = styled(Button)( const UserBoxButton = styled(Button)(
({ theme }) => ` ({ theme }) => `
@ -63,8 +64,11 @@ function HeaderUserbox() {
const tokencontext = useContext(TokenContext); const tokencontext = useContext(TokenContext);
const { token, setNewToken, removeToken } = tokencontext; const { token, setNewToken, removeToken } = tokencontext;
const navigate = useNavigate(); const navigate = useNavigate();
const webSocketsContext = useContext(WebSocketContext);
const { closeSocket } = webSocketsContext;
const handleSubmit = () => { const handleSubmit = () => {
closeSocket();
axiosConfig.post('/Logout').then(() => { axiosConfig.post('/Logout').then(() => {
removeToken(); removeToken();
removeUser(); removeUser();