mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
feat(ui): hide dashboard self-polling internals via default filter
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
This commit is contained in:
1 parent
e04598eef7
commit
4a77251021
3 files changed
+56
No files matched your search
@@ -44,6 +44,9 @@ export interface LogsFiltersProps {
|
|||||||
onRequestIdChange?: (v: string) => void;
|
onRequestIdChange?: (v: string) => void;
|
||||||
timeWindow?: LogsTimeWindow;
|
timeWindow?: LogsTimeWindow;
|
||||||
onTimeWindowChange?: (v: LogsTimeWindow) => void;
|
onTimeWindowChange?: (v: LogsTimeWindow) => void;
|
||||||
|
/** When true, hides entries from `web-server:*` sources. Default ON. */
|
||||||
|
hideDashboardInternals?: boolean;
|
||||||
|
onHideDashboardInternalsChange?: (next: boolean) => void;
|
||||||
onClearAll?: () => void;
|
onClearAll?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +91,8 @@ export function LogsFilters({
|
|||||||
onRequestIdChange,
|
onRequestIdChange,
|
||||||
timeWindow = 'all',
|
timeWindow = 'all',
|
||||||
onTimeWindowChange,
|
onTimeWindowChange,
|
||||||
|
hideDashboardInternals = true,
|
||||||
|
onHideDashboardInternalsChange,
|
||||||
onClearAll,
|
onClearAll,
|
||||||
}: LogsFiltersProps) {
|
}: LogsFiltersProps) {
|
||||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||||
@@ -312,6 +317,31 @@ export function LogsFilters({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{onHideDashboardInternalsChange ? (
|
||||||
|
<div className="flex items-start justify-between gap-3 rounded border border-border/60 bg-muted/20 p-2">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label
|
||||||
|
htmlFor="logs-hide-internals"
|
||||||
|
className="block text-[12px] font-medium text-foreground"
|
||||||
|
>
|
||||||
|
Hide dashboard internals
|
||||||
|
</Label>
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
Suppress <code className="rounded bg-background px-1">web-server:*</code>{' '}
|
||||||
|
self-polling.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="logs-hide-internals"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,18 @@ export function LogsShell({ workspace, updateConfig }: LogsShellProps) {
|
|||||||
[workspace.entriesQuery.data]
|
[workspace.entriesQuery.data]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Derive header stat strip values once per data refetch.
|
||||||
|
const headerStats = useMemo(() => {
|
||||||
|
const data = workspace.entriesQuery.data ?? [];
|
||||||
|
const requestIds = new Set<string>();
|
||||||
|
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 focusSearch = useCallback(() => {
|
||||||
const input = document.getElementById('logs-search') as HTMLInputElement | null;
|
const input = document.getElementById('logs-search') as HTMLInputElement | null;
|
||||||
if (input) {
|
if (input) {
|
||||||
@@ -180,6 +192,8 @@ export function LogsShell({ workspace, updateConfig }: LogsShellProps) {
|
|||||||
onRequestIdChange={workspace.setRequestIdFilter}
|
onRequestIdChange={workspace.setRequestIdFilter}
|
||||||
timeWindow={workspace.timeWindow}
|
timeWindow={workspace.timeWindow}
|
||||||
onTimeWindowChange={workspace.setTimeWindow}
|
onTimeWindowChange={workspace.setTimeWindow}
|
||||||
|
hideDashboardInternals={workspace.hideDashboardInternals}
|
||||||
|
onHideDashboardInternalsChange={workspace.setHideDashboardInternals}
|
||||||
onClearAll={workspace.clearAdvancedFilters}
|
onClearAll={workspace.clearAdvancedFilters}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -228,6 +242,7 @@ export function LogsShell({ workspace, updateConfig }: LogsShellProps) {
|
|||||||
isFetching={workspace.entriesQuery.isFetching || workspace.sourcesQuery.isFetching}
|
isFetching={workspace.entriesQuery.isFetching || workspace.sourcesQuery.isFetching}
|
||||||
hasError={Boolean(workspace.entriesQuery.error)}
|
hasError={Boolean(workspace.entriesQuery.error)}
|
||||||
capturedCount={workspace.entriesQuery.data?.length ?? 0}
|
capturedCount={workspace.entriesQuery.data?.length ?? 0}
|
||||||
|
stats={headerStats}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
onOpenSettings={openSettings}
|
onOpenSettings={openSettings}
|
||||||
onOpenShortcuts={() => setShortcutsOpen(true)}
|
onOpenShortcuts={() => setShortcutsOpen(true)}
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ export function useLogsWorkspace() {
|
|||||||
const [limit, setLimit] = useState(DEFAULT_LIMIT);
|
const [limit, setLimit] = useState(DEFAULT_LIMIT);
|
||||||
const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
|
const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
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<Set<string>>(new Set());
|
const frozenIdsRef = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
const deferredSearch = useDeferredValue(search.trim());
|
const deferredSearch = useDeferredValue(search.trim());
|
||||||
@@ -151,6 +155,7 @@ export function useLogsWorkspace() {
|
|||||||
debouncedRequestId,
|
debouncedRequestId,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
limit,
|
limit,
|
||||||
|
hideDashboardInternals ? 'hide-internals' : 'show-internals',
|
||||||
mockEnabled ? 'mock' : 'live',
|
mockEnabled ? 'mock' : 'live',
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -191,6 +196,9 @@ export function useLogsWorkspace() {
|
|||||||
const ts = Date.parse(entry.timestamp);
|
const ts = Date.parse(entry.timestamp);
|
||||||
if (Number.isFinite(ts) && now - ts > cutoffMs) return false;
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
const head = filtered[0];
|
const head = filtered[0];
|
||||||
@@ -265,6 +273,7 @@ export function useLogsWorkspace() {
|
|||||||
setStageFilter('');
|
setStageFilter('');
|
||||||
setRequestIdFilter('');
|
setRequestIdFilter('');
|
||||||
setTimeWindow('all');
|
setTimeWindow('all');
|
||||||
|
setHideDashboardInternals(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -285,6 +294,8 @@ export function useLogsWorkspace() {
|
|||||||
setRequestIdFilter,
|
setRequestIdFilter,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
setTimeWindow,
|
setTimeWindow,
|
||||||
|
hideDashboardInternals,
|
||||||
|
setHideDashboardInternals,
|
||||||
limit,
|
limit,
|
||||||
setLimit,
|
setLimit,
|
||||||
selectedEntryId: activeSelectedEntryId,
|
selectedEntryId: activeSelectedEntryId,
|
||||||
|
|||||||
Reference in new issue
Block a user