Merge branch 'main' of 91.92.155.224:Innovenergy/Innovenergy_trunk

This commit is contained in:
Yinyin Liu 2024-05-28 11:17:32 +02:00
commit 9526ed0739
17 changed files with 412 additions and 46 deletions

View File

@ -109,7 +109,7 @@ public static partial class Db
} }
else else
{ {
Console.WriteLine("---------------Added the new Error to the database-----------------"); Console.WriteLine("---------------Added the new Warning to the database-----------------");
Create(newWarning); Create(newWarning);
} }
} }

View File

@ -8,4 +8,4 @@ public class AlarmOrWarning
public required String Time { get; set; } public required String Time { get; set; }
public required String Description { get; set; } public required String Description { get; set; }
public required String CreatedBy { get; set; } public required String CreatedBy { get; set; }
} }

View File

@ -50,6 +50,8 @@ public static class RabbitMqManager
var message = Encoding.UTF8.GetString(body); var message = Encoding.UTF8.GetString(body);
//A message can be an alarm, a warning or a heartbit //A message can be an alarm, a warning or a heartbit
StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message); StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
lock (WebsocketManager.InstallationConnections) lock (WebsocketManager.InstallationConnections)
{ {
@ -57,9 +59,9 @@ public static class RabbitMqManager
if (receivedStatusMessage != null) if (receivedStatusMessage != null)
{ {
Console.WriteLine("----------------------------------------------"); 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. //This is a heartbit message, just update the timestamp for this installation.
//There is no need to notify the corresponding front-ends. //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 //Traverse the Warnings list, and store each of them to the database
if (receivedStatusMessage.Warnings != null) if (receivedStatusMessage.Warnings != null)
{ {
foreach (var warning in receivedStatusMessage.Warnings) foreach (var warning in receivedStatusMessage.Warnings)
{ {
Warning newWarning = new Warning Warning newWarning = new Warning
{ {
InstallationId = receivedStatusMessage.InstallationId, InstallationId = installationId,
Description = warning.Description, Description = warning.Description,
Date = warning.Date, Date = warning.Date,
Time = warning.Time, Time = warning.Time,
@ -85,7 +88,8 @@ public static class RabbitMqManager
Seen = false Seen = false
}; };
//Create a new warning and add it to the database //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 //Traverse the Alarm list, and store each of them to the database
if (receivedStatusMessage.Alarms != null) if (receivedStatusMessage.Alarms != null)
{ {
Console.WriteLine("Add an alarm for installation "+receivedStatusMessage.InstallationId);
foreach (var alarm in receivedStatusMessage.Alarms) foreach (var alarm in receivedStatusMessage.Alarms)
{ {
Error newError = new Error Error newError = new Error
{ {
InstallationId = receivedStatusMessage.InstallationId, InstallationId = installationId,
Description = alarm.Description, Description = alarm.Description,
Date = alarm.Date, Date = alarm.Date,
Time = alarm.Time, Time = alarm.Time,
DeviceCreatedTheMessage = alarm.CreatedBy, DeviceCreatedTheMessage = alarm.CreatedBy,
Seen = false Seen = false
}; }; Console.WriteLine("Add an alarm for installation "+installationId);
//Create a new error and add it to the database //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); Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);

View File

@ -4,7 +4,8 @@ namespace InnovEnergy.App.Backend.Websockets;
public class StatusMessage public class StatusMessage
{ {
public required Int32 InstallationId { get; set; } 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 required MessageType Type { get; set; }
public List<AlarmOrWarning>? Warnings { get; set; } public List<AlarmOrWarning>? Warnings { get; set; }
public List<AlarmOrWarning>? Alarms { get; set; } public List<AlarmOrWarning>? Alarms { get; set; }

View File

@ -16,8 +16,10 @@ dotnet publish \
-r linux-x64 -r linux-x64
echo -e "\n============================ Deploy ============================\n" echo -e "\n============================ Deploy ============================\n"
ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29") #ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.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.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 for ip_address in "${ip_addresses[@]}"; do

View File

@ -5,6 +5,7 @@ namespace InnovEnergy.App.SaliMax.DataTypes;
public class StatusMessage public class StatusMessage
{ {
public required Int32 InstallationId { get; set; } public required Int32 InstallationId { get; set; }
public required Int32 Product { get; set; }
public required SalimaxAlarmState Status { get; set; } public required SalimaxAlarmState Status { get; set; }
public required MessageType Type { get; set; } public required MessageType Type { get; set; }
public List<AlarmOrWarning>? Warnings { get; set; } public List<AlarmOrWarning>? Warnings { get; set; }

View File

@ -486,6 +486,7 @@ internal static class Program
var returnedStatus = new StatusMessage var returnedStatus = new StatusMessage
{ {
InstallationId = installationId, InstallationId = installationId,
Product = 0,
Status = salimaxAlarmsState, Status = salimaxAlarmsState,
Type = MessageType.AlarmOrWarning, Type = MessageType.AlarmOrWarning,
Alarms = alarmList, Alarms = alarmList,
@ -675,6 +676,10 @@ internal static class Program
var s3Path = timeStamp.ToUnixTime() + ".csv"; var s3Path = timeStamp.ToUnixTime() + ".csv";
var request = s3Config.CreatePutRequest(s3Path); 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 // Compress CSV data to a byte array
byte[] compressedBytes; byte[] compressedBytes;
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())

Binary file not shown.

View File

@ -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) { function generateSeries(chartData, category, color) {
const series = []; const series = [];
const pathsToSearch = [ const pathsToSearch = [
@ -223,11 +233,14 @@ function MainStats(props: MainStatsProps) {
setErrorDateModalOpen(false); setErrorDateModalOpen(false);
}; };
const startZoom = () => {
setIsZooming(true);
};
const handleBeforeZoom = (chartContext, { xaxis }) => { const handleBeforeZoom = (chartContext, { xaxis }) => {
const startX = parseInt(xaxis.min) / 1000; const startX = parseInt(xaxis.min) / 1000;
const endX = parseInt(xaxis.max) / 1000; const endX = parseInt(xaxis.max) / 1000;
setLoading(true);
const resultPromise: Promise<{ const resultPromise: Promise<{
chartData: BatteryDataInterface; chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
@ -246,7 +259,7 @@ function MainStats(props: MainStatsProps) {
}) })
); );
setLoading(false); setIsZooming(false);
setChartState(batteryViewDataArray.length); setChartState(batteryViewDataArray.length);
}) })
.catch((error) => { .catch((error) => {
@ -509,7 +522,10 @@ function MainStats(props: MainStatsProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -568,7 +584,10 @@ function MainStats(props: MainStatsProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -626,7 +645,10 @@ function MainStats(props: MainStatsProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -684,7 +706,10 @@ function MainStats(props: MainStatsProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -742,7 +767,10 @@ function MainStats(props: MainStatsProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}

View File

@ -288,6 +288,26 @@ function InformationSalidomo(props: InformationSalidomoProps) {
/> />
</div> </div>
<div>
<TextField
label="S3 Write Key"
name="s3writesecretkey"
value={formValues.s3WriteKey}
variant="outlined"
fullWidth
/>
</div>
<div>
<TextField
label="S3 Write Key"
name="s3writesecretkey"
value={formValues.s3WriteKey}
variant="outlined"
fullWidth
/>
</div>
<div <div
style={{ style={{
display: 'flex', display: 'flex',

View File

@ -25,9 +25,29 @@ export const fetchDailyData = (
if (r.status === 404) { if (r.status === 404) {
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) { } else if (r.status === 200) {
const text = await r.text(); // const text = await r.text();
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
//const response = await fetch(url); // Fetch the resource from the server
const contentEncoding = r.headers.get('content-type');
if (contentEncoding != 'application/base64; charset=utf-8') {
return parseCsv(csvtext);
}
const byteArray = Uint8Array.from(atob(csvtext), (c) =>
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)); //console.log(parseCsv(text));
return parseCsv(text); //return parseCsv(text);
} else { } else {
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);
} }

View File

@ -34,7 +34,7 @@ function InstallationTabs() {
useContext(InstallationsContext); useContext(InstallationsContext);
const webSocketsContext = useContext(WebSocketContext); const webSocketsContext = useContext(WebSocketContext);
const { socket, openSocket } = webSocketsContext; const { socket, openSocket, closeSocket } = webSocketsContext;
useEffect(() => { useEffect(() => {
let path = location.pathname.split('/'); let path = location.pathname.split('/');
@ -50,7 +50,11 @@ function InstallationTabs() {
}, [location]); }, [location]);
useEffect(() => { useEffect(() => {
if (!socket && salimaxInstallations.length > 0) { if (salimaxInstallations && salimaxInstallations.length > 0) {
if (socket) {
closeSocket();
}
openSocket(salimaxInstallations); openSocket(salimaxInstallations);
} }
}, [salimaxInstallations]); }, [salimaxInstallations]);
@ -135,6 +139,33 @@ function InstallationTabs() {
) )
} }
] ]
: currentUser.userType == UserType.partner
? [
{
value: 'live',
label: <FormattedMessage id="live" defaultMessage="Live" />
},
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{
value: 'batteryview',
label: (
<FormattedMessage
id="batteryview"
defaultMessage="Battery View"
/>
)
},
{
value: 'information',
label: (
<FormattedMessage id="information" defaultMessage="Information" />
)
}
]
: [ : [
{ {
value: 'live', value: 'live',
@ -219,7 +250,8 @@ function InstallationTabs() {
) )
} }
] ]
: [ : currentUser.userType == UserType.partner
? [
{ {
value: 'list', value: 'list',
icon: <ListIcon id="mode-toggle-button-list-icon" /> icon: <ListIcon id="mode-toggle-button-list-icon" />
@ -249,6 +281,37 @@ function InstallationTabs() {
) )
}, },
{
value: 'information',
label: (
<FormattedMessage
id="information"
defaultMessage="Information"
/>
)
}
]
: [
{
value: 'list',
icon: <ListIcon id="mode-toggle-button-list-icon" />
},
{
value: 'tree',
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
},
{
value: 'live',
label: <FormattedMessage id="live" defaultMessage="Live" />
},
{
value: 'overview',
label: (
<FormattedMessage id="overview" defaultMessage="Overview" />
)
},
{ {
value: 'information', value: 'information',
label: ( label: (

View File

@ -71,6 +71,15 @@ function Overview(props: OverviewProps) {
const [startDate, setStartDate] = useState(dayjs().add(-1, 'day')); const [startDate, setStartDate] = useState(dayjs().add(-1, 'day'));
const [endDate, setEndDate] = useState(dayjs()); 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(() => { useEffect(() => {
const resultPromise: Promise<{ const resultPromise: Promise<{
@ -97,11 +106,14 @@ function Overview(props: OverviewProps) {
}); });
}, []); }, []);
const startZoom = () => {
setIsZooming(true);
};
const handleBeforeZoom = (chartContext, { xaxis }) => { const handleBeforeZoom = (chartContext, { xaxis }) => {
const startX = parseInt(xaxis.min) / 1000; const startX = parseInt(xaxis.min) / 1000;
const endX = parseInt(xaxis.max) / 1000; const endX = parseInt(xaxis.max) / 1000;
setLoading(true);
const resultPromise: Promise<{ const resultPromise: Promise<{
chartData: chartDataInterface; chartData: chartDataInterface;
chartOverview: overviewInterface; chartOverview: overviewInterface;
@ -111,21 +123,31 @@ function Overview(props: OverviewProps) {
UnixTime.fromTicks(endX) UnixTime.fromTicks(endX)
); );
let isComponentMounted = true;
resultPromise resultPromise
.then((result) => { .then((result) => {
setDailyDataArray((prevData) => if (isComponentMounted) {
prevData.concat({ setDailyDataArray((prevData) =>
chartData: result.chartData, prevData.concat({
chartOverview: result.chartOverview chartData: result.chartData,
}) chartOverview: result.chartOverview
); })
);
setLoading(false); setIsZooming(false);
setChartState(dailyDataArray.length); setChartState(dailyDataArray.length);
}
}) })
.catch((error) => { .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 = () => { const handle24HourData = () => {
@ -595,7 +617,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -761,7 +786,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -851,7 +879,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -982,7 +1013,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -1040,7 +1074,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -1111,7 +1148,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}
@ -1196,7 +1236,10 @@ function Overview(props: OverviewProps) {
), ),
chart: { chart: {
events: { events: {
beforeZoom: handleBeforeZoom beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
} }
} }
}} }}

View File

@ -1,6 +1,7 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { import {
Card, Card,
CircularProgress,
Grid, Grid,
Table, Table,
TableBody, TableBody,
@ -16,6 +17,7 @@ import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import CancelIcon from '@mui/icons-material/Cancel';
interface FlatInstallationViewProps { interface FlatInstallationViewProps {
installations: I_Installation[]; installations: I_Installation[];
@ -29,6 +31,21 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1); const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
const currentLocation = useLocation(); 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 => { const handleSelectOneInstallation = (installationID: number): void => {
if (selectedInstallation != installationID) { if (selectedInstallation != installationID) {
setSelectedInstallation(installationID); setSelectedInstallation(installationID);
@ -95,10 +112,13 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableCell> <TableCell>
<FormattedMessage id="VRM Link" defaultMessage="VRM Link" /> <FormattedMessage id="VRM Link" defaultMessage="VRM Link" />
</TableCell> </TableCell>
<TableCell>
<FormattedMessage id="status" defaultMessage="Status" />
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{props.installations.map((installation) => { {sortedInstallations.map((installation) => {
const isInstallationSelected = const isInstallationSelected =
installation.s3BucketId === selectedInstallation; installation.s3BucketId === selectedInstallation;
@ -200,6 +220,57 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
</a> </a>
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell>
<div
style={{
display: 'flex',
alignItems: 'center',
marginLeft: '15px'
}}
>
{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>
</TableCell>
</TableRow> </TableRow>
); );
})} })}

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from 'react'; 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 { I_Installation } from 'src/interfaces/InstallationTypes';
import { UserContext } from 'src/contexts/userContext'; import { UserContext } from 'src/contexts/userContext';
import { TimeSpan, UnixTime } from 'src/dataCache/time'; 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 routes from '../../../Resources/routes.json';
import InformationSalidomo from '../Information/InformationSalidomo'; import InformationSalidomo from '../Information/InformationSalidomo';
import BatteryView from '../BatteryView/BatteryView'; import BatteryView from '../BatteryView/BatteryView';
import Log from '../Log/Log';
import CancelIcon from '@mui/icons-material/Cancel';
interface singleInstallationProps { interface singleInstallationProps {
current_installation?: I_Installation; current_installation?: I_Installation;
@ -174,6 +176,69 @@ function Installation(props: singleInstallationProps) {
{props.current_installation.installationName} {props.current_installation.installationName}
</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: '75px',
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
@ -195,6 +260,16 @@ function Installation(props: singleInstallationProps) {
} }
/> />
<Route
path={routes.log}
element={
<Log
errorLoadingS3Data={errorLoadingS3Data}
id={props.current_installation.id}
></Log>
}
/>
<Route <Route
path={routes.batteryview + '*'} path={routes.batteryview + '*'}
element={ element={

View File

@ -23,7 +23,7 @@ function SalidomoInstallationTabs() {
useContext(InstallationsContext); useContext(InstallationsContext);
const webSocketsContext = useContext(WebSocketContext); const webSocketsContext = useContext(WebSocketContext);
const { socket, openSocket } = webSocketsContext; const { socket, openSocket, closeSocket } = webSocketsContext;
useEffect(() => { useEffect(() => {
let path = location.pathname.split('/'); let path = location.pathname.split('/');
@ -43,6 +43,15 @@ function SalidomoInstallationTabs() {
} }
}, [salidomoInstallations]); }, [salidomoInstallations]);
useEffect(() => {
if (salidomoInstallations && salidomoInstallations.length > 0) {
if (socket) {
closeSocket();
}
openSocket(salidomoInstallations);
}
}, [salidomoInstallations]);
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
setCurrentTab(value); setCurrentTab(value);
}; };
@ -83,6 +92,10 @@ function SalidomoInstallationTabs() {
/> />
) )
}, },
{
value: 'log',
label: <FormattedMessage id="log" defaultMessage="Log" />
},
{ {
value: 'information', value: 'information',

View File

@ -9,11 +9,30 @@ import { InstallationsContext } from 'src/contexts/InstallationsContextProvider'
import { Route, Routes } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import Folder from './Folder'; import Folder from './Folder';
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
function InstallationTree() { function InstallationTree() {
const { foldersAndInstallations, fetchAllFoldersAndInstallations } = const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
useContext(InstallationsContext); 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(() => { useEffect(() => {
fetchAllFoldersAndInstallations(); fetchAllFoldersAndInstallations();
}, []); }, []);
@ -23,7 +42,7 @@ function InstallationTree() {
return ( return (
node.parentId == parent_id && ( node.parentId == parent_id && (
<CustomTreeItem node={node} parent_id={parent_id}> <CustomTreeItem node={node} parent_id={parent_id}>
{foldersAndInstallations.map((subnode) => { {sortedInstallations.map((subnode) => {
return ( return (
subnode != node && subnode != node &&
subnode.parentId == node.id && ( subnode.parentId == node.id && (