Merge branch 'release/0.0.3'

This commit is contained in:
jwu-fisker
2023-03-13 17:16:28 -07:00
28 changed files with 1549 additions and 56 deletions

View File

@@ -0,0 +1,329 @@
import React, { useEffect, useState } from "react";
import {
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow
} from "@material-ui/core";
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import FormLabel from '@mui/material/FormLabel';
import LinearProgress from '@mui/material/LinearProgress';
import {
KeyboardDatePicker, MuiPickersUtilsProvider
} from '@material-ui/pickers';
import clsx from "clsx";
import api from "../../../services/vehiclesAPI";
import DateFnsUtils from '@date-io/date-fns';
import { TableHead } from "@mui/material";
import { logger } from "../../../services/monitoring";
import { useStatusContext } from "../../Contexts/StatusContext";
const tableColumns = [
{
id: "level",
label: "Level",
width: "5%",
},
{
id: "trex_timestamp",
label: "T.Rex Timestamp",
width: "10%",
},
{
id: "cloud_timestamp",
label: "Cloud Timestamp",
width: "10%",
},
{
id: "line_number",
label: "Line Number",
width: "5%",
},
{
id: "filename",
label: "Filename",
width: "10%",
},
{
id: "msg",
label: "Message",
width: "60%",
},
];
const logLevelCheckBoxes = [
{
"id": "trace",
"label": "Trace"
},
{
"id": "debug",
"label": "Debug"
},
{
"id": "info",
"label": "Info"
},
{
"id": "warning",
"label": "Warning"
},
{
"id": "error",
"label": "Error"
},
{
"id": "critical",
"label": "Critical"
},
]
const transformLogs = (logs) =>
logs.map((log) => {
const { level, timestamp, received_timestamp, line_number, filename, msg } = log;
//const trex_time = new Date(timestamp)
//const cloud_time = new Date(received_timestamp)
return {
level: level,
trex_timestamp: timestamp,
cloud_timestamp: received_timestamp,
line_number: line_number,
filename: filename,
msg: msg
};
})
.flat()
const fromatDateForRequest = (date) => {
const getYear = date.toLocaleString("default", { year: "numeric" });
const getMonth = date.toLocaleString("default", { month: "2-digit" });
const getDay = date.toLocaleString("default", { day: "2-digit" });
return getYear + "-" + getMonth + "-" + getDay
};
//read at least 50000 bytes per one API request
const ONE_READ_SIZE = 50000
const DEFAULT_PAGE_SIZE = 25
const TRexLogsTable = ({ vin, token, classes }) => {
const [allLogsFetched, setAllLogsFetched] = useState(false)
const [blobSize, setBlobSize] = useState(0)
const [currentOffset, setCurrentOffset] = useState(0)
const [currectLogLevels, setCurrentLogLevels] = useState({
trace: true,
debug: true,
info: true,
warning: true,
error: true,
critical: true
})
const [logs, setLogs] = useState([]);
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [selectedDate, setSelectedDate] = useState(new Date());
const [total, setTotal] = useState(0);
const { setMessage } = useStatusContext();
let controller = new AbortController()
const readBlob = async (offset, count) => {
return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token, controller)
}
const getDesiredSize = () => {
return pageSize * pageIndex + pageSize
}
const getReadPercentage = () => {
return (currentOffset * 100 / blobSize).toFixed(2);
}
const getFilteredLogs = (logs) => {
return logs.filter(log => currectLogLevels[log.level] === true)
}
const fetchAllLogs = async () => {
let fetched = []
let offset = currentOffset
let readSize = ONE_READ_SIZE
for (; ;) {
const result = await readBlob(offset, readSize)
if (result.error) {
fetched.error = result.error
break
}
setBlobSize(result.blobSize)
readSize *= 2
offset = result.RealOffset + result.bytesRead
setCurrentOffset(offset)
fetched = transformLogs(result.data).concat(fetched)
setLogs(fetched)
if (offset >= result.blobSize) {
setMessage(`All log for ${fromatDateForRequest(selectedDate)} fetched`)
setAllLogsFetched(true)
break
}
}
if (fetched.length === 0) {
if (logs.length !== 0) {
setMessage(`No more T.Rex logs for ${fromatDateForRequest(selectedDate)}`)
return
}
setTotal(0)
const msg = `Cannot fetch logs for ${fromatDateForRequest(selectedDate)}`
setMessage(msg)
return
}
setCurrentOffset(offset)
return fetched
}
useEffect(() => {
(async () => {
try {
if (!vin || !token) return;
const desiredSize = getDesiredSize()
if (desiredSize < logs.length || allLogsFetched) {
setTotal(getFilteredLogs(logs).length)
return
}
let fetched = await fetchAllLogs()
if (!fetched || fetched.length === 0) {
return
}
setTotal(getFilteredLogs(fetched).length);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
})();
return () => controller?.abort()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin, token, pageIndex, pageSize, selectedDate, currectLogLevels]);
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setTotal(getFilteredLogs(logs).length)
setPageIndex(0);
};
const handleNewDate = (newValue) => {
setPageIndex(0);
setCurrentOffset(0)
setLogs([])
setAllLogsFetched(false)
setBlobSize(0)
setSelectedDate(newValue)
};
const handleNewFilter = (event) => {
setPageIndex(0)
setCurrentLogLevels({
...currectLogLevels,
[event.target.defaultValue]: event.target.checked,
});
};
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Table
style={{ tableLayout: "fixed", width: "100%" }}
>
<TableRow>
<TableCell align="center">
<FormControl component="fieldset">
<FormLabel component="legend">Log levels</FormLabel>
<FormGroup aria-label="position" row>
{logLevelCheckBoxes.map((box) => (
<FormControlLabel
value={box.id}
control={<Checkbox defaultChecked disabled={!allLogsFetched} />}
label={box.label}
labelPlacement="bottom"
onChange={handleNewFilter}
/>
))}
</FormGroup>
</FormControl>
</TableCell>
<TableCell align="center">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
variant="inline"
format="yyyy/MM/dd"
margin="normal"
id="date-picker-inline"
label="Choose date"
value={selectedDate}
onChange={handleNewDate}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</MuiPickersUtilsProvider>
</TableCell>
<TableCell align="center">
{
blobSize === 0 ? `No logs for ${fromatDateForRequest(selectedDate)}` :
`Read ${getReadPercentage()}% of logs`
}
{
<LinearProgress
variant="determinate"
align="center"
value={getReadPercentage()} />
}
</TableCell>
</TableRow>
</Table>
<Table
style={{ tableLayout: "fixed", width: "100%" }}
>
<TableHead classes={classes}>
<TableRow>
{tableColumns.map((column) => (
<TableCell style={{ width: column.width }} align="center" key={column.label || "none"} className={classes.tableHeader} >{column.label}</TableCell>
))}
</TableRow>
</TableHead >
<TableBody>
{getFilteredLogs(logs).slice(-getDesiredSize(), (pageIndex === 0 ? undefined : -(pageSize * pageIndex))).map((log, i) => (
<TableRow key={log.trex_timestamp + log.cloud_timestamp} >
<TableCell align="center">{log.level}</TableCell>
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.trex_timestamp}</TableCell>
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.cloud_timestamp}</TableCell>
<TableCell align="center">{log.line_number}</TableCell>
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.filename}</TableCell>
<TableCell align="left" style={{ wordBreak: "break-all" }}>{log.msg}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[25, 100, 1000, 10000]}
colSpan={6}
count={total}
rowsPerPage={pageSize}
page={!pageIndex || pageIndex <= 0 ? 0 : pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={(_, newValue) => setPageIndex(newValue)}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
</div >
);
};
export default TRexLogsTable;