trading-platform-frontend/src/modules/admin/pages/AdminDashboard.tsx

324 lines
13 KiB
TypeScript

/**
* Admin Dashboard Page
* Main dashboard for admin users showing ML models, agents, and system health
*/
import { useState, useEffect } from 'react';
import {
CpuChipIcon,
ChartBarIcon,
UserGroupIcon,
ServerIcon,
ArrowTrendingUpIcon,
ArrowTrendingDownIcon,
CheckCircleIcon,
ExclamationTriangleIcon,
XCircleIcon,
} from '@heroicons/react/24/solid';
import {
getAdminDashboard,
getSystemHealth,
getMLModels,
getAgents,
type AdminStats,
type SystemHealth,
type MLModel,
type AgentPerformance,
} from '../../../services/adminService';
export default function AdminDashboard() {
const [stats, setStats] = useState<AdminStats | null>(null);
const [health, setHealth] = useState<SystemHealth | null>(null);
const [models, setModels] = useState<MLModel[]>([]);
const [agents, setAgents] = useState<AgentPerformance[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadDashboardData();
}, []);
const loadDashboardData = async () => {
setLoading(true);
try {
const [dashboardData, healthData, modelsData, agentsData] = await Promise.all([
getAdminDashboard(),
getSystemHealth(),
getMLModels(),
getAgents(),
]);
setStats(dashboardData);
setHealth(healthData);
setModels(Array.isArray(modelsData) ? modelsData : []);
setAgents(Array.isArray(agentsData) ? agentsData : []);
} catch (error) {
console.error('Error loading dashboard data:', error);
} finally {
setLoading(false);
}
};
const getHealthIcon = (status: string) => {
switch (status) {
case 'healthy':
return <CheckCircleIcon className="w-5 h-5 text-green-400" />;
case 'degraded':
return <ExclamationTriangleIcon className="w-5 h-5 text-yellow-400" />;
default:
return <XCircleIcon className="w-5 h-5 text-red-400" />;
}
};
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
}).format(value);
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-500"></div>
</div>
);
}
return (
<div className="p-6 space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-white">Admin Dashboard</h1>
<p className="text-gray-400">OrbiQuant IA Platform Overview</p>
</div>
<button
onClick={loadDashboardData}
className="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg transition-colors"
>
Refresh Data
</button>
</div>
{/* System Health Banner */}
{health && (
<div className={`p-4 rounded-lg ${
health.status === 'healthy' ? 'bg-green-900/30 border border-green-700' :
health.status === 'degraded' ? 'bg-yellow-900/30 border border-yellow-700' :
'bg-red-900/30 border border-red-700'
}`}>
<div className="flex items-center gap-3">
{getHealthIcon(health.status)}
<span className="text-white font-semibold">
System Status: {health.status.charAt(0).toUpperCase() + health.status.slice(1)}
</span>
<span className="text-gray-400 text-sm ml-auto">
Last updated: {new Date(health.timestamp).toLocaleString()}
</span>
</div>
</div>
)}
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* ML Models Card */}
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 bg-blue-600 rounded-lg">
<CpuChipIcon className="w-6 h-6 text-white" />
</div>
<span className="text-gray-400">ML Models</span>
</div>
<p className="text-3xl font-bold text-white">{models.length}</p>
<p className="text-sm text-green-400">
{models.filter(m => m.status === 'active' || m.status === 'training').length} active
</p>
</div>
{/* Trading Agents Card */}
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 bg-purple-600 rounded-lg">
<UserGroupIcon className="w-6 h-6 text-white" />
</div>
<span className="text-gray-400">Trading Agents</span>
</div>
<p className="text-3xl font-bold text-white">{agents.length}</p>
<p className="text-sm text-green-400">
{agents.filter(a => a.status === 'active').length} running
</p>
</div>
{/* Total P&L Card */}
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 bg-green-600 rounded-lg">
<ChartBarIcon className="w-6 h-6 text-white" />
</div>
<span className="text-gray-400">Today's P&L</span>
</div>
<p className={`text-3xl font-bold ${(stats?.total_pnl_today || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{formatCurrency(stats?.total_pnl_today || 0)}
</p>
<div className="flex items-center gap-1 text-sm">
{(stats?.total_pnl_today || 0) >= 0 ? (
<ArrowTrendingUpIcon className="w-4 h-4 text-green-400" />
) : (
<ArrowTrendingDownIcon className="w-4 h-4 text-red-400" />
)}
<span className="text-gray-400">vs yesterday</span>
</div>
</div>
{/* Predictions Card */}
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 bg-orange-600 rounded-lg">
<ServerIcon className="w-6 h-6 text-white" />
</div>
<span className="text-gray-400">Predictions Today</span>
</div>
<p className="text-3xl font-bold text-white">{stats?.total_predictions_today || 0}</p>
<p className="text-sm text-blue-400">
{((stats?.overall_accuracy || 0) * 100).toFixed(1)}% accuracy
</p>
</div>
</div>
{/* Services Health */}
{health && (
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<h2 className="text-lg font-bold text-white mb-4">Services Health</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="flex items-center justify-between p-4 bg-gray-900 rounded-lg">
<div className="flex items-center gap-3">
<ServerIcon className="w-5 h-5 text-gray-400" />
<span className="text-white">Database</span>
</div>
{getHealthIcon(health.services.database.status)}
</div>
<div className="flex items-center justify-between p-4 bg-gray-900 rounded-lg">
<div className="flex items-center gap-3">
<CpuChipIcon className="w-5 h-5 text-gray-400" />
<span className="text-white">ML Engine</span>
</div>
{getHealthIcon(health.services.mlEngine.status)}
</div>
<div className="flex items-center justify-between p-4 bg-gray-900 rounded-lg">
<div className="flex items-center gap-3">
<UserGroupIcon className="w-5 h-5 text-gray-400" />
<span className="text-white">Trading Agents</span>
</div>
{getHealthIcon(health.services.tradingAgents.status)}
</div>
</div>
</div>
)}
{/* ML Models Overview */}
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-white">ML Models</h2>
<a href="/admin/models" className="text-primary-400 hover:text-primary-300 text-sm">
View All
</a>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{models.slice(0, 6).map((model, index) => (
<div key={index} className="p-4 bg-gray-900 rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-white font-semibold">{model.name || model.type}</span>
<span className={`px-2 py-1 rounded-full text-xs font-semibold ${
model.status === 'active' ? 'bg-green-900/50 text-green-400' :
model.status === 'training' ? 'bg-yellow-900/50 text-yellow-400' :
'bg-gray-700 text-gray-400'
}`}>
{model.status}
</span>
</div>
<div className="text-sm text-gray-400">
<span>Accuracy: {((model.accuracy || 0) * 100).toFixed(1)}%</span>
</div>
</div>
))}
</div>
</div>
{/* Trading Agents Overview */}
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-white">Trading Agents</h2>
<a href="/admin/agents" className="text-primary-400 hover:text-primary-300 text-sm">
View All
</a>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{agents.map((agent, index) => (
<div key={index} className="p-4 bg-gray-900 rounded-lg">
<div className="flex items-center justify-between mb-3">
<span className="text-white font-semibold">{agent.name}</span>
<span className={`px-2 py-1 rounded-full text-xs font-semibold ${
agent.status === 'active' ? 'bg-green-900/50 text-green-400' :
agent.status === 'paused' ? 'bg-yellow-900/50 text-yellow-400' :
'bg-red-900/50 text-red-400'
}`}>
{agent.status}
</span>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Win Rate</span>
<span className="text-white">{((agent.win_rate || 0) * 100).toFixed(1)}%</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Total P&L</span>
<span className={agent.total_pnl >= 0 ? 'text-green-400' : 'text-red-400'}>
{formatCurrency(agent.total_pnl || 0)}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Trades</span>
<span className="text-white">{agent.total_trades || 0}</span>
</div>
</div>
</div>
))}
{agents.length === 0 && (
<div className="col-span-3 text-center text-gray-500 py-8">
No trading agents configured yet
</div>
)}
</div>
</div>
{/* System Info */}
{health && (
<div className="bg-gray-800 rounded-xl p-5 border border-gray-700">
<h2 className="text-lg font-bold text-white mb-4">System Info</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-4 bg-gray-900 rounded-lg">
<span className="text-gray-400 text-sm">Uptime</span>
<p className="text-white font-semibold">
{Math.floor(health.system.uptime / 3600)}h {Math.floor((health.system.uptime % 3600) / 60)}m
</p>
</div>
<div className="p-4 bg-gray-900 rounded-lg">
<span className="text-gray-400 text-sm">Memory Usage</span>
<p className="text-white font-semibold">
{health.system.memory.percentage.toFixed(1)}%
</p>
</div>
<div className="p-4 bg-gray-900 rounded-lg">
<span className="text-gray-400 text-sm">Last Update</span>
<p className="text-white font-semibold">
{new Date(health.timestamp).toLocaleTimeString()}
</p>
</div>
</div>
</div>
)}
</div>
);
}