CEC-3577: fetch T.Rex log from the cloud (#283)
* CEC-3577: fetch T.Rex log from the cloud * tabs? * CSS * smells * fix smells and warnings * suggestions
This commit is contained in:
@@ -9851,7 +9851,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
ECUs
|
||||
T.Rex logs
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
@@ -9869,7 +9869,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Remote Commands
|
||||
ECUs
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
@@ -9883,6 +9883,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Remote Commands
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-controls="tabpanel-8"
|
||||
aria-selected="false"
|
||||
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||
id="tab-8"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
@@ -10124,6 +10142,12 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
id="tabpanel-7"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-8"
|
||||
hidden=""
|
||||
id="tabpanel-8"
|
||||
role="tabpanel"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
36
src/components/Cars/Status/TRexLogsTab.jsx
Normal file
36
src/components/Cars/Status/TRexLogsTab.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Typography } from "@material-ui/core";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||
import TRexLogsTable from "../../Controls/TRexLogs";
|
||||
import useStyles from "../../useStyles";
|
||||
|
||||
const MainForm = () => {
|
||||
const { vin } = useParams();
|
||||
const classes = useStyles();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
},
|
||||
} = useUserContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||
<Typography variant="h6" className={classes.labelInline}>
|
||||
T.Rex Logs
|
||||
</Typography>
|
||||
<TRexLogsTable vin={vin} token={token} classes={classes} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TRexLogsTab = () => (
|
||||
<VehicleProvider>
|
||||
<MainForm />
|
||||
</VehicleProvider>
|
||||
);
|
||||
|
||||
export default TRexLogsTab;
|
||||
@@ -135,7 +135,7 @@ exports[`CarStatus Render 1`] = `
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
ECUs
|
||||
T.Rex logs
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
@@ -153,7 +153,7 @@ exports[`CarStatus Render 1`] = `
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Remote Commands
|
||||
ECUs
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
@@ -167,6 +167,24 @@ exports[`CarStatus Render 1`] = `
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Remote Commands
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-controls="tabpanel-8"
|
||||
aria-selected="false"
|
||||
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||
id="tab-8"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
@@ -352,6 +370,12 @@ exports[`CarStatus Render 1`] = `
|
||||
id="tabpanel-7"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-8"
|
||||
hidden=""
|
||||
id="tabpanel-8"
|
||||
role="tabpanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ import DigitalTwinTab from "./DigitalTwinTab";
|
||||
import ECUsTab from "./ECUsTab";
|
||||
import FleetsTab from "./FleetsTab";
|
||||
import RemoteCommandsTab from "./RemoteCommandsTab";
|
||||
import TRexLogsTab from "./TRexLogsTab"
|
||||
|
||||
const tabHashes = ["details", "updates", "filters"];
|
||||
|
||||
@@ -42,6 +43,10 @@ const TabViews = [
|
||||
label: "CAN Signals",
|
||||
component: CANSignalsTab,
|
||||
},
|
||||
{
|
||||
label: "T.Rex logs",
|
||||
component: TRexLogsTab,
|
||||
},
|
||||
{
|
||||
label: "ECUs",
|
||||
component: ECUsTab,
|
||||
|
||||
258
src/components/Controls/TRexLogs/index.jsx
Normal file
258
src/components/Controls/TRexLogs/index.jsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import api from "../../../services/vehiclesAPI";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
MuiPickersUtilsProvider,
|
||||
KeyboardDatePicker,
|
||||
} from '@material-ui/pickers';
|
||||
import clsx from "clsx";
|
||||
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { logger } from "../../../services/monitoring";
|
||||
import { TableHead } from "@mui/material";
|
||||
import DateFnsUtils from '@date-io/date-fns';
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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%",
|
||||
},
|
||||
];
|
||||
//read at least 30000 bytes per one API request
|
||||
const ONE_READ_SIZE = 30000
|
||||
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 [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();
|
||||
|
||||
const readBlob = async (offset, count) => {
|
||||
console.log(`reading from offset: ${offset}`)
|
||||
return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token)
|
||||
}
|
||||
const getDesiredSize = () => {
|
||||
return pageSize * pageIndex + pageSize
|
||||
}
|
||||
const getReadPercentage = () => {
|
||||
if (blobSize === 0) {
|
||||
return `No logs for ${fromatDateForRequest(selectedDate)}`
|
||||
}
|
||||
return `Read ${(currentOffset * 100 / blobSize).toFixed(2)}% of logs`
|
||||
}
|
||||
const fetchLogs = async (desiredSize) => {
|
||||
let fetched = []
|
||||
let offset = currentOffset
|
||||
let readSize = ONE_READ_SIZE
|
||||
do {
|
||||
const result = await readBlob(offset, readSize)
|
||||
if (result.error) {
|
||||
fetched.error = result.error
|
||||
break
|
||||
}
|
||||
console.log(`ret.RealOffset ${result.RealOffset}\nret.bytesRead ${result.bytesRead}\ndesired offset ${offset}\ndesired read size ${readSize}\nblobsize: ${result.blobSize}`)
|
||||
setBlobSize(result.blobSize)
|
||||
//if read two times less then neccessary, then adjust read size
|
||||
if (fetched.length < desiredSize / 2) {
|
||||
readSize *= 2
|
||||
console.log(`new read size: ${readSize}`)
|
||||
}
|
||||
offset = result.RealOffset + result.bytesRead
|
||||
fetched = transformLogs(result.data).concat(fetched)
|
||||
if (offset >= result.blobSize) {
|
||||
setMessage(`All log for ${fromatDateForRequest(selectedDate)} fetched`)
|
||||
setAllLogsFetched(true)
|
||||
break
|
||||
}
|
||||
} while (fetched.length <= desiredSize)
|
||||
|
||||
if (fetched.length === 0) {
|
||||
if (logs.length !== 0) {
|
||||
setMessage(`No more T.Rex logs for ${fromatDateForRequest(selectedDate)}`)
|
||||
return
|
||||
}
|
||||
setTotal(0)
|
||||
const msg = `Can not fetch logs for ${fromatDateForRequest(selectedDate)}`
|
||||
setMessage(msg)
|
||||
console.log(`${msg}, Cloud error:\n${fetched.error}`);
|
||||
return
|
||||
}
|
||||
setCurrentOffset(offset)
|
||||
return fetched
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (!vin || !token) return;
|
||||
const desiredSize = getDesiredSize()
|
||||
console.log(`desired size: ${desiredSize}, actual size: ${logs.length}`)
|
||||
if (desiredSize < logs.length || allLogsFetched) {
|
||||
console.log(`enough logs in cache`)
|
||||
return
|
||||
}
|
||||
let fetched = await fetchLogs(desiredSize)
|
||||
if (!fetched || fetched.length === 0) {
|
||||
return
|
||||
}
|
||||
fetched = fetched.concat(logs)
|
||||
setLogs(fetched);
|
||||
setTotal(fetched.length);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
logger.warn(e.stack);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vin, token, pageIndex, pageSize, selectedDate]);
|
||||
|
||||
const handleChangePageSize = (event) => {
|
||||
setPageSize(parseInt(event.target.value, 10));
|
||||
setPageIndex(0);
|
||||
};
|
||||
|
||||
const handleNewDate = (newValue) => {
|
||||
setPageIndex(0);
|
||||
setCurrentOffset(0)
|
||||
setLogs([])
|
||||
setAllLogsFetched(false)
|
||||
setBlobSize(0)
|
||||
setSelectedDate(newValue)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||
<Table
|
||||
style={{ tableLayout: "fixed", minWidth: "1400px" }}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<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="left">
|
||||
{getReadPercentage()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Table>
|
||||
<Table
|
||||
style={{ tableLayout: "fixed", minWidth: "1400px" }}
|
||||
>
|
||||
<TableHead
|
||||
classes={classes}>
|
||||
|
||||
<TableRow>
|
||||
{tableColumns.map((column) => (
|
||||
<TableCell style={{ width: column.width }} align="center" key={column.label || "none"}>{column.label}</TableCell>
|
||||
))} </TableRow>
|
||||
</TableHead >
|
||||
<TableBody>
|
||||
{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">{log.trex_timestamp}</TableCell>
|
||||
<TableCell align="center">{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;
|
||||
Reference in New Issue
Block a user