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 { useRefreshInterval } from './use-refresh-interval';
|
||||||
import { useGlobalFilters } from '@cameleer/design-system';
|
import { useGlobalFilters } from '@cameleer/design-system';
|
||||||
import { useEnvironmentStore } from '../environment-store';
|
import { useEnvironmentStore } from '../environment-store';
|
||||||
|
import { useInfiniteStream, type UseInfiniteStreamResult } from '../../hooks/useInfiniteStream';
|
||||||
|
|
||||||
export interface LogEntryResponse {
|
export interface LogEntryResponse {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -157,3 +158,79 @@ export function useStartupLogs(
|
|||||||
refetchInterval: isStarting ? 3_000 : false,
|
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