diff --git a/csharp/App/Backend/Database/Create.cs b/csharp/App/Backend/Database/Create.cs index 14825c49f..d1b50e107 100644 --- a/csharp/App/Backend/Database/Create.cs +++ b/csharp/App/Backend/Database/Create.cs @@ -109,7 +109,7 @@ public static partial class Db } else { - Console.WriteLine("---------------Added the new Error to the database-----------------"); + Console.WriteLine("---------------Added the new Warning to the database-----------------"); Create(newWarning); } } diff --git a/csharp/App/Backend/Websockets/AlarmOrWarning.cs b/csharp/App/Backend/Websockets/AlarmOrWarning.cs index 4c4d2c31a..6f737032b 100644 --- a/csharp/App/Backend/Websockets/AlarmOrWarning.cs +++ b/csharp/App/Backend/Websockets/AlarmOrWarning.cs @@ -8,4 +8,4 @@ public class AlarmOrWarning public required String Time { get; set; } public required String Description { get; set; } public required String CreatedBy { get; set; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index fb300b18f..dcefab264 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -50,6 +50,8 @@ public static class RabbitMqManager var message = Encoding.UTF8.GetString(body); //A message can be an alarm, a warning or a heartbit StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize(message); + + lock (WebsocketManager.InstallationConnections) { @@ -57,9 +59,9 @@ public static class RabbitMqManager if (receivedStatusMessage != null) { Console.WriteLine("----------------------------------------------"); - Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status); - int installationId = (int)Db.Installations.Where(f => f.Product == 0 && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.Id).FirstOrDefault(); + int installationId = (int)Db.Installations.Where(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.Id).FirstOrDefault(); + Console.WriteLine("Received a message from installation: " + installationId + " , product is: "+receivedStatusMessage.Product+ " and status is: " + receivedStatusMessage.Status); //This is a heartbit message, just update the timestamp for this installation. //There is no need to notify the corresponding front-ends. @@ -73,11 +75,12 @@ public static class RabbitMqManager //Traverse the Warnings list, and store each of them to the database if (receivedStatusMessage.Warnings != null) { + foreach (var warning in receivedStatusMessage.Warnings) { Warning newWarning = new Warning { - InstallationId = receivedStatusMessage.InstallationId, + InstallationId = installationId, Description = warning.Description, Date = warning.Date, Time = warning.Time, @@ -85,7 +88,8 @@ public static class RabbitMqManager Seen = false }; //Create a new warning and add it to the database - Db.HandleWarning(newWarning, receivedStatusMessage.InstallationId); + Console.WriteLine("Add a warning for installation "+installationId); + Db.HandleWarning(newWarning, installationId); } } @@ -93,20 +97,20 @@ public static class RabbitMqManager //Traverse the Alarm list, and store each of them to the database if (receivedStatusMessage.Alarms != null) { - Console.WriteLine("Add an alarm for installation "+receivedStatusMessage.InstallationId); + foreach (var alarm in receivedStatusMessage.Alarms) { Error newError = new Error { - InstallationId = receivedStatusMessage.InstallationId, + InstallationId = installationId, Description = alarm.Description, Date = alarm.Date, Time = alarm.Time, DeviceCreatedTheMessage = alarm.CreatedBy, Seen = false - }; + }; Console.WriteLine("Add an alarm for installation "+installationId); //Create a new error and add it to the database - Db.HandleError(newError, receivedStatusMessage.InstallationId); + Db.HandleError(newError, installationId); } } } @@ -139,6 +143,7 @@ public static class RabbitMqManager } } + } }; Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer); diff --git a/csharp/App/Backend/Websockets/StatusMessage.cs b/csharp/App/Backend/Websockets/StatusMessage.cs index 0a4a707bd..73009a3a9 100644 --- a/csharp/App/Backend/Websockets/StatusMessage.cs +++ b/csharp/App/Backend/Websockets/StatusMessage.cs @@ -4,7 +4,8 @@ namespace InnovEnergy.App.Backend.Websockets; public class StatusMessage { public required Int32 InstallationId { get; set; } - public required int Status { get; set; } + public required Int32 Product { get; set; } + public required Int32 Status { get; set; } public required MessageType Type { get; set; } public List? Warnings { get; set; } public List? Alarms { get; set; } diff --git a/csharp/App/SaliMax/deploy_all_installations.sh b/csharp/App/SaliMax/deploy_all_installations.sh index 8b3c12c57..4f5c7fd27 100755 --- a/csharp/App/SaliMax/deploy_all_installations.sh +++ b/csharp/App/SaliMax/deploy_all_installations.sh @@ -16,8 +16,10 @@ dotnet publish \ -r linux-x64 echo -e "\n============================ Deploy ============================\n" -ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29") +#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29") #ip_addresses=("10.2.4.154" "10.2.4.29") +ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.29") + for ip_address in "${ip_addresses[@]}"; do diff --git a/csharp/App/SaliMax/src/DataTypes/StatusMessage.cs b/csharp/App/SaliMax/src/DataTypes/StatusMessage.cs index 15169c2b2..6b24a74cf 100644 --- a/csharp/App/SaliMax/src/DataTypes/StatusMessage.cs +++ b/csharp/App/SaliMax/src/DataTypes/StatusMessage.cs @@ -5,6 +5,7 @@ namespace InnovEnergy.App.SaliMax.DataTypes; public class StatusMessage { public required Int32 InstallationId { get; set; } + public required Int32 Product { get; set; } public required SalimaxAlarmState Status { get; set; } public required MessageType Type { get; set; } public List? Warnings { get; set; } diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index 50b5049d4..c51db600d 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -486,6 +486,7 @@ internal static class Program var returnedStatus = new StatusMessage { InstallationId = installationId, + Product = 0, Status = salimaxAlarmsState, Type = MessageType.AlarmOrWarning, Alarms = alarmList, @@ -675,6 +676,10 @@ internal static class Program var s3Path = timeStamp.ToUnixTime() + ".csv"; var request = s3Config.CreatePutRequest(s3Path); + // This is temporary for Wittman, but now it's for all Instalattion + await File.WriteAllTextAsync("/var/www/html/status.csv", csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines()); + + // Compress CSV data to a byte array byte[] compressedBytes; using (var memoryStream = new MemoryStream()) diff --git a/csharp/App/VrmGrabber/db.sqlite b/csharp/App/VrmGrabber/db.sqlite index 4847f0a8b..29961ffce 100644 Binary files a/csharp/App/VrmGrabber/db.sqlite and b/csharp/App/VrmGrabber/db.sqlite differ diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx index 3744c5799..7861a57c8 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx @@ -114,6 +114,16 @@ function MainStats(props: MainStatsProps) { }); }, []); + const [isZooming, setIsZooming] = useState(false); + + useEffect(() => { + if (isZooming) { + setLoading(true); + } else if (!isZooming && batteryViewDataArray.length > 0) { + setLoading(false); + } + }, [isZooming, batteryViewDataArray]); + function generateSeries(chartData, category, color) { const series = []; const pathsToSearch = [ @@ -223,11 +233,14 @@ function MainStats(props: MainStatsProps) { setErrorDateModalOpen(false); }; + const startZoom = () => { + setIsZooming(true); + }; + const handleBeforeZoom = (chartContext, { xaxis }) => { const startX = parseInt(xaxis.min) / 1000; const endX = parseInt(xaxis.max) / 1000; - setLoading(true); const resultPromise: Promise<{ chartData: BatteryDataInterface; chartOverview: BatteryOverviewInterface; @@ -246,7 +259,7 @@ function MainStats(props: MainStatsProps) { }) ); - setLoading(false); + setIsZooming(false); setChartState(batteryViewDataArray.length); }) .catch((error) => { @@ -509,7 +522,10 @@ function MainStats(props: MainStatsProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -568,7 +584,10 @@ function MainStats(props: MainStatsProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -626,7 +645,10 @@ function MainStats(props: MainStatsProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -684,7 +706,10 @@ function MainStats(props: MainStatsProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -742,7 +767,10 @@ function MainStats(props: MainStatsProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx index fa4edbd13..4e4ba1481 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx @@ -288,6 +288,26 @@ function InformationSalidomo(props: InformationSalidomoProps) { /> +
+ +
+ +
+ +
+
+ c.charCodeAt(0) + ); + + //Decompress the byte array using JSZip + const zip = await JSZip.loadAsync(byteArray); + // Assuming the CSV file is named "data.csv" inside the ZIP archive + const csvContent = await zip.file('data.csv').async('text'); + + return parseCsv(csvContent); + //console.log(parseCsv(text)); - return parseCsv(text); + //return parseCsv(text); } else { return Promise.resolve(FetchResult.notAvailable); } diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index 197a141dc..07e4003a0 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -34,7 +34,7 @@ function InstallationTabs() { useContext(InstallationsContext); const webSocketsContext = useContext(WebSocketContext); - const { socket, openSocket } = webSocketsContext; + const { socket, openSocket, closeSocket } = webSocketsContext; useEffect(() => { let path = location.pathname.split('/'); @@ -50,7 +50,11 @@ function InstallationTabs() { }, [location]); useEffect(() => { - if (!socket && salimaxInstallations.length > 0) { + if (salimaxInstallations && salimaxInstallations.length > 0) { + if (socket) { + closeSocket(); + } + openSocket(salimaxInstallations); } }, [salimaxInstallations]); @@ -135,6 +139,33 @@ function InstallationTabs() { ) } ] + : currentUser.userType == UserType.partner + ? [ + { + value: 'live', + label: + }, + { + value: 'overview', + label: + }, + { + value: 'batteryview', + label: ( + + ) + }, + + { + value: 'information', + label: ( + + ) + } + ] : [ { value: 'live', @@ -219,7 +250,8 @@ function InstallationTabs() { ) } ] - : [ + : currentUser.userType == UserType.partner + ? [ { value: 'list', icon: @@ -249,6 +281,37 @@ function InstallationTabs() { ) }, + { + value: 'information', + label: ( + + ) + } + ] + : [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + }, + + { + value: 'live', + label: + }, + { + value: 'overview', + label: ( + + ) + }, + { value: 'information', label: ( diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index 4b96a1343..8881c7f1e 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -71,6 +71,15 @@ function Overview(props: OverviewProps) { const [startDate, setStartDate] = useState(dayjs().add(-1, 'day')); const [endDate, setEndDate] = useState(dayjs()); + const [isZooming, setIsZooming] = useState(false); + + useEffect(() => { + if (isZooming) { + setLoading(true); + } else if (!isZooming && dailyDataArray.length > 0) { + setLoading(false); + } + }, [isZooming, dailyDataArray]); useEffect(() => { const resultPromise: Promise<{ @@ -97,11 +106,14 @@ function Overview(props: OverviewProps) { }); }, []); + const startZoom = () => { + setIsZooming(true); + }; + const handleBeforeZoom = (chartContext, { xaxis }) => { const startX = parseInt(xaxis.min) / 1000; const endX = parseInt(xaxis.max) / 1000; - setLoading(true); const resultPromise: Promise<{ chartData: chartDataInterface; chartOverview: overviewInterface; @@ -111,21 +123,31 @@ function Overview(props: OverviewProps) { UnixTime.fromTicks(endX) ); + let isComponentMounted = true; + resultPromise .then((result) => { - setDailyDataArray((prevData) => - prevData.concat({ - chartData: result.chartData, - chartOverview: result.chartOverview - }) - ); - - setLoading(false); - setChartState(dailyDataArray.length); + if (isComponentMounted) { + setDailyDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + setIsZooming(false); + setChartState(dailyDataArray.length); + } }) .catch((error) => { - console.error('Error:', error); + if (isComponentMounted) { + console.error('Error:', error); + setLoading(false); // Ensure loading is turned off even if there is an error + } }); + + return () => { + isComponentMounted = false; + }; }; const handle24HourData = () => { @@ -595,7 +617,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -761,7 +786,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -851,7 +879,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -982,7 +1013,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -1040,7 +1074,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -1111,7 +1148,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} @@ -1196,7 +1236,10 @@ function Overview(props: OverviewProps) { ), chart: { events: { - beforeZoom: handleBeforeZoom + beforeZoom: (chartContext, options) => { + startZoom(); + handleBeforeZoom(chartContext, options); + } } } }} diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx index ce419e002..54cac80e9 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx @@ -1,6 +1,7 @@ import React, { useContext, useState } from 'react'; import { Card, + CircularProgress, Grid, Table, TableBody, @@ -16,6 +17,7 @@ import { WebSocketContext } from 'src/contexts/WebSocketContextProvider'; import { FormattedMessage } from 'react-intl'; import { useLocation, useNavigate } from 'react-router-dom'; import routes from '../../../Resources/routes.json'; +import CancelIcon from '@mui/icons-material/Cancel'; interface FlatInstallationViewProps { installations: I_Installation[]; @@ -29,6 +31,21 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { const [selectedInstallation, setSelectedInstallation] = useState(-1); const currentLocation = useLocation(); + const sortedInstallations = [...props.installations].sort((a, b) => { + // Compare the status field of each installation and sort them based on the status. + //Installations with alarms go first + let a_status = getStatus(a.id); + let b_status = getStatus(b.id); + + if (a_status > b_status) { + return -1; + } + if (a_status < b_status) { + return 1; + } + return 0; + }); + const handleSelectOneInstallation = (installationID: number): void => { if (selectedInstallation != installationID) { setSelectedInstallation(installationID); @@ -95,10 +112,13 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { + + + - {props.installations.map((installation) => { + {sortedInstallations.map((installation) => { const isInstallationSelected = installation.s3BucketId === selectedInstallation; @@ -200,6 +220,57 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { + + +
+ {status === -1 ? ( + + ) : ( + '' + )} + + {status === -2 ? ( + + ) : ( + '' + )} + +
+
+ ); })} diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx index 4519132f0..b673f0d31 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Card, 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 { TimeSpan, UnixTime } from 'src/dataCache/time'; @@ -15,6 +15,8 @@ import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import routes from '../../../Resources/routes.json'; import InformationSalidomo from '../Information/InformationSalidomo'; import BatteryView from '../BatteryView/BatteryView'; +import Log from '../Log/Log'; +import CancelIcon from '@mui/icons-material/Cancel'; interface singleInstallationProps { current_installation?: I_Installation; @@ -174,6 +176,69 @@ function Installation(props: singleInstallationProps) { {props.current_installation.installationName}
+
+ + Status: + +
+ {status === -1 ? ( + + ) : ( + '' + )} + + {status === -2 ? ( + + ) : ( + '' + )} + +
+
+
+ + } + /> + { let path = location.pathname.split('/'); @@ -43,6 +43,15 @@ function SalidomoInstallationTabs() { } }, [salidomoInstallations]); + useEffect(() => { + if (salidomoInstallations && salidomoInstallations.length > 0) { + if (socket) { + closeSocket(); + } + openSocket(salidomoInstallations); + } + }, [salidomoInstallations]); + const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { setCurrentTab(value); }; @@ -83,6 +92,10 @@ function SalidomoInstallationTabs() { /> ) }, + { + value: 'log', + label: + }, { value: 'information', diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/InstallationTree.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/InstallationTree.tsx index afb84ab99..3f842fd4c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/InstallationTree.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/InstallationTree.tsx @@ -9,11 +9,30 @@ import { InstallationsContext } from 'src/contexts/InstallationsContextProvider' import { Route, Routes } from 'react-router-dom'; import routes from '../../../Resources/routes.json'; import Folder from './Folder'; +import { WebSocketContext } from '../../../contexts/WebSocketContextProvider'; function InstallationTree() { const { foldersAndInstallations, fetchAllFoldersAndInstallations } = useContext(InstallationsContext); + const webSocketContext = useContext(WebSocketContext); + const { getStatus } = webSocketContext; + + const sortedInstallations = [...foldersAndInstallations].sort((a, b) => { + // Compare the status field of each installation and sort them based on the status. + //Installations with alarms go first + let a_status = getStatus(a.id); + let b_status = getStatus(b.id); + + if (a_status > b_status) { + return -1; + } + if (a_status < b_status) { + return 1; + } + return 0; + }); + useEffect(() => { fetchAllFoldersAndInstallations(); }, []); @@ -23,7 +42,7 @@ function InstallationTree() { return ( node.parentId == parent_id && ( - {foldersAndInstallations.map((subnode) => { + {sortedInstallations.map((subnode) => { return ( subnode != node && subnode.parentId == node.id && (