Load incrementally

This commit is contained in:
Nicolas Mowen 2024-04-03 07:39:25 -06:00
parent 4a1758b0ae
commit 9a8f93d204
2 changed files with 112 additions and 26 deletions

View File

@ -27,15 +27,23 @@ function Logs() {
// log data handling // log data handling
const [logRange, setLogRange] = useState<LogRange>({ start: 0, end: 0 });
const [logs, setLogs] = useState<string[]>([]); const [logs, setLogs] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
axios.get(`logs/${logService}`).then((resp) => { axios
if (resp.status == 200) { .get(`logs/${logService}?start=-100`)
const data = resp.data as LogData; .then((resp) => {
setLogs(data.lines); 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]); }, [logService]);
useEffect(() => { useEffect(() => {
@ -44,20 +52,30 @@ function Logs() {
} }
const id = setTimeout(() => { const id = setTimeout(() => {
axios.get(`logs/${logService}?start=${logs.length}`).then((resp) => { axios
if (resp.status == 200) { .get(`logs/${logService}?start=${logRange.end}`)
const data = resp.data as LogData; .then((resp) => {
setLogs([...logs, ...data.lines]); if (resp.status == 200) {
} const data = resp.data as LogData;
});
}, 1000); if (data.lines.length > 0) {
setLogRange({
start: logRange.start,
end: data.totalLines,
});
setLogs([...logs, ...data.lines]);
}
}
})
.catch(() => {});
}, 5000);
return () => { return () => {
if (id) { if (id) {
clearTimeout(id); clearTimeout(id);
} }
}; };
}, [logs, logService]); }, [logs, logService, logRange]);
// convert to log data // convert to log data
@ -161,23 +179,81 @@ function Logs() {
// scroll to bottom // scroll to bottom
const [atBottom, setAtBottom] = useState(false);
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
const [endVisible, setEndVisible] = useState(true); const [endVisible, setEndVisible] = useState(true);
const observer = useRef<IntersectionObserver | null>(null); const endObserver = useRef<IntersectionObserver | null>(null);
const endLogRef = useCallback( const endLogRef = useCallback(
(node: HTMLElement | null) => { (node: HTMLElement | null) => {
if (observer.current) observer.current.disconnect(); if (endObserver.current) endObserver.current.disconnect();
try { try {
observer.current = new IntersectionObserver((entries) => { endObserver.current = new IntersectionObserver((entries) => {
setEndVisible(entries[0].isIntersecting); setEndVisible(entries[0].isIntersecting);
}); });
if (node) observer.current.observe(node); if (node) endObserver.current.observe(node);
} catch (e) { } catch (e) {
// no op // no op
} }
}, },
[setEndVisible], [setEndVisible],
); );
const startObserver = useRef<IntersectionObserver | null>(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 ( return (
<div className="size-full p-2 flex flex-col"> <div className="size-full p-2 flex flex-col">
@ -187,9 +263,12 @@ function Logs() {
type="single" type="single"
size="sm" size="sm"
value={logService} value={logService}
onValueChange={(value: LogType) => onValueChange={(value: LogType) => {
value ? setLogService(value) : null if (value) {
} // don't allow the severity to be unselected setLogs([]);
setLogService(value);
}
}} // don't allow the severity to be unselected
> >
{Object.values(logTypes).map((item) => ( {Object.values(logTypes).map((item) => (
<ToggleGroupItem <ToggleGroupItem
@ -247,20 +326,27 @@ function Logs() {
Message Message
</div> </div>
</div> </div>
{logLines.length > 0 && logRange.start > 0 && <div ref={startLogRef} />}
{logLines.map((log, idx) => ( {logLines.map((log, idx) => (
<LogLineData key={`${idx}-${log.content}`} offset={idx} line={log} /> <LogLineData
key={`${idx}-${log.content}`}
className={atBottom ? "" : "invisible"}
offset={idx}
line={log}
/>
))} ))}
<div id="page-bottom" ref={endLogRef} /> {logLines.length > 0 && <div id="page-bottom" ref={endLogRef} />}
</div> </div>
</div> </div>
); );
} }
type LogLineDataProps = { type LogLineDataProps = {
className: string;
line: LogLine; line: LogLine;
offset: number; offset: number;
}; };
function LogLineData({ line, offset }: LogLineDataProps) { function LogLineData({ className, line, offset }: LogLineDataProps) {
// long log message // long log message
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
@ -291,7 +377,7 @@ function LogLineData({ line, offset }: LogLineDataProps) {
return ( return (
<div <div
className={`py-2 grid grid-cols-5 sm:grid-cols-8 md:grid-cols-12 gap-2 ${offset % 2 == 0 ? "bg-secondary" : "bg-secondary/80"} border-t border-x`} className={`py-2 grid grid-cols-5 sm:grid-cols-8 md:grid-cols-12 gap-2 ${offset % 2 == 0 ? "bg-secondary" : "bg-secondary/80"} border-t border-x ${className}`}
> >
<div <div
className={`h-full p-1 flex items-center gap-2 capitalize ${severityClassName}`} className={`h-full p-1 flex items-center gap-2 capitalize ${severityClassName}`}

View File

@ -1,5 +1,5 @@
export type LogData = { export type LogData = {
lineCount: number; totalLines: number;
lines: string[]; lines: string[];
}; };