Persistent logging (erros and warning)

This commit is contained in:
Noe 2023-11-22 09:35:29 +01:00
parent ac8f874255
commit 1204a28ab5
14 changed files with 757 additions and 204 deletions

View File

@ -446,6 +446,41 @@ public class Controller : ControllerBase
return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User).HideWriteKeyIfUserIsNotAdmin(session.User.HasWriteAccess);
}
[HttpPost(nameof(AcknowledgeError))]
public ActionResult AcknowledgeError(Int64 id, Token authToken)
{
var session = Db.GetSession(authToken);
if (session == null)
return Unauthorized();
var error=Db.Errors
.FirstOrDefault(error => error.Id == id);
error.Seen = true;
return Db.Update(error)
? Ok()
: Unauthorized();
}
[HttpPost(nameof(AcknowledgeWarning))]
public ActionResult AcknowledgeWarning(Int64 id, Token authToken)
{
var session = Db.GetSession(authToken);
if (session == null)
return Unauthorized();
var warning=Db.Warnings
.FirstOrDefault(warning => warning.Id == id);
warning.Seen = true;
return Db.Update(warning)
? Ok()
: Unauthorized();
}
[HttpPut(nameof(UpdateFolder))]
public ActionResult<Folder> UpdateFolder([FromBody] Folder folder, Token authToken)

View File

@ -8,7 +8,8 @@ public abstract class LogEntry
public Int64 Id { get; set; }
public Int64 InstallationId { get; set; }
public String Description { get; set; } = null!;
public DateTime CreatedAt { get; set; }
public String Date { get; set; } = null!;
public String Time { get; set; } = null!;
public String DeviceCreatedTheMessage{ get; set; } = null!;
public Boolean Seen { get; set; }
}

View File

@ -70,7 +70,7 @@ public static partial class Db
{
var oldestError =
Errors.Where(error => error.InstallationId == installationId)
.OrderBy(error => error.CreatedAt)
.OrderBy(error => error.Date)
.FirstOrDefault();
//Remove the old error
@ -96,7 +96,7 @@ public static partial class Db
{
var oldestWarning =
Warnings.Where(warning => warning.InstallationId == installationId)
.OrderBy(warning => warning.CreatedAt)
.OrderBy(warning => warning.Date)
.FirstOrDefault();
//Remove the old error

View File

@ -17,6 +17,17 @@ public static partial class Db
return Update(obj: folder);
}
public static Boolean Update(Error error)
{
return Update(obj: error);
}
public static Boolean Update(Warning warning)
{
return Update(obj: warning);
}
public static Boolean Update(Installation installation)
{
return Update(obj: installation);

View File

@ -1,4 +1,4 @@
{
"Key": "EXOea18f5a82bd358896154c783",
"Secret": "lYtzU7R5e0L6XKOgBaLVPFr41nEBDxDdXU47zBAEI6M"
"Key": "EXO4d838d1360ba9fb7d51648b0",
"Secret": "_bmrp6ewWAvNwdAQoeJuC-9y02Lsx7NV6zD-WjljzCU"
}

View File

@ -3,7 +3,8 @@ public class StatusMessage
{
public required int InstallationId { get; init; }
public required int Status { get; init; }
public DateTime CreatedAt { get; set; }
public String Date { get; set; } = null!;
public String Time { get; set; } = null!;
public String Description { get; init; } = null!;
public String CreatedBy { get; init; } = null!;
}

View File

@ -100,7 +100,8 @@ public static class WebsocketManager
{
InstallationId = receivedStatusMessage.InstallationId,
Description = receivedStatusMessage.Description,
CreatedAt = receivedStatusMessage.CreatedAt,
Date = receivedStatusMessage.Date,
Time = receivedStatusMessage.Time,
DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy,
Seen = false
};
@ -115,7 +116,8 @@ public static class WebsocketManager
{
InstallationId = receivedStatusMessage.InstallationId,
Description = receivedStatusMessage.Description,
CreatedAt = receivedStatusMessage.CreatedAt,
Date = receivedStatusMessage.Date,
Time = receivedStatusMessage.Time,
DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy,
Seen = false
};

View File

@ -6,7 +6,8 @@ public class StatusMessage
public required int InstallationId { get; set; }
public required int Status { get; set; }
public DateTime CreatedAt { get; set; }
public String Date { get; set; } = null!;
public String Time { get; set; } = null!;
public String Description { get; set; } = null!;
public String CreatedBy { get; set; } = null!;
}

View File

@ -356,13 +356,15 @@ internal static class Program
if (status == 2)
{
jsonObject.CreatedAt = DateTime.Now;
jsonObject.Description = "Battery Temperature High";
jsonObject.CreatedBy = "Battery/1";
jsonObject.Date = DateTime.Now.ToString("yyyy-MM-dd");
jsonObject.Time = DateTime.Now.ToString("HH:mm:ss");
jsonObject.Description = "Battery Overvoltage";
jsonObject.CreatedBy = "Battery/4";
}
else if (status == 1)
{
jsonObject.CreatedAt = DateTime.Now;
jsonObject.Date = DateTime.Now.ToString("yyyy-MM-dd");
jsonObject.Time = DateTime.Now.ToString("HH:mm:ss");
jsonObject.Description = "Temp warning message";
jsonObject.CreatedBy = "Battery/4";
}

View File

@ -27,6 +27,16 @@ public static class TaskUtils
return await task; // await the task to bubble up any errors etc
}
public static void SupressAwaitWarning(this Task task)
{
}
public static void SupressAwaitWarning<T>(this Task<T> task)
{
}
public static Task WhenAny(this IEnumerable<Task> tasks)

View File

@ -6,12 +6,6 @@ import {
Divider,
Grid,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
useTheme
} from '@mui/material';
import Typography from '@mui/material/Typography';
@ -25,6 +19,8 @@ import { useNavigate } from 'react-router-dom';
import { TokenContext } from '../../../contexts/tokenContext';
import { ErrorMessage } from '../../../interfaces/S3Types';
import Button from '@mui/material/Button';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
interface LogProps {
errorLoadingS3Data: boolean;
@ -37,7 +33,7 @@ function Log(props: LogProps) {
const [errors, setErrors] = useState<ErrorMessage[]>([]);
const [errorButtonPressed, setErrorButtonPressed] = useState(false);
const [warningButtonPressed, setWarningButtonPressed] = useState(false);
const [updateCount, setUpdateCount] = useState(0);
const navigate = useNavigate();
const tokencontext = useContext(TokenContext);
const { removeToken } = tokencontext;
@ -66,7 +62,7 @@ function Log(props: LogProps) {
navigate(routes.login);
}
});
}, []);
}, [updateCount]);
const handleErrorButtonPressed = () => {
setErrorButtonPressed(!errorButtonPressed);
@ -76,6 +72,34 @@ function Log(props: LogProps) {
setWarningButtonPressed(!warningButtonPressed);
};
const handleErrorAcknowledgeButtonPressed = (errorId: number) => {
axiosConfig
.post(`/AcknowledgeError?id=${errorId}`)
.then((res: AxiosResponse<ErrorMessage[]>) => {
setUpdateCount(updateCount + 1);
})
.catch((err: AxiosError) => {
if (err.response && err.response.status == 401) {
removeToken();
navigate(routes.login);
}
});
};
const handleWarningAcknowledgeButtonPressed = (warningId: number) => {
axiosConfig
.post(`/AcknowledgeWarning?id=${warningId}`)
.then((res: AxiosResponse<ErrorMessage[]>) => {
setUpdateCount(updateCount + 1);
})
.catch((err: AxiosError) => {
if (err.response && err.response.status == 401) {
removeToken();
navigate(routes.login);
}
});
};
return (
<Container maxWidth="xl">
<Grid container>
@ -91,97 +115,278 @@ function Log(props: LogProps) {
{errorButtonPressed && errors.length > 0 && (
<Card sx={{ marginTop: '10px' }}>
<Divider />
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>
<div>
<div
style={{
height: '40px',
marginBottom: '10px',
display: 'flex',
alignItems: 'center'
}}
>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="type" defaultMessage="Type" />
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage
id="description"
defaultMessage="Description"
/>
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="device" defaultMessage="Device" />
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="date" defaultMessage="Date" />
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="time" defaultMessage="Time" />
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="seen" defaultMessage="Seen" />
</TableCell>
</TableRow>
</TableHead>
<TableBody>
</Typography>
</div>
</div>
<Divider />
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
{errors.map((error, index) => (
<TableRow hover key={index}>
<TableCell>
<>
<Divider />
<div
key={index}
style={{
height: '40px',
marginBottom: '10px',
display: 'flex',
alignItems: 'center'
}}
>
<div
style={{
flex: 1,
marginTop: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<ErrorIcon
sx={{
color: 'red',
width: 25,
height: 25,
marginLeft: '5px',
marginTop: '8px'
height: 25
}}
/>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.description}
</Typography>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.deviceCreatedTheMessage}
</Typography>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.createdAt}
{error.date}
</Typography>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{error.seen == false ? 'No' : 'Yes'}
{error.time}
</Typography>
</TableCell>
</TableRow>
</div>
<div
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<FormControlLabel
control={
<Checkbox
checked={error.seen}
onChange={() =>
error.seen === false &&
handleErrorAcknowledgeButtonPressed(error.id)
}
sx={{
marginLeft: '25px'
}}
/>
}
label=""
sx={{ marginTop: 1 }}
/>
</div>
</div>
</>
))}
</TableBody>
</Table>
</TableContainer>
</div>
</div>
</Card>
)}
@ -224,99 +429,280 @@ function Log(props: LogProps) {
{warningButtonPressed && warnings.length > 0 && (
<Card sx={{ marginTop: '10px' }}>
<Divider />
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>
<div>
<div
style={{
height: '40px',
marginBottom: '10px',
display: 'flex',
alignItems: 'center'
}}
>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="type" defaultMessage="Type" />
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage
id="description"
defaultMessage="Description"
/>
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="device" defaultMessage="Device" />
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="date" defaultMessage="Date" />
</TableCell>
<TableCell>
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="time" defaultMessage="Time" />
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="seen" defaultMessage="Seen" />
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{warnings.map((warning, index) => {
return (
<TableRow hover key={index}>
<TableCell>
</Typography>
</div>
</div>
<Divider />
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
{warnings.map((warning, index) => (
<>
<Divider />
<div
key={index}
style={{
height: '40px',
marginBottom: '10px',
display: 'flex',
alignItems: 'center'
}}
>
<div
style={{
flex: 1,
marginTop: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<WarningIcon
sx={{
color: 'orange',
width: 25,
height: 25,
marginLeft: '5px',
marginTop: '8px'
height: 25
}}
/>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{warning.description}
</Typography>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px' }}
>
{warning.deviceCreatedTheMessage}
</Typography>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{warning.createdAt}
{warning.date}
</Typography>
</TableCell>
<TableCell>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '5px', verticalAlign: 'middle' }}
>
{warning.seen == false ? 'No' : 'Yes'}
{warning.time}
</Typography>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</div>
<div
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<FormControlLabel
control={
<Checkbox
checked={warning.seen}
onChange={() =>
warning.seen === false &&
handleWarningAcknowledgeButtonPressed(
warning.id
)
}
sx={{
marginLeft: '25px'
}}
/>
}
label=""
sx={{ marginTop: 1 }}
/>
</div>
</div>
</>
))}
</div>
</div>
</Card>
)}

View File

@ -1,7 +1,9 @@
import React, { useContext, useEffect, useState } from 'react';
import {
Alert,
FormControl,
Grid,
IconButton,
InputAdornment,
TextField,
useTheme
@ -13,6 +15,7 @@ import Button from '@mui/material/Button';
import UserForm from './userForm';
import { UserContext } from '../../../contexts/userContext';
import { FormattedMessage } from 'react-intl';
import { Close as CloseIcon } from '@mui/icons-material';
function UsersSearch() {
const theme = useTheme();
@ -21,6 +24,8 @@ function UsersSearch() {
const [filteredData, setFilteredData] = useState(availableUsers);
const [openModal, setOpenModal] = useState(false);
const context = useContext(UserContext);
const [userCreated, setUserCreated] = useState(false);
const [errorOccured, setErrorOccured] = useState(false);
const { currentUser, setUser } = context;
useEffect(() => {
@ -43,13 +48,27 @@ function UsersSearch() {
};
const handleUserFormSubmit = () => {
setOpenModal(false);
setUserCreated(true);
fetchAvailableUsers();
setTimeout(() => {
setUserCreated(false);
}, 8000);
};
const handleUserFormCancel = () => {
setOpenModal(false);
};
const handleUserError = () => {
setOpenModal(false);
setErrorOccured(true);
setTimeout(() => {
setErrorOccured(false);
}, 3000);
};
const isMobile = window.innerWidth <= 1490;
return (
@ -63,8 +82,67 @@ function UsersSearch() {
)}
</Grid>
</Grid>
{userCreated && (
<Grid container spacing={1}>
<Grid item xs={12} md={3}>
<Alert
severity="success"
sx={{
mt: 1,
display: 'flex',
alignItems: 'center'
}}
>
<FormattedMessage
id="successfullyCreatedUser"
defaultMessage="Successfully Updated User"
/>
<IconButton
color="inherit"
size="small"
onClick={() => setUserCreated(false)}
sx={{ marginLeft: '4px' }}
>
<CloseIcon fontSize="small" />
</IconButton>
</Alert>
</Grid>
</Grid>
)}
{errorOccured && (
<Grid container spacing={1}>
<Grid item xs={12} md={3}>
<Alert
severity="error"
sx={{
mt: 1,
display: 'flex',
alignItems: 'center'
}}
>
<FormattedMessage
id="errorOccured"
defaultMessage="An error has occurred"
/>
<IconButton
color="inherit"
size="small"
onClick={() => setErrorOccured(false)}
sx={{ marginLeft: '4px' }}
>
<CloseIcon fontSize="small" />
</IconButton>
</Alert>
</Grid>
</Grid>
)}
{openModal && (
<UserForm cancel={handleUserFormCancel} submit={handleUserFormSubmit} />
<UserForm
cancel={handleUserFormCancel}
submit={handleUserFormSubmit}
error={handleUserError}
/>
)}
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
<Grid item xs={12} md={isMobile ? 5 : 3}>

View File

@ -25,6 +25,7 @@ import { FormattedMessage } from 'react-intl';
interface userFormProps {
cancel: () => void;
submit: () => void;
error: () => void;
}
function userForm(props: userFormProps) {
@ -106,19 +107,34 @@ function userForm(props: userFormProps) {
const isMobile = window.innerWidth <= 1490;
const handleSubmit = async (e) => {
const res = await axiosConfig.post('/CreateUser', {
const res = await axiosConfig
.post('/CreateUser', {
...formValues,
password: '',
language: 'english'
})
.catch((err) => {
setLoading(false);
if (err.response) {
props.error();
}
});
if (res) {
try {
for (const folderName of selectedFolderNames) {
const folder = folders.find((folder) => folder.name === folderName);
await axiosConfig.post(
await axiosConfig
.post(
`/GrantUserAccessToFolder?UserId=${res.data.id}&FolderId=${folder.id}`
);
)
.catch((err) => {
setLoading(false);
if (err.response) {
props.error();
}
});
}
for (const installationName of selectedInstallationNames) {
@ -126,9 +142,16 @@ function userForm(props: userFormProps) {
(installation) => installation.name === installationName
);
await axiosConfig.post(
await axiosConfig
.post(
`/GrantUserAccessToInstallation?UserId=${res.data.id}&InstallationId=${installation.id}`
);
)
.catch((err) => {
setLoading(false);
if (err.response) {
props.error();
}
});
}
setLoading(false);
@ -145,6 +168,7 @@ function userForm(props: userFormProps) {
}, 2000);
});
}
}
};
const handleCancelSubmit = (e) => {

View File

@ -7,9 +7,11 @@ export interface I_S3Credentials {
}
export interface ErrorMessage {
id: number;
installationId: number;
description: string;
createdAt: Date;
date: string;
time: string;
deviceCreatedTheMessage: string;
seen: boolean;
}