Fixed bug when updating offline installation in frontend

Update offline table every 1 minute in backend
This commit is contained in:
Noe 2024-08-30 14:33:47 +02:00
parent b60d279157
commit d6172e6fa8
10 changed files with 224 additions and 268 deletions

View File

@ -36,9 +36,9 @@ public static class WebsocketManager
foreach (var installationConnection in InstallationConnections){ foreach (var installationConnection in InstallationConnections){
if (installationConnection.Value.Product==1 && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(30)) if (installationConnection.Value.Product==1 && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(30))
{ {
Console.WriteLine("Installation ID is "+installationConnection.Key); // Console.WriteLine("Installation ID is "+installationConnection.Key);
Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp); // Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); // Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
installationConnection.Value.Status = -1; installationConnection.Value.Status = -1;
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}

View File

@ -13,9 +13,9 @@ DEVICE_INSTANCE = 1
SERVICE_NAME_PREFIX = 'com.victronenergy.battery.' SERVICE_NAME_PREFIX = 'com.victronenergy.battery.'
#s3 configuration #s3 configuration
S3BUCKET = "195-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" S3BUCKET = "637-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXO4657447df7b842d19b40af13" S3KEY = "EXOe9a2f9b47c34cf9f1b615b09"
S3SECRET = "1ZAZ-ftzKsfYEL7SxldkWKg-3Ik_yQ6vXffLMIz5ACU" S3SECRET = "S8MuM7k3KGAVw2iPiociaCfYVrJ5RXvozL1wY_f_i90"
# driver configuration # driver configuration

View File

@ -54,6 +54,6 @@ INNOVENERGY_PROTOCOL_VERSION = '48TL200V3'
# S3 Credentials # S3 Credentials
S3BUCKET = "199-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" S3BUCKET = "425-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXOe09da11251853e22b8b84bbb" S3KEY = "EXO3fb358076b23f6daebe779ac"
S3SECRET = "bFH9uwoiZjMx-ADsoAOaEADx4I65DRnXF1YtQ7rfzls" S3SECRET = "HnspAdjwfRjtB_6vm0aH2BUYPsPOvZW6Hya_OU0gSLU"

View File

@ -15,135 +15,64 @@ echo -e "\n============================ Deploy ============================\n"
# Steiger, Rheinau 10.2.0.188 failed with ssh # Steiger, Rheinau 10.2.0.188 failed with ssh
#ip_addresses=("10.2.0.144") ip_addresses=("10.2.1.115" "10.2.0.238" "10.2.0.115" "10.2.0.160" "10.2.0.149")
#
ip_addresses=( #ip_addresses=(
"10.2.1.35" #"10.2.1.70"
"10.2.1.159" #"10.2.0.135"
"10.2.0.227" #"10.2.0.247"
"10.2.0.211" #"10.2.0.134"
"10.2.1.134" #"10.2.1.21"
"10.2.0.130" #"10.2.1.73"
"10.2.1.169" #"10.2.0.243"
"10.2.0.105" #"10.2.1.19"
"10.2.0.220" #"10.2.1.166"
"10.2.1.124" #"10.2.0.192"
"10.2.1.2" #"10.2.1.12"
"10.2.1.158" #"10.2.2.188"
"10.2.0.195" #"10.2.0.158"
"10.2.1.171" #"10.2.1.146"
"10.2.0.225" #"10.2.1.27"
"10.2.1.53" #"10.2.0.202"
"10.2.0.107" #"10.2.0.157"
"10.2.0.153" #"10.2.1.55"
"10.2.1.106" #"10.2.1.16"
"10.2.1.117" #"10.2.1.28"
"10.2.0.145" #"10.2.0.254"
"10.2.0.110" #"10.2.1.128"
"10.2.1.177" #"10.2.1.58"
"10.2.1.247" #"10.2.0.187"
"10.2.0.101" #"10.2.1.156"
"10.2.0.108" #"10.2.1.137"
"10.2.1.120" #"10.2.1.135"
"10.2.1.160" #"10.2.1.24"
"10.2.1.173" #"10.2.1.109"
"10.2.0.113" #"10.2.1.90"
"10.2.0.150" #"10.2.1.153"
"10.2.0.233" #"10.2.1.111"
"10.2.1.162" #"10.2.1.71"
"10.2.1.138" #"10.2.1.37"
"10.2.1.100" #"10.2.0.127"
"10.2.1.165" #"10.2.0.126"
"10.2.1.163" #"10.2.1.22"
"10.2.1.32" #"10.2.1.91"
"10.2.1.110" #"10.2.1.123"
"10.2.0.103" #"10.2.1.220"
"10.2.1.113" #"10.2.1.114"
"10.2.0.216" #"10.2.1.41"
"10.2.0.184" #"10.2.0.125"
"10.2.0.191" #"10.2.1.62"
"10.2.1.60" #"10.2.0.161"
"10.2.1.219" #"10.2.1.121"
"10.2.0.214" #"10.2.1.10"
"10.2.1.83" #"10.2.0.112"
"10.2.0.217" #"10.2.0.111"
"10.2.1.39" #"10.2.0.218"
"10.2.0.194" #"10.2.1.49"
"10.2.1.81" #"10.2.0.230"
"10.2.1.125" #"10.2.1.170"
"10.2.0.154" #"10.2.0.114"
"10.2.0.196" #)
"10.2.1.84"
"10.2.1.130"
"10.2.1.38"
"10.2.1.33"
"10.2.0.249"
"10.2.0.133"
"10.2.1.118"
"10.2.0.138"
"10.2.0.144"
"10.2.0.188"
"10.2.1.74"
"10.2.1.141"
"10.2.1.142"
"10.2.0.193"
"10.2.1.145"
"10.2.1.15"
"10.2.1.70"
"10.2.0.135"
"10.2.0.247"
"10.2.0.134"
"10.2.1.21"
"10.2.1.73"
"10.2.0.243"
"10.2.1.19"
"10.2.1.166"
"10.2.0.192"
"10.2.1.12"
"10.2.2.188"
"10.2.0.158"
"10.2.1.146"
"10.2.1.27"
"10.2.0.202"
"10.2.0.157"
"10.2.1.55"
"10.2.1.16"
"10.2.1.28"
"10.2.0.254"
"10.2.1.128"
"10.2.1.58"
"10.2.0.187"
"10.2.1.156"
"10.2.1.137"
"10.2.1.135"
"10.2.1.24"
"10.2.1.109"
"10.2.1.90"
"10.2.1.153"
"10.2.1.111"
"10.2.1.71"
"10.2.1.37"
"10.2.0.127"
"10.2.0.126"
"10.2.1.22"
"10.2.1.91"
"10.2.1.123"
"10.2.1.220"
"10.2.1.114"
"10.2.1.41"
"10.2.0.125"
"10.2.1.62"
"10.2.0.161"
"10.2.1.121"
"10.2.1.10"
"10.2.0.112"
"10.2.0.111"
"10.2.0.218"
"10.2.1.49"
"10.2.0.230"
"10.2.1.170"
"10.2.0.114"
)
## scp template ## scp template

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { I_S3Credentials } from '../../../interfaces/S3Types'; import { I_S3Credentials } from '../../../interfaces/S3Types';
import { import {
Box, Box,
@ -21,6 +21,8 @@ import { useNavigate } from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import axiosConfig from '../../../Resources/axiosConfig'; import axiosConfig from '../../../Resources/axiosConfig';
import { UserType } from '../../../interfaces/UserTypes';
import { UserContext } from '../../../contexts/userContext';
interface DetailedBatteryViewProps { interface DetailedBatteryViewProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
@ -119,6 +121,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
backgroundColor: '#bfbfbf' backgroundColor: '#bfbfbf'
}; };
const context = useContext(UserContext);
const { currentUser } = context;
const FirmwareModalHandleProceed = async (e) => { const FirmwareModalHandleProceed = async (e) => {
setOpenModalFirmwareUpdate(false); setOpenModalFirmwareUpdate(false);
@ -559,6 +564,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
<IconButton <IconButton
aria-label="go back" aria-label="go back"
sx={{ sx={{
marginTop:
currentUser.userType != UserType.admin ? '30px' : '0px',
backgroundColor: 'grey', backgroundColor: 'grey',
color: '#000000', color: '#000000',
'&:hover': { bgcolor: '#f7b34d' } '&:hover': { bgcolor: '#f7b34d' }
@ -568,6 +575,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
{currentUser.userType == UserType.admin && (
<>
<Select <Select
value={selectedVersion} value={selectedVersion}
onChange={(event) => setSelectedVersion(event.target.value)} onChange={(event) => setSelectedVersion(event.target.value)}
@ -611,6 +620,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
> >
Download Battery Log Download Battery Log
</Button> </Button>
</>
)}
</Grid> </Grid>
</Grid> </Grid>
<Grid container> <Grid container>

View File

@ -180,12 +180,7 @@ function Information(props: InformationProps) {
> >
<div> <div>
<TextField <TextField
label={ label={<FormattedMessage id="name" defaultMessage="name" />}
<FormattedMessage
id="customerName"
defaultMessage="Customer Name"
/>
}
name="name" name="name"
value={formValues.name} value={formValues.name}
onChange={handleChange} onChange={handleChange}

View File

@ -25,6 +25,7 @@ import { InstallationsContext } from '../../../contexts/InstallationsContextProv
import { UserContext } from '../../../contexts/userContext'; import { UserContext } from '../../../contexts/userContext';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { UserType } from '../../../interfaces/UserTypes';
interface InformationSalidomoProps { interface InformationSalidomoProps {
values: I_Installation; values: I_Installation;
@ -41,7 +42,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
const { currentUser } = context; const { currentUser } = context;
const theme = useTheme(); const theme = useTheme();
const [formValues, setFormValues] = useState(props.values); const [formValues, setFormValues] = useState(props.values);
const requiredFields = ['installationName', 'region', 'location', 'country']; const requiredFields = ['name', 'region', 'location', 'country'];
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] = const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
useState(false); useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -336,6 +337,8 @@ function InformationSalidomo(props: InformationSalidomoProps) {
/> />
</div> </div>
{currentUser.userType == UserType.admin && (
<>
<div> <div>
<TextField <TextField
label="S3 Bucket Name" label="S3 Bucket Name"
@ -368,6 +371,8 @@ function InformationSalidomo(props: InformationSalidomoProps) {
fullWidth fullWidth
/> />
</div> </div>
</>
)}
<div <div
style={{ style={{
@ -390,6 +395,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
/> />
</Button> </Button>
{currentUser.userType == UserType.admin && (
<Button <Button
variant="contained" variant="contained"
onClick={handleDelete} onClick={handleDelete}
@ -402,6 +408,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
defaultMessage="Delete Installation" defaultMessage="Delete Installation"
/> />
</Button> </Button>
)}
{loading && ( {loading && (
<CircularProgress <CircularProgress

View File

@ -3,6 +3,7 @@ import {
Card, Card,
CircularProgress, CircularProgress,
Grid, Grid,
styled,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@ -25,7 +26,6 @@ interface FlatInstallationViewProps {
} }
const FlatInstallationView = (props: FlatInstallationViewProps) => { const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [isRowHovered, setHoveredRow] = useState(-1);
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
const { getStatus, getTestingMode } = webSocketContext; const { getStatus, getTestingMode } = webSocketContext;
const navigate = useNavigate(); const navigate = useNavigate();
@ -70,13 +70,12 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const theme = useTheme(); const theme = useTheme();
const handleRowMouseEnter = (id: number) => { const HoverableTableRow = styled(TableRow)(({ theme }) => ({
setHoveredRow(id); cursor: 'pointer',
}; '&:hover': {
backgroundColor: theme.palette.action.hover // Or any other color
const handleRowMouseLeave = () => { }
setHoveredRow(-1); }));
};
return ( return (
<Grid container spacing={1} sx={{ marginTop: 0.1 }}> <Grid container spacing={1} sx={{ marginTop: 0.1 }}>
@ -126,27 +125,13 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
installation.s3BucketId === selectedInstallation; installation.s3BucketId === selectedInstallation;
const status = getStatus(installation.id); const status = getStatus(installation.id);
const rowStyles =
isRowHovered === installation.s3BucketId
? {
cursor: 'pointer',
backgroundColor: theme.colors.primary.lighter
}
: {};
return ( return (
<TableRow <HoverableTableRow
hover
key={installation.s3BucketId} key={installation.s3BucketId}
selected={isInstallationSelected}
style={rowStyles}
onClick={() => onClick={() =>
handleSelectOneInstallation(installation.s3BucketId) handleSelectOneInstallation(installation.s3BucketId)
} }
onMouseEnter={() =>
handleRowMouseEnter(installation.s3BucketId)
}
onMouseLeave={() => handleRowMouseLeave()}
> >
<TableCell> <TableCell>
<Typography <Typography
@ -336,7 +321,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
)} )}
</div> </div>
</TableCell> </TableCell>
</TableRow> </HoverableTableRow>
); );
})} })}
</TableBody> </TableBody>

View File

@ -96,7 +96,11 @@ function CustomTreeItem(props: CustomTreeItemProps) {
nodeId={props.node.id.toString() + props.node.type} nodeId={props.node.id.toString() + props.node.type}
label={ label={
<div className={itemClasses.join(' ')}> <div className={itemClasses.join(' ')}>
<ListItemIcon color="inherit" className={classes.labelIcon}> <ListItemIcon
color="inherit"
className={classes.labelIcon}
sx={{ marginLeft: '10px' }}
>
{renderIcon()} {renderIcon()}
</ListItemIcon> </ListItemIcon>
<Typography <Typography

View File

@ -46,10 +46,14 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => {
} }
}, 10000); // Send a ping every 10 seconds }, 10000); // Send a ping every 10 seconds
// Listen for messages let messageBuffer = [];
let isProcessing = false;
socket.addEventListener('message', (event) => { socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data); // Parse the JSON data const message = JSON.parse(event.data); // Parse the JSON data
if (Array.isArray(message)) { if (Array.isArray(message)) {
// Existing code for handling arrays, if necessary
setInstallationMode((prevMode) => { setInstallationMode((prevMode) => {
const newMode = new Map(prevMode); const newMode = new Map(prevMode);
message.forEach((item) => { message.forEach((item) => {
@ -66,20 +70,41 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => {
return newStatus; return newStatus;
}); });
} else if (message.id != -1) { } else if (message.id != -1) {
const installation_id = message.id; // Accumulate messages in the buffer
messageBuffer.push(message);
setInstallationMode((prevMode) => { // Process the buffer if not already processing
const newMode = new Map(prevMode); if (!isProcessing) {
newMode.set(message.id, message.testingMode); isProcessing = true;
return newMode;
}); // Use setTimeout to process the buffer periodically
setInstallationStatus((prevStatus) => { setTimeout(() => {
const newStatus = new Map(prevStatus); const newInstallationMode = new Map();
newStatus.set(message.id, message.status); const newInstallationStatus = new Map();
return newStatus;
// Process all accumulated messages
messageBuffer.forEach((msg) => {
newInstallationMode.set(msg.id, msg.testingMode);
newInstallationStatus.set(msg.id, msg.status);
}); });
// Update the state with the accumulated messages
setInstallationMode(
(prevMode) => new Map([...prevMode, ...newInstallationMode])
);
setInstallationStatus(
(prevStatus) =>
new Map([...prevStatus, ...newInstallationStatus])
);
// Clear the buffer after processing
messageBuffer = [];
isProcessing = false; // Reset processing flag
}, 100); // Adjust the delay as needed to control processing frequency
}
} }
}); });
setSocket(socket); setSocket(socket);
} }
}, [installations]); }, [installations]);