perf: virtualize log rendering, cache API data, harden WS/backend - #19
Merged
Merged
Conversation
- StreamLog LogViewer rows so DOM work stays constant regardless of buffer size - Add cached/deduplicated API fetch for autocomplete and autocomplete data - Add WebSocket cleanup on stream unmount to prevent connection leaks - Buffer parse-failure entries while paused - Add stable log ids to avoid row remounts on ring-buffer rollover - Precompile client-side log filters once per log change - Enforce WebSocket origin allowlist (same-origin or ALLOWED_ORIGINS) - Reuse a single Kubernetes client per context across streams - Use --namespace= value form so kubectl args cannot be flag-injected
…-reconnect - App.jsx: validate stored context against fetched list, not just falsy check - useAutoComplete: cleanup targets correct localStorage key (stern-ui-cluster) - LogViewer: remove select-none so log lines are copyable - LogFilters: fix double-unwrapped onChange breaking search/level inputs - LogFilters: wire into StreamPanel (was exported but never rendered) - StreamPanel: use ?? instead of || for query default so cleared pods stay cleared - StreamPanel: add query to auto-reconnect check on pod change - logUtils: default unrecognized logs to info instead of unknown
There was a problem hiding this comment.
Pull request overview
This PR focuses on improving UI performance and hardening backend/frontend behavior for streaming logs and cluster data access by reducing DOM/render cost, deduplicating repeated API fetches, and tightening WebSocket/backend handling.
Changes:
- Virtualizes log rendering and improves stream log buffering/append behavior to keep UI responsive under large log volumes.
- Adds a shared in-memory
cachedFetch()with TTL + in-flight deduplication and wires it into autocomplete and initial context loading. - Hardens the backend WebSocket origin policy and reuses Kubernetes clients per context; updates kubectl namespace flag usage to avoid flag-injection.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| main.go | Adds WS origin allowlist, refactors until-time parsing, caches kube clients per context, and hardens kubectl namespace args |
| main_test.go | Updates WebSocket origin policy tests to cover allow/reject cases |
| frontend/src/utils/logUtils.js | Precompiles client-side filters to avoid per-log regex compilation |
| frontend/src/utils/cache.js | Introduces cached fetch with TTL + in-flight request deduplication |
| frontend/src/utils/tests/logUtils.test.js | Adds unit coverage for compileLogFilters |
| frontend/src/test/setup.js | Adds ResizeObserver mock for jsdom tests |
| frontend/src/hooks/useWebSocket.js | Fixes socket lifecycle leak, buffers parse failures while paused, and adds monotonic log ids |
| frontend/src/hooks/useAutoComplete.js | Switches autocomplete requests to cachedFetch() with TTLs |
| frontend/src/components/stream/StreamPanel.jsx | Uses precompiled filters to reduce per-update filtering overhead |
| frontend/src/components/logs/LogViewer.jsx | Implements virtualized log row rendering with tail-follow behavior |
| frontend/src/App.jsx | Switches initial contexts load to cachedFetch() |
| CHANGELOG.md | Documents the performance and hardening changes |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
added 3 commits
August 10, 2026 07:46
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (4)
main.go:310
startCredentialRefresher(context.Background(), ...)creates a per-context goroutine that never stops. In environments where users can switch across many contexts over time, this can leak goroutines and cached clients indefinitely (no eviction/lifecycle), which is a reliability concern for long-lived backend processes.
startCredentialRefresher(context.Background(), entry.clientset, contextName, &entry.mu)
frontend/src/components/logs/LogViewer.jsx:138
- Virtualization assumes every rendered row is exactly
ROW_HEIGHTtall, but the row markup doesn't currently enforce a 20px height/line-height. If CSS or font metrics change (or any wrapping/padding sneaks in), scroll math can drift and the wrong rows will be shown.
className="flex gap-2 hover:bg-gray-900/50 whitespace-nowrap"
frontend/src/App.jsx:34
- The initial context selection clears to
''when/api/contextsfails (because the list becomes[]), even if a previously stored context exists. This can cause the app to lose the last-known context during transient backend errors; consider falling back to the stored value when the fetched list is empty.
if (stored && list.includes(stored)) {
setContext(stored);
} else {
setContext(list[0] || '');
}
frontend/src/utils/cache.js:33
cachedFetch()is new shared infrastructure (TTL handling, in-flight dedup, error cache invalidation) but there are no unit tests covering its caching semantics. Given there are existing utility tests infrontend/src/utils/__tests__, adding coverage here would help prevent regressions.
export function cachedFetch(url, { ttl = 30_000 } = {}) {
const now = Date.now();
const entry = cache.get(url);
if (entry && entry.expiresAt > now) {
return Promise.resolve(entry.value);
}
if (entry && entry.promise) {
return entry.promise;
}
const promise = apiFetch(url)
.then((value) => {
cache.set(url, { value, expiresAt: Date.now() + ttl });
return value;
})
.catch((err) => {
cache.delete(url);
throw err;
});
cache.set(url, { promise });
return promise;
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Performance and hardening pass driven by a review of the previous agent feedback. Focus is on the two real bottlenecks (log rendering and repeated API fetches), plus leaks/security issues found on deeper review.
Changes
Frontend
LogViewernow virtualizes rows: only the visible window (+overscan) is in the DOM, so per-message rendering is constant regardless of buffered log count. Auto-follow only when pinned to the tail.cachedFetch()utility: in-memory cache with TTL + in-flight dedup. Namespaces/contexts/nodes cached 60s, pods/containers 8s. Shared across all stream panels and the header.useWebSocket: socket closed on unmount (fixes stream/connection leak when switching views), parse-failure entries buffered while paused, append path avoids double array copy, logs carry stable monotonic ids used as render keys.Backend
CheckOriginallowlist: same-origin orALLOWED_ORIGINSenv (dev atlocalhost:5173). Prevents arbitrary web pages from reaching this backend's kubectl/stern surface.kubectlnamespace filters use--namespace=form so a value starting with-can't be parsed as a flag.Tests / checks
compileLogFiltersunit coverage.go vet,go build, Go tests, frontend lint (76 tests) all green.Release note: no API/CLI changes;
ALLOWED_ORIGINSis additive for deployments that proxy from another host.