2026-03-25 18:56:13 +01:00
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
|
import { config } from '../../config';
|
|
|
|
|
import { useAuthStore } from '../../auth/auth-store';
|
|
|
|
|
import { useRefreshInterval } from './use-refresh-interval';
|
|
|
|
|
import { useGlobalFilters } from '@cameleer/design-system';
|
|
|
|
|
|
|
|
|
|
export interface LogEntryResponse {
|
|
|
|
|
timestamp: string;
|
|
|
|
|
level: string;
|
|
|
|
|
loggerName: string | null;
|
|
|
|
|
message: string;
|
|
|
|
|
threadName: string | null;
|
|
|
|
|
stackTrace: string | null;
|
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
|
|
|
exchangeId: string | null;
|
|
|
|
|
instanceId: string | null;
|
|
|
|
|
application: string | null;
|
|
|
|
|
mdc: Record<string, string> | null;
|
2026-03-25 18:56:13 +01:00
|
|
|
}
|
|
|
|
|
|
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
|
|
|
export interface LogSearchPageResponse {
|
|
|
|
|
data: LogEntryResponse[];
|
|
|
|
|
nextCursor: string | null;
|
|
|
|
|
hasMore: boolean;
|
|
|
|
|
levelCounts: Record<string, number>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LogSearchParams {
|
|
|
|
|
q?: string;
|
|
|
|
|
level?: string;
|
|
|
|
|
application?: string;
|
|
|
|
|
agentId?: string;
|
|
|
|
|
exchangeId?: string;
|
|
|
|
|
logger?: string;
|
|
|
|
|
from?: string;
|
|
|
|
|
to?: string;
|
|
|
|
|
cursor?: string;
|
|
|
|
|
limit?: number;
|
|
|
|
|
sort?: 'asc' | 'desc';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchLogs(params: LogSearchParams): Promise<LogSearchPageResponse> {
|
|
|
|
|
const token = useAuthStore.getState().accessToken;
|
|
|
|
|
const urlParams = new URLSearchParams();
|
|
|
|
|
if (params.q) urlParams.set('q', params.q);
|
|
|
|
|
if (params.level) urlParams.set('level', params.level);
|
|
|
|
|
if (params.application) urlParams.set('application', params.application);
|
|
|
|
|
if (params.agentId) urlParams.set('agentId', params.agentId);
|
|
|
|
|
if (params.exchangeId) urlParams.set('exchangeId', params.exchangeId);
|
|
|
|
|
if (params.logger) urlParams.set('logger', params.logger);
|
|
|
|
|
if (params.from) urlParams.set('from', params.from);
|
|
|
|
|
if (params.to) urlParams.set('to', params.to);
|
|
|
|
|
if (params.cursor) urlParams.set('cursor', params.cursor);
|
|
|
|
|
if (params.limit) urlParams.set('limit', String(params.limit));
|
|
|
|
|
if (params.sort) urlParams.set('sort', params.sort);
|
|
|
|
|
|
|
|
|
|
const res = await fetch(`${config.apiBaseUrl}/logs?${urlParams}`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
'X-Cameleer-Protocol-Version': '1',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) throw new Error('Failed to load logs');
|
|
|
|
|
return res.json() as Promise<LogSearchPageResponse>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Primary log search hook with cursor pagination and level counts.
|
|
|
|
|
*/
|
|
|
|
|
export function useLogs(
|
|
|
|
|
params: LogSearchParams,
|
|
|
|
|
options?: { enabled?: boolean; refetchInterval?: number | false },
|
|
|
|
|
) {
|
|
|
|
|
const defaultRefetch = useRefreshInterval(15_000);
|
|
|
|
|
|
|
|
|
|
return useQuery({
|
|
|
|
|
queryKey: ['logs', params],
|
|
|
|
|
queryFn: () => fetchLogs(params),
|
|
|
|
|
enabled: options?.enabled ?? true,
|
|
|
|
|
placeholderData: (prev) => prev,
|
|
|
|
|
refetchInterval: options?.refetchInterval ?? defaultRefetch,
|
|
|
|
|
staleTime: 300,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Backward-compatible wrapper for existing consumers (LogTab, AgentHealth, AgentInstance).
|
|
|
|
|
* Returns the same shape they expect: data is the LogEntryResponse[] (unwrapped from the page response).
|
|
|
|
|
*/
|
2026-03-25 18:56:13 +01:00
|
|
|
export function useApplicationLogs(
|
|
|
|
|
application?: string,
|
|
|
|
|
agentId?: string,
|
2026-03-26 10:26:30 +01:00
|
|
|
options?: { limit?: number; toOverride?: string; exchangeId?: string },
|
2026-03-25 18:56:13 +01:00
|
|
|
) {
|
|
|
|
|
const refetchInterval = useRefreshInterval(15_000);
|
|
|
|
|
const { timeRange } = useGlobalFilters();
|
2026-03-25 22:41:00 +01:00
|
|
|
const to = options?.toOverride ?? timeRange.end.toISOString();
|
2026-03-26 11:03:38 +01:00
|
|
|
const useTimeRange = !options?.exchangeId;
|
2026-03-25 18:56:13 +01:00
|
|
|
|
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
|
|
|
const params: LogSearchParams = {
|
|
|
|
|
application: application || undefined,
|
|
|
|
|
agentId: agentId || undefined,
|
|
|
|
|
exchangeId: options?.exchangeId || undefined,
|
|
|
|
|
from: useTimeRange ? timeRange.start.toISOString() : undefined,
|
|
|
|
|
to: useTimeRange ? to : undefined,
|
|
|
|
|
limit: options?.limit,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const query = useQuery({
|
|
|
|
|
queryKey: ['logs', 'compat', application, agentId,
|
2026-03-26 11:03:38 +01:00
|
|
|
useTimeRange ? timeRange.start.toISOString() : null,
|
|
|
|
|
useTimeRange ? to : null,
|
|
|
|
|
options?.limit, options?.exchangeId],
|
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
|
|
|
queryFn: () => fetchLogs(params),
|
2026-03-25 18:56:13 +01:00
|
|
|
enabled: !!application,
|
2026-03-26 11:03:38 +01:00
|
|
|
placeholderData: (prev) => prev,
|
2026-03-25 18:56:13 +01:00
|
|
|
refetchInterval,
|
|
|
|
|
});
|
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
|
|
|
|
|
|
|
|
// Unwrap: existing consumers expect data to be LogEntryResponse[] directly
|
|
|
|
|
return {
|
|
|
|
|
...query,
|
|
|
|
|
data: query.data?.data ?? (undefined as LogEntryResponse[] | undefined),
|
|
|
|
|
};
|
2026-03-25 18:56:13 +01:00
|
|
|
}
|