webui fixes
This commit is contained in:
@ -98,7 +98,8 @@
|
|||||||
- Create comprehensive API documentation
|
- Create comprehensive API documentation
|
||||||
- _Requirements: 4.1, 4.2, 4.4, 6.3_
|
- _Requirements: 4.1, 4.2, 4.4, 6.3_
|
||||||
|
|
||||||
- [-] 9. Implement web dashboard for visualization
|
- [ ] 9. Implement web dashboard for visualization
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ REST API server for COBY system.
|
|||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Request, Query, Path, WebSocket, WebSocketDisconnect
|
from fastapi import FastAPI, HTTPException, Request, Query, Path, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse, HTMLResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -77,8 +77,6 @@ def create_app(config_obj=None) -> FastAPI:
|
|||||||
redoc_url="/redoc"
|
redoc_url="/redoc"
|
||||||
)
|
)
|
||||||
|
|
||||||
# We'll mount static files AFTER defining all API routes to avoid conflicts
|
|
||||||
|
|
||||||
# Add CORS middleware
|
# Add CORS middleware
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
@ -98,58 +96,94 @@ def create_app(config_obj=None) -> FastAPI:
|
|||||||
|
|
||||||
@app.websocket("/ws/dashboard")
|
@app.websocket("/ws/dashboard")
|
||||||
async def websocket_endpoint(websocket: WebSocket):
|
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)
|
await connection_manager.connect(websocket)
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
# Send periodic updates
|
# Send periodic status updates
|
||||||
await asyncio.sleep(5) # Update every 5 seconds
|
status_data = {
|
||||||
|
"type": "status",
|
||||||
# Gather system status
|
|
||||||
system_data = {
|
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
"performance": {
|
"connections": len(connection_manager.active_connections),
|
||||||
"cpu_usage": 25.5, # Stub data
|
"system": "healthy"
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection_manager.send_personal_message(
|
await connection_manager.send_personal_message(
|
||||||
json.dumps(system_data),
|
json.dumps(status_data), websocket
|
||||||
websocket
|
|
||||||
)
|
)
|
||||||
|
await asyncio.sleep(30) # Send update every 30 seconds
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
connection_manager.disconnect(websocket)
|
connection_manager.disconnect(websocket)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"WebSocket error: {e}")
|
logger.error(f"WebSocket error: {e}")
|
||||||
connection_manager.disconnect(websocket)
|
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")
|
@app.get("/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""Health check endpoint"""
|
"""Health check endpoint"""
|
||||||
return response_formatter.health(healthy=True, components={
|
try:
|
||||||
"api": {"healthy": True},
|
# Check Redis connection
|
||||||
"cache": {"healthy": redis_manager.is_connected()},
|
redis_healthy = await redis_manager.ping()
|
||||||
"database": {"healthy": True} # Stub
|
|
||||||
})
|
|
||||||
|
|
||||||
@app.get("/")
|
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("/", response_class=HTMLResponse)
|
||||||
async def root():
|
async def root():
|
||||||
"""Root endpoint - serve dashboard"""
|
"""Root endpoint - serve dashboard HTML"""
|
||||||
return {"message": "COBY Multi-Exchange Data Aggregation System", "status": "running"}
|
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="""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>COBY System</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>COBY Multi-Exchange Data Aggregation System</h1>
|
||||||
|
<p>System is running. Dashboard files not found.</p>
|
||||||
|
<p><a href="/api/health">Health Check</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""")
|
||||||
|
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
async def rate_limit_middleware(request: Request, call_next):
|
async def rate_limit_middleware(request: Request, call_next):
|
||||||
@ -203,53 +237,6 @@ def create_app(config_obj=None) -> FastAPI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"API server shutdown error: {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
|
# Heatmap endpoints
|
||||||
@app.get("/api/v1/heatmap/{symbol}")
|
@app.get("/api/v1/heatmap/{symbol}")
|
||||||
async def get_heatmap(
|
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")
|
static_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "web", "static")
|
||||||
if os.path.exists(static_path):
|
if os.path.exists(static_path):
|
||||||
app.mount("/static", StaticFiles(directory=static_path), name="static")
|
app.mount("/static", StaticFiles(directory=static_path), name="static")
|
||||||
# Serve index.html at root for dashboard, but this should be last
|
# Don't mount at root to avoid conflicts with WebSocket and API routes
|
||||||
app.mount("/", StaticFiles(directory=static_path, html=True), name="dashboard")
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
163
COBY/test_fixes.py
Normal file
163
COBY/test_fixes.py
Normal file
@ -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)
|
@ -322,7 +322,9 @@
|
|||||||
|
|
||||||
function updateSystemStatus(data) {
|
function updateSystemStatus(data) {
|
||||||
const systemStatus = document.getElementById('system-status');
|
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';
|
const healthy = status === 'healthy';
|
||||||
|
|
||||||
systemStatus.innerHTML = `
|
systemStatus.innerHTML = `
|
||||||
@ -333,6 +335,9 @@
|
|||||||
<p style="margin-top: 0.5rem; font-size: 0.9rem; color: #666;">
|
<p style="margin-top: 0.5rem; font-size: 0.9rem; color: #666;">
|
||||||
Last updated: ${new Date(data.timestamp || Date.now()).toLocaleString()}
|
Last updated: ${new Date(data.timestamp || Date.now()).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
|
<p style="margin-top: 0.25rem; font-size: 0.8rem; color: #888;">
|
||||||
|
Redis: ${actualData.redis || 'unknown'} | Version: ${actualData.version || 'unknown'}
|
||||||
|
</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user