import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useAuthStore } from '../../auth/auth-store' import { config } from '../../config' // ── Application Config ──────────────────────────────────────────────────── export interface TapDefinition { tapId: string processorId: string target: 'INPUT' | 'OUTPUT' | 'BOTH' expression: string language: string attributeName: string attributeType: 'BUSINESS_OBJECT' | 'CORRELATION' | 'EVENT' | 'CUSTOM' enabled: boolean version: number } export interface ApplicationConfig { application: string version: number updatedAt?: string engineLevel?: string payloadCaptureMode?: string applicationLogLevel?: string agentLogLevel?: string metricsEnabled: boolean samplingRate: number tracedProcessors: Record taps: TapDefinition[] tapVersion: number routeRecording: Record compressSuccess: boolean sensitiveKeys?: string[] globalSensitiveKeys?: string[] mergedSensitiveKeys?: string[] } /** Authenticated fetch using the JWT from auth store. Paths are relative to apiBaseUrl. */ function authFetch(path: string, init?: RequestInit): Promise { const token = useAuthStore.getState().accessToken const headers = new Headers(init?.headers) if (token) headers.set('Authorization', `Bearer ${token}`) headers.set('X-Cameleer-Protocol-Version', '1') return fetch(`${config.apiBaseUrl}${path}`, { ...init, headers }) } export function useAllApplicationConfigs() { return useQuery({ queryKey: ['applicationConfig', 'all'], queryFn: async () => { const res = await authFetch('/config') if (!res.ok) throw new Error('Failed to fetch configs') return res.json() as Promise }, }) } export function useApplicationConfig(application: string | undefined, environment: string | undefined) { return useQuery({ queryKey: ['applicationConfig', application, environment], queryFn: async () => { const envParam = environment ? `?environment=${encodeURIComponent(environment)}` : '' const res = await authFetch(`/config/${application}${envParam}`) if (!res.ok) throw new Error(`Failed to fetch config: ${res.status}`) const data = await res.json() // Server returns AppConfigResponse: { config, globalSensitiveKeys, mergedSensitiveKeys } const cfg = data.config ?? data cfg.globalSensitiveKeys = data.globalSensitiveKeys ?? null cfg.mergedSensitiveKeys = data.mergedSensitiveKeys ?? null return cfg as ApplicationConfig }, enabled: !!application && !!environment, }) } export interface ConfigUpdateResponse { config: ApplicationConfig pushResult: CommandGroupResponse } export function useUpdateApplicationConfig() { const queryClient = useQueryClient() return useMutation({ mutationFn: async ({ config, environment }: { config: ApplicationConfig; environment?: string }) => { const envParam = environment ? `?environment=${encodeURIComponent(environment)}` : '' const res = await authFetch(`/config/${config.application}${envParam}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config), }) if (!res.ok) throw new Error('Failed to update config') return res.json() as Promise }, onSuccess: (result) => { queryClient.setQueryData(['applicationConfig', result.config.application], result.config) queryClient.invalidateQueries({ queryKey: ['applicationConfig', 'all'] }) }, }) } // ── Processor → Route Mapping ───────────────────────────────────────────── export function useProcessorRouteMapping(application?: string, environment?: string) { return useQuery({ queryKey: ['config', application, environment, 'processor-routes'], queryFn: async () => { const res = await authFetch( `/config/${application}/processor-routes?environment=${encodeURIComponent(environment!)}`) if (!res.ok) throw new Error('Failed to fetch processor-route mapping') return res.json() as Promise> }, enabled: !!application && !!environment, }) } // ── Group Command Response ─────────────────────────────────────────────── export interface CommandGroupResponse { success: boolean total: number responded: number responses: { agentId: string; status: string; message: string }[] timedOut: string[] } // ── Generic Group Command (kept for non-config commands) ────────────────── interface SendGroupCommandParams { group: string type: string payload: Record environment?: string } export function useSendGroupCommand() { return useMutation({ mutationFn: async ({ group, type, payload, environment }: SendGroupCommandParams) => { const envParam = environment ? `?environment=${encodeURIComponent(environment)}` : '' const res = await authFetch(`/agents/groups/${encodeURIComponent(group)}/commands${envParam}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type, payload }), }) if (!res.ok) throw new Error('Failed to send command') return res.json() as Promise }, }) } // ── Test Expression ─────────────────────────────────────────────────────── export function useTestExpression() { return useMutation({ mutationFn: async ({ application, expression, language, body, target, }: { application: string expression: string language: string body: string target: string }) => { const res = await authFetch( `/config/${encodeURIComponent(application)}/test-expression`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expression, language, body, target }), }, ) if (!res.ok) { if (res.status === 404) throw new Error('No live agent available') if (res.status === 504) throw new Error('Expression test timed out') throw new Error('Failed to test expression') } return res.json() as Promise<{ result?: string; error?: string }> }, }) } // ── Route Control ──────────────────────────────────────────────────────── export function useSendRouteCommand() { return useMutation({ mutationFn: async ({ application, action, routeId, environment }: { application: string action: 'start' | 'stop' | 'suspend' | 'resume' routeId: string environment?: string }) => { const envParam = environment ? `?environment=${encodeURIComponent(environment)}` : '' const res = await authFetch(`/agents/groups/${encodeURIComponent(application)}/commands${envParam}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'route-control', payload: { routeId, action, nonce: crypto.randomUUID() } }), }) if (!res.ok) throw new Error('Failed to send route command') return res.json() as Promise }, }) } // ── Replay Exchange ─────────────────────────────────────────────────────── export interface ReplayResult { status: string message: string data?: string } export function useReplayExchange() { return useMutation({ mutationFn: async ({ agentId, routeId, headers, body, originalExchangeId, }: { agentId: string routeId: string headers?: Record body: string originalExchangeId?: string }): Promise => { const res = await authFetch(`/agents/${encodeURIComponent(agentId)}/replay`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ routeId, body, headers: headers ?? {}, originalExchangeId }), }) if (!res.ok) { if (res.status === 404) throw new Error('Agent not found') if (res.status === 504) throw new Error('Replay timed out — agent did not respond') throw new Error('Failed to send replay command') } return res.json() as Promise }, }) }