import axios, { AxiosResponse } from 'axios'; import { Agent, UpdatePackage, AgentUpdatePackage, DashboardStats, AgentListResponse, UpdateListResponse, UpdateApprovalRequest, ScanRequest, ListQueryParams, ApiError, DockerContainer, DockerImage, DockerContainerListResponse, DockerStats, DockerUpdateRequest, BulkDockerUpdateRequest, RegistrationToken, CreateRegistrationTokenRequest, RegistrationTokenStats, RateLimitConfig, RateLimitStats, RateLimitUsage, RateLimitSummary, AgentSubsystem, SubsystemConfig, SubsystemStats } from '@/types'; // Base URL for API - use nginx proxy export const API_BASE_URL = '/api/v1'; // Create axios instance const api = axios.create({ baseURL: API_BASE_URL, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // Request interceptor to add auth token api.interceptors.request.use((config) => { const token = localStorage.getItem('auth_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Response interceptor to handle errors api.interceptors.response.use( (response: AxiosResponse) => response, async (error) => { if (error.response?.status === 401) { const { useAuthStore } = await import('./store'); useAuthStore.getState().logout(); localStorage.removeItem('auth_token'); localStorage.removeItem('user'); window.location.href = '/login'; } return Promise.reject(error); } ); // API endpoints export const agentApi = { // Get all agents getAgents: async (params?: ListQueryParams): Promise => { const response = await api.get('/agents', { params }); return response.data; }, // Get single agent getAgent: async (id: string): Promise => { const response = await api.get(`/agents/${id}`); return response.data; }, // Trigger scan on agents triggerScan: async (request: ScanRequest): Promise => { await api.post('/agents/scan', request); }, // Trigger scan on single agent scanAgent: async (id: string): Promise => { await api.post(`/agents/${id}/scan`); }, // Trigger heartbeat toggle on single agent toggleHeartbeat: async (id: string, enabled: boolean, durationMinutes: number = 10): Promise<{ message: string; command_id: string; enabled: boolean }> => { const response = await api.post(`/agents/${id}/heartbeat`, { enabled: enabled, duration_minutes: durationMinutes, }); return response.data; }, // Get heartbeat status for single agent getHeartbeatStatus: async (id: string): Promise<{ enabled: boolean; until: string | null; active: boolean; duration_minutes: number }> => { const response = await api.get(`/agents/${id}/heartbeat`); return response.data; }, // Trigger agent reboot rebootAgent: async (id: string, delayMinutes: number = 1, message?: string): Promise => { await api.post(`/agents/${id}/reboot`, { delay_minutes: delayMinutes, message: message || 'System reboot requested by RedFlag' }); }, // Unregister/remove agent unregisterAgent: async (id: string): Promise => { await api.delete(`/agents/${id}`); }, // Subsystem Management getSubsystems: async (agentId: string): Promise => { const response = await api.get(`/agents/${agentId}/subsystems`); return response.data; }, getSubsystem: async (agentId: string, subsystem: string): Promise => { const response = await api.get(`/agents/${agentId}/subsystems/${subsystem}`); return response.data; }, updateSubsystem: async (agentId: string, subsystem: string, config: SubsystemConfig): Promise<{ message: string }> => { const response = await api.patch(`/agents/${agentId}/subsystems/${subsystem}`, config); return response.data; }, enableSubsystem: async (agentId: string, subsystem: string): Promise<{ message: string }> => { const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/enable`); return response.data; }, disableSubsystem: async (agentId: string, subsystem: string): Promise<{ message: string }> => { const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/disable`); return response.data; }, triggerSubsystem: async (agentId: string, subsystem: string): Promise<{ message: string; command_id: string }> => { const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/trigger`); return response.data; }, getSubsystemStats: async (agentId: string, subsystem: string): Promise => { const response = await api.get(`/agents/${agentId}/subsystems/${subsystem}/stats`); return response.data; }, setSubsystemAutoRun: async (agentId: string, subsystem: string, autoRun: boolean): Promise<{ message: string }> => { const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/auto-run`, { auto_run: autoRun }); return response.data; }, setSubsystemInterval: async (agentId: string, subsystem: string, intervalMinutes: number): Promise<{ message: string }> => { const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/interval`, { interval_minutes: intervalMinutes }); return response.data; }, // Update single agent updateAgent: async (agentId: string, updateData: { version: string; platform: string; scheduled?: string; }): Promise<{ message: string; update_id: string; download_url: string; signature: string; checksum: string; file_size: number; estimated_time: number }> => { const response = await api.post(`/agents/${agentId}/update`, updateData); return response.data; }, // Check if update available for agent checkForUpdateAvailable: async (agentId: string): Promise<{ hasUpdate: boolean; currentVersion: string; latestVersion?: string; reason?: string; platform?: string }> => { const response = await api.get(`/agents/${agentId}/updates/available`); return response.data; }, // Get update status for agent getUpdateStatus: async (agentId: string): Promise<{ status: string; progress?: number; error?: string }> => { const response = await api.get(`/agents/${agentId}/updates/status`); return response.data; }, // Generate update nonce for agent (new security feature) generateUpdateNonce: async (agentId: string, targetVersion: string): Promise<{ agent_id: string; target_version: string; update_nonce: string; expires_at: number; expires_in_seconds: number }> => { const response = await api.post(`/agents/${agentId}/update-nonce?target_version=${targetVersion}`); return response.data; }, // Get agent metrics getAgentMetrics: async (agentId: string): Promise => { const response = await api.get(`/agents/${agentId}/metrics`); return response.data; }, // Get agent storage metrics getAgentStorageMetrics: async (agentId: string): Promise => { const response = await api.get(`/agents/${agentId}/metrics/storage`); return response.data; }, // Get agent system metrics getAgentSystemMetrics: async (agentId: string): Promise => { const response = await api.get(`/agents/${agentId}/metrics/system`); return response.data; }, // Get agent Docker images getAgentDockerImages: async (agentId: string, params?: any): Promise => { const response = await api.get(`/agents/${agentId}/docker-images`, { params }); return response.data; }, // Get agent Docker info getAgentDockerInfo: async (agentId: string): Promise => { const response = await api.get(`/agents/${agentId}/docker-info`); return response.data; }, // Update multiple agents (bulk) updateMultipleAgents: async (updateData: { agent_ids: string[]; version: string; platform: string; scheduled?: string; }): Promise<{ message: string; updated: Array<{ agent_id: string; hostname: string; update_id: string; status: string }>; failed: string[]; total_agents: number; package_info: any }> => { const response = await api.post('/agents/bulk-update', updateData); return response.data; }, }; export const updateApi = { // Get all updates getUpdates: async (params?: ListQueryParams): Promise => { const response = await api.get('/updates', { params }); return response.data; }, // Get single update getUpdate: async (id: string): Promise => { const response = await api.get(`/updates/${id}`); return response.data; }, // Approve updates approveUpdates: async (request: UpdateApprovalRequest): Promise => { await api.post('/updates/approve', request); }, // Approve single update approveUpdate: async (id: string, scheduledAt?: string): Promise => { await api.post(`/updates/${id}/approve`, { scheduled_at: scheduledAt }); }, // Approve multiple updates approveMultiple: async (updateIds: string[]): Promise => { await api.post('/updates/approve', { update_ids: updateIds }); }, // Reject/cancel update rejectUpdate: async (id: string): Promise => { await api.post(`/updates/${id}/reject`); }, // Install update immediately installUpdate: async (id: string): Promise => { await api.post(`/updates/${id}/install`); }, // Get update logs getUpdateLogs: async (id: string, limit?: number): Promise<{ logs: any[]; count: number }> => { const response = await api.get(`/updates/${id}/logs`, { params: limit ? { limit } : undefined }); return response.data; }, // Retry a failed, timed_out, or cancelled command retryCommand: async (commandId: string): Promise<{ message: string; command_id: string; new_id: string }> => { const response = await api.post(`/commands/${commandId}/retry`); return response.data; }, // Cancel a pending or sent command cancelCommand: async (commandId: string): Promise<{ message: string }> => { const response = await api.post(`/commands/${commandId}/cancel`); return response.data; }, // Get active commands for live command control getActiveCommands: async (): Promise<{ commands: any[]; count: number }> => { const response = await api.get('/commands/active'); return response.data; }, // Get recent commands for retry functionality getRecentCommands: async (limit?: number): Promise<{ commands: any[]; count: number; limit: number }> => { const response = await api.get('/commands/recent', { params: limit ? { limit } : undefined }); return response.data; }, // Clear failed commands with filtering options clearFailedCommands: async (options?: { olderThanDays?: number; onlyRetried?: boolean; allFailed?: boolean; }): Promise<{ message: string; count: number; cheeky_warning?: string }> => { const params = new URLSearchParams(); if (options?.olderThanDays !== undefined) { params.append('older_than_days', options.olderThanDays.toString()); } if (options?.onlyRetried) { params.append('only_retried', 'true'); } if (options?.allFailed) { params.append('all_failed', 'true'); } const response = await api.delete(`/commands/failed${params.toString() ? '?' + params.toString() : ''}`); return response.data; }, // Get available update packages getPackages: async (params?: { version?: string; platform?: string; limit?: number; offset?: number; }): Promise<{ packages: AgentUpdatePackage[]; total: number; limit: number; offset: number }> => { const response = await api.get('/updates/packages', { params }); return response.data; }, // Sign new update package signPackage: async (packageData: { version: string; platform: string; architecture: string; binary_path: string; }): Promise<{ message: string; package: UpdatePackage }> => { const response = await api.post('/updates/packages/sign', packageData); return response.data; }, }; export const statsApi = { // Get dashboard statistics getDashboardStats: async (): Promise => { const response = await api.get('/stats/summary'); return response.data; }, }; export const logApi = { // Get all logs with filtering for universal log view getAllLogs: async (params?: { page?: number; page_size?: number; agent_id?: string; action?: string; result?: string; since?: string; }): Promise<{ logs: any[]; total: number; page: number; page_size: number }> => { const response = await api.get('/logs', { params }); return response.data; }, // Get active operations for live status view getActiveOperations: async (): Promise<{ operations: any[]; count: number }> => { const response = await api.get('/logs/active'); return response.data; }, // Get active commands for live command control getActiveCommands: async (): Promise<{ commands: any[]; count: number }> => { const response = await api.get('/commands/active'); return response.data; }, // Get recent commands for retry functionality getRecentCommands: async (limit?: number): Promise<{ commands: any[]; count: number; limit: number }> => { const response = await api.get('/commands/recent', { params: limit ? { limit } : undefined }); return response.data; }, }; export const authApi = { // Login with username and password login: async (credentials: { username: string; password: string }): Promise<{ token: string; user: any }> => { const response = await api.post('/auth/login', credentials); return response.data; }, // Verify token verifyToken: async (): Promise<{ valid: boolean }> => { const response = await api.get('/auth/verify'); return response.data; }, // Logout logout: async (): Promise => { await api.post('/auth/logout'); }, }; // Setup API for server configuration (uses nginx proxy) const setupApiInstance = axios.create({ baseURL: '/api', timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); export const setupApi = { // Check server health and status checkHealth: async (): Promise<{ status: string }> => { const response = await setupApiInstance.get('/health'); return response.data; }, // Submit server configuration configure: async (config: { adminUser: string; adminPassword: string; dbHost: string; dbPort: string; dbName: string; dbUser: string; dbPassword: string; serverHost: string; serverPort: string; maxSeats: string; }): Promise<{ message: string; jwtSecret?: string; envContent?: string; manualRestartRequired?: boolean; manualRestartCommand?: string; configFilePath?: string }> => { const response = await setupApiInstance.post('/setup/configure', config); return response.data; }, }; // Utility functions export const createQueryString = (params: Record): string => { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { if (Array.isArray(value)) { value.forEach(v => searchParams.append(key, v)); } else { searchParams.append(key, value.toString()); } } }); return searchParams.toString(); }; // Error handling utility export const handleApiError = (error: any): ApiError => { if (axios.isAxiosError(error)) { const status = error.response?.status; const data = error.response?.data; if (status === 401) { return { message: 'Authentication required. Please log in.', code: 'UNAUTHORIZED', }; } if (status === 403) { return { message: 'Access denied. You do not have permission to perform this action.', code: 'FORBIDDEN', }; } if (status === 404) { return { message: 'The requested resource was not found.', code: 'NOT_FOUND', }; } if (status === 429) { return { message: 'Too many requests. Please try again later.', code: 'RATE_LIMIT_EXCEEDED', }; } if (status && status >= 500) { return { message: 'Server error. Please try again later.', code: 'SERVER_ERROR', }; } return { message: data?.message || error.message || 'An error occurred', code: data?.code || 'UNKNOWN_ERROR', details: data?.details, }; } return { message: error.message || 'An unexpected error occurred', code: 'UNKNOWN_ERROR', }; }; // Docker-specific API endpoints export const dockerApi = { // Get all Docker containers and images across all agents getContainers: async (params?: { page?: number; page_size?: number; agent?: string; status?: string; search?: string; }): Promise => { const response = await api.get('/docker/containers', { params }); return response.data; }, // Get Docker containers for a specific agent getAgentContainers: async (agentId: string, params?: { page?: number; page_size?: number; status?: string; search?: string; }): Promise => { const response = await api.get(`/agents/${agentId}/docker`, { params }); return response.data; }, // Get Docker statistics getStats: async (): Promise => { const response = await api.get('/docker/stats'); return response.data; }, // Approve Docker image update approveUpdate: async (containerId: string, imageId: string, scheduledAt?: string): Promise => { await api.post(`/docker/containers/${containerId}/images/${imageId}/approve`, { scheduled_at: scheduledAt, }); }, // Reject Docker image update rejectUpdate: async (containerId: string, imageId: string): Promise => { await api.post(`/docker/containers/${containerId}/images/${imageId}/reject`); }, // Install Docker image update installUpdate: async (containerId: string, imageId: string): Promise => { await api.post(`/docker/containers/${containerId}/images/${imageId}/install`); }, // Bulk approve Docker updates bulkApproveUpdates: async (updates: Array<{ containerId: string; imageId: string }>, scheduledAt?: string): Promise<{ approved: number }> => { const response = await api.post('/docker/updates/bulk-approve', { updates, scheduled_at: scheduledAt, }); return response.data; }, // Bulk reject Docker updates bulkRejectUpdates: async (updates: Array<{ containerId: string; imageId: string }>): Promise<{ rejected: number }> => { const response = await api.post('/docker/updates/bulk-reject', { updates, }); return response.data; }, // Trigger Docker scan on agents triggerScan: async (agentIds?: string[]): Promise => { await api.post('/docker/scan', { agent_ids: agentIds }); }, }; // Admin API endpoints export const adminApi = { // Registration Token Management tokens: { // Get all registration tokens getTokens: async (params?: { page?: number; page_size?: number; is_active?: boolean; label?: string; }): Promise<{ tokens: RegistrationToken[]; total: number; page: number; page_size: number }> => { const response = await api.get('/admin/registration-tokens', { params }); return response.data; }, // Get single registration token getToken: async (id: string): Promise => { const response = await api.get(`/admin/registration-tokens/${id}`); return response.data; }, // Create new registration token createToken: async (request: CreateRegistrationTokenRequest): Promise => { const response = await api.post('/admin/registration-tokens', request); return response.data; }, // Revoke registration token (soft delete) revokeToken: async (id: string): Promise => { await api.delete(`/admin/registration-tokens/${id}`); }, // Delete registration token (hard delete) deleteToken: async (id: string): Promise => { await api.delete(`/admin/registration-tokens/delete/${id}`); }, // Get registration token statistics getStats: async (): Promise => { const response = await api.get('/admin/registration-tokens/stats'); return response.data; }, // Cleanup expired tokens cleanup: async (): Promise<{ cleaned: number }> => { const response = await api.post('/admin/registration-tokens/cleanup'); return response.data; }, }, // Rate Limiting Management rateLimits: { // Get all rate limit configurations getConfigs: async (): Promise => { const response = await api.get('/admin/rate-limits'); // Backend returns { settings: {...}, updated_at: "..." } // Transform settings object to array format expected by frontend const settings = response.data.settings || {}; const configs: RateLimitConfig[] = Object.entries(settings).map(([endpoint, config]: [string, any]) => ({ ...config, endpoint, updated_at: response.data.updated_at, // Preserve update timestamp })); return configs; }, // Update rate limit configuration updateConfig: async (endpoint: string, config: Partial): Promise => { const response = await api.put(`/admin/rate-limits/${endpoint}`, config); return response.data; }, // Update all rate limit configurations updateAllConfigs: async (configs: RateLimitConfig[]): Promise => { const response = await api.put('/admin/rate-limits', { configs }); return response.data; }, // Reset rate limit configurations to defaults resetConfigs: async (): Promise => { const response = await api.post('/admin/rate-limits/reset'); return response.data; }, // Get rate limit statistics getStats: async (): Promise => { const response = await api.get('/admin/rate-limits/stats'); return response.data; }, // Get rate limit usage getUsage: async (): Promise => { const response = await api.get('/admin/rate-limits/usage'); return response.data; }, // Get rate limit summary getSummary: async (): Promise => { const response = await api.get('/admin/rate-limits/summary'); return response.data; }, // Cleanup expired rate limit data cleanup: async (): Promise<{ cleaned: number }> => { const response = await api.post('/admin/rate-limits/cleanup'); return response.data; }, }, // System Administration system: { // Get system health and status getHealth: async (): Promise<{ status: 'healthy' | 'degraded' | 'unhealthy'; uptime: number; version: string; database_status: 'connected' | 'disconnected'; active_agents: number; active_tokens: number; rate_limits_enabled: boolean; }> => { const response = await api.get('/admin/system/health'); return response.data; }, // Get active agents getActiveAgents: async (): Promise<{ agents: Array<{ id: string; hostname: string; last_seen: string; status: string; }>; count: number; }> => { const response = await api.get('/admin/system/active-agents'); return response.data; }, // Get system configuration getConfig: async (): Promise> => { const response = await api.get('/admin/system/config'); return response.data; }, // Update system configuration updateConfig: async (config: Record): Promise> => { const response = await api.put('/admin/system/config', config); return response.data; }, }, }; // Security API endpoints export const securityApi = { // Get comprehensive security overview getOverview: async (): Promise<{ timestamp: string; overall_status: 'healthy' | 'degraded' | 'unhealthy'; subsystems: { ed25519_signing: { status: string; enabled: boolean }; nonce_validation: { status: string; enabled: boolean }; machine_binding: { status: string; enabled: boolean }; command_validation: { status: string; enabled: boolean }; }; alerts: string[]; recommendations: string[]; }> => { const response = await api.get('/security/overview'); return response.data; }, // Get Ed25519 signing service status getSigningStatus: async (): Promise<{ status: string; timestamp: string; checks: { service_initialized: boolean; public_key_available: boolean; signing_operational: boolean; }; public_key_fingerprint?: string; algorithm?: string; }> => { const response = await api.get('/security/signing'); return response.data; }, // Get nonce validation status getNonceStatus: async (): Promise<{ status: string; timestamp: string; checks: { validation_enabled: boolean; max_age_minutes: number; recent_validations: number; validation_failures: number; }; details: { nonce_format: string; signature_algorithm: string; replay_protection: string; }; }> => { const response = await api.get('/security/nonce'); return response.data; }, // Get machine binding status getMachineBindingStatus: async (): Promise<{ status: string; timestamp: string; checks: { binding_enforced: boolean; min_agent_version: string; fingerprint_required: boolean; recent_violations: number; }; details: { enforcement_method: string; binding_scope: string; violation_action: string; }; }> => { const response = await api.get('/security/machine-binding'); return response.data; }, // Get command validation status getCommandValidationStatus: async (): Promise<{ status: string; timestamp: string; metrics: { total_pending_commands: number; agents_with_pending: number; commands_last_hour: number; commands_last_24h: number; }; checks: { command_processing: string; backpressure_active: boolean; agent_responsive: string; }; }> => { const response = await api.get('/security/commands'); return response.data; }, // Get detailed security metrics getMetrics: async (): Promise<{ timestamp: string; signing: { public_key_fingerprint: string; algorithm: string; key_size: number; configured: boolean; }; nonce: { max_age_seconds: number; format: string; }; machine_binding: { min_version: string; enforcement: string; }; command_processing: { backpressure_threshold: number; rate_limit_per_second: number; }; }> => { const response = await api.get('/security/metrics'); return response.data; }, }; // Storage Metrics API export const storageMetricsApi = { // Report storage metrics (agent only) async reportStorageMetrics(agentID: string, data: any): Promise { await api.post(`/agents/${agentID}/storage-metrics`, data); }, // Get storage metrics for an agent async getStorageMetrics(agentID: string): Promise { const response = await api.get(`/agents/${agentID}/storage-metrics`); return response.data; }, }; export default api;