diff --git a/.kiro/specs/multi-exchange-data-aggregation/tasks.md b/.kiro/specs/multi-exchange-data-aggregation/tasks.md index 5d3a6d5..acd853a 100644 --- a/.kiro/specs/multi-exchange-data-aggregation/tasks.md +++ b/.kiro/specs/multi-exchange-data-aggregation/tasks.md @@ -98,7 +98,8 @@ - Create comprehensive API documentation - _Requirements: 4.1, 4.2, 4.4, 6.3_ -- [-] 9. Implement web dashboard for visualization +- [ ] 9. Implement web dashboard for visualization + diff --git a/COBY/api/rest_api.py b/COBY/api/rest_api.py index 69585b3..45eba26 100644 --- a/COBY/api/rest_api.py +++ b/COBY/api/rest_api.py @@ -4,7 +4,7 @@ REST API server for COBY system. from fastapi import FastAPI, HTTPException, Request, Query, Path, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse +from fastapi.responses import JSONResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from typing import Optional, List import asyncio @@ -77,8 +77,6 @@ def create_app(config_obj=None) -> FastAPI: redoc_url="/redoc" ) - # We'll mount static files AFTER defining all API routes to avoid conflicts - # Add CORS middleware app.add_middleware( CORSMiddleware, @@ -98,58 +96,94 @@ def create_app(config_obj=None) -> FastAPI: @app.websocket("/ws/dashboard") async def websocket_endpoint(websocket: WebSocket): - """WebSocket endpoint for dashboard real-time updates""" + """WebSocket endpoint for real-time dashboard updates""" await connection_manager.connect(websocket) try: while True: - # Send periodic updates - await asyncio.sleep(5) # Update every 5 seconds - - # Gather system status - system_data = { + # Send periodic status updates + status_data = { + "type": "status", "timestamp": time.time(), - "performance": { - "cpu_usage": 25.5, # Stub data - "memory_usage": 45.2, - "throughput": 1250, - "avg_latency": 12.3 - }, - "exchanges": { - "binance": "connected", - "coinbase": "connected", - "kraken": "disconnected", - "bybit": "connected" - }, - "processing": { - "active": True, - "total_processed": 15420 - } + "connections": len(connection_manager.active_connections), + "system": "healthy" } - await connection_manager.send_personal_message( - json.dumps(system_data), - websocket + json.dumps(status_data), websocket ) - + await asyncio.sleep(30) # Send update every 30 seconds except WebSocketDisconnect: connection_manager.disconnect(websocket) except Exception as e: logger.error(f"WebSocket error: {e}") connection_manager.disconnect(websocket) - + + @app.get("/api/health") + async def api_health_check(): + """API Health check endpoint for dashboard""" + try: + # Check Redis connection + redis_healthy = await redis_manager.ping() + + health_data = { + 'status': 'healthy' if redis_healthy else 'degraded', + 'redis': 'connected' if redis_healthy else 'disconnected', + 'version': '1.0.0', + 'timestamp': time.time() + } + + return response_formatter.status_response(health_data) + + except Exception as e: + logger.error(f"Health check failed: {e}") + return JSONResponse( + status_code=503, + content=response_formatter.error("Service unavailable", "HEALTH_CHECK_FAILED") + ) + @app.get("/health") async def health_check(): """Health check endpoint""" - return response_formatter.health(healthy=True, components={ - "api": {"healthy": True}, - "cache": {"healthy": redis_manager.is_connected()}, - "database": {"healthy": True} # Stub - }) + try: + # Check Redis connection + redis_healthy = await redis_manager.ping() + + health_data = { + 'status': 'healthy' if redis_healthy else 'degraded', + 'redis': 'connected' if redis_healthy else 'disconnected', + 'version': '1.0.0' + } + + return response_formatter.status_response(health_data) + + except Exception as e: + logger.error(f"Health check failed: {e}") + return JSONResponse( + status_code=503, + content=response_formatter.error("Service unavailable", "HEALTH_CHECK_FAILED") + ) - @app.get("/") + @app.get("/", response_class=HTMLResponse) async def root(): - """Root endpoint - serve dashboard""" - return {"message": "COBY Multi-Exchange Data Aggregation System", "status": "running"} + """Root endpoint - serve dashboard HTML""" + static_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "web", "static") + index_path = os.path.join(static_path, "index.html") + + if os.path.exists(index_path): + with open(index_path, 'r', encoding='utf-8') as f: + return HTMLResponse(content=f.read()) + else: + # Fallback if index.html doesn't exist + return HTMLResponse(content=""" + + + COBY System + +

COBY Multi-Exchange Data Aggregation System

+

System is running. Dashboard files not found.

+

Health Check

+ + + """) @app.middleware("http") async def rate_limit_middleware(request: Request, call_next): @@ -203,53 +237,6 @@ def create_app(config_obj=None) -> FastAPI: except Exception as e: logger.error(f"API server shutdown error: {e}") - # API Health check endpoint (for dashboard) - @app.get("/api/health") - async def api_health_check(): - """API Health check endpoint for dashboard""" - try: - # Check Redis connection - redis_healthy = await redis_manager.ping() - - health_data = { - 'status': 'healthy' if redis_healthy else 'degraded', - 'redis': 'connected' if redis_healthy else 'disconnected', - 'version': '1.0.0', - 'timestamp': time.time() - } - - return response_formatter.status_response(health_data) - - except Exception as e: - logger.error(f"Health check failed: {e}") - return JSONResponse( - status_code=503, - content=response_formatter.error("Service unavailable", "HEALTH_CHECK_FAILED") - ) - - # Health check endpoint - @app.get("/health") - async def health_check(): - """Health check endpoint""" - try: - # Check Redis connection - redis_healthy = await redis_manager.ping() - - health_data = { - 'status': 'healthy' if redis_healthy else 'degraded', - 'redis': 'connected' if redis_healthy else 'disconnected', - 'version': '1.0.0' - } - - return response_formatter.status_response(health_data) - - except Exception as e: - logger.error(f"Health check failed: {e}") - return JSONResponse( - status_code=503, - content=response_formatter.error("Service unavailable", "HEALTH_CHECK_FAILED") - ) - # Heatmap endpoints @app.get("/api/v1/heatmap/{symbol}") async def get_heatmap( @@ -519,8 +506,7 @@ def create_app(config_obj=None) -> FastAPI: static_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "web", "static") if os.path.exists(static_path): app.mount("/static", StaticFiles(directory=static_path), name="static") - # Serve index.html at root for dashboard, but this should be last - app.mount("/", StaticFiles(directory=static_path, html=True), name="dashboard") + # Don't mount at root to avoid conflicts with WebSocket and API routes return app diff --git a/COBY/test_fixes.py b/COBY/test_fixes.py new file mode 100644 index 0000000..7a62fdc --- /dev/null +++ b/COBY/test_fixes.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +""" +Test script to verify COBY system fixes. +""" + +import asyncio +import sys +import os + +# Add the current directory to Python path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +try: + from api.rest_api import create_app + from caching.redis_manager import redis_manager + from utils.logging import get_logger, setup_logging + print("✓ All imports successful") +except ImportError as e: + print(f"✗ Import error: {e}") + sys.exit(1) + +logger = get_logger(__name__) + +async def test_health_endpoints(): + """Test health endpoints""" + print("\n--- Testing Health Endpoints ---") + + try: + # Test Redis manager + await redis_manager.initialize() + ping_result = await redis_manager.ping() + print(f"✓ Redis ping: {ping_result}") + + # Test app creation + app = create_app() + print("✓ FastAPI app created successfully") + + # Test health endpoint logic + from api.response_formatter import ResponseFormatter + formatter = ResponseFormatter() + + health_data = { + 'status': 'healthy' if ping_result else 'degraded', + 'redis': 'connected' if ping_result else 'disconnected', + 'version': '1.0.0' + } + + response = formatter.status_response(health_data) + print(f"✓ Health response format: {type(response)}") + + return True + + except Exception as e: + print(f"✗ Health endpoint test failed: {e}") + return False + +async def test_static_files(): + """Test static file serving""" + print("\n--- Testing Static Files ---") + + try: + static_path = os.path.join(os.path.dirname(__file__), "web", "static") + index_path = os.path.join(static_path, "index.html") + + if os.path.exists(static_path): + print(f"✓ Static directory exists: {static_path}") + else: + print(f"✗ Static directory missing: {static_path}") + return False + + if os.path.exists(index_path): + print(f"✓ Index.html exists: {index_path}") + + # Test reading the file + with open(index_path, 'r', encoding='utf-8') as f: + content = f.read() + if "COBY" in content: + print("✓ Index.html contains COBY content") + else: + print("✗ Index.html missing COBY content") + return False + else: + print(f"✗ Index.html missing: {index_path}") + return False + + return True + + except Exception as e: + print(f"✗ Static files test failed: {e}") + return False + +async def test_websocket_config(): + """Test WebSocket configuration""" + print("\n--- Testing WebSocket Configuration ---") + + try: + from api.simple_websocket_server import WebSocketServer + from simple_config import config + + ws_server = WebSocketServer( + host=config.api.host, + port=config.api.websocket_port + ) + print(f"✓ WebSocket server configured: {config.api.host}:{config.api.websocket_port}") + + return True + + except Exception as e: + print(f"✗ WebSocket config test failed: {e}") + return False + +async def main(): + """Run all tests""" + print("COBY System Fix Verification") + print("=" * 40) + + # Setup logging + setup_logging(level='INFO') + + tests = [ + test_health_endpoints, + test_static_files, + test_websocket_config + ] + + results = [] + for test in tests: + try: + result = await test() + results.append(result) + except Exception as e: + print(f"✗ Test {test.__name__} failed with exception: {e}") + results.append(False) + + print("\n" + "=" * 40) + print("Test Results Summary:") + + passed = sum(results) + total = len(results) + + for i, result in enumerate(results): + status = "✓ PASS" if result else "✗ FAIL" + print(f" Test {i+1}: {status}") + + print(f"\nOverall: {passed}/{total} tests passed") + + if passed == total: + print("🎉 All tests passed! COBY system should work correctly.") + return 0 + else: + print("❌ Some tests failed. Please check the issues above.") + return 1 + +if __name__ == "__main__": + try: + exit_code = asyncio.run(main()) + sys.exit(exit_code) + except KeyboardInterrupt: + print("\nTest interrupted by user") + sys.exit(1) + except Exception as e: + print(f"Test failed with error: {e}") + sys.exit(1) \ No newline at end of file diff --git a/COBY/web/static/index.html b/COBY/web/static/index.html index b7ab135..266b824 100644 --- a/COBY/web/static/index.html +++ b/COBY/web/static/index.html @@ -322,7 +322,9 @@ function updateSystemStatus(data) { const systemStatus = document.getElementById('system-status'); - const status = data.status || 'unknown'; + // The API returns nested data structure: data.data.status + const actualData = data.data || data; + const status = actualData.status || 'unknown'; const healthy = status === 'healthy'; systemStatus.innerHTML = ` @@ -333,6 +335,9 @@

Last updated: ${new Date(data.timestamp || Date.now()).toLocaleString()}

+

+ Redis: ${actualData.redis || 'unknown'} | Version: ${actualData.version || 'unknown'} +

`; }