diff --git a/ui/src/lib/components/AnsiDisplay/component.svelte b/ui/src/lib/components/AnsiDisplay/component.svelte index 32ad2127..54176359 100644 --- a/ui/src/lib/components/AnsiDisplay/component.svelte +++ b/ui/src/lib/components/AnsiDisplay/component.svelte @@ -16,17 +16,24 @@ }, }) + // refs to rendered elements let container: HTMLDivElement let scrollAnchor: HTMLDivElement + let scrollTimeout: number - // Two-way binding for autoScroll + // Track log elements + let logElements: HTMLDivElement[] = [] + export let autoScroll = true let lastScrollTop = 0 + // Debounce auto-scrolling, if many logs come in at once, this creates a smooth scroll UX const debouncedScroll = () => { if (!autoScroll) return + // if a new log arrives during the timeout, clear it and start over + // this prevents the scroll from jumping around if (scrollTimeout) { window.clearTimeout(scrollTimeout) } @@ -41,7 +48,7 @@ const isScrollingUp = target.scrollTop < lastScrollTop const isAtBottom = Math.abs(target.scrollHeight - target.clientHeight - target.scrollTop) < 50 - // Update autoScroll based on scroll behavior + // enable autoscroll if user is at the bottom, else disable it autoScroll = !isScrollingUp && isAtBottom lastScrollTop = target.scrollTop } @@ -51,6 +58,7 @@ debouncedScroll() } + // used by parent component to add messages export const addMessage = (message: string) => { if (!message || !scrollAnchor?.parentElement) return @@ -61,10 +69,27 @@ window.requestAnimationFrame(() => { scrollAnchor.parentElement?.insertBefore(lineElement, scrollAnchor) + logElements.push(lineElement) + // Keep only last 1000 lines in memory to prevent memory leaks + if (logElements.length > 1000) { + logElements.shift() + } debouncedScroll() }) } + // Function to get last N lines of logs + export const getLastNLines = (n: number): string => { + const lastN = logElements.slice(-n) + return lastN + .map((el) => { + // Convert HTML back to plain text and remove ANSI codes + const text = el.textContent || '' + return text.replace(/\n$/, '') // Remove trailing newline if present + }) + .join('\n') + } + onMount(() => { return () => { if (scrollTimeout) { diff --git a/ui/src/lib/components/k8s/Logs/component.svelte b/ui/src/lib/components/k8s/Logs/component.svelte index 647e2518..fcecf028 100644 --- a/ui/src/lib/components/k8s/Logs/component.svelte +++ b/ui/src/lib/components/k8s/Logs/component.svelte @@ -2,12 +2,13 @@