From 4a772510215a0e8081853eee6c32d1cb8e47ec9b Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 30 Apr 2026 14:31:32 -0400 Subject: [PATCH] feat(ui): hide dashboard self-polling internals via default filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On real backend, the logs page was drowning in dashboard self-polling noise — every `/api/logs` poll emitted a 149-stage `web-server:http` trace, burying real provider activity. - New workspace state `hideDashboardInternals` defaults ON. Entries whose `source` matches `/^web-server:/i` are filtered client-side before they reach the list. - Toggle exposed in advanced filters as a labelled checkbox/switch with a one-line description, so users can opt in to see internals when debugging the dashboard itself. - `clearAdvancedFilters` resets the toggle back to ON to keep the default signal-clean experience. - Header stat strip (entries / traces / errors) wired through the shell. Backend-side `web-server:*` instrumentation stays intact (still useful for ops debugging) — just hidden from the user-facing log feed by default. Refs #1138, #1141, #1142 --- ui/src/components/logs/logs-filters.tsx | 30 +++++++++++++++++++++++++ ui/src/components/logs/logs-shell.tsx | 15 +++++++++++++ ui/src/hooks/use-logs.ts | 11 +++++++++ 3 files changed, 56 insertions(+) diff --git a/ui/src/components/logs/logs-filters.tsx b/ui/src/components/logs/logs-filters.tsx index f1c4436c..4d00d830 100644 --- a/ui/src/components/logs/logs-filters.tsx +++ b/ui/src/components/logs/logs-filters.tsx @@ -44,6 +44,9 @@ export interface LogsFiltersProps { onRequestIdChange?: (v: string) => void; timeWindow?: LogsTimeWindow; onTimeWindowChange?: (v: LogsTimeWindow) => void; + /** When true, hides entries from `web-server:*` sources. Default ON. */ + hideDashboardInternals?: boolean; + onHideDashboardInternalsChange?: (next: boolean) => void; onClearAll?: () => void; } @@ -88,6 +91,8 @@ export function LogsFilters({ onRequestIdChange, timeWindow = 'all', onTimeWindowChange, + hideDashboardInternals = true, + onHideDashboardInternalsChange, onClearAll, }: LogsFiltersProps) { const [advancedOpen, setAdvancedOpen] = useState(false); @@ -312,6 +317,31 @@ export function LogsFilters({ ) : null} + {onHideDashboardInternalsChange ? ( +
+
+ +

+ Suppress web-server:*{' '} + self-polling. +

+
+ onHideDashboardInternalsChange(e.target.checked)} + className={cn('mt-0.5 h-4 w-4 cursor-pointer accent-foreground', FOCUS_RING)} + aria-label="Hide dashboard internals" + /> +
+ ) : null} diff --git a/ui/src/components/logs/logs-shell.tsx b/ui/src/components/logs/logs-shell.tsx index 2e985696..a659ee27 100644 --- a/ui/src/components/logs/logs-shell.tsx +++ b/ui/src/components/logs/logs-shell.tsx @@ -137,6 +137,18 @@ export function LogsShell({ workspace, updateConfig }: LogsShellProps) { [workspace.entriesQuery.data] ); + // Derive header stat strip values once per data refetch. + const headerStats = useMemo(() => { + const data = workspace.entriesQuery.data ?? []; + const requestIds = new Set(); + let errors = 0; + for (const entry of data) { + if (entry.requestId) requestIds.add(entry.requestId); + if (entry.level === 'error') errors += 1; + } + return { entries: data.length, traces: requestIds.size, errors }; + }, [workspace.entriesQuery.data]); + const focusSearch = useCallback(() => { const input = document.getElementById('logs-search') as HTMLInputElement | null; if (input) { @@ -180,6 +192,8 @@ export function LogsShell({ workspace, updateConfig }: LogsShellProps) { onRequestIdChange={workspace.setRequestIdFilter} timeWindow={workspace.timeWindow} onTimeWindowChange={workspace.setTimeWindow} + hideDashboardInternals={workspace.hideDashboardInternals} + onHideDashboardInternalsChange={workspace.setHideDashboardInternals} onClearAll={workspace.clearAdvancedFilters} /> ); @@ -228,6 +242,7 @@ export function LogsShell({ workspace, updateConfig }: LogsShellProps) { isFetching={workspace.entriesQuery.isFetching || workspace.sourcesQuery.isFetching} hasError={Boolean(workspace.entriesQuery.error)} capturedCount={workspace.entriesQuery.data?.length ?? 0} + stats={headerStats} onRefresh={handleRefresh} onOpenSettings={openSettings} onOpenShortcuts={() => setShortcutsOpen(true)} diff --git a/ui/src/hooks/use-logs.ts b/ui/src/hooks/use-logs.ts index 254b9285..702db1f6 100644 --- a/ui/src/hooks/use-logs.ts +++ b/ui/src/hooks/use-logs.ts @@ -116,6 +116,10 @@ export function useLogsWorkspace() { const [limit, setLimit] = useState(DEFAULT_LIMIT); const [selectedEntryId, setSelectedEntryId] = useState(null); const [isPaused, setIsPaused] = useState(false); + // Default ON: dashboard self-polling generates 100s of identical entries + // per refresh which drown out real provider activity. Users can opt in to + // see internals via the advanced filter toggle. + const [hideDashboardInternals, setHideDashboardInternals] = useState(true); const frozenIdsRef = useRef>(new Set()); const deferredSearch = useDeferredValue(search.trim()); @@ -151,6 +155,7 @@ export function useLogsWorkspace() { debouncedRequestId, timeWindow, limit, + hideDashboardInternals ? 'hide-internals' : 'show-internals', mockEnabled ? 'mock' : 'live', ], queryFn: async () => { @@ -191,6 +196,9 @@ export function useLogsWorkspace() { const ts = Date.parse(entry.timestamp); if (Number.isFinite(ts) && now - ts > cutoffMs) return false; } + // Hide dashboard self-polling unless user opted in. Preserves the + // signal-to-noise ratio for fresh-load investigations. + if (hideDashboardInternals && /^web-server:/i.test(entry.source)) return false; return true; }); const head = filtered[0]; @@ -265,6 +273,7 @@ export function useLogsWorkspace() { setStageFilter(''); setRequestIdFilter(''); setTimeWindow('all'); + setHideDashboardInternals(true); }, []); return { @@ -285,6 +294,8 @@ export function useLogsWorkspace() { setRequestIdFilter, timeWindow, setTimeWindow, + hideDashboardInternals, + setHideDashboardInternals, limit, setLimit, selectedEntryId: activeSelectedEntryId,