Fixed bug in database backup.

Updated view with icons in history actions
Increased performance when deleting an action
This commit is contained in:
Noe 2024-07-18 11:38:15 +02:00
parent ce50b7ef3e
commit 44f9fb7f7d
9 changed files with 472 additions and 435 deletions

View File

@ -808,10 +808,10 @@ public class Controller : ControllerBase
[HttpPost(nameof(DeleteAction))] [HttpPost(nameof(DeleteAction))]
public async Task<ActionResult<IEnumerable<Object>>> DeleteAction([FromBody] UserAction action, Token authToken) public async Task<ActionResult<IEnumerable<Object>>> DeleteAction(Int64 actionId, Token authToken)
{ {
var session = Db.GetSession(authToken); var session = Db.GetSession(authToken);
var actionSuccess = await session.DeleteUserAction(action); var actionSuccess = await session.DeleteUserAction(actionId);
return actionSuccess ? Ok() : Unauthorized(); return actionSuccess ? Ok() : Unauthorized();
} }

View File

@ -152,21 +152,19 @@ public static class SessionMethods
if (user is null || user.UserType == 0) if (user is null || user.UserType == 0)
return false; return false;
action.UserName = user.Name;
Db.UpdateAction(action); Db.UpdateAction(action);
return true; return true;
} }
public static async Task<Boolean> DeleteUserAction(this Session? session, UserAction action) public static async Task<Boolean> DeleteUserAction(this Session? session, Int64 actionId)
{ {
var user = session?.User; var user = session?.User;
if (user is null || user.UserType == 0) if (user is null || user.UserType == 0)
return false; return false;
var action = Db.GetActionById(actionId);
action.UserName = user.Name;
Db.Delete(action); Db.Delete(action);
Console.WriteLine("---------------Deleted the Action in the database-----------------"); Console.WriteLine("---------------Deleted the Action in the database-----------------");

View File

@ -115,7 +115,7 @@ public static partial class Db
{ {
existingAction.Description = updatedAction.Description; existingAction.Description = updatedAction.Description;
existingAction.Timestamp = updatedAction.Timestamp; existingAction.Timestamp = updatedAction.Timestamp;
//Update(existingAction); Update(existingAction);
Console.WriteLine("---------------Updated the Action in the database-----------------"); Console.WriteLine("---------------Updated the Action in the database-----------------");
} }
} }

View File

@ -7,7 +7,7 @@ set -e
echo -e "\n============================ Deploy ============================\n" echo -e "\n============================ Deploy ============================\n"
ip_addresses_usb0=("10.2.2.118" "10.2.4.155" "10.2.3.244" "10.2.4.127") ip_addresses_usb0=("10.2.2.118" "10.2.4.155" "10.2.3.244" "10.2.4.127" "10.2.4.96")
ip_addresses_usb1=("10.2.0.179" ) ip_addresses_usb1=("10.2.0.179" )

View File

@ -37,8 +37,12 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
const [openModalFirmwareUpdate, setOpenModalFirmwareUpdate] = useState(false); const [openModalFirmwareUpdate, setOpenModalFirmwareUpdate] = useState(false);
const [openModalResultFirmwareUpdate, setOpenModalResultFirmwareUpdate] = const [openModalResultFirmwareUpdate, setOpenModalResultFirmwareUpdate] =
useState(false); useState(false);
const [openModalDownloadBatteryLog, setOpenModalDownloadBatteryLog] = useState(false); const [openModalDownloadBatteryLog, setOpenModalDownloadBatteryLog] =
const [openModalStartDownloadBatteryLog, setOpenModalStartDownloadBatteryLog] = useState(false); useState(false);
const [
openModalStartDownloadBatteryLog,
setOpenModalStartDownloadBatteryLog
] = useState(false);
const [openModalError, setOpenModalError] = useState(false); const [openModalError, setOpenModalError] = useState(false);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
@ -153,7 +157,6 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
setOpenModalDownloadBatteryLog(false); setOpenModalDownloadBatteryLog(false);
}; };
const DownloadBatteryLogModalHandleProceed = async () => { const DownloadBatteryLogModalHandleProceed = async () => {
setOpenModalDownloadBatteryLog(false); setOpenModalDownloadBatteryLog(false);
setOpenModalStartDownloadBatteryLog(true); setOpenModalStartDownloadBatteryLog(true);
@ -161,7 +164,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
try { try {
// Start the job to generate the battery log // Start the job to generate the battery log
const startRes = await axiosConfig.post( const startRes = await axiosConfig.post(
`/StartDownloadBatteryLog?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${props.installationId}` `/StartDownloadBatteryLog?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${
props.installationId
}`
); );
if (startRes.status === 200) { if (startRes.status === 200) {
@ -170,27 +175,29 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
// Polling to check the job status // Polling to check the job status
const checkJobStatus = async () => { const checkJobStatus = async () => {
try { try {
const statusRes = await axiosConfig.get(`/GetJobResult?jobId=${jobId}`); const statusRes = await axiosConfig.get(
`/GetJobResult?jobId=${jobId}`
);
if (statusRes.status === 200) { if (statusRes.status === 200) {
const jobStatus = statusRes.data.status; const jobStatus = statusRes.data.status;
switch (jobStatus) { switch (jobStatus) {
case "Completed": case 'Completed':
return statusRes.data.fileName; // Return FileName upon completion return statusRes.data.fileName; // Return FileName upon completion
case "Failed": case 'Failed':
throw new Error("Job processing failed."); throw new Error('Job processing failed.');
case "Processing": case 'Processing':
await new Promise(resolve => setTimeout(resolve, 60000)); // Wait for 60 seconds before next check await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 60 seconds before next check
return checkJobStatus(); return checkJobStatus();
default: default:
throw new Error("Unknown download battery log job status."); throw new Error('Unknown download battery log job status.');
} }
} else { } else {
throw new Error("Unexpected error occurred."); throw new Error('Unexpected error occurred.');
} }
} catch (error) { } catch (error) {
throw new Error("Failed to fetch job status."); // Catch errors from status check throw new Error('Failed to fetch job status.'); // Catch errors from status check
} }
}; };
@ -198,9 +205,12 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
const fileName = await checkJobStatus(); const fileName = await checkJobStatus();
// Once job is completed, download the file // Once job is completed, download the file
const res = await axiosConfig.get(`/DownloadBatteryLog?jobId=${jobId}`, { const res = await axiosConfig.get(
responseType: 'blob', `/DownloadBatteryLog?jobId=${jobId}`,
}); {
responseType: 'blob'
}
);
const finalFileName = fileName || 'unknown_file_name'; // Default filename if not received const finalFileName = fileName || 'unknown_file_name'; // Default filename if not received
console.log('Downloaded file name:', finalFileName); console.log('Downloaded file name:', finalFileName);
@ -216,10 +226,13 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
// Delete the file after successful download // Delete the file after successful download
console.log('Deleted file name:', finalFileName); console.log('Deleted file name:', finalFileName);
await axiosConfig.delete(`/DeleteBatteryLog`, { params: { fileName: finalFileName } }); await axiosConfig.delete(`/DeleteBatteryLog`, {
params: { fileName: finalFileName }
});
} else { } else {
console.error('Failed to start downloading battery log in the backend.'); console.error(
'Failed to start downloading battery log in the backend.'
);
} }
} catch (error) { } catch (error) {
console.error('Error:', error.message); console.error('Error:', error.message);
@ -322,7 +335,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
</Typography> </Typography>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
This action requires the battery service to be stopped for around 10-15 minutes. This action requires the battery service to be stopped for around
10-15 minutes.
</Typography> </Typography>
<div <div
@ -389,7 +403,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
gutterBottom gutterBottom
sx={{ fontWeight: 'bold' }} sx={{ fontWeight: 'bold' }}
> >
The battery log is getting downloaded. It will be saved in the Downloads folder. Please wait... The battery log is getting downloaded. It will be saved in the
Downloads folder. Please wait...
</Typography> </Typography>
<div <div
@ -429,7 +444,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
top: '50%', top: '50%',
left: '50%', left: '50%',
transform: 'translate(-50%, -50%)', transform: 'translate(-50%, -50%)',
width: 420, width: 500,
bgcolor: 'background.paper', bgcolor: 'background.paper',
borderRadius: 4, borderRadius: 4,
boxShadow: 24, boxShadow: 24,
@ -448,7 +463,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
</Typography> </Typography>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
This action requires the battery service to be stopped for around 10-15 minutes. This action requires the battery service to be stopped for around
10-15 minutes.
</Typography> </Typography>
<div <div
@ -518,9 +534,17 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
{errorMessage} {errorMessage}
</Typography> </Typography>
<div style={{ display: 'flex', alignItems: 'center', marginTop: 10 }}> <div
style={{ display: 'flex', alignItems: 'center', marginTop: 10 }}
>
<Button <Button
sx={{ marginTop: 2, textTransform: 'none', bgcolor: '#ffc04d', color: '#111111', '&:hover': { bgcolor: '#f7b34d' } }} sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={ErrorModalHandleOk} onClick={ErrorModalHandleOk}
> >
Ok Ok
@ -581,7 +605,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
marginLeft: '20px', marginLeft: '20px',
backgroundColor: '#ffc04d', backgroundColor: '#ffc04d',
color: '#000000', color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }, '&:hover': { bgcolor: '#f7b34d' }
}} }}
> >
Download Battery Log Download Battery Log

View File

@ -5,14 +5,13 @@ import {
Card, Card,
Container, Container,
Divider, Divider,
FormControlLabel,
Grid, Grid,
IconButton, IconButton,
Modal, Modal,
TextField,
useTheme,
Switch, Switch,
FormControlLabel, TextField,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper useTheme
} from '@mui/material'; } from '@mui/material';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -27,7 +26,9 @@ import Button from '@mui/material/Button';
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; 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 dayjs from 'dayjs'; import dayjs from 'dayjs';
import { UserContext } from 'src/contexts/userContext'; import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import { UserContext } from '../../../contexts/userContext';
interface HistoryProps { interface HistoryProps {
errorLoadingS3Data: boolean; errorLoadingS3Data: boolean;
@ -37,14 +38,13 @@ interface HistoryProps {
function HistoryOfActions(props: HistoryProps) { function HistoryOfActions(props: HistoryProps) {
const theme = useTheme(); const theme = useTheme();
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
const context = useContext(UserContext);
const { currentUser } = context;
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 [actionDate, setActionDate] = useState(dayjs());
const { removeToken } = tokencontext; const { removeToken } = tokencontext;
const [openModalAddAction, setOpenModalAddAction] = useState(false); const [openModalAddAction, setOpenModalAddAction] = useState(false);
const requiredFields = ['description', 'timestamp']; const requiredFields = ['description', 'timestamp'];
const [newAction, setNewAction] = useState<Partial<Action>>({ const [newAction, setNewAction] = useState<Partial<Action>>({
installationId: props.id, installationId: props.id,
@ -53,12 +53,15 @@ function HistoryOfActions(props: HistoryProps) {
}); });
const { testModeMap, setTestMode } = useTestMode(); const { testModeMap, setTestMode } = useTestMode();
const isTestMode = testModeMap[props.id] || false; const isTestMode = testModeMap[props.id] || false;
const context = useContext(UserContext);
const { currentUser, setUser } = context;
const [isRowHovered, setHoveredRow] = useState(-1); const [isRowHovered, setHoveredRow] = useState(-1);
const [selectedAction, setSelectedAction] = useState<number>(-1); const [selectedAction, setSelectedAction] = useState<number>(-1);
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const handleTestModeToggle = () => { const handleTestModeToggle = () => {
setTestMode(props.id, !isTestMode); setTestMode(props.id, !isTestMode);
}; };
const handleDateChange = (newdate) => { const handleDateChange = (newdate) => {
setActionDate(newdate); setActionDate(newdate);
setNewAction({ setNewAction({
@ -76,33 +79,36 @@ function HistoryOfActions(props: HistoryProps) {
}; };
const resetNewAction = () => { const resetNewAction = () => {
console.log("Reset action to default"); console.log('Reset action to default');
setActionDate(dayjs()); setActionDate(dayjs());
setNewAction({ setNewAction({
...newAction, ...newAction,
['description']: '', ['description']: ''
}); });
setEditMode(false); setEditMode(false);
}; };
const handleAddActionButton = () => { const handleAddActionButton = () => {
resetNewAction(); resetNewAction();
//setOpenModalAddAction(!openModalAddAction);
setOpenModalAddAction(true); setOpenModalAddAction(true);
}; };
const SumbitNewAction = () => { const handleEdit = (action) => {
setEditMode(true);
setNewAction(action);
setOpenModalAddAction(true);
};
const SumbitNewAction = async () => {
const endpoint = editMode ? `/UpdateAction` : `/InsertNewAction`; const endpoint = editMode ? `/UpdateAction` : `/InsertNewAction`;
console.log("Add an action", endpoint); //console.log('Add an action', endpoint);
const res=axiosConfig.post(endpoint,newAction).catch((err)=>{ const res = await axiosConfig.post(endpoint, newAction).catch((err) => {
if (err.response) { if (err.response) {
// setError(true);
// setLoading(false);
} }
}); });
if (res) { if (res) {
//setOpenModalAddAction(!openModalAddAction); getHistory();
setOpenModalAddAction(false); setOpenModalAddAction(false);
setEditMode(false); setEditMode(false);
} }
@ -113,19 +119,16 @@ function HistoryOfActions(props: HistoryProps) {
setEditMode(false); setEditMode(false);
}; };
const HandleDelete = (e) => { const HandleDelete = async (action) => {
console.log("Delete this action"); const res = await axiosConfig
const res = axiosConfig.post(`/DeleteAction`, newAction).catch((err) => { .post(`/DeleteAction?actionId=${action.id}`)
.catch((err) => {
if (err.response) { if (err.response) {
// setError(true);
// setLoading(false);
} }
}); });
if (res) { if (res) {
console.log("Delete this action is successful"); getHistory();
setOpenModalAddAction(false);
setEditMode(false);
} }
}; };
@ -138,34 +141,7 @@ function HistoryOfActions(props: HistoryProps) {
return true; return true;
}; };
const handleSelectOneAction = (action) => { const getHistory = () => {
if (selectedAction != action.id) {
setSelectedAction(action.id);
setSelectedAction(-1);
if (action.userName === currentUser.name){
console.log("Select this action");
setActionDate(dayjs(action.timestamp));
setNewAction({ description: action.description, timestamp: action.timestamp });
setEditMode(true);
setOpenModalAddAction(true);
} else {
console.log('The user is not authorized to edit this action.');
}
} else {
setSelectedAction(-1);
}
};
const handleRowMouseEnter = (id) => {
setHoveredRow(id);
};
const handleRowMouseLeave = () => {
setHoveredRow(-1);
};
useEffect(() => {
axiosConfig axiosConfig
.get(`/GetHistoryForInstallation?id=${props.id}`) .get(`/GetHistoryForInstallation?id=${props.id}`)
.then((res: AxiosResponse<Action[]>) => { .then((res: AxiosResponse<Action[]>) => {
@ -177,7 +153,10 @@ function HistoryOfActions(props: HistoryProps) {
navigate(routes.login); navigate(routes.login);
} }
}); });
console.log("Update the tab:", openModalAddAction); };
useEffect(() => {
getHistory();
}, [openModalAddAction]); }, [openModalAddAction]);
return ( return (
@ -206,27 +185,27 @@ function HistoryOfActions(props: HistoryProps) {
}} }}
> >
<div> <div>
{/*<DateTimePicker*/}
{/* label="Select Action Date"*/}
{/* name="timestamp"*/}
{/* value={actionDate}*/}
{/* onChange={(newDate) => handleDateChange(newDate)}*/}
{/* sx={{*/}
{/* width: 450,*/}
{/* marginTop: 2*/}
{/* }}*/}
{/*/>*/}
<DateTimePicker <DateTimePicker
label="Select Action Date" label="Select Action Date"
value={actionDate} name="timestamp"
onChange={handleDateChange} value={editMode ? dayjs(newAction.timestamp) : actionDate}
renderInput={(params) => ( onChange={(newDate) => handleDateChange(newDate)}
<TextField sx={{
{...params} width: 450,
sx={{ width: 450, marginTop: 2 }} marginTop: 2
/> }}
)}
/> />
{/*<DateTimePicker*/}
{/* label="Select Action Date"*/}
{/* value={actionDate}*/}
{/* onChange={handleDateChange}*/}
{/* renderInput={(params) => (*/}
{/* <TextField*/}
{/* {...params}*/}
{/* sx={{ width: 450, marginTop: 2 }}*/}
{/* />*/}
{/* )}*/}
{/*/>*/}
<TextField <TextField
label="Description" label="Description"
@ -252,7 +231,12 @@ function HistoryOfActions(props: HistoryProps) {
}} }}
> >
<FormControlLabel <FormControlLabel
control={<Switch checked={isTestMode} onChange={handleTestModeToggle} />} control={
<Switch
checked={isTestMode}
onChange={handleTestModeToggle}
/>
}
label="Test Mode" label="Test Mode"
/> />
@ -282,28 +266,12 @@ function HistoryOfActions(props: HistoryProps) {
> >
Cancel Cancel
</Button> </Button>
{editMode && (
<Button
sx={{
marginLeft: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={HandleDelete}
>
Delete
</Button>
)}
</div> </div>
</Box> </Box>
</LocalizationProvider> </LocalizationProvider>
</Modal> </Modal>
)} )}
{!openModalAddAction && (
<Container maxWidth="xl"> <Container maxWidth="xl">
<Grid container> <Grid container>
<Grid container> <Grid container>
@ -328,254 +296,238 @@ function HistoryOfActions(props: HistoryProps) {
<Grid item xs={12} md={12}> <Grid item xs={12} md={12}>
{history.length > 0 && ( {history.length > 0 && (
// <Card sx={{ marginTop: '10px' }}>
// <Divider />
// <div>
// <div
// style={{
// height: '40px',
// marginBottom: '10px',
// display: 'flex',
// alignItems: 'center'
// }}
// >
// <div
// style={{
// flex: 2,
// 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
// 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" />
// </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: 6,
// marginTop: '15px',
// display: 'flex',
// alignItems: 'center',
// justifyContent: 'center'
// }}
// >
// <Typography
// variant="body1"
// color="dimgrey"
// fontWeight="bold"
// fontSize="1rem"
// gutterBottom
// noWrap
// >
// <FormattedMessage
// id="description"
// defaultMessage="Description"
// />
// </Typography>
// </div>
// </div>
// <Divider />
// <div style={{ maxHeight: '400px', overflowY: 'auto' }}>
// {history.map((action, index) => {
// // Parse the timestamp string to a Date object
// const date = new Date(action.timestamp);
//
// // Extract the date part (e.g., "2023-05-31")
// const datePart = date.toLocaleDateString();
//
// // Extract the time part (e.g., "12:34:56")
// const timePart = date.toLocaleTimeString();
//
// return (
// <React.Fragment key={index}>
// <Divider />
// <div
// style={{
// minHeight: '40px',
// marginBottom: '10px',
// display: 'flex',
// alignItems: 'center'
// }}
// >
// <div
// style={{
// flex: 2,
// 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={{
// whiteSpace: 'normal',
// wordBreak: 'break-word'
// }}
// >
// {action.description}
// </Typography>
// </div>
// </div>
// </React.Fragment>
// );
// })}
// </div>
// </div>
// </Card>
<Card sx={{ marginTop: '10px' }}> <Card sx={{ marginTop: '10px' }}>
<Divider /> <Divider />
<TableContainer component={Paper}> <div>
<Table> <div
<TableHead> style={{
<TableRow> height: '40px',
<TableCell>User</TableCell> marginBottom: '10px',
<TableCell>Date</TableCell> display: 'flex',
<TableCell>Time</TableCell> alignItems: 'center'
<TableCell>Description</TableCell> }}
</TableRow> >
</TableHead> <div
<TableBody> style={{
{history.map((action, index) => { flex: 2,
const isActionSelected = marginTop: '15px',
action.id === selectedAction; display: 'flex',
const date = new Date(action.timestamp); alignItems: 'center',
const datePart = date.toLocaleDateString(); justifyContent: 'center'
const timePart = date.toLocaleTimeString(); }}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage id="user" defaultMessage="User" />
</Typography>
</div>
const rowStyles = isRowHovered === action.id <div
? { cursor: 'pointer', backgroundColor: 'lightgray' } 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" />
</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: 6,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="body1"
color="dimgrey"
fontWeight="bold"
fontSize="1rem"
gutterBottom
noWrap
>
<FormattedMessage
id="description"
defaultMessage="Description"
/>
</Typography>
</div>
</div>
<Divider />
<div style={{ maxHeight: '600px', overflowY: 'auto' }}>
{history.map((action, index) => {
// Parse the timestamp string to a Date object
const date = new Date(action.timestamp);
// Extract the date part (e.g., "2023-05-31")
const datePart = date.toLocaleDateString();
// Extract the time part (e.g., "12:34:56")
const timePart = date.toLocaleTimeString();
const iconStyle =
action.userName === currentUser.name
? {}
: { color: 'rgba(0, 0, 0, 0.26)' };
return ( return (
<TableRow <React.Fragment key={index}>
hover <Divider />
key={action.id} <div
selected={isActionSelected} style={{
style={rowStyles} minHeight: '40px',
onClick={() => handleSelectOneAction(action)} marginBottom: '10px',
onMouseEnter={() => handleRowMouseEnter(action.id)} display: 'flex',
onMouseLeave={handleRowMouseLeave} alignItems: 'center'
}}
> >
<TableCell>{action.userName}</TableCell> <div
<TableCell>{datePart}</TableCell> style={{
<TableCell>{timePart}</TableCell> flex: 2,
<TableCell>{action.description}</TableCell> marginTop: '15px',
</TableRow> 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={{
whiteSpace: 'normal',
wordBreak: 'break-word'
}}
>
{action.description}
</Typography>
</div>
<div
style={{
flex: 1,
marginTop: '15px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<IconButton
style={iconStyle}
onClick={() => handleEdit(action)}
disabled={action.userName != currentUser.name}
>
<EditIcon />
</IconButton>
<IconButton
style={iconStyle}
onClick={() => HandleDelete(action)}
disabled={action.userName != currentUser.name}
>
<DeleteIcon />
</IconButton>
</div>
</div>
</React.Fragment>
); );
})} })}
</TableBody> </div>
</Table> </div>
</TableContainer>
</Card> </Card>
)} )}
@ -625,7 +577,6 @@ function HistoryOfActions(props: HistoryProps) {
)} )}
</Grid> </Grid>
</Container> </Container>
)}
</> </>
); );
} }

View File

@ -1,5 +1,11 @@
import React, { useContext, useEffect, useRef, useState } from 'react'; import React, { useContext, useEffect, useRef, useState } from 'react';
import { Card, CircularProgress, Grid, Typography } from '@mui/material'; import {
Card,
CircularProgress,
Container,
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 AccessContextProvider from 'src/contexts/AccessContextProvider'; import AccessContextProvider from 'src/contexts/AccessContextProvider';
@ -45,6 +51,7 @@ function Installation(props: singleInstallationProps) {
const [values, setValues] = useState<TopologyValues | null>(null); const [values, setValues] = useState<TopologyValues | null>(null);
const status = getStatus(props.current_installation.id); const status = getStatus(props.current_installation.id);
const [connected, setConnected] = useState(true); const [connected, setConnected] = useState(true);
const [loading, setLoading] = useState(true);
const { testModeMap } = useTestMode(); const { testModeMap } = useTestMode();
if (props.current_installation == undefined) { if (props.current_installation == undefined) {
@ -94,9 +101,11 @@ function Installation(props: singleInstallationProps) {
if (i <= 0) { if (i <= 0) {
setConnected(false); setConnected(false);
setLoading(false);
return false; return false;
} }
setConnected(true); setConnected(true);
setLoading(false);
const timestamp = Object.keys(res)[Object.keys(res).length - 1]; const timestamp = Object.keys(res)[Object.keys(res).length - 1];
@ -133,9 +142,11 @@ function Installation(props: singleInstallationProps) {
if (i <= 0) { if (i <= 0) {
setConnected(false); setConnected(false);
setLoading(false);
return false; return false;
} }
setConnected(true); setConnected(true);
setLoading(false);
console.log('NUMBER OF FILES=' + Object.keys(res).length); console.log('NUMBER OF FILES=' + Object.keys(res).length);
while (continueFetching.current) { while (continueFetching.current) {
@ -259,6 +270,7 @@ function Installation(props: singleInstallationProps) {
{props.current_installation.name} {props.current_installation.name}
</Typography> </Typography>
</div> </div>
{currentTab == 'live' && values && ( {currentTab == 'live' && values && (
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<Typography <Typography
@ -359,13 +371,35 @@ function Installation(props: singleInstallationProps) {
borderRadius: '50%', borderRadius: '50%',
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
marginLeft: status === -1 || status === -2 ? '-23px' : '2px', marginLeft: status === -1 || status === -2 ? '-23px' : '2px'
}} }}
/> />
)} )}
</div> </div>
</div> </div>
{loading && currentTab != 'information' && currentTab != 'history' && (
<Container
maxWidth="xl"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '70vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography
variant="body2"
style={{ color: 'black', fontWeight: 'bold' }}
mt={2}
>
Connecting to the device...
</Typography>
</Container>
)}
<Card variant="outlined"> <Card variant="outlined">
<Grid <Grid
container container

View File

@ -1,5 +1,11 @@
import React, { useContext, useEffect, useRef, useState } from 'react'; import React, { useContext, useEffect, useRef, useState } from 'react';
import { Card, CircularProgress, Grid, Typography } from '@mui/material'; import {
Card,
CircularProgress,
Container,
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';
@ -39,6 +45,7 @@ function Installation(props: singleInstallationProps) {
setFailedToCommunicateWithInstallation setFailedToCommunicateWithInstallation
] = useState(0); ] = useState(0);
const [connected, setConnected] = useState(true); const [connected, setConnected] = useState(true);
const [loading, setLoading] = useState(true);
if (props.current_installation == undefined) { if (props.current_installation == undefined) {
return null; return null;
@ -90,9 +97,11 @@ function Installation(props: singleInstallationProps) {
if (i <= 0) { if (i <= 0) {
setConnected(false); setConnected(false);
setLoading(false);
return false; return false;
} }
setConnected(true); setConnected(true);
setLoading(false);
console.log('NUMBER OF FILES=' + Object.keys(res).length); console.log('NUMBER OF FILES=' + Object.keys(res).length);
while (continueFetching.current) { while (continueFetching.current) {
@ -267,6 +276,27 @@ function Installation(props: singleInstallationProps) {
/> />
</div> </div>
</div> </div>
{loading && currentTab != 'information' && currentTab != 'overview' && (
<Container
maxWidth="xl"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '70vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography
variant="body2"
style={{ color: 'black', fontWeight: 'bold' }}
mt={2}
>
Connecting to the device...
</Typography>
</Container>
)}
<Card variant="outlined"> <Card variant="outlined">
<Grid <Grid

View File

@ -92,10 +92,10 @@ function SalidomoInstallationTabs() {
/> />
) )
}, },
// { {
// value: 'overview', value: 'overview',
// label: <FormattedMessage id="overview" defaultMessage="Overview" /> label: <FormattedMessage id="overview" defaultMessage="Overview" />
// }, },
{ {
value: 'log', value: 'log',
label: <FormattedMessage id="log" defaultMessage="Log" /> label: <FormattedMessage id="log" defaultMessage="Log" />