🚀 Release Hive Platform v1.1 - Complete Authentication & Architecture Overhaul
Major Features: ✅ JWT Bearer Token authentication system with secure token management ✅ API key generation and management with scoped permissions ✅ Complete user management (registration, login, logout, password change) ✅ Frontend authentication components and context integration Backend Architecture Improvements: ✅ CORS configuration via environment variables (CORS_ORIGINS) ✅ Dependency injection pattern for unified coordinator ✅ Database schema fixes with UUID support and SQLAlchemy compliance ✅ Task persistence replaced in-memory storage with database-backed system ✅ Service separation following Single Responsibility Principle ✅ Fixed SQLAlchemy metadata column naming conflicts Infrastructure & Testing: ✅ Comprehensive Jest unit testing and Playwright e2e testing infrastructure ✅ GitHub Actions CI/CD pipeline integration ✅ Enhanced API clients matching PROJECT_PLAN.md specifications ✅ Docker Swarm deployment with proper networking and service connectivity Database & Security: ✅ UUID-based user models with proper validation ✅ Unified database schema with authentication tables ✅ Token blacklisting and refresh token management ✅ Secure password hashing with bcrypt ✅ API key scoping and permissions system API Enhancements: ✅ Authentication endpoints (/api/auth/*) ✅ Task management with database persistence ✅ Enhanced monitoring and health check endpoints ✅ Comprehensive error handling and validation Deployment: ✅ Successfully deployed to Docker Swarm at https://hive.home.deepblack.cloud ✅ All services operational with proper networking ✅ Environment-based configuration support 🛠️ Technical Debt Resolved: - Fixed global coordinator instances with proper dependency injection - Replaced hardcoded CORS origins with environment variables - Unified User model schema conflicts across authentication system - Implemented database persistence for critical task storage - Created comprehensive testing infrastructure This release transforms Hive from a development prototype into a production-ready distributed AI orchestration platform with enterprise-grade authentication, proper architectural patterns, and robust deployment infrastructure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
382
frontend/src/api/agents.ts
Normal file
382
frontend/src/api/agents.ts
Normal file
@@ -0,0 +1,382 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Types
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
endpoint: string;
|
||||
model: string;
|
||||
specialty: string;
|
||||
max_concurrent: number;
|
||||
current_tasks: number;
|
||||
agent_type: 'ollama' | 'cli';
|
||||
status: 'online' | 'offline' | 'busy' | 'error';
|
||||
hardware?: {
|
||||
gpu_type?: string;
|
||||
vram_gb?: number;
|
||||
cpu_cores?: number;
|
||||
};
|
||||
capabilities: string[];
|
||||
specializations: string[];
|
||||
performance_history?: number[];
|
||||
last_heartbeat: string;
|
||||
uptime?: number;
|
||||
cli_config?: Record<string, any>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentRequest {
|
||||
name: string;
|
||||
endpoint: string;
|
||||
model: string;
|
||||
specialty: string;
|
||||
max_concurrent?: number;
|
||||
agent_type?: 'ollama' | 'cli';
|
||||
hardware?: {
|
||||
gpu_type?: string;
|
||||
vram_gb?: number;
|
||||
cpu_cores?: number;
|
||||
};
|
||||
capabilities?: string[];
|
||||
specializations?: string[];
|
||||
cli_config?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface UpdateAgentRequest {
|
||||
name?: string;
|
||||
endpoint?: string;
|
||||
model?: string;
|
||||
specialty?: string;
|
||||
max_concurrent?: number;
|
||||
hardware?: {
|
||||
gpu_type?: string;
|
||||
vram_gb?: number;
|
||||
cpu_cores?: number;
|
||||
};
|
||||
capabilities?: string[];
|
||||
specializations?: string[];
|
||||
cli_config?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface AgentCapability {
|
||||
id: string;
|
||||
agent_id: string;
|
||||
capability: string;
|
||||
proficiency_score: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface AgentPerformance {
|
||||
agent_id: string;
|
||||
timestamp: string;
|
||||
response_time: number;
|
||||
cpu_usage: number;
|
||||
memory_usage: number;
|
||||
gpu_usage?: number;
|
||||
gpu_memory?: number;
|
||||
tasks_completed: number;
|
||||
tasks_failed: number;
|
||||
throughput: number;
|
||||
}
|
||||
|
||||
export interface AgentHealth {
|
||||
agent_id: string;
|
||||
status: 'healthy' | 'degraded' | 'unhealthy';
|
||||
response_time: number;
|
||||
last_check: string;
|
||||
error_message?: string;
|
||||
details: {
|
||||
connectivity: boolean;
|
||||
model_loaded: boolean;
|
||||
resources_available: boolean;
|
||||
queue_size: number;
|
||||
};
|
||||
}
|
||||
|
||||
// API client
|
||||
const apiClient = axios.create({
|
||||
baseURL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Request interceptor to add auth token
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor for error handling
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Clear tokens and redirect to login
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Agent CRUD operations
|
||||
export const getAgents = async (params?: {
|
||||
status?: string;
|
||||
specialty?: string;
|
||||
agent_type?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<Agent[]> => {
|
||||
const response = await apiClient.get('/api/agents', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgent = async (agentId: string): Promise<Agent> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const createAgent = async (data: CreateAgentRequest): Promise<Agent> => {
|
||||
const response = await apiClient.post('/api/agents', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateAgent = async (agentId: string, data: UpdateAgentRequest): Promise<Agent> => {
|
||||
const response = await apiClient.put(`/api/agents/${agentId}`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteAgent = async (agentId: string): Promise<void> => {
|
||||
await apiClient.delete(`/api/agents/${agentId}`);
|
||||
};
|
||||
|
||||
// Agent Status & Health
|
||||
export const getAgentStatus = async (agentId: string): Promise<any> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/status`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgentHealth = async (agentId: string): Promise<AgentHealth> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/health`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const checkAgentHealth = async (agentId: string): Promise<AgentHealth> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/health-check`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const pingAgent = async (agentId: string): Promise<{ success: boolean; response_time: number }> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/ping`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Agent Capabilities
|
||||
export const getAgentCapabilities = async (agentId: string): Promise<AgentCapability[]> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/capabilities`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const addAgentCapability = async (agentId: string, capability: string, proficiencyScore: number): Promise<AgentCapability> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/capabilities`, {
|
||||
capability,
|
||||
proficiency_score: proficiencyScore,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateAgentCapability = async (agentId: string, capabilityId: string, proficiencyScore: number): Promise<AgentCapability> => {
|
||||
const response = await apiClient.put(`/api/agents/${agentId}/capabilities/${capabilityId}`, {
|
||||
proficiency_score: proficiencyScore,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeAgentCapability = async (agentId: string, capabilityId: string): Promise<void> => {
|
||||
await apiClient.delete(`/api/agents/${agentId}/capabilities/${capabilityId}`);
|
||||
};
|
||||
|
||||
// Agent Performance
|
||||
export const getAgentPerformance = async (agentId: string, timeRange: string = '1h'): Promise<AgentPerformance[]> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/performance?time_range=${timeRange}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgentMetrics = async (agentId: string): Promise<any> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/metrics`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Agent Tasks
|
||||
export const getAgentTasks = async (agentId: string, params?: {
|
||||
status?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<any[]> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/tasks`, { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const assignTaskToAgent = async (agentId: string, taskId: string): Promise<any> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/tasks`, { task_id: taskId });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeTaskFromAgent = async (agentId: string, taskId: string): Promise<void> => {
|
||||
await apiClient.delete(`/api/agents/${agentId}/tasks/${taskId}`);
|
||||
};
|
||||
|
||||
// Agent Models & Configuration
|
||||
export const getAgentModels = async (agentId: string): Promise<string[]> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/models`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const switchAgentModel = async (agentId: string, model: string): Promise<Agent> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/switch-model`, { model });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgentConfig = async (agentId: string): Promise<Record<string, any>> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/config`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateAgentConfig = async (agentId: string, config: Record<string, any>): Promise<Record<string, any>> => {
|
||||
const response = await apiClient.put(`/api/agents/${agentId}/config`, config);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Agent Control
|
||||
export const startAgent = async (agentId: string): Promise<Agent> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/start`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const stopAgent = async (agentId: string): Promise<Agent> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/stop`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const restartAgent = async (agentId: string): Promise<Agent> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/restart`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const pauseAgent = async (agentId: string): Promise<Agent> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/pause`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const resumeAgent = async (agentId: string): Promise<Agent> => {
|
||||
const response = await apiClient.post(`/api/agents/${agentId}/resume`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// CLI Agent specific functions
|
||||
export const getCliAgents = async (): Promise<Agent[]> => {
|
||||
const response = await apiClient.get('/api/cli-agents');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const registerCliAgent = async (data: {
|
||||
id: string;
|
||||
host: string;
|
||||
node_version: string;
|
||||
model?: string;
|
||||
specialization?: string;
|
||||
max_concurrent?: number;
|
||||
agent_type?: string;
|
||||
command_timeout?: number;
|
||||
ssh_timeout?: number;
|
||||
}): Promise<Agent> => {
|
||||
const response = await apiClient.post('/api/cli-agents/register', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const registerPredefinedCliAgents = async (): Promise<Agent[]> => {
|
||||
const response = await apiClient.post('/api/cli-agents/register-predefined');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const healthCheckCliAgent = async (agentId: string): Promise<any> => {
|
||||
const response = await apiClient.post(`/api/cli-agents/${agentId}/health-check`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getCliAgentStatistics = async (): Promise<any> => {
|
||||
const response = await apiClient.get('/api/cli-agents/statistics/all');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const unregisterCliAgent = async (agentId: string): Promise<any> => {
|
||||
const response = await apiClient.delete(`/api/cli-agents/${agentId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Bulk operations
|
||||
export const getAvailableAgents = async (specialty?: string): Promise<Agent[]> => {
|
||||
const response = await apiClient.get('/api/agents/available', {
|
||||
params: specialty ? { specialty } : undefined,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgentsBySpecialty = async (specialty: string): Promise<Agent[]> => {
|
||||
const response = await apiClient.get(`/api/agents/specialty/${specialty}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getOptimalAgent = async (taskType: string, requirements?: Record<string, any>): Promise<Agent> => {
|
||||
const response = await apiClient.post('/api/agents/optimal', {
|
||||
task_type: taskType,
|
||||
requirements,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default {
|
||||
getAgents,
|
||||
getAgent,
|
||||
createAgent,
|
||||
updateAgent,
|
||||
deleteAgent,
|
||||
getAgentStatus,
|
||||
getAgentHealth,
|
||||
checkAgentHealth,
|
||||
pingAgent,
|
||||
getAgentCapabilities,
|
||||
addAgentCapability,
|
||||
updateAgentCapability,
|
||||
removeAgentCapability,
|
||||
getAgentPerformance,
|
||||
getAgentMetrics,
|
||||
getAgentTasks,
|
||||
assignTaskToAgent,
|
||||
removeTaskFromAgent,
|
||||
getAgentModels,
|
||||
switchAgentModel,
|
||||
getAgentConfig,
|
||||
updateAgentConfig,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
restartAgent,
|
||||
pauseAgent,
|
||||
resumeAgent,
|
||||
getCliAgents,
|
||||
registerCliAgent,
|
||||
registerPredefinedCliAgents,
|
||||
healthCheckCliAgent,
|
||||
getCliAgentStatistics,
|
||||
unregisterCliAgent,
|
||||
getAvailableAgents,
|
||||
getAgentsBySpecialty,
|
||||
getOptimalAgent,
|
||||
};
|
||||
@@ -75,8 +75,9 @@ apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Clear token and redirect to login
|
||||
// Clear tokens and redirect to login
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
|
||||
171
frontend/src/api/index.ts
Normal file
171
frontend/src/api/index.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
// Re-export all API modules for centralized access
|
||||
export * from './auth';
|
||||
export * from './tasks';
|
||||
export * from './websocket';
|
||||
|
||||
// Re-export specific exports to avoid conflicts
|
||||
export {
|
||||
// Agent API - avoid conflicts with monitoring
|
||||
getAgents,
|
||||
getAgent,
|
||||
createAgent,
|
||||
updateAgent,
|
||||
deleteAgent,
|
||||
getAgentStatus,
|
||||
getAgentCapabilities,
|
||||
addAgentCapability,
|
||||
updateAgentCapability,
|
||||
removeAgentCapability,
|
||||
getAgentPerformance,
|
||||
getAgentTasks,
|
||||
assignTaskToAgent,
|
||||
removeTaskFromAgent,
|
||||
getAgentModels,
|
||||
switchAgentModel,
|
||||
getAgentConfig,
|
||||
updateAgentConfig,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
restartAgent,
|
||||
pauseAgent,
|
||||
resumeAgent,
|
||||
getCliAgents,
|
||||
registerCliAgent,
|
||||
registerPredefinedCliAgents,
|
||||
healthCheckCliAgent,
|
||||
getCliAgentStatistics,
|
||||
unregisterCliAgent,
|
||||
getAvailableAgents,
|
||||
getAgentsBySpecialty,
|
||||
getOptimalAgent
|
||||
} from './agents';
|
||||
|
||||
// Monitoring API - use different names for conflicting exports
|
||||
export {
|
||||
getSystemHealth,
|
||||
getSystemStatus,
|
||||
getSystemMetrics,
|
||||
getAgentMetrics as getAgentMonitoringMetrics,
|
||||
getAgentHealth as getAgentMonitoringHealth,
|
||||
getPerformanceMetrics,
|
||||
getTaskPerformance,
|
||||
getWorkflowPerformance,
|
||||
getAlerts,
|
||||
getAlert,
|
||||
acknowledgeAlert,
|
||||
resolveAlert,
|
||||
getAlertRules,
|
||||
createAlertRule,
|
||||
updateAlertRule,
|
||||
deleteAlertRule,
|
||||
getSystemLogs,
|
||||
getAgentLogs,
|
||||
getTaskLogs,
|
||||
getWorkflowLogs
|
||||
} from './monitoring';
|
||||
|
||||
// Import the enhanced services from services/api.ts
|
||||
export {
|
||||
projectApi,
|
||||
workflowApi,
|
||||
executionApi,
|
||||
agentApi,
|
||||
systemApi,
|
||||
clusterApi
|
||||
} from '../services/api';
|
||||
|
||||
// Import default exports with aliases to avoid conflicts
|
||||
export { default as authApi } from './auth';
|
||||
export { default as agentsApi } from './agents';
|
||||
export { default as tasksApi } from './tasks';
|
||||
export { default as monitoringApi } from './monitoring';
|
||||
export { default as webSocketService } from './websocket';
|
||||
|
||||
// Common types that might be used across multiple API modules
|
||||
export interface PaginationParams {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export interface SortParams {
|
||||
sort_by?: string;
|
||||
sort_order?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface FilterParams {
|
||||
search?: string;
|
||||
filters?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface APIResponse<T> {
|
||||
data: T;
|
||||
total?: number;
|
||||
page?: number;
|
||||
pages?: number;
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface APIError {
|
||||
detail: string;
|
||||
status_code: number;
|
||||
timestamp: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
// Unified API configuration
|
||||
export const API_CONFIG = {
|
||||
BASE_URL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
|
||||
TIMEOUT: 30000,
|
||||
RETRY_ATTEMPTS: 3,
|
||||
RETRY_DELAY: 1000,
|
||||
};
|
||||
|
||||
// Helper function to handle API errors consistently
|
||||
export const handleAPIError = (error: unknown): APIError => {
|
||||
if (error && typeof error === 'object' && 'response' in error) {
|
||||
const axiosError = error as any;
|
||||
if (axiosError.response?.data) {
|
||||
return {
|
||||
detail: axiosError.response.data.detail || axiosError.response.data.message || 'Unknown error',
|
||||
status_code: axiosError.response.status,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: axiosError.config?.url,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (error && typeof error === 'object' && 'message' in error) {
|
||||
return {
|
||||
detail: (error as Error).message || 'Unknown error',
|
||||
status_code: 0,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
detail: 'Network error',
|
||||
status_code: 0,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
// Generic API function with retry logic
|
||||
export const apiCall = async <T>(
|
||||
apiFunction: () => Promise<T>,
|
||||
retries: number = API_CONFIG.RETRY_ATTEMPTS,
|
||||
delay: number = API_CONFIG.RETRY_DELAY
|
||||
): Promise<T> => {
|
||||
try {
|
||||
return await apiFunction();
|
||||
} catch (error: unknown) {
|
||||
const axiosError = error as any;
|
||||
if (retries > 0 && axiosError.response?.status >= 500) {
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return apiCall(apiFunction, retries - 1, delay * 2);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
281
frontend/src/api/monitoring.ts
Normal file
281
frontend/src/api/monitoring.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Types
|
||||
export interface SystemHealth {
|
||||
status: 'healthy' | 'degraded' | 'unhealthy';
|
||||
uptime: number;
|
||||
version: string;
|
||||
components: {
|
||||
database: ComponentHealth;
|
||||
redis: ComponentHealth;
|
||||
agents: ComponentHealth;
|
||||
workflows: ComponentHealth;
|
||||
};
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface ComponentHealth {
|
||||
status: 'healthy' | 'degraded' | 'unhealthy';
|
||||
response_time?: number;
|
||||
error_message?: string;
|
||||
last_check: string;
|
||||
}
|
||||
|
||||
export interface SystemMetrics {
|
||||
timestamp: string;
|
||||
cpu_usage: number;
|
||||
memory_usage: number;
|
||||
disk_usage: number;
|
||||
active_connections: number;
|
||||
total_agents: number;
|
||||
active_agents: number;
|
||||
total_tasks: number;
|
||||
active_tasks: number;
|
||||
completed_tasks_today: number;
|
||||
failed_tasks_today: number;
|
||||
average_task_duration: number;
|
||||
system_load: number;
|
||||
}
|
||||
|
||||
export interface AgentMetrics {
|
||||
agent_id: string;
|
||||
agent_name: string;
|
||||
status: 'online' | 'offline' | 'busy' | 'error';
|
||||
cpu_usage: number;
|
||||
memory_usage: number;
|
||||
gpu_usage?: number;
|
||||
gpu_memory?: number;
|
||||
current_tasks: number;
|
||||
completed_tasks: number;
|
||||
failed_tasks: number;
|
||||
average_response_time: number;
|
||||
last_heartbeat: string;
|
||||
uptime: number;
|
||||
}
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
time_range: string;
|
||||
metrics: {
|
||||
timestamp: string;
|
||||
task_throughput: number;
|
||||
average_response_time: number;
|
||||
error_rate: number;
|
||||
agent_utilization: number;
|
||||
system_cpu: number;
|
||||
system_memory: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
id: string;
|
||||
type: 'critical' | 'warning' | 'info';
|
||||
severity: 'high' | 'medium' | 'low';
|
||||
title: string;
|
||||
message: string;
|
||||
component: string;
|
||||
created_at: string;
|
||||
resolved_at?: string;
|
||||
acknowledged_at?: string;
|
||||
acknowledged_by?: string;
|
||||
is_resolved: boolean;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface AlertRule {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: 'threshold' | 'anomaly' | 'health_check';
|
||||
metric: string;
|
||||
operator: 'gt' | 'lt' | 'eq' | 'ne';
|
||||
threshold: number;
|
||||
severity: 'high' | 'medium' | 'low';
|
||||
enabled: boolean;
|
||||
notification_channels: string[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// API client
|
||||
const apiClient = axios.create({
|
||||
baseURL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Request interceptor to add auth token
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor for error handling
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Clear tokens and redirect to login
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// System Health & Status
|
||||
export const getSystemHealth = async (): Promise<SystemHealth> => {
|
||||
const response = await apiClient.get('/api/health');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getSystemStatus = async (): Promise<any> => {
|
||||
const response = await apiClient.get('/api/status');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getSystemMetrics = async (): Promise<SystemMetrics> => {
|
||||
const response = await apiClient.get('/api/monitoring/metrics');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Agent Monitoring
|
||||
export const getAgentMetrics = async (agentId?: string): Promise<AgentMetrics[]> => {
|
||||
const url = agentId ? `/api/monitoring/agents/${agentId}/metrics` : '/api/monitoring/agents/metrics';
|
||||
const response = await apiClient.get(url);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgentHealth = async (agentId: string): Promise<ComponentHealth> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/health`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Performance Monitoring
|
||||
export const getPerformanceMetrics = async (timeRange: string = '1h'): Promise<PerformanceMetrics> => {
|
||||
const response = await apiClient.get(`/api/monitoring/performance?time_range=${timeRange}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTaskPerformance = async (timeRange: string = '1h'): Promise<any> => {
|
||||
const response = await apiClient.get(`/api/monitoring/tasks/performance?time_range=${timeRange}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getWorkflowPerformance = async (timeRange: string = '1h'): Promise<any> => {
|
||||
const response = await apiClient.get(`/api/monitoring/workflows/performance?time_range=${timeRange}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Alerts
|
||||
export const getAlerts = async (params?: {
|
||||
type?: string;
|
||||
severity?: string;
|
||||
resolved?: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<Alert[]> => {
|
||||
const response = await apiClient.get('/api/monitoring/alerts', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAlert = async (alertId: string): Promise<Alert> => {
|
||||
const response = await apiClient.get(`/api/monitoring/alerts/${alertId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const acknowledgeAlert = async (alertId: string): Promise<Alert> => {
|
||||
const response = await apiClient.post(`/api/monitoring/alerts/${alertId}/acknowledge`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const resolveAlert = async (alertId: string, resolution?: string): Promise<Alert> => {
|
||||
const response = await apiClient.post(`/api/monitoring/alerts/${alertId}/resolve`, {
|
||||
resolution,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Alert Rules
|
||||
export const getAlertRules = async (): Promise<AlertRule[]> => {
|
||||
const response = await apiClient.get('/api/monitoring/alert-rules');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const createAlertRule = async (rule: Omit<AlertRule, 'id' | 'created_at' | 'updated_at'>): Promise<AlertRule> => {
|
||||
const response = await apiClient.post('/api/monitoring/alert-rules', rule);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateAlertRule = async (ruleId: string, rule: Partial<AlertRule>): Promise<AlertRule> => {
|
||||
const response = await apiClient.put(`/api/monitoring/alert-rules/${ruleId}`, rule);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteAlertRule = async (ruleId: string): Promise<void> => {
|
||||
await apiClient.delete(`/api/monitoring/alert-rules/${ruleId}`);
|
||||
};
|
||||
|
||||
// Logs
|
||||
export const getSystemLogs = async (params?: {
|
||||
level?: 'debug' | 'info' | 'warning' | 'error' | 'critical';
|
||||
component?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<any[]> => {
|
||||
const response = await apiClient.get('/api/monitoring/logs', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAgentLogs = async (agentId: string, params?: {
|
||||
level?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
limit?: number;
|
||||
}): Promise<any[]> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/logs`, { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTaskLogs = async (taskId: string): Promise<any[]> => {
|
||||
const response = await apiClient.get(`/api/tasks/${taskId}/logs`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getWorkflowLogs = async (workflowId: string, executionId?: string): Promise<any[]> => {
|
||||
const url = executionId
|
||||
? `/api/workflows/${workflowId}/executions/${executionId}/logs`
|
||||
: `/api/workflows/${workflowId}/logs`;
|
||||
const response = await apiClient.get(url);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Export all functions
|
||||
export default {
|
||||
getSystemHealth,
|
||||
getSystemStatus,
|
||||
getSystemMetrics,
|
||||
getAgentMetrics,
|
||||
getAgentHealth,
|
||||
getPerformanceMetrics,
|
||||
getTaskPerformance,
|
||||
getWorkflowPerformance,
|
||||
getAlerts,
|
||||
getAlert,
|
||||
acknowledgeAlert,
|
||||
resolveAlert,
|
||||
getAlertRules,
|
||||
createAlertRule,
|
||||
updateAlertRule,
|
||||
deleteAlertRule,
|
||||
getSystemLogs,
|
||||
getAgentLogs,
|
||||
getTaskLogs,
|
||||
getWorkflowLogs,
|
||||
};
|
||||
161
frontend/src/api/tasks.ts
Normal file
161
frontend/src/api/tasks.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Types
|
||||
export interface Task {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
type: string; // AgentType
|
||||
priority: number;
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
context: Record<string, any>;
|
||||
payload: Record<string, any>;
|
||||
assigned_agent?: string;
|
||||
result?: Record<string, any>;
|
||||
created_at: string;
|
||||
completed_at?: string;
|
||||
workflow_id?: string;
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
export interface CreateTaskRequest {
|
||||
title: string;
|
||||
description?: string;
|
||||
type: string;
|
||||
priority?: number;
|
||||
context: Record<string, any>;
|
||||
workflow_id?: string;
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateTaskRequest {
|
||||
title?: string;
|
||||
description?: string;
|
||||
priority?: number;
|
||||
status?: string;
|
||||
assigned_agent?: string;
|
||||
result?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TaskStatistics {
|
||||
total: number;
|
||||
pending: number;
|
||||
in_progress: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
success_rate: number;
|
||||
average_completion_time: number;
|
||||
}
|
||||
|
||||
// API client
|
||||
const apiClient = axios.create({
|
||||
baseURL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Request interceptor to add auth token
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor for error handling
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Clear tokens and redirect to login
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Task API functions
|
||||
export const getTasks = async (params?: {
|
||||
status?: string;
|
||||
assigned_agent?: string;
|
||||
workflow_id?: string;
|
||||
priority?: number;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<Task[]> => {
|
||||
const response = await apiClient.get('/api/tasks', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTask = async (taskId: string): Promise<Task> => {
|
||||
const response = await apiClient.get(`/api/tasks/${taskId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const createTask = async (data: CreateTaskRequest): Promise<Task> => {
|
||||
const response = await apiClient.post('/api/tasks', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateTask = async (taskId: string, data: UpdateTaskRequest): Promise<Task> => {
|
||||
const response = await apiClient.put(`/api/tasks/${taskId}`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteTask = async (taskId: string): Promise<void> => {
|
||||
await apiClient.delete(`/api/tasks/${taskId}`);
|
||||
};
|
||||
|
||||
export const cancelTask = async (taskId: string): Promise<Task> => {
|
||||
const response = await apiClient.post(`/api/tasks/${taskId}/cancel`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const retryTask = async (taskId: string): Promise<Task> => {
|
||||
const response = await apiClient.post(`/api/tasks/${taskId}/retry`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const assignTask = async (taskId: string, agentId: string): Promise<Task> => {
|
||||
const response = await apiClient.post(`/api/tasks/${taskId}/assign`, { agent_id: agentId });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTasksByAgent = async (agentId: string): Promise<Task[]> => {
|
||||
const response = await apiClient.get(`/api/agents/${agentId}/tasks`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTasksByWorkflow = async (workflowId: string): Promise<Task[]> => {
|
||||
const response = await apiClient.get(`/api/workflows/${workflowId}/tasks`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTaskStatistics = async (): Promise<TaskStatistics> => {
|
||||
const response = await apiClient.get('/api/tasks/statistics');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTaskQueue = async (): Promise<Task[]> => {
|
||||
const response = await apiClient.get('/api/tasks/queue');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default {
|
||||
getTasks,
|
||||
getTask,
|
||||
createTask,
|
||||
updateTask,
|
||||
deleteTask,
|
||||
cancelTask,
|
||||
retryTask,
|
||||
assignTask,
|
||||
getTasksByAgent,
|
||||
getTasksByWorkflow,
|
||||
getTaskStatistics,
|
||||
getTaskQueue,
|
||||
};
|
||||
297
frontend/src/api/websocket.ts
Normal file
297
frontend/src/api/websocket.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import React from 'react';
|
||||
|
||||
// Types for real-time events
|
||||
export interface TaskUpdate {
|
||||
task_id: string;
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
progress?: number;
|
||||
result?: any;
|
||||
error?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface AgentUpdate {
|
||||
agent_id: string;
|
||||
status: 'online' | 'offline' | 'busy' | 'error';
|
||||
current_tasks: number;
|
||||
cpu_usage?: number;
|
||||
memory_usage?: number;
|
||||
gpu_usage?: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface WorkflowUpdate {
|
||||
workflow_id: string;
|
||||
execution_id: string;
|
||||
status: 'running' | 'completed' | 'failed' | 'paused';
|
||||
current_step?: string;
|
||||
progress?: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface SystemAlert {
|
||||
id: string;
|
||||
type: 'critical' | 'warning' | 'info';
|
||||
severity: 'high' | 'medium' | 'low';
|
||||
title: string;
|
||||
message: string;
|
||||
component: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface MetricsUpdate {
|
||||
timestamp: string;
|
||||
system: {
|
||||
cpu_usage: number;
|
||||
memory_usage: number;
|
||||
disk_usage: number;
|
||||
active_connections: number;
|
||||
};
|
||||
cluster: {
|
||||
total_agents: number;
|
||||
active_agents: number;
|
||||
total_tasks: number;
|
||||
active_tasks: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Event handlers type
|
||||
export interface WebSocketEventHandlers {
|
||||
onTaskUpdate?: (update: TaskUpdate) => void;
|
||||
onAgentUpdate?: (update: AgentUpdate) => void;
|
||||
onWorkflowUpdate?: (update: WorkflowUpdate) => void;
|
||||
onSystemAlert?: (alert: SystemAlert) => void;
|
||||
onMetricsUpdate?: (metrics: MetricsUpdate) => void;
|
||||
onConnect?: () => void;
|
||||
onDisconnect?: () => void;
|
||||
onError?: (error: any) => void;
|
||||
}
|
||||
|
||||
// WebSocket service class
|
||||
export class WebSocketService {
|
||||
private socket: Socket | null = null;
|
||||
private handlers: WebSocketEventHandlers = {};
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 5;
|
||||
private reconnectDelay = 1000;
|
||||
|
||||
constructor() {
|
||||
this.connect();
|
||||
}
|
||||
|
||||
private connect(): void {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
console.warn('No auth token found for WebSocket connection');
|
||||
return;
|
||||
}
|
||||
|
||||
const baseURL = process.env.VITE_API_BASE_URL || 'http://localhost:8087';
|
||||
|
||||
this.socket = io(baseURL, {
|
||||
auth: {
|
||||
token: `Bearer ${token}`,
|
||||
},
|
||||
transports: ['websocket', 'polling'],
|
||||
});
|
||||
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
private setupEventListeners(): void {
|
||||
if (!this.socket) return;
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
console.log('WebSocket connected');
|
||||
this.reconnectAttempts = 0;
|
||||
this.handlers.onConnect?.();
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', (reason) => {
|
||||
console.log('WebSocket disconnected:', reason);
|
||||
this.handlers.onDisconnect?.();
|
||||
|
||||
if (reason === 'io server disconnect') {
|
||||
// Server initiated disconnect, try to reconnect
|
||||
this.handleReconnect();
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('connect_error', (error) => {
|
||||
console.error('WebSocket connection error:', error);
|
||||
this.handlers.onError?.(error);
|
||||
this.handleReconnect();
|
||||
});
|
||||
|
||||
// Task events
|
||||
this.socket.on('task_update', (update: TaskUpdate) => {
|
||||
this.handlers.onTaskUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('task_started', (update: TaskUpdate) => {
|
||||
this.handlers.onTaskUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('task_completed', (update: TaskUpdate) => {
|
||||
this.handlers.onTaskUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('task_failed', (update: TaskUpdate) => {
|
||||
this.handlers.onTaskUpdate?.(update);
|
||||
});
|
||||
|
||||
// Agent events
|
||||
this.socket.on('agent_update', (update: AgentUpdate) => {
|
||||
this.handlers.onAgentUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('agent_connected', (update: AgentUpdate) => {
|
||||
this.handlers.onAgentUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('agent_disconnected', (update: AgentUpdate) => {
|
||||
this.handlers.onAgentUpdate?.(update);
|
||||
});
|
||||
|
||||
// Workflow events
|
||||
this.socket.on('workflow_update', (update: WorkflowUpdate) => {
|
||||
this.handlers.onWorkflowUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('workflow_started', (update: WorkflowUpdate) => {
|
||||
this.handlers.onWorkflowUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('workflow_completed', (update: WorkflowUpdate) => {
|
||||
this.handlers.onWorkflowUpdate?.(update);
|
||||
});
|
||||
|
||||
this.socket.on('workflow_failed', (update: WorkflowUpdate) => {
|
||||
this.handlers.onWorkflowUpdate?.(update);
|
||||
});
|
||||
|
||||
// System events
|
||||
this.socket.on('system_alert', (alert: SystemAlert) => {
|
||||
this.handlers.onSystemAlert?.(alert);
|
||||
});
|
||||
|
||||
this.socket.on('metrics_update', (metrics: MetricsUpdate) => {
|
||||
this.handlers.onMetricsUpdate?.(metrics);
|
||||
});
|
||||
}
|
||||
|
||||
private handleReconnect(): void {
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
console.error('Max reconnection attempts reached');
|
||||
return;
|
||||
}
|
||||
|
||||
this.reconnectAttempts++;
|
||||
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
||||
|
||||
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts}) in ${delay}ms`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
public setEventHandlers(handlers: WebSocketEventHandlers): void {
|
||||
this.handlers = { ...this.handlers, ...handlers };
|
||||
}
|
||||
|
||||
public subscribe(event: string, handler: (data: any) => void): void {
|
||||
this.socket?.on(event, handler);
|
||||
}
|
||||
|
||||
public unsubscribe(event: string, handler?: (data: any) => void): void {
|
||||
if (handler) {
|
||||
this.socket?.off(event, handler);
|
||||
} else {
|
||||
this.socket?.off(event);
|
||||
}
|
||||
}
|
||||
|
||||
public emit(event: string, data?: any): void {
|
||||
this.socket?.emit(event, data);
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this.socket?.disconnect();
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
public isConnected(): boolean {
|
||||
return this.socket?.connected ?? false;
|
||||
}
|
||||
|
||||
// Room management for targeted updates
|
||||
public joinRoom(room: string): void {
|
||||
this.socket?.emit('join_room', room);
|
||||
}
|
||||
|
||||
public leaveRoom(room: string): void {
|
||||
this.socket?.emit('leave_room', room);
|
||||
}
|
||||
|
||||
// Subscribe to specific agent updates
|
||||
public subscribeToAgent(agentId: string): void {
|
||||
this.joinRoom(`agent_${agentId}`);
|
||||
}
|
||||
|
||||
public unsubscribeFromAgent(agentId: string): void {
|
||||
this.leaveRoom(`agent_${agentId}`);
|
||||
}
|
||||
|
||||
// Subscribe to specific workflow updates
|
||||
public subscribeToWorkflow(workflowId: string): void {
|
||||
this.joinRoom(`workflow_${workflowId}`);
|
||||
}
|
||||
|
||||
public unsubscribeFromWorkflow(workflowId: string): void {
|
||||
this.leaveRoom(`workflow_${workflowId}`);
|
||||
}
|
||||
|
||||
// Subscribe to specific task updates
|
||||
public subscribeToTask(taskId: string): void {
|
||||
this.joinRoom(`task_${taskId}`);
|
||||
}
|
||||
|
||||
public unsubscribeFromTask(taskId: string): void {
|
||||
this.leaveRoom(`task_${taskId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const webSocketService = new WebSocketService();
|
||||
|
||||
// React hook for using WebSocket in components
|
||||
export const useWebSocket = (handlers: WebSocketEventHandlers) => {
|
||||
React.useEffect(() => {
|
||||
webSocketService.setEventHandlers(handlers);
|
||||
|
||||
return () => {
|
||||
// Clean up handlers when component unmounts
|
||||
Object.keys(handlers).forEach(key => {
|
||||
webSocketService.setEventHandlers({ [key]: undefined });
|
||||
});
|
||||
};
|
||||
}, [handlers]);
|
||||
|
||||
return {
|
||||
isConnected: webSocketService.isConnected(),
|
||||
subscribe: webSocketService.subscribe.bind(webSocketService),
|
||||
unsubscribe: webSocketService.unsubscribe.bind(webSocketService),
|
||||
emit: webSocketService.emit.bind(webSocketService),
|
||||
subscribeToAgent: webSocketService.subscribeToAgent.bind(webSocketService),
|
||||
unsubscribeFromAgent: webSocketService.unsubscribeFromAgent.bind(webSocketService),
|
||||
subscribeToWorkflow: webSocketService.subscribeToWorkflow.bind(webSocketService),
|
||||
unsubscribeFromWorkflow: webSocketService.unsubscribeFromWorkflow.bind(webSocketService),
|
||||
subscribeToTask: webSocketService.subscribeToTask.bind(webSocketService),
|
||||
unsubscribeFromTask: webSocketService.unsubscribeFromTask.bind(webSocketService),
|
||||
};
|
||||
};
|
||||
|
||||
export default webSocketService;
|
||||
@@ -33,6 +33,7 @@ interface AuthContextType {
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
register: (userData: { email: string; password: string; full_name: string; username: string }) => Promise<void>;
|
||||
logout: () => void;
|
||||
refreshToken: () => Promise<boolean>;
|
||||
updateUser: (userData: Partial<User>) => void;
|
||||
@@ -173,6 +174,41 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const register = async (userData: { email: string; password: string; full_name: string; username: string }): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Registration failed');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const newTokens: AuthTokens = {
|
||||
access_token: data.access_token,
|
||||
refresh_token: data.refresh_token,
|
||||
token_type: data.token_type,
|
||||
expires_in: 3600,
|
||||
};
|
||||
|
||||
setTokens(newTokens);
|
||||
setUser(data.user);
|
||||
|
||||
// Store in localStorage
|
||||
localStorage.setItem('hive_tokens', JSON.stringify(newTokens));
|
||||
localStorage.setItem('hive_user', JSON.stringify(data.user));
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
try {
|
||||
// Call logout endpoint if we have a token
|
||||
@@ -220,6 +256,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
refreshToken,
|
||||
updateUser,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Workflow, WorkflowExecution } from '../types/workflow';
|
||||
|
||||
// Create axios instance with base configuration
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
baseURL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
@@ -14,7 +14,7 @@ const api = axios.create({
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
// Add auth token if available
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
@@ -31,7 +31,8 @@ api.interceptors.response.use(
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Handle unauthorized access
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
@@ -148,12 +149,36 @@ export const executionApi = {
|
||||
|
||||
// Cancel an execution
|
||||
cancelExecution: async (id: string): Promise<void> => {
|
||||
await api.post(`/executions/${id}/cancel`);
|
||||
await api.post(`/api/executions/${id}/cancel`);
|
||||
},
|
||||
|
||||
// Retry an execution
|
||||
retryExecution: async (id: string): Promise<WorkflowExecution> => {
|
||||
const response = await api.post(`/executions/${id}/retry`);
|
||||
const response = await api.post(`/api/executions/${id}/retry`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Pause an execution
|
||||
pauseExecution: async (id: string): Promise<WorkflowExecution> => {
|
||||
const response = await api.post(`/api/executions/${id}/pause`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Resume an execution
|
||||
resumeExecution: async (id: string): Promise<WorkflowExecution> => {
|
||||
const response = await api.post(`/api/executions/${id}/resume`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get execution logs
|
||||
getExecutionLogs: async (id: string): Promise<any[]> => {
|
||||
const response = await api.get(`/api/executions/${id}/logs`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get execution steps
|
||||
getExecutionSteps: async (id: string): Promise<any[]> => {
|
||||
const response = await api.get(`/api/executions/${id}/steps`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -229,19 +254,54 @@ export const agentApi = {
|
||||
export const systemApi = {
|
||||
// Get system status
|
||||
getStatus: async () => {
|
||||
const response = await api.get('/status');
|
||||
const response = await api.get('/api/status');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get system health
|
||||
getHealth: async () => {
|
||||
const response = await api.get('/health');
|
||||
const response = await api.get('/api/health');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get system metrics
|
||||
getMetrics: async () => {
|
||||
const response = await api.get('/metrics');
|
||||
const response = await api.get('/api/metrics');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get system configuration
|
||||
getConfig: async () => {
|
||||
const response = await api.get('/api/config');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Update system configuration
|
||||
updateConfig: async (config: Record<string, any>) => {
|
||||
const response = await api.put('/api/config', config);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get system logs
|
||||
getLogs: async (params?: {
|
||||
level?: string;
|
||||
component?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
limit?: number;
|
||||
}) => {
|
||||
const response = await api.get('/api/logs', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// System control
|
||||
restart: async () => {
|
||||
const response = await api.post('/api/system/restart');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
shutdown: async () => {
|
||||
const response = await api.post('/api/system/shutdown');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -250,43 +310,70 @@ export const systemApi = {
|
||||
export const clusterApi = {
|
||||
// Get cluster overview
|
||||
getOverview: async () => {
|
||||
const response = await api.get('/cluster/overview');
|
||||
const response = await api.get('/api/cluster/overview');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get cluster nodes
|
||||
getNodes: async () => {
|
||||
const response = await api.get('/cluster/nodes');
|
||||
const response = await api.get('/api/cluster/nodes');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get node details
|
||||
getNode: async (nodeId: string) => {
|
||||
const response = await api.get(`/cluster/nodes/${nodeId}`);
|
||||
const response = await api.get(`/api/cluster/nodes/${nodeId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get available models
|
||||
getModels: async () => {
|
||||
const response = await api.get('/cluster/models');
|
||||
const response = await api.get('/api/cluster/models');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get n8n workflows
|
||||
getWorkflows: async () => {
|
||||
const response = await api.get('/cluster/workflows');
|
||||
const response = await api.get('/api/cluster/workflows');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get cluster metrics
|
||||
getMetrics: async () => {
|
||||
const response = await api.get('/cluster/metrics');
|
||||
const response = await api.get('/api/cluster/metrics');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get workflow executions
|
||||
getExecutions: async (limit: number = 10) => {
|
||||
const response = await api.get(`/cluster/executions?limit=${limit}`);
|
||||
const response = await api.get(`/api/cluster/executions?limit=${limit}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Add/remove cluster nodes
|
||||
addNode: async (nodeData: any) => {
|
||||
const response = await api.post('/api/cluster/nodes', nodeData);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
removeNode: async (nodeId: string) => {
|
||||
const response = await api.delete(`/api/cluster/nodes/${nodeId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Node control
|
||||
startNode: async (nodeId: string) => {
|
||||
const response = await api.post(`/api/cluster/nodes/${nodeId}/start`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
stopNode: async (nodeId: string) => {
|
||||
const response = await api.post(`/api/cluster/nodes/${nodeId}/stop`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
restartNode: async (nodeId: string) => {
|
||||
const response = await api.post(`/api/cluster/nodes/${nodeId}/restart`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user