feat(ui): add useInfiniteApplicationLogs hook
Server-side filters on source/level/time-range, client-side text search on top of flattened items. Leaves useApplicationLogs and useStartupLogs untouched for bounded consumers (LogTab, StartupLogPanel).
This commit is contained in:
@@ -4,6 +4,7 @@ import { useAuthStore } from '../../auth/auth-store';
|
||||
import { useRefreshInterval } from './use-refresh-interval';
|
||||
import { useGlobalFilters } from '@cameleer/design-system';
|
||||
import { useEnvironmentStore } from '../environment-store';
|
||||
import { useInfiniteStream, type UseInfiniteStreamResult } from '../../hooks/useInfiniteStream';
|
||||
|
||||
export interface LogEntryResponse {
|
||||
timestamp: string;
|
||||
@@ -157,3 +158,79 @@ export function useStartupLogs(
|
||||
refetchInterval: isStarting ? 3_000 : false,
|
||||
});
|
||||
}
|
||||
|
||||
export interface UseInfiniteApplicationLogsArgs {
|
||||
application?: string;
|
||||
agentId?: string;
|
||||
sources?: string[]; // multi-select, server-side OR
|
||||
levels?: string[]; // multi-select, server-side OR
|
||||
exchangeId?: string;
|
||||
isAtTop: boolean;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cursor-paginated log stream. Filters `sources`, `levels`, and the global
|
||||
* time range are applied server-side. Free-text search is applied by the
|
||||
* caller on top of the flattened items.
|
||||
*/
|
||||
export function useInfiniteApplicationLogs(
|
||||
args: UseInfiniteApplicationLogsArgs,
|
||||
): UseInfiniteStreamResult<LogEntryResponse> {
|
||||
const { timeRange } = useGlobalFilters();
|
||||
const selectedEnv = useEnvironmentStore((s) => s.environment);
|
||||
|
||||
const useTimeRange = !args.exchangeId;
|
||||
const fromIso = useTimeRange ? timeRange.start.toISOString() : undefined;
|
||||
const toIso = useTimeRange ? timeRange.end.toISOString() : undefined;
|
||||
|
||||
const sortedSources = (args.sources ?? []).slice().sort();
|
||||
const sortedLevels = (args.levels ?? []).slice().sort();
|
||||
const sourcesParam = sortedSources.join(',');
|
||||
const levelsParam = sortedLevels.join(',');
|
||||
const pageSize = args.pageSize ?? 100;
|
||||
|
||||
return useInfiniteStream<LogEntryResponse>({
|
||||
queryKey: [
|
||||
'logs', 'infinite',
|
||||
selectedEnv ?? '',
|
||||
args.application ?? '',
|
||||
args.agentId ?? '',
|
||||
args.exchangeId ?? '',
|
||||
sourcesParam,
|
||||
levelsParam,
|
||||
fromIso ?? '',
|
||||
toIso ?? '',
|
||||
pageSize,
|
||||
],
|
||||
enabled: !!args.application && !!selectedEnv,
|
||||
isAtTop: args.isAtTop,
|
||||
fetchPage: async (cursor) => {
|
||||
const token = useAuthStore.getState().accessToken;
|
||||
const qp = new URLSearchParams();
|
||||
if (args.application) qp.set('application', args.application);
|
||||
if (args.agentId) qp.set('agentId', args.agentId);
|
||||
if (args.exchangeId) qp.set('exchangeId', args.exchangeId);
|
||||
if (sourcesParam) qp.set('source', sourcesParam);
|
||||
if (levelsParam) qp.set('level', levelsParam);
|
||||
if (fromIso) qp.set('from', fromIso);
|
||||
if (toIso) qp.set('to', toIso);
|
||||
if (cursor) qp.set('cursor', cursor);
|
||||
qp.set('limit', String(pageSize));
|
||||
qp.set('sort', 'desc');
|
||||
|
||||
const res = await fetch(
|
||||
`${config.apiBaseUrl}/environments/${encodeURIComponent(selectedEnv ?? '')}/logs?${qp}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-Cameleer-Protocol-Version': '1',
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!res.ok) throw new Error('Failed to load logs');
|
||||
const page: LogSearchPageResponse = await res.json();
|
||||
return { data: page.data, nextCursor: page.nextCursor, hasMore: page.hasMore };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user