diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 2dac5f879..8afccf35e 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -533,14 +533,14 @@ public class Controller : ControllerBase } [HttpPost(nameof(UpdateFirmware))] - public async Task UpdateFirmware(Int64 batteryNode, Int64 installationId,Token authToken) + public async Task UpdateFirmware(Int64 batteryNode, Int64 installationId,String version,Token authToken) { var session = Db.GetSession(authToken); var installationToUpdate = Db.GetInstallationById(installationId); if (installationToUpdate != null) { - _ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode); + _ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode,version); } return Ok(); diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index 3975ea939..8d48ab2e5 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -8,6 +8,7 @@ public class Installation : TreeNode public String Region { get; set; } = ""; public String Country { get; set; } = ""; public String VpnIp { get; set; } = ""; + public String InstallationName { get; set; } = ""; public String S3Region { get; set; } = "sos-ch-dk-2"; public String S3Provider { get; set; } = "exo.io"; @@ -15,6 +16,7 @@ public class Installation : TreeNode public String S3Key { get; set; } = ""; public String S3WriteSecret { get; set; } = ""; public String S3Secret { get; set; } = ""; + public int S3BucketId { get; set; } = 0; public String ReadRoleId { get; set; } = ""; public String WriteRoleId { get; set; } = ""; diff --git a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs index 25513343a..2231f0413 100644 --- a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs +++ b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs @@ -131,7 +131,7 @@ public static class ExoCmd if (response.StatusCode != HttpStatusCode.OK){ Console.WriteLine("Fuck"); } - //Console.WriteLine($"Created Key for {installation.Name}"); + //Console.WriteLine($"Created Key for {installation.InstallationName}"); var responseString = await response.Content.ReadAsStringAsync(); var responseJson = JsonNode.Parse(responseString) ; @@ -143,10 +143,12 @@ public static class ExoCmd { const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role"; const String method = "iam-role"; + String rolename = installation.Product==0?Db.Installations.Count(f => f.Product == 0) + installation.InstallationName:Db.Installations.Count(f => f.Product == 1) + installation.InstallationName; + var contentString = $$""" { - "name" : "{{installation.Id + installation.Name}}", + "name" : "{{rolename}}", "policy" : { "default-service-strategy": "deny", "services": { @@ -243,10 +245,11 @@ public static class ExoCmd { const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role"; const String method = "iam-role"; + String rolename = installation.Product==0?Db.Installations.Count(f => f.Product == 0) + installation.InstallationName:Db.Installations.Count(f => f.Product == 1) + installation.InstallationName; var contentString = $$""" { - "name" : "WRITE{{installation.Id + installation.Name}}", + "name" : "WRITE{{rolename}}", "policy" : { "default-service-strategy": "deny", "services": { diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs index 253eaa438..cc0446a71 100644 --- a/csharp/App/Backend/DataTypes/Methods/Installation.cs +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -18,10 +18,10 @@ public static class InstallationMethods { if (installation.Product == 0) { - return $"{installation.Id}-{BucketNameSalt}"; + return $"{installation.S3BucketId}-{BucketNameSalt}"; } - return $"{installation.Id}-{SalidomoBucketNameSalt}"; + return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}"; } diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 93ffa1ef7..c55df6ade 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -68,7 +68,7 @@ public static class SessionMethods .Apply(Db.Update); } - public static async Task RunScriptInBackground(this Session? session, String vpnIp, Int64 batteryNode) + public static async Task RunScriptInBackground(this Session? session, String vpnIp, Int64 batteryNode,String version) { Console.WriteLine("-----------------------------------Start updating firmware-----------------------------------"); string scriptPath = "/home/ubuntu/backend/uploadBatteryFw/update_firmware.sh"; @@ -77,7 +77,7 @@ public static class SessionMethods { Process process = new Process(); process.StartInfo.FileName = "/bin/bash"; - process.StartInfo.Arguments = $"{scriptPath} {vpnIp} {batteryNode}"; + process.StartInfo.Arguments = $"{scriptPath} {vpnIp} {batteryNode} {version}"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; @@ -118,6 +118,8 @@ public static class SessionMethods { var user = session?.User; + + //Salimax installation if (installation.Product==0) { diff --git a/csharp/App/Backend/Database/Create.cs b/csharp/App/Backend/Database/Create.cs index 8900e6f1c..14825c49f 100644 --- a/csharp/App/Backend/Database/Create.cs +++ b/csharp/App/Backend/Database/Create.cs @@ -16,6 +16,8 @@ public static partial class Db public static Boolean Create(Installation installation) { + installation.S3BucketId = Installations.Count(f => f.Product == installation.Product)+1; + // SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion return Insert(installation); } diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 3253a08bb..130c10649 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -108,7 +108,7 @@ public static partial class Db Connection.CreateTable(); }); - UpdateKeys(); + //UpdateKeys(); CleanupSessions().SupressAwaitWarning(); } @@ -147,7 +147,19 @@ public static partial class Db .Select(i => i.S3WriteKey) .Distinct() .ToList(); + + Console.WriteLine("VALID READ KEYS"); + for (int i = 0; i < validReadKeys.Count; i++) + { + Console.WriteLine(validReadKeys[i]); + } + Console.WriteLine("VALID WRITE KEYS"); + + for (int i = 0; i < validReadKeys.Count; i++) + { + Console.WriteLine(validWriteKeys[i]); + } const String provider = "exo.io"; @@ -157,7 +169,7 @@ public static partial class Db { if (keyMetadata["key"].ToString()!="EXOa0b53cf10517307cec1bf00e" && !validReadKeys.Contains(keyMetadata["key"].ToString()) && !validWriteKeys.Contains(keyMetadata["key"].ToString())) { - await ExoCmd.RevokeReadKey(keyMetadata["key"].ToString()); + //await ExoCmd.RevokeReadKey(keyMetadata["key"].ToString()); Console.WriteLine("Deleted key "+keyMetadata["key"]); } diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 0530e1bf2..805a32f76 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -1,3 +1,4 @@ +using Flurl.Http; using Hellang.Middleware.ProblemDetails; using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Websockets; @@ -5,11 +6,13 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; using InnovEnergy.Lib.Utils; +using Org.BouncyCastle.Math.EC; + namespace InnovEnergy.App.Backend; public static class Program { - public static void Main(String[] args) + public static async Task Main(String[] args) { Watchdog.NotifyReady(); Db.Init(); @@ -18,7 +21,7 @@ public static class Program RabbitMqManager.InitializeEnvironment(); RabbitMqManager.StartRabbitMqConsumer(); WebsocketManager.MonitorInstallationTable(); - + builder.Services.AddControllers(); builder.Services.AddProblemDetails(setup => { diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index a8bbfc9b9..fb300b18f 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -58,7 +58,8 @@ public static class RabbitMqManager { Console.WriteLine("----------------------------------------------"); Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status); - var installationId = receivedStatusMessage.InstallationId; + int installationId = (int)Db.Installations.Where(f => f.Product == 0 && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.Id).FirstOrDefault(); + //This is a heartbit message, just update the timestamp for this installation. //There is no need to notify the corresponding front-ends. diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx index 897212f1a..1ad54388b 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx @@ -32,21 +32,21 @@ interface BatteryViewProps { s3Credentials: I_S3Credentials; installationId: number; productNum: number; + connected: boolean; } function BatteryView(props: BatteryViewProps) { - if (props.values === null) { + if (props.values === null && props.connected == true) { return null; } const currentLocation = useLocation(); const navigate = useNavigate(); - const sortedBatteryView = [...props.values.batteryView].sort( - (a, b) => b.BatteryId - a.BatteryId - ); + const sortedBatteryView = + props.values != null + ? [...props.values.batteryView].sort((a, b) => b.BatteryId - a.BatteryId) + : []; - const [loading, setLoading] = useState( - sortedBatteryView.length == 0 ? true : false - ); + const [loading, setLoading] = useState(sortedBatteryView.length == 0); const handleMainStatsButton = () => { navigate(routes.mainstats); @@ -70,7 +70,31 @@ function BatteryView(props: BatteryViewProps) { return ( <> - {loading && ( + {!props.connected && ( + + + + Unable to communicate with the installation + + + Please wait or refresh the page + + + )} + {loading && props.connected && ( )} - {!loading && ( + {!loading && props.connected && ( { navigate(location.pathname.split('/').slice(0, -2).join('/')); }; @@ -113,7 +118,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { .post( `/UpdateFirmware?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${ props.installationId - }` + }&version=${selectedVersion}` ) .catch((err) => { if (err.response) { @@ -264,7 +269,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { {props.productNum === 0 && ( - + <> + + + )} diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index 067ad9eca..e812e91fa 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -1,11 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; -import { - Card, - CircularProgress, - Container, - Grid, - Typography -} from '@mui/material'; +import { Card, CircularProgress, Grid, Typography } from '@mui/material'; import { I_Installation } from 'src/interfaces/InstallationTypes'; import { UserContext } from 'src/contexts/userContext'; import AccessContextProvider from 'src/contexts/AccessContextProvider'; @@ -49,10 +43,7 @@ function Installation(props: singleInstallationProps) { failedToCommunicateWithInstallation, setFailedToCommunicateWithInstallation ] = useState(0); - const [ - openModalUnableToCommunicateWIthInstallation, - setOpenModalUnableToCommunicateWIthInstallation - ] = useState(false); + const [connected, setConnected] = useState(true); if (props.current_installation == undefined) { return null; @@ -83,7 +74,7 @@ function Installation(props: singleInstallationProps) { if (res != FetchResult.notAvailable && res != FetchResult.tryLater) { setFailedToCommunicateWithInstallation(0); - setOpenModalUnableToCommunicateWIthInstallation(false); + setConnected(true); setValues( extractValues({ time: now, @@ -94,7 +85,7 @@ function Installation(props: singleInstallationProps) { } else { setFailedToCommunicateWithInstallation((prevCount) => { if (prevCount + 1 >= 3) { - setOpenModalUnableToCommunicateWIthInstallation(true); + setConnected(false); } return prevCount + 1; }); @@ -125,7 +116,7 @@ function Installation(props: singleInstallationProps) { useEffect(() => { if (status === -1) { - setOpenModalUnableToCommunicateWIthInstallation(true); + setConnected(false); } }, [status]); @@ -160,10 +151,6 @@ function Installation(props: singleInstallationProps) { } }, [currentTab, location]); - const UnableToCommunicateModalHandleOk = () => { - setOpenModalUnableToCommunicateWIthInstallation(false); - }; - return ( <> @@ -298,31 +285,6 @@ function Installation(props: singleInstallationProps) { alignItems="stretch" spacing={0} > - {openModalUnableToCommunicateWIthInstallation && ( - - - - Unable to communicate with the installation - - - Please wait or refresh the page - - - )} - } > @@ -353,7 +316,9 @@ function Installation(props: singleInstallationProps) { } + element={ + + } /> (undefined); const [values, setValues] = useState(null); const status = getStatus(props.current_installation.id); + const [ + failedToCommunicateWithInstallation, + setFailedToCommunicateWithInstallation + ] = useState(0); + const [connected, setConnected] = useState(true); if (props.current_installation == undefined) { return null; @@ -58,6 +63,8 @@ function Installation(props: singleInstallationProps) { const res = await fetchData(now, s3Credentials); if (res != FetchResult.notAvailable && res != FetchResult.tryLater) { + setConnected(true); + setFailedToCommunicateWithInstallation(0); setValues( extractValues({ time: now, @@ -66,6 +73,14 @@ function Installation(props: singleInstallationProps) { ); return true; } + else { + setFailedToCommunicateWithInstallation((prevCount) => { + if (prevCount + 1 >= 3) { + setConnected(false); + } + return prevCount + 1; + }); + } } catch (err) { return false; } @@ -120,6 +135,12 @@ function Installation(props: singleInstallationProps) { }; } }, [currentTab, location]); + + useEffect(() => { + if (status === -1) { + setConnected(false); + } + }, [status]); return ( <> @@ -183,6 +204,7 @@ function Installation(props: singleInstallationProps) { s3Credentials={s3Credentials} installationId={props.current_installation.id} productNum={props.current_installation.product} + connected={connected} > } > diff --git a/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx b/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx index 216b68a26..17a73e4b2 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx @@ -1,5 +1,11 @@ import React, { useState } from 'react'; -import { Container, Grid, Switch, Typography } from '@mui/material'; +import { + CircularProgress, + Container, + Grid, + Switch, + Typography +} from '@mui/material'; import TopologyColumn from './topologyColumn'; import { getAmount, @@ -9,14 +15,15 @@ import { interface TopologyProps { values: TopologyValues; + connected: boolean; } function Topology(props: TopologyProps) { - if (props.values === null) { + if (props.values === null && props.connected == true) { return null; } - - const highestConnectionValue = getHighestConnectionValue(props.values); + const highestConnectionValue = + props.values != null ? getHighestConnectionValue(props.values) : 0; const [showValues, setShowValues] = useState(false); const handleSwitch = () => () => { @@ -28,290 +35,322 @@ function Topology(props: TopologyProps) { return ( - -
- - Display Values + {!props.connected && ( + + + + Unable to communicate with the installation - + Please wait or refresh the page + + + )} + + {props.connected && ( + <> + -
-
+ > +
+ + Display Values + + +
+
- - - + + - - - - - - + amount: props.values.gridBusToIslandBusConnection + ? getAmount( + highestConnectionValue, + props.values.gridBusToIslandBusConnection + ) + : 0, + data: props.values.gridBusToIslandBusConnection, + showValues: showValues + }} + bottomBox={{ + title: 'AC Loads', + data: props.values.gridBusToLoadOnGridBusConnection, + connected: + props.values.loadOnAcGridBox[0].value.toString() != + 'Disabled' + }} + bottomConnection={{ + orientation: 'vertical', + position: 'bottom', + data: props.values.gridBusToLoadOnGridBusConnection, + amount: props.values.gridBusToLoadOnGridBusConnection + ? getAmount( + highestConnectionValue, + props.values.gridBusToLoadOnGridBusConnection + ) + : 0, + showValues: showValues + }} + isLast={false} + isFirst={false} + /> + + + + + +
+ + )}
); diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index 290d6b492..7b65dfb36 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -86,7 +86,7 @@ export const transformInputToBatteryViewData = async ( const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current']; const pathCategories = [ 'Soc', - 'Temperatures/Cells/Center', + 'Temperatures/Cells/Average', 'Dc/Power', 'Dc/Voltage', 'Dc/Current'