Update firmware on frontend

This commit is contained in:
Noe 2024-03-19 17:42:33 +01:00
parent 3a3f2fe1b8
commit 2f24f97304
5 changed files with 486 additions and 282 deletions

View File

@ -514,18 +514,16 @@ public class Controller : ControllerBase
} }
[HttpPost(nameof(UpdateFirmware))] [HttpPost(nameof(UpdateFirmware))]
public ActionResult UpdateFirmware(Int64 batteryNode, Int64 installationId,Token authToken) public async Task<ActionResult> UpdateFirmware(Int64 batteryNode, Int64 installationId,Token authToken)
{ {
var session = Db.GetSession(authToken); var session = Db.GetSession(authToken);
var installationToUpdate = Db.GetInstallationById(installationId); var installationToUpdate = Db.GetInstallationById(installationId);
Console.WriteLine("Inside firmware function controller,batteryNode="+batteryNode+ "and installation is "+installationId); Console.WriteLine("Inside firmware function controller,batteryNode="+batteryNode+ "and installation is "+installationId);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
if (installationToUpdate != null) if (installationToUpdate != null)
{ {
session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode); _ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode);
} }
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
return Ok(); return Ok();
} }

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Container, Container,
Grid, Grid,
@ -8,7 +8,8 @@ import {
TableCell, TableCell,
TableContainer, TableContainer,
TableHead, TableHead,
TableRow TableRow,
Typography
} from '@mui/material'; } from '@mui/material';
import { TopologyValues } from '../Log/graph.util'; import { TopologyValues } from '../Log/graph.util';
import { import {
@ -24,10 +25,12 @@ import { I_S3Credentials } from '../../../interfaces/S3Types';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import MainStats from './MainStats'; import MainStats from './MainStats';
import DetailedBatteryView from './DetailedBatteryView'; import DetailedBatteryView from './DetailedBatteryView';
import CircularProgress from '@mui/material/CircularProgress';
interface BatteryViewProps { interface BatteryViewProps {
values: TopologyValues; values: TopologyValues;
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
installationId: number;
} }
function BatteryView(props: BatteryViewProps) { function BatteryView(props: BatteryViewProps) {
@ -40,6 +43,10 @@ function BatteryView(props: BatteryViewProps) {
(a, b) => b.BatteryId - a.BatteryId (a, b) => b.BatteryId - a.BatteryId
); );
const [loading, setLoading] = useState(
sortedBatteryView.length == 0 ? true : false
);
const handleMainStatsButton = () => { const handleMainStatsButton = () => {
navigate(routes.mainstats); navigate(routes.mainstats);
}; };
@ -52,15 +59,116 @@ function BatteryView(props: BatteryViewProps) {
} }
}; };
useEffect(() => {
if (sortedBatteryView.length == 0) {
setLoading(true);
} else {
setLoading(false);
}
}, [sortedBatteryView]);
return ( return (
<> <>
<Container maxWidth="xl"> {loading && (
<Grid container> <Container
<Grid maxWidth="xl"
item sx={{
xs={6} display: 'flex',
md={6} flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '70vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography
variant="body2"
style={{ color: 'black', fontWeight: 'bold' }}
mt={2}
>
Battery service is not available at the moment
</Typography>
<Typography variant="body2" style={{ color: 'black' }}>
Please wait or refresh the page
</Typography>
</Container>
)}
{!loading && (
<Container maxWidth="xl">
<Grid container>
<Grid
item
xs={6}
md={6}
sx={{
display:
!currentLocation.pathname.includes('detailed_view') &&
!currentLocation.pathname.includes('mainstats')
? 'block'
: 'none'
}}
>
<Button
variant="contained"
sx={{
marginTop: '20px',
backgroundColor: '#808080',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage
id="battery_view"
defaultMessage="Battery View"
/>
</Button>
<Button
variant="contained"
onClick={handleMainStatsButton}
sx={{
marginTop: '20px',
marginLeft: '20px',
backgroundColor: '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
</Button>
</Grid>
</Grid>
<Grid container>
<Routes>
<Route
path={routes.mainstats + '*'}
element={
<MainStats s3Credentials={props.s3Credentials}></MainStats>
}
/>
{props.values.batteryView.map((battery) => (
<Route
key={routes.detailed_view + battery.BatteryId}
path={routes.detailed_view + battery.BatteryId}
element={
<DetailedBatteryView
s3Credentials={props.s3Credentials}
batteryData={findBatteryData(battery.BatteryId)}
installationId={props.installationId}
></DetailedBatteryView>
}
/>
))}
</Routes>
</Grid>
<TableContainer
component={Paper}
sx={{ sx={{
marginTop: '20px',
marginBottom: '20px',
display: display:
!currentLocation.pathname.includes('detailed_view') && !currentLocation.pathname.includes('detailed_view') &&
!currentLocation.pathname.includes('mainstats') !currentLocation.pathname.includes('mainstats')
@ -68,237 +176,175 @@ function BatteryView(props: BatteryViewProps) {
: 'none' : 'none'
}} }}
> >
<Button <Table sx={{ minWidth: 650 }} aria-label="simple table">
variant="contained" <TableHead>
sx={{ <TableRow>
marginTop: '20px', <TableCell align="center">Battery</TableCell>
backgroundColor: '#808080', <TableCell align="center">Firmware</TableCell>
color: '#000000', <TableCell align="center">Power</TableCell>
'&:hover': { bgcolor: '#f7b34d' } <TableCell align="center">Voltage</TableCell>
}} <TableCell align="center">SoC</TableCell>
> <TableCell align="center">Temperature</TableCell>
<FormattedMessage <TableCell align="center">Warnings</TableCell>
id="battery_view" <TableCell align="center">Alarms</TableCell>
defaultMessage="Battery View"
/>
</Button>
<Button
variant="contained"
onClick={handleMainStatsButton}
sx={{
marginTop: '20px',
marginLeft: '20px',
backgroundColor: '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
</Button>
</Grid>
</Grid>
<Grid container>
<Routes>
<Route
path={routes.mainstats + '*'}
element={
<MainStats s3Credentials={props.s3Credentials}></MainStats>
}
/>
{props.values.batteryView.map((battery) => (
<Route
key={routes.detailed_view + battery.BatteryId}
path={routes.detailed_view + battery.BatteryId}
element={
<DetailedBatteryView
s3Credentials={props.s3Credentials}
batteryData={findBatteryData(battery.BatteryId)}
></DetailedBatteryView>
}
/>
))}
</Routes>
</Grid>
<TableContainer
component={Paper}
sx={{
marginTop: '20px',
marginBottom: '20px',
display:
!currentLocation.pathname.includes('detailed_view') &&
!currentLocation.pathname.includes('mainstats')
? 'block'
: 'none'
}}
>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center">Battery</TableCell>
<TableCell align="center">Firmware</TableCell>
<TableCell align="center">Power</TableCell>
<TableCell align="center">Voltage</TableCell>
<TableCell align="center">SoC</TableCell>
<TableCell align="center">Temperature</TableCell>
<TableCell align="center">Warnings</TableCell>
<TableCell align="center">Alarms</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sortedBatteryView.map((battery) => (
<TableRow
key={battery.BatteryId}
style={{
height: '10px'
}}
>
<TableCell
component="th"
scope="row"
align="center"
sx={{ fontWeight: 'bold' }}
>
<Link
style={{ color: 'black' }}
to={routes.detailed_view + battery.BatteryId.toString()}
>
{'Node ' + battery.BatteryId}
</Link>
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center'
}}
>
{battery.FwVersion.value}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center'
}}
>
{battery.Power.value + ' ' + battery.Power.unit}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center',
backgroundColor:
battery.Voltage.value < 44 || battery.Voltage.value > 57
? '#FF033E'
: '#32CD32',
color: battery.Voltage.value === '' ? 'white' : 'inherit'
}}
>
{battery.Voltage.value + ' ' + battery.Voltage.unit}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center',
backgroundColor:
battery.Soc.value < 20
? '#FF033E'
: battery.Soc.value < 50
? '#ffbf00'
: '#32CD32',
color: battery.Soc.value === '' ? 'white' : 'inherit'
}}
>
{battery.Soc.value + ' ' + battery.Soc.unit}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center',
backgroundColor:
battery.AverageTemperature.value > 270
? '#FF033E'
: '#32CD32 '
}}
>
{battery.AverageTemperature.value +
' ' +
battery.AverageTemperature.unit}
</TableCell>
<TableCell
style={{
width: '20%',
textAlign: 'center',
padding: '8px',
fontWeight:
battery.Warnings.value !== '' ? 'bold' : 'inherit',
backgroundColor:
battery.Warnings.value === '' ? 'inherit' : '#ff9900',
color: battery.Warnings.value != '' ? 'black' : 'inherit'
}}
>
{battery.Warnings.value === '' ? (
'None'
) : battery.Warnings.value.toString().split('-').length >
1 ? (
<Link
style={{ color: 'black' }}
to={
currentLocation.pathname.substring(
0,
currentLocation.pathname.lastIndexOf('/') + 1
) +
routes.log +
'?open=warning'
}
>
Multiple Warnings
</Link>
) : (
battery.Warnings.value
)}
</TableCell>
<TableCell
sx={{
width: '20%',
textAlign: 'center',
fontWeight:
battery.Alarms.value !== '' ? 'bold' : 'inherit',
backgroundColor:
battery.Alarms.value === '' ? 'inherit' : '#FF033E',
color: battery.Alarms.value != '' ? 'black' : 'inherit'
}}
>
{battery.Alarms.value === '' ? (
'None'
) : battery.Alarms.value.toString().split('-').length >
1 ? (
<Link
style={{ color: 'black' }}
to={
currentLocation.pathname.substring(
0,
currentLocation.pathname.lastIndexOf('/') + 1
) +
routes.log +
'?open=error'
}
>
Multiple Alarms
</Link>
) : (
battery.Alarms.value
)}
</TableCell>
</TableRow> </TableRow>
))} </TableHead>
</TableBody> <TableBody>
</Table> {sortedBatteryView.map((battery) => (
</TableContainer> <TableRow
</Container> key={battery.BatteryId}
style={{
height: '10px'
}}
>
<TableCell
component="th"
scope="row"
align="center"
sx={{ fontWeight: 'bold' }}
>
<Link
style={{ color: 'black' }}
to={routes.detailed_view + battery.BatteryId.toString()}
>
{'Node ' + battery.BatteryId}
</Link>
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center'
}}
>
{battery.FwVersion.value}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center'
}}
>
{battery.Power.value + ' ' + battery.Power.unit}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center',
backgroundColor:
battery.Voltage.value < 44 ||
battery.Voltage.value > 57
? '#FF033E'
: '#32CD32',
color:
battery.Voltage.value === '' ? 'white' : 'inherit'
}}
>
{battery.Voltage.value + ' ' + battery.Voltage.unit}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center',
backgroundColor:
battery.Soc.value < 20
? '#FF033E'
: battery.Soc.value < 50
? '#ffbf00'
: '#32CD32',
color: battery.Soc.value === '' ? 'white' : 'inherit'
}}
>
{battery.Soc.value + ' ' + battery.Soc.unit}
</TableCell>
<TableCell
sx={{
width: '10%',
textAlign: 'center',
backgroundColor:
battery.AverageTemperature.value > 270
? '#FF033E'
: '#32CD32 '
}}
>
{battery.AverageTemperature.value +
' ' +
battery.AverageTemperature.unit}
</TableCell>
<TableCell
style={{
width: '20%',
textAlign: 'center',
padding: '8px',
fontWeight:
battery.Warnings.value !== '' ? 'bold' : 'inherit',
backgroundColor:
battery.Warnings.value === '' ? 'inherit' : '#ff9900',
color:
battery.Warnings.value != '' ? 'black' : 'inherit'
}}
>
{battery.Warnings.value === '' ? (
'None'
) : battery.Warnings.value.toString().split('-').length >
1 ? (
<Link
style={{ color: 'black' }}
to={
currentLocation.pathname.substring(
0,
currentLocation.pathname.lastIndexOf('/') + 1
) +
routes.log +
'?open=warning'
}
>
Multiple Warnings
</Link>
) : (
battery.Warnings.value
)}
</TableCell>
<TableCell
sx={{
width: '20%',
textAlign: 'center',
fontWeight:
battery.Alarms.value !== '' ? 'bold' : 'inherit',
backgroundColor:
battery.Alarms.value === '' ? 'inherit' : '#FF033E',
color: battery.Alarms.value != '' ? 'black' : 'inherit'
}}
>
{battery.Alarms.value === '' ? (
'None'
) : battery.Alarms.value.toString().split('-').length >
1 ? (
<Link
style={{ color: 'black' }}
to={
currentLocation.pathname.substring(
0,
currentLocation.pathname.lastIndexOf('/') + 1
) +
routes.log +
'?open=error'
}
>
Multiple Alarms
</Link>
) : (
battery.Alarms.value
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Container>
)}
</> </>
); );
} }

View File

@ -1,8 +1,11 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { I_S3Credentials } from '../../../interfaces/S3Types'; import { I_S3Credentials } from '../../../interfaces/S3Types';
import { import {
Box,
Card, Card,
Grid, Grid,
IconButton,
Modal,
Paper, Paper,
Table, Table,
TableBody, TableBody,
@ -12,14 +15,16 @@ import {
Typography Typography
} from '@mui/material'; } from '@mui/material';
import { Battery } from '../Log/graph.util'; import { Battery } from '../Log/graph.util';
import { useNavigate } from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { useNavigate } from 'react-router-dom'; import axiosConfig from '../../../Resources/axiosConfig';
import routes from '../../../Resources/routes.json';
interface DetailedBatteryViewProps { interface DetailedBatteryViewProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
batteryData: Battery; batteryData: Battery;
installationId: number;
} }
function DetailedBatteryView(props: DetailedBatteryViewProps) { function DetailedBatteryView(props: DetailedBatteryViewProps) {
@ -27,16 +32,20 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
return null; return null;
} }
const navigate = useNavigate(); const navigate = useNavigate();
const [openModalFirmwareUpdate, setOpenModalFirmwareUpdate] = useState(false);
const [openModalResultFirmwareUpdate, setOpenModalResultFirmwareUpdate] =
useState(false);
const handleBatteryViewButton = () => { const handleBatteryViewButton = () => {
navigate(location.pathname.split('/').slice(0, -2).join('/')); navigate(location.pathname.split('/').slice(0, -2).join('/'));
}; };
const handleMainStatsButton = () => {
navigate( const handleUpdateFirmware = () => {
location.pathname.split('/').slice(0, -2).join('/') + setOpenModalFirmwareUpdate(true);
'/' + };
routes.mainstats
); const firmwareModalResultHandleOk = () => {
navigate(location.pathname.split('/').slice(0, -2).join('/'));
}; };
const [GreenisBlinking, setGreenisBlinking] = useState( const [GreenisBlinking, setGreenisBlinking] = useState(
@ -96,26 +105,177 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
backgroundColor: '#bfbfbf' backgroundColor: '#bfbfbf'
}; };
const FirmwareModalHandleProceed = async (e) => {
setOpenModalFirmwareUpdate(false);
const res = await axiosConfig
.post(
`/UpdateFirmware?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${
props.installationId
}`
)
.catch((err) => {
if (err.response) {
// setError(true);
// setLoading(false);
}
});
//if (res) {
setOpenModalResultFirmwareUpdate(true);
//}
};
const FirmwareModalHandleCancel = () => {
setOpenModalFirmwareUpdate(false);
};
return ( return (
<> <>
{openModalResultFirmwareUpdate && (
<Modal
open={openModalResultFirmwareUpdate}
aria-labelledby="error-modal"
aria-describedby="error-modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 420,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography
variant="body1"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
The firmware is getting updated. Please wait...
</Typography>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 10
}}
>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={firmwareModalResultHandleOk}
>
Ok
</Button>
</div>
</Box>
</Modal>
)}
{openModalFirmwareUpdate && (
<Modal
open={openModalFirmwareUpdate}
onClose={FirmwareModalHandleCancel}
aria-labelledby="error-modal"
aria-describedby="error-modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 420,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography
variant="body1"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
Do you really want to update the firmware?
</Typography>
<Typography variant="body1" gutterBottom>
This action requires the battery service to be stopped.
</Typography>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 10
}}
>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={FirmwareModalHandleProceed}
>
Proceed
</Button>
<Button
sx={{
marginTop: 2,
marginLeft: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={FirmwareModalHandleCancel}
>
Cancel
</Button>
</div>
</Box>
</Modal>
)}
<Grid container> <Grid container>
<Grid item xs={6} md={6}> <Grid item xs={6} md={6}>
<Button <IconButton
variant="contained" aria-label="go back"
onClick={handleBatteryViewButton}
sx={{ sx={{
marginTop: '20px', marginTop: '20px',
backgroundColor: '#ffc04d', backgroundColor: 'grey',
color: '#000000', color: '#000000',
'&:hover': { bgcolor: '#f7b34d' } '&:hover': { bgcolor: '#f7b34d' }
}} }}
onClick={handleBatteryViewButton}
> >
<FormattedMessage id="main_stats" defaultMessage="Battery View" /> <ArrowBackIcon />
</Button> </IconButton>
<Button <Button
variant="contained" variant="contained"
onClick={handleMainStatsButton} onClick={handleUpdateFirmware}
sx={{ sx={{
marginTop: '20px', marginTop: '20px',
marginLeft: '20px', marginLeft: '20px',
@ -124,7 +284,10 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
'&:hover': { bgcolor: '#f7b34d' } '&:hover': { bgcolor: '#f7b34d' }
}} }}
> >
<FormattedMessage id="main_stats" defaultMessage="Main Stats" /> <FormattedMessage
id="update_firmware"
defaultMessage="Update Firmware"
/>
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -1,4 +1,12 @@
import { Box, Card, Container, Grid, Modal, Typography } from '@mui/material'; import {
Box,
Card,
Container,
Grid,
IconButton,
Modal,
Typography
} from '@mui/material';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { I_S3Credentials } from '../../../interfaces/S3Types'; import { I_S3Credentials } from '../../../interfaces/S3Types';
@ -16,6 +24,7 @@ import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
interface MainStatsProps { interface MainStatsProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
@ -384,31 +393,18 @@ function MainStats(props: MainStatsProps) {
{!loading && ( {!loading && (
<> <>
<Grid item xs={6} md={6}> <Grid item xs={6} md={6}>
<Button <IconButton
variant="contained" aria-label="go back"
sx={{
marginTop: '20px',
backgroundColor: 'grey',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleBatteryViewButton} onClick={handleBatteryViewButton}
sx={{
marginTop: '20px',
backgroundColor: '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
> >
<FormattedMessage id="main_stats" defaultMessage="Battery View" /> <ArrowBackIcon />
</Button> </IconButton>
<Button
variant="contained"
sx={{
marginTop: '20px',
marginLeft: '20px',
backgroundColor: '#808080',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
</Button>
<Button <Button
variant="contained" variant="contained"

View File

@ -278,6 +278,7 @@ function Installation(props: singleInstallationProps) {
<BatteryView <BatteryView
values={values} values={values}
s3Credentials={s3Credentials} s3Credentials={s3Credentials}
installationId={props.current_installation.id}
></BatteryView> ></BatteryView>
} }
></Route> ></Route>