cdslogviewer/frontend/app/logreader/LogContent.tsx (133 lines of code) (raw):
import React, { useState, useEffect, useRef } from "react";
import {
FormControlLabel,
makeStyles,
Switch,
Typography,
} from "@material-ui/core";
import { loadMoreLogLines } from "../data-loading";
import { parseISO, formatDistanceToNow, isFuture } from "date-fns";
import {
SystemNotification,
SystemNotifcationKind,
} from "@guardian/pluto-headers";
import { formatError } from "../common/format_error";
interface LogContentProps {
routeName: string;
logName: string;
refreshTimeout?: number; //in ms
}
const useStyles = makeStyles((theme) => ({
logContainer: {
backgroundColor: theme.palette.logviewer.background,
color: theme.palette.logviewer.main,
listStyle: "none",
height: "90%",
marginRight: "1em",
overflow: "auto",
},
logBlock: {
fontFamily: ["Courier", "Courier New", "serif"].join(","),
},
}));
const LogContent: React.FC<LogContentProps> = (props) => {
const [loadedLineCount, setLoadedLineCount] = useState(0);
const [logLines, setLogLines] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [lastModified, setLastModified] = useState<Date | undefined>(undefined);
const [scrollToBottom, setScrollToBottom] = useState(true);
const classes = useStyles();
let logEnd: HTMLDivElement | null = null;
const loadedLineCountRef = useRef<number>();
loadedLineCountRef.current = loadedLineCount;
useEffect(() => {
const timerId = window.setInterval(
regularUpdate,
props.refreshTimeout ?? 3000
);
loadedLineCountRef.current = 0;
regularUpdate();
return () => {
setLogLines([]);
setLoadedLineCount(0);
window.clearInterval(timerId);
};
}, [props.routeName, props.logName]);
const regularUpdate = () => {
loadMoreLogLines(
props.routeName,
props.logName,
loadedLineCountRef.current ?? 0
)
.then((results) => {
setIsLoading(false);
if (results.lastModified) {
try {
setLastModified(parseISO(results.lastModified));
} catch (e) {
console.error(
"Could not parse last modified string ",
results.lastModified,
": ",
e
);
}
}
if (results.count > 0) {
console.log(`Received ${results.count} more log lines`);
setLoadedLineCount((prevValue) => prevValue + results.count);
setLogLines((prevValue) => prevValue.concat(...results.content));
} else {
console.log("Received no more extra content");
}
})
.catch((err) => {
console.error("Could not load in more log lines: ", err);
SystemNotification.open(
SystemNotifcationKind.Error,
`Could not load in more log lines: ${formatError(err, false)}`
);
});
};
/**
* if the user wants us to, scroll to the end of the log whenever the log lines change or when they check 'Keep in view'
*/
useEffect(() => {
if (scrollToBottom && logEnd) {
logEnd.scrollIntoView({ behavior: "smooth" });
}
}, [logLines, scrollToBottom]);
const lastModifiedString = () => {
if (lastModified) {
let timestr;
if (isFuture(lastModified)) {
timestr = `is in ${formatDistanceToNow(lastModified)}`;
} else {
timestr = `was ${formatDistanceToNow(lastModified)} ago`;
}
return `Last update ${timestr}`;
} else {
return "";
}
};
return (
<>
<FormControlLabel
label="Keep end of log in view"
control={
<Switch
checked={scrollToBottom}
onChange={(evt) => setScrollToBottom(evt.target.checked)}
/>
}
/>
<Typography>
Auto-refresh is enabled for the log. {lastModifiedString()}
</Typography>
<div className={classes.logContainer}>
<pre className={classes.logBlock}>{logLines.join("\n")}</pre>
<div
style={{ float: "left", clear: "both" }}
ref={(el) => {
logEnd = el;
}}
/>
</div>
</>
);
};
export default LogContent;