Compare commits

..

No commits in common. "50e501c37bea470ef1a7ea5ca6da22c18d092217" and "6886c72a860ace1799a8b25dabee7ea70cb937d6" have entirely different histories.

6 changed files with 281 additions and 463 deletions

View File

@ -564,20 +564,12 @@ public class Controller : ControllerBase
return Ok(); return Ok();
} }
[HttpPost(nameof(InsertNewAction))]
public async Task<ActionResult<IEnumerable<Object>>> InsertNewAction([FromBody] UserAction action, Token authToken)
{
var session = Db.GetSession(authToken);
var actionSuccess = await session.RecordUserAction(action);
return actionSuccess ? Ok() : Unauthorized();
}
[HttpPost(nameof(EditInstallationConfig))] [HttpPost(nameof(EditInstallationConfig))]
public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,Token authToken) public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId, Token authToken)
{ {
var session = Db.GetSession(authToken); var session = Db.GetSession(authToken);
//Console.WriteLine(config.GridSetPoint);
// Send configuration changes // Send configuration changes
var success = await session.SendInstallationConfig(installationId, config); var success = await session.SendInstallationConfig(installationId, config);
@ -585,15 +577,7 @@ public class Controller : ControllerBase
// Record configuration change // Record configuration change
if (success) if (success)
{ {
// Create a new UserAction object var actionSuccess = await session.RecordUserAction(installationId, config);
var action = new UserAction
{
InstallationId = installationId,
Timestamp = DateTime.Now,
Description = config.GetConfigurationString()
};
var actionSuccess = await session.RecordUserAction(action);
return actionSuccess?Ok():Unauthorized(); return actionSuccess?Ok():Unauthorized();
} }

View File

@ -102,14 +102,22 @@ public static class SessionMethods
&& await installation.SendConfig(configuration); && await installation.SendConfig(configuration);
} }
public static async Task<Boolean> RecordUserAction(this Session? session, UserAction action) public static async Task<Boolean> RecordUserAction(this Session? session, Int64 installationId, Configuration newConfiguration)
{ {
var user = session?.User; var user = session?.User;
var timestamp = DateTime.Now;
if (user is null || user.UserType == 0) if (user is null || user.UserType == 0)
return false; return false;
action.UserName = user.Name; // Create a new UserAction object
var action = new UserAction
{
UserName = user.Name,
InstallationId = installationId,
Timestamp = timestamp,
Description = newConfiguration.GetConfigurationString()
};
// Save the configuration change to the database // Save the configuration change to the database
Db.HandleAction(action); Db.HandleAction(action);

View File

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

View File

@ -59,8 +59,23 @@ public static class RabbitMqManager
//Consumer received a message //Consumer received a message
if (receivedStatusMessage != null) if (receivedStatusMessage != null)
{ {
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId); Console.WriteLine("----------------------------------------------");
int installationId = (int )installation.Id;
int installationId = (int)Db.Installations.Where(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.Id).FirstOrDefault();
string installationName = (string)Db.Installations.Where(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.InstallationName).FirstOrDefault();
int bucketId = (int)Db.Installations.Where(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.S3BucketId).FirstOrDefault();
int productId = (int)Db.Installations.Where(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId).Select(f => f.Product).FirstOrDefault();
string monitorLink = "";
if (productId == 0)
{
monitorLink =
$"https://monitor.innov.energy/installations/list/installation/{bucketId}/batteryview";
}
else
{
monitorLink =
$"https://monitor.innov.energy/salidomo_installations/list/installation/{bucketId}/batteryview";
}
Console.WriteLine("Received a message from installation: " + installationId + " , product is: "+receivedStatusMessage.Product+ " and status is: " + receivedStatusMessage.Status); 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.
@ -98,32 +113,17 @@ public static class RabbitMqManager
if (receivedStatusMessage.Alarms != null) if (receivedStatusMessage.Alarms != null)
{ {
string monitorLink;
if (installation.Product == 0)
{
monitorLink =
$"https://monitor.innov.energy/installations/list/installation/{installation.S3BucketId}/batteryview";
}
else
{
monitorLink =
$"https://monitor.innov.energy/salidomo_installations/list/installation/{installation.S3BucketId}/batteryview";
}
foreach (var alarm in receivedStatusMessage.Alarms) foreach (var alarm in receivedStatusMessage.Alarms)
{ {
Error newError = new Error Error newError = new Error
{ {
InstallationId = installation.Id, 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);
Console.WriteLine("Add an alarm for installation "+installationId);
// Send replace battery email to support team if this alarm is "NeedToReplaceBattery" // Send replace battery email to support team if this alarm is "NeedToReplaceBattery"
if (alarm.Description == "NeedToReplaceBattery" || alarm.Description == "2 or more string are disabled") if (alarm.Description == "NeedToReplaceBattery" || alarm.Description == "2 or more string are disabled")
{ {
@ -132,7 +132,7 @@ public static class RabbitMqManager
string subject = "Battery Alarm: 2 or more strings broken"; string subject = "Battery Alarm: 2 or more strings broken";
string text = $"Dear InnovEnergy Support Team,\n" + string text = $"Dear InnovEnergy Support Team,\n" +
$"\n"+ $"\n"+
$"Installation Name: {installation.InstallationName}\n"+ $"Installation Name: {installationName}\n"+
$"\n"+ $"\n"+
$"Installation Monitor Link: {monitorLink}\n"+ $"Installation Monitor Link: {monitorLink}\n"+
$"\n"+ $"\n"+

View File

@ -1,14 +1,11 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { import {
Alert, Alert,
Box,
Card, Card,
Container, Container,
Divider, Divider,
Grid, Grid,
IconButton, IconButton,
Modal,
TextField,
useTheme useTheme
} from '@mui/material'; } from '@mui/material';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
@ -19,10 +16,6 @@ import routes from '../../../Resources/routes.json';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { TokenContext } from '../../../contexts/tokenContext'; import { TokenContext } from '../../../contexts/tokenContext';
import { Action } from '../../../interfaces/S3Types'; import { Action } from '../../../interfaces/S3Types';
import Button from '@mui/material/Button';
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import dayjs from 'dayjs';
interface HistoryProps { interface HistoryProps {
errorLoadingS3Data: boolean; errorLoadingS3Data: boolean;
@ -36,61 +29,7 @@ function HistoryOfActions(props: HistoryProps) {
const [history, setHistory] = useState<Action[]>([]); const [history, setHistory] = useState<Action[]>([]);
const navigate = useNavigate(); const navigate = useNavigate();
const tokencontext = useContext(TokenContext); const tokencontext = useContext(TokenContext);
const [actionDate, setActionDate] = useState(dayjs());
const { removeToken } = tokencontext; const { removeToken } = tokencontext;
const [openModalAddAction, setOpenModalAddAction] = useState(false);
const requiredFields = ['description', 'timestamp'];
const [newAction, setNewAction] = useState<Partial<Action>>({
installationId: props.id,
timestamp: actionDate.toDate(),
description: ''
});
const handleDateChange = (newdate) => {
setActionDate(newdate);
setNewAction({
...newAction,
['timestamp']: newdate
});
};
const handleChange = (e) => {
const { name, value } = e.target;
setNewAction({
...newAction,
[name]: value
});
};
const handleAddActionButton = () => {
setOpenModalAddAction(!openModalAddAction);
};
const SumbitNewAction = () => {
const res = axiosConfig.post(`/InsertNewAction`, newAction).catch((err) => {
if (err.response) {
// setError(true);
// setLoading(false);
}
});
if (res) {
setOpenModalAddAction(!openModalAddAction);
}
};
const deleteUserModalHandleCancel = (e) => {
setOpenModalAddAction(false);
};
const areRequiredFieldsFilled = () => {
for (const field of requiredFields) {
if (!newAction[field]) {
return false;
}
}
return true;
};
useEffect(() => { useEffect(() => {
axiosConfig axiosConfig
@ -104,378 +43,265 @@ function HistoryOfActions(props: HistoryProps) {
navigate(routes.login); navigate(routes.login);
} }
}); });
}, [openModalAddAction]); }, []);
return ( return (
<> <Container maxWidth="xl">
{openModalAddAction && ( <Grid container>
<Modal <Grid item xs={12} md={12}>
open={openModalAddAction} {history.length > 0 && (
aria-labelledby="error-modal" <Card sx={{ marginTop: '10px' }}>
aria-describedby="error-modal-description" <Divider />
>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 500,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<div> <div>
<DateTimePicker <div
label="Select Action Date" style={{
name="timestamp" height: '40px',
value={actionDate} marginBottom: '10px',
onChange={(newDate) => handleDateChange(newDate.toDate())} display: 'flex',
sx={{ alignItems: 'center'
width: 450,
marginTop: 2
}}
/>
<TextField
label="Description"
variant="outlined"
name="description"
value={newAction.description}
onChange={handleChange}
fullWidth
multiline
rows={4} // Adding rows prop to make it a multiline field with more space
sx={{
marginBottom: 2,
marginTop: 2,
height: 'auto'
}} // 'auto' height works better with multiline fields
/>
</div>
<div
style={{
display: 'flex',
alignItems: 'center'
}}
>
<Button
sx={{
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={SumbitNewAction}
disabled={!areRequiredFieldsFilled()}
>
Submit
</Button>
<Button
sx={{
marginLeft: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={deleteUserModalHandleCancel}
>
Cancel
</Button>
</div>
</Box>
</LocalizationProvider>
</Modal>
)}
{!openModalAddAction && (
<Container maxWidth="xl">
<Grid container>
<Grid container>
<Grid item xs={6} md={6}>
<Button
variant="contained"
onClick={handleAddActionButton}
sx={{
marginTop: '20px',
backgroundColor: '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}} }}
> >
<FormattedMessage <div
id="add_action" style={{
defaultMessage="Add New Action" flex: 1,
/> marginTop: '15px',
</Button> display: 'flex',
</Grid> alignItems: 'center',
</Grid> justifyContent: 'center'
}}
<Grid item xs={12} md={12}> >
{history.length > 0 && ( <Typography
<Card sx={{ marginTop: '10px' }}> variant="body1"
<Divider /> color="dimgrey"
<div> fontWeight="bold"
<div fontSize="1rem"
style={{ gutterBottom
height: '40px', noWrap
marginBottom: '10px',
display: 'flex',
alignItems: 'center'
}}
> >
<div <FormattedMessage id="user" defaultMessage="User" />
style={{ </Typography>
flex: 1, </div>
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="user" defaultMessage="User" />
</Typography>
</div>
<div <div
style={{ style={{
flex: 1, flex: 1,
marginTop: '15px', marginTop: '15px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}} }}
> >
<Typography <Typography
variant="body1" variant="body1"
color="dimgrey" color="dimgrey"
fontWeight="bold" fontWeight="bold"
fontSize="1rem" fontSize="1rem"
gutterBottom gutterBottom
noWrap noWrap
> >
<FormattedMessage id="date" defaultMessage="Date" /> <FormattedMessage id="date" defaultMessage="Date" />
</Typography> </Typography>
</div> </div>
<div <div
style={{ style={{
flex: 1, flex: 1,
marginTop: '15px', marginTop: '15px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}} }}
> >
<Typography <Typography
variant="body1" variant="body1"
color="dimgrey" color="dimgrey"
fontWeight="bold" fontWeight="bold"
fontSize="1rem" fontSize="1rem"
gutterBottom gutterBottom
noWrap noWrap
> >
<FormattedMessage id="time" defaultMessage="Time" /> <FormattedMessage id="time" defaultMessage="Time" />
</Typography> </Typography>
</div> </div>
<div <div
style={{ style={{
flex: 6, flex: 6,
marginTop: '15px', marginTop: '15px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}} }}
> >
<Typography <Typography
variant="body1" variant="body1"
color="dimgrey" color="dimgrey"
fontWeight="bold" fontWeight="bold"
fontSize="1rem" fontSize="1rem"
gutterBottom gutterBottom
noWrap noWrap
> >
<FormattedMessage <FormattedMessage
id="description" id="description"
defaultMessage="Description" defaultMessage="Description"
/> />
</Typography> </Typography>
</div> </div>
</div> </div>
<Divider /> <Divider />
<div style={{ maxHeight: '400px', overflowY: 'auto' }}> <div style={{ maxHeight: '400px', overflowY: 'auto' }}>
{history.map((action, index) => { {history.map((action, index) => {
// Parse the timestamp string to a Date object // Parse the timestamp string to a Date object
const date = new Date(action.timestamp); const date = new Date(action.timestamp);
// Extract the date part (e.g., "2023-05-31") // Extract the date part (e.g., "2023-05-31")
const datePart = date.toLocaleDateString(); const datePart = date.toLocaleDateString();
// Extract the time part (e.g., "12:34:56") // Extract the time part (e.g., "12:34:56")
const timePart = date.toLocaleTimeString(); const timePart = date.toLocaleTimeString();
return ( return (
<React.Fragment key={index}> <React.Fragment key={index}>
<Divider /> <Divider />
<div <div
style={{
minHeight: '40px',
marginBottom: '10px',
display: 'flex',
alignItems: 'center'
}}
>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
>
{action.userName}
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
>
{datePart}
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
>
{timePart}
</Typography>
</div>
<div
style={{
flex: 6,
display: 'flex',
marginTop: '15px',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
style={{ style={{
minHeight: '40px', whiteSpace: 'normal',
marginBottom: '10px', wordBreak: 'break-word'
display: 'flex',
alignItems: 'center'
}} }}
> >
<div {action.description}
style={{ </Typography>
flex: 1, </div>
marginTop: '15px', </div>
display: 'flex', </React.Fragment>
alignItems: 'center', );
justifyContent: 'center' })}
}} </div>
> </div>
<Typography </Card>
variant="body1" )}
fontWeight="bold"
color="text.primary"
gutterBottom
>
{action.userName}
</Typography>
</div>
<div {!props.errorLoadingS3Data && history.length == 0 && (
style={{ <Alert
flex: 1, severity="error"
marginTop: '15px', sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' marginTop: '20px'
}} }}
> >
<Typography <FormattedMessage
variant="body1" id="nohistory"
fontWeight="bold" defaultMessage="There is no history of actions"
color="text.primary" />
gutterBottom <IconButton
> color="inherit"
{datePart} size="small"
</Typography> sx={{ marginLeft: '4px' }}
</div> ></IconButton>
<div </Alert>
style={{ )}
flex: 1, </Grid>
marginTop: '15px', </Grid>
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
>
{timePart}
</Typography>
</div>
<div <Grid item xs={12} md={12} style={{ marginBottom: '20px' }}>
style={{ {props.errorLoadingS3Data && (
flex: 6, <Alert
display: 'flex', severity="error"
marginTop: '15px', sx={{
alignItems: 'center', display: 'flex',
justifyContent: 'center' alignItems: 'center',
}} marginTop: '20px'
> }}
<Typography >
variant="body1" <FormattedMessage
fontWeight="bold" id="cannotloadloggingdata"
color="text.primary" defaultMessage="Cannot load logging data"
gutterBottom />
style={{ <IconButton
whiteSpace: 'normal', color="inherit"
wordBreak: 'break-word' size="small"
}} sx={{ marginLeft: '4px' }}
> ></IconButton>
{action.description} </Alert>
</Typography> )}
</div> </Grid>
</div> </Container>
</React.Fragment>
);
})}
</div>
</div>
</Card>
)}
{!props.errorLoadingS3Data && history.length == 0 && (
<Alert
severity="error"
sx={{
display: 'flex',
alignItems: 'center',
marginTop: '20px'
}}
>
<FormattedMessage
id="nohistory"
defaultMessage="There is no history of actions"
/>
<IconButton
color="inherit"
size="small"
sx={{ marginLeft: '4px' }}
></IconButton>
</Alert>
)}
</Grid>
</Grid>
<Grid item xs={12} md={12} style={{ marginBottom: '20px' }}>
{props.errorLoadingS3Data && (
<Alert
severity="error"
sx={{
display: 'flex',
alignItems: 'center',
marginTop: '20px'
}}
>
<FormattedMessage
id="cannotloadloggingdata"
defaultMessage="Cannot load logging data"
/>
<IconButton
color="inherit"
size="small"
sx={{ marginLeft: '4px' }}
></IconButton>
</Alert>
)}
</Grid>
</Container>
)}
</>
); );
} }

View File

@ -21,6 +21,6 @@ export interface Action {
id: number; id: number;
userName: string; userName: string;
installationId: number; installationId: number;
timestamp: Date; timestamp: string;
description: string; description: String;
} }