diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 05fd294e5..daf0a3ade 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -27,15 +27,23 @@ function Logs() { // log data handling + const [logRange, setLogRange] = useState({ start: 0, end: 0 }); const [logs, setLogs] = useState([]); useEffect(() => { - axios.get(`logs/${logService}`).then((resp) => { - if (resp.status == 200) { - const data = resp.data as LogData; - setLogs(data.lines); - } - }); + axios + .get(`logs/${logService}?start=-100`) + .then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + setLogRange({ + start: Math.max(0, data.totalLines - 100), + end: data.totalLines, + }); + setLogs(data.lines); + } + }) + .catch(() => {}); }, [logService]); useEffect(() => { @@ -44,20 +52,30 @@ function Logs() { } const id = setTimeout(() => { - axios.get(`logs/${logService}?start=${logs.length}`).then((resp) => { - if (resp.status == 200) { - const data = resp.data as LogData; - setLogs([...logs, ...data.lines]); - } - }); - }, 1000); + axios + .get(`logs/${logService}?start=${logRange.end}`) + .then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + + if (data.lines.length > 0) { + setLogRange({ + start: logRange.start, + end: data.totalLines, + }); + setLogs([...logs, ...data.lines]); + } + } + }) + .catch(() => {}); + }, 5000); return () => { if (id) { clearTimeout(id); } }; - }, [logs, logService]); + }, [logs, logService, logRange]); // convert to log data @@ -161,23 +179,81 @@ function Logs() { // scroll to bottom + const [atBottom, setAtBottom] = useState(false); + const contentRef = useRef(null); const [endVisible, setEndVisible] = useState(true); - const observer = useRef(null); + const endObserver = useRef(null); const endLogRef = useCallback( (node: HTMLElement | null) => { - if (observer.current) observer.current.disconnect(); + if (endObserver.current) endObserver.current.disconnect(); try { - observer.current = new IntersectionObserver((entries) => { + endObserver.current = new IntersectionObserver((entries) => { setEndVisible(entries[0].isIntersecting); }); - if (node) observer.current.observe(node); + if (node) endObserver.current.observe(node); } catch (e) { // no op } }, [setEndVisible], ); + const startObserver = useRef(null); + const startLogRef = useCallback( + (node: HTMLElement | null) => { + if (startObserver.current) startObserver.current.disconnect(); + + if (logs.length == 0 || !atBottom) { + return; + } + + try { + startObserver.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + const start = Math.max(0, logRange.start - 100); + + axios + .get(`logs/${logService}?start=${start}&end=${logRange.start}`) + .then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + + if (data.lines.length > 0) { + setLogRange({ + start: start, + end: logRange.end, + }); + setLogs([...data.lines, ...logs]); + } + } + }) + .catch(() => {}); + } + }); + if (node) startObserver.current.observe(node); + } catch (e) { + // no op + } + }, + [logRange], + ); + + useEffect(() => { + if (logLines.length == 0) { + setAtBottom(false); + return; + } + + if (!contentRef.current) { + return; + } + + contentRef.current?.scrollTo({ + top: contentRef.current?.scrollHeight, + behavior: "instant", + }); + setTimeout(() => setAtBottom(true), 300); + }, [logLines, logService]); return (
@@ -187,9 +263,12 @@ function Logs() { type="single" size="sm" value={logService} - onValueChange={(value: LogType) => - value ? setLogService(value) : null - } // don't allow the severity to be unselected + onValueChange={(value: LogType) => { + if (value) { + setLogs([]); + setLogService(value); + } + }} // don't allow the severity to be unselected > {Object.values(logTypes).map((item) => (
+ {logLines.length > 0 && logRange.start > 0 &&
} {logLines.map((log, idx) => ( - + ))} -
+ {logLines.length > 0 &&
}
); } type LogLineDataProps = { + className: string; line: LogLine; offset: number; }; -function LogLineData({ line, offset }: LogLineDataProps) { +function LogLineData({ className, line, offset }: LogLineDataProps) { // long log message const contentRef = useRef(null); @@ -291,7 +377,7 @@ function LogLineData({ line, offset }: LogLineDataProps) { return (