CEC-3577: trex logs (#285)
* CEC-3577: trex logs add filtering add progress bar for log fetching always fetch all the logs request canceling * don't hide progress
This commit is contained in:
@@ -8,6 +8,13 @@ import {
|
|||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow
|
TableRow
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
import LinearProgress from '@mui/material/LinearProgress';
|
||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import FormGroup from '@mui/material/FormGroup';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
import FormLabel from '@mui/material/FormLabel';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
KeyboardDatePicker, MuiPickersUtilsProvider
|
KeyboardDatePicker, MuiPickersUtilsProvider
|
||||||
} from '@material-ui/pickers';
|
} from '@material-ui/pickers';
|
||||||
@@ -52,6 +59,33 @@ const tableColumns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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) =>
|
const transformLogs = (logs) =>
|
||||||
logs.map((log) => {
|
logs.map((log) => {
|
||||||
const { level, timestamp, received_timestamp, line_number, filename, msg } = log;
|
const { level, timestamp, received_timestamp, line_number, filename, msg } = log;
|
||||||
@@ -75,14 +109,22 @@ const fromatDateForRequest = (date) => {
|
|||||||
return getYear + "-" + getMonth + "-" + getDay
|
return getYear + "-" + getMonth + "-" + getDay
|
||||||
};
|
};
|
||||||
|
|
||||||
//read at least 30000 bytes per one API request
|
//read at least 50000 bytes per one API request
|
||||||
const ONE_READ_SIZE = 30000
|
const ONE_READ_SIZE = 50000
|
||||||
const DEFAULT_PAGE_SIZE = 25
|
const DEFAULT_PAGE_SIZE = 25
|
||||||
|
|
||||||
const TRexLogsTable = ({ vin, token, classes }) => {
|
const TRexLogsTable = ({ vin, token, classes }) => {
|
||||||
const [allLogsFetched, setAllLogsFetched] = useState(false)
|
const [allLogsFetched, setAllLogsFetched] = useState(false)
|
||||||
const [blobSize, setBlobSize] = useState(0)
|
const [blobSize, setBlobSize] = useState(0)
|
||||||
const [currentOffset, setCurrentOffset] = 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 [logs, setLogs] = useState([]);
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
@@ -90,24 +132,25 @@ const TRexLogsTable = ({ vin, token, classes }) => {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
let controller = new AbortController()
|
||||||
const readBlob = async (offset, count) => {
|
const readBlob = async (offset, count) => {
|
||||||
console.log(`reading from offset: ${offset}`)
|
console.log(`reading from offset: ${offset}`)
|
||||||
return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token)
|
return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token, controller)
|
||||||
}
|
}
|
||||||
const getDesiredSize = () => {
|
const getDesiredSize = () => {
|
||||||
return pageSize * pageIndex + pageSize
|
return pageSize * pageIndex + pageSize
|
||||||
}
|
}
|
||||||
const getReadPercentage = () => {
|
const getReadPercentage = () => {
|
||||||
if (blobSize === 0) {
|
return (currentOffset * 100 / blobSize).toFixed(2);
|
||||||
return `No logs for ${fromatDateForRequest(selectedDate)}`
|
|
||||||
}
|
}
|
||||||
return `Read ${(currentOffset * 100 / blobSize).toFixed(2)}% of logs`
|
const getFilteredLogs = (logs) => {
|
||||||
|
return logs.filter(log => currectLogLevels[log.level] === true)
|
||||||
}
|
}
|
||||||
const fetchLogs = async (desiredSize) => {
|
const fetchAllLogs = async () => {
|
||||||
let fetched = []
|
let fetched = []
|
||||||
let offset = currentOffset
|
let offset = currentOffset
|
||||||
let readSize = ONE_READ_SIZE
|
let readSize = ONE_READ_SIZE
|
||||||
do {
|
for (; ;) {
|
||||||
const result = await readBlob(offset, readSize)
|
const result = await readBlob(offset, readSize)
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
fetched.error = result.error
|
fetched.error = result.error
|
||||||
@@ -115,19 +158,18 @@ const TRexLogsTable = ({ vin, token, classes }) => {
|
|||||||
}
|
}
|
||||||
console.log(`ret.RealOffset ${result.RealOffset}\nret.bytesRead ${result.bytesRead}\ndesired offset ${offset}\ndesired read size ${readSize}\nblobsize: ${result.blobSize}`)
|
console.log(`ret.RealOffset ${result.RealOffset}\nret.bytesRead ${result.bytesRead}\ndesired offset ${offset}\ndesired read size ${readSize}\nblobsize: ${result.blobSize}`)
|
||||||
setBlobSize(result.blobSize)
|
setBlobSize(result.blobSize)
|
||||||
//if read two times less then neccessary, then adjust read size
|
|
||||||
if (fetched.length < desiredSize / 2) {
|
|
||||||
readSize *= 2
|
readSize *= 2
|
||||||
console.log(`new read size: ${readSize}`)
|
console.log(`new read size: ${readSize}`)
|
||||||
}
|
|
||||||
offset = result.RealOffset + result.bytesRead
|
offset = result.RealOffset + result.bytesRead
|
||||||
|
setCurrentOffset(offset)
|
||||||
fetched = transformLogs(result.data).concat(fetched)
|
fetched = transformLogs(result.data).concat(fetched)
|
||||||
|
setLogs(fetched)
|
||||||
if (offset >= result.blobSize) {
|
if (offset >= result.blobSize) {
|
||||||
setMessage(`All log for ${fromatDateForRequest(selectedDate)} fetched`)
|
setMessage(`All log for ${fromatDateForRequest(selectedDate)} fetched`)
|
||||||
setAllLogsFetched(true)
|
setAllLogsFetched(true)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} while (fetched.length <= desiredSize)
|
}
|
||||||
|
|
||||||
if (fetched.length === 0) {
|
if (fetched.length === 0) {
|
||||||
if (logs.length !== 0) {
|
if (logs.length !== 0) {
|
||||||
@@ -152,25 +194,26 @@ const TRexLogsTable = ({ vin, token, classes }) => {
|
|||||||
console.log(`desired size: ${desiredSize}, actual size: ${logs.length}`)
|
console.log(`desired size: ${desiredSize}, actual size: ${logs.length}`)
|
||||||
if (desiredSize < logs.length || allLogsFetched) {
|
if (desiredSize < logs.length || allLogsFetched) {
|
||||||
console.log(`enough logs in cache`)
|
console.log(`enough logs in cache`)
|
||||||
|
setTotal(getFilteredLogs(logs).length)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let fetched = await fetchLogs(desiredSize)
|
let fetched = await fetchAllLogs()
|
||||||
if (!fetched || fetched.length === 0) {
|
if (!fetched || fetched.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fetched = fetched.concat(logs)
|
setTotal(getFilteredLogs(fetched).length);
|
||||||
setLogs(fetched);
|
|
||||||
setTotal(fetched.length);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
logger.warn(e.stack);
|
logger.warn(e.stack);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
return () => controller?.abort()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [vin, token, pageIndex, pageSize, selectedDate]);
|
}, [vin, token, pageIndex, pageSize, selectedDate, currectLogLevels]);
|
||||||
|
|
||||||
const handleChangePageSize = (event) => {
|
const handleChangePageSize = (event) => {
|
||||||
setPageSize(parseInt(event.target.value, 10));
|
setPageSize(parseInt(event.target.value, 10));
|
||||||
|
setTotal(getFilteredLogs(logs).length)
|
||||||
setPageIndex(0);
|
setPageIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,12 +226,37 @@ const TRexLogsTable = ({ vin, token, classes }) => {
|
|||||||
setSelectedDate(newValue)
|
setSelectedDate(newValue)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNewFilter = (event) => {
|
||||||
|
setPageIndex(0)
|
||||||
|
console.log(event)
|
||||||
|
setCurrentLogLevels({
|
||||||
|
...currectLogLevels,
|
||||||
|
[event.target.defaultValue]: event.target.checked,
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
<Table
|
<Table
|
||||||
style={{ tableLayout: "fixed", minWidth: "1400px" }}
|
style={{ tableLayout: "fixed", width: "100%" }}
|
||||||
>
|
>
|
||||||
<TableRow>
|
<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">
|
<TableCell align="center">
|
||||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||||
<KeyboardDatePicker
|
<KeyboardDatePicker
|
||||||
@@ -207,12 +275,22 @@ const TRexLogsTable = ({ vin, token, classes }) => {
|
|||||||
</MuiPickersUtilsProvider>
|
</MuiPickersUtilsProvider>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{getReadPercentage()}
|
{
|
||||||
|
blobSize === 0 ? `No logs for ${fromatDateForRequest(selectedDate)}` :
|
||||||
|
`Read ${getReadPercentage()}% of logs`
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
align="center"
|
||||||
|
value={getReadPercentage()} />
|
||||||
|
}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
style={{ tableLayout: "fixed", minWidth: "1400px" }}
|
style={{ tableLayout: "fixed", width: "100%" }}
|
||||||
>
|
>
|
||||||
<TableHead classes={classes}>
|
<TableHead classes={classes}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -222,11 +300,11 @@ const TRexLogsTable = ({ vin, token, classes }) => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead >
|
</TableHead >
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{logs.slice(-getDesiredSize(), (pageIndex === 0 ? undefined : -(pageSize * pageIndex))).map((log, i) => (
|
{getFilteredLogs(logs).slice(-getDesiredSize(), (pageIndex === 0 ? undefined : -(pageSize * pageIndex))).map((log, i) => (
|
||||||
<TableRow key={log.trex_timestamp + log.cloud_timestamp} >
|
<TableRow key={log.trex_timestamp + log.cloud_timestamp} >
|
||||||
<TableCell align="center">{log.level}</TableCell>
|
<TableCell align="center">{log.level}</TableCell>
|
||||||
<TableCell align="center">{log.trex_timestamp}</TableCell>
|
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.trex_timestamp}</TableCell>
|
||||||
<TableCell align="center">{log.cloud_timestamp}</TableCell>
|
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.cloud_timestamp}</TableCell>
|
||||||
<TableCell align="center">{log.line_number}</TableCell>
|
<TableCell align="center">{log.line_number}</TableCell>
|
||||||
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.filename}</TableCell>
|
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.filename}</TableCell>
|
||||||
<TableCell align="left" style={{ wordBreak: "break-all" }}>{log.msg}</TableCell>
|
<TableCell align="left" style={{ wordBreak: "break-all" }}>{log.msg}</TableCell>
|
||||||
|
|||||||
@@ -173,13 +173,14 @@ const vehiclesAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
getTRexLogs: async (vin, date, offset, count, direction, token) =>
|
getTRexLogs: async (vin, date, offset, count, direction, token, controller) =>
|
||||||
fetch(`${API_ENDPOINT}/vehicle/${vin}/trex-logs?date=${date}&offset=${offset}&count=${count}&direction=${direction}`, {
|
fetch(`${API_ENDPOINT}/vehicle/${vin}/trex-logs?date=${date}&offset=${offset}&count=${count}&direction=${direction}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: Object.assign(
|
headers: Object.assign(
|
||||||
{ "Content-Type": "application/json" },
|
{ "Content-Type": "application/json" },
|
||||||
getAuthHeaderOptions(token)
|
getAuthHeaderOptions(token)
|
||||||
),
|
),
|
||||||
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|||||||
Reference in New Issue
Block a user