Merge pull request #1424 from kaitranntt/codex/fix-logs-filtering-to-show-dashboard-audits

fix(ui): show dashboard audit logs by default
This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-30 16:15:18 -04:00
committed by GitHub
3 changed files with 133 additions and 13 deletions
+5 -6
View File
@@ -44,7 +44,7 @@ export interface LogsFiltersProps {
onRequestIdChange?: (v: string) => void;
timeWindow?: LogsTimeWindow;
onTimeWindowChange?: (v: LogsTimeWindow) => void;
/** When true, hides entries from `web-server:*` sources. Default ON. */
/** When true, hides entries from `web-server:*` sources. Default OFF. */
hideDashboardInternals?: boolean;
onHideDashboardInternalsChange?: (next: boolean) => void;
onClearAll?: () => void;
@@ -91,7 +91,7 @@ export function LogsFilters({
onRequestIdChange,
timeWindow = 'all',
onTimeWindowChange,
hideDashboardInternals = true,
hideDashboardInternals = false,
onHideDashboardInternalsChange,
onClearAll,
}: LogsFiltersProps) {
@@ -324,11 +324,10 @@ export function LogsFilters({
htmlFor="logs-hide-internals"
className="block text-[12px] font-medium text-foreground"
>
Hide dashboard internals
Hide dashboard web-server logs
</Label>
<p className="text-[11px] text-muted-foreground">
Suppress <code className="rounded bg-background px-1">web-server:*</code>{' '}
self-polling.
Optional noise reduction. Audit entries are visible by default.
</p>
</div>
<input
@@ -338,7 +337,7 @@ export function LogsFilters({
checked={hideDashboardInternals}
onChange={(e) => onHideDashboardInternalsChange(e.target.checked)}
className={cn('mt-0.5 h-4 w-4 cursor-pointer accent-foreground', FOCUS_RING)}
aria-label="Hide dashboard internals"
aria-label="Hide dashboard web-server logs"
/>
</div>
) : null}
+8 -7
View File
@@ -116,10 +116,10 @@ export function useLogsWorkspace() {
const [limit, setLimit] = useState(DEFAULT_LIMIT);
const [selectedEntryId, setSelectedEntryId] = useState<string | null>(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);
// Default OFF: web-server:* entries include dashboard access and WebSocket
// audit evidence, so keep them visible unless the operator opts into noise
// reduction from the advanced filter toggle.
const [hideDashboardInternals, setHideDashboardInternals] = useState(false);
const frozenIdsRef = useRef<Set<string>>(new Set());
const deferredSearch = useDeferredValue(search.trim());
@@ -196,8 +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.
// Optional noise reduction only: web-server:* entries can contain
// security-relevant dashboard access and WebSocket audit evidence, so
// they remain visible by default.
if (hideDashboardInternals && /^web-server:/i.test(entry.source)) return false;
return true;
});
@@ -273,7 +274,7 @@ export function useLogsWorkspace() {
setStageFilter('');
setRequestIdFilter('');
setTimeWindow('all');
setHideDashboardInternals(true);
setHideDashboardInternals(false);
}, []);
return {
+120
View File
@@ -0,0 +1,120 @@
import { act, renderHook, waitFor } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import type { ReactNode } from 'react';
import { AllProviders } from '../../setup/test-utils';
import { useLogsWorkspace } from '@/hooks/use-logs';
import type { LogsEntry } from '@/lib/api-client';
function createJsonResponse(body: Record<string, unknown>, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json' },
});
}
const entries: LogsEntry[] = [
{
id: 'http-audit-1',
timestamp: '2026-05-29T00:00:00.000Z',
level: 'error',
source: 'web-server:http',
event: 'request.completed',
message: 'Dashboard request completed',
processId: 1,
runId: null,
requestId: 'dashboard-audit',
},
{
id: 'ws-audit-1',
timestamp: '2026-05-29T00:00:01.000Z',
level: 'warn',
source: 'web-server:websocket',
event: 'message.invalid',
message: 'WebSocket client sent invalid JSON',
processId: 1,
runId: null,
requestId: 'dashboard-audit',
},
{
id: 'provider-1',
timestamp: '2026-05-29T00:00:02.000Z',
level: 'info',
source: 'provider:codex',
event: 'request.completed',
message: 'Provider request completed',
processId: 2,
runId: null,
requestId: 'provider-trace',
},
];
function mockLogsApi(): void {
vi.stubGlobal(
'fetch',
vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.startsWith('/api/logs/config')) {
return createJsonResponse({
logging: {
enabled: true,
level: 'info',
rotate_mb: 10,
retain_days: 7,
redact: true,
live_buffer_size: 150,
},
});
}
if (url.startsWith('/api/logs/sources')) {
return createJsonResponse({
sources: entries.map((entry) => ({
source: entry.source,
label: entry.source,
kind: 'native',
count: 1,
lastTimestamp: entry.timestamp,
})),
});
}
if (url.startsWith('/api/logs/entries')) {
return createJsonResponse({ entries });
}
return createJsonResponse({ error: `Unexpected URL: ${url}` }, 404);
})
);
}
describe('useLogsWorkspace', () => {
it('keeps dashboard audit events visible by default and only hides them on opt-in', async () => {
mockLogsApi();
const wrapper = ({ children }: { children: ReactNode }) => (
<AllProviders>{children}</AllProviders>
);
const { result } = renderHook(() => useLogsWorkspace(), { wrapper });
await waitFor(() => {
expect(result.current.entriesQuery.data?.map((entry) => entry.id)).toEqual([
'http-audit-1',
'ws-audit-1',
'provider-1',
]);
});
act(() => result.current.setHideDashboardInternals(true));
await waitFor(() => {
expect(result.current.entriesQuery.data?.map((entry) => entry.id)).toEqual(['provider-1']);
});
act(() => result.current.clearAdvancedFilters());
await waitFor(() => {
expect(result.current.entriesQuery.data?.map((entry) => entry.id)).toEqual([
'http-audit-1',
'ws-audit-1',
'provider-1',
]);
});
});
});