import React, { useEffect, useState } from 'react';
import {
Activity,
FileText,
Upload,
AlertCircle,
Clock,
Users
} from 'lucide-react';
import { api } from '../api';
interface SystemStats {
uptime: number;
totalFiles: number;
totalContexts: number;
activeConnections: number;
memoryUsage: {
used: number;
total: number;
};
diskUsage: {
used: number;
total: number;
};
recentActivity: Array<{
id: string;
type: 'upload' | 'extraction' | 'context' | 'error';
message: string;
timestamp: string;
}>;
}
const Dashboard: React.FC = () => {
const [stats, setStats] = useState<SystemStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchStats = async () => {
try {
const response = await api.get('/api/stats');
setStats(response.data);
} catch (err) {
setError('Failed to fetch system statistics');
console.error('Error fetching stats:', err);
} finally {
setLoading(false);
}
};
fetchStats();
const interval = setInterval(fetchStats, 30000); // Refresh every 30 seconds
return () => clearInterval(interval);
}, []);
const formatUptime = (seconds: number) => {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
const formatBytes = (bytes: number) => {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};
const getActivityIcon = (type: string) => {
switch (type) {
case 'upload': return Upload;
case 'extraction': return FileText;
case 'context': return Users;
case 'error': return AlertCircle;
default: return Activity;
}
};
const getActivityColor = (type: string) => {
switch (type) {
case 'upload': return 'text-blue-500';
case 'extraction': return 'text-green-500';
case 'context': return 'text-purple-500';
case 'error': return 'text-red-500';
default: return 'text-gray-500';
}
};
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
}
if (error) {
return (
<div className="rounded-md bg-destructive/10 p-4">
<div className="flex">
<AlertCircle className="h-5 w-5 text-destructive" />
<div className="ml-3">
<h3 className="text-sm font-medium text-destructive">Error</h3>
<p className="mt-1 text-sm text-destructive">{error}</p>
</div>
</div>
</div>
);
}
return (
<div className="space-y-6 animate-fade-in">
<div>
<h1 className="text-3xl font-bold text-foreground">Dashboard</h1>
<p className="mt-2 text-muted-foreground">
Monitor your MCP Server performance and activity
</p>
</div>
{/* System Status Cards */}
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="flex items-center">
<div className="flex-shrink-0">
<Clock className="h-8 w-8 text-primary" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-muted-foreground">Uptime</p>
<p className="text-2xl font-bold text-foreground">
{stats ? formatUptime(stats.uptime) : '0m'}
</p>
</div>
</div>
</div>
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="flex items-center">
<div className="flex-shrink-0">
<FileText className="h-8 w-8 text-success" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-muted-foreground">Total Files</p>
<p className="text-2xl font-bold text-foreground">
{stats?.totalFiles || 0}
</p>
</div>
</div>
</div>
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="flex items-center">
<div className="flex-shrink-0">
<Users className="h-8 w-8 text-warning" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-muted-foreground">Contexts</p>
<p className="text-2xl font-bold text-foreground">
{stats?.totalContexts || 0}
</p>
</div>
</div>
</div>
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="flex items-center">
<div className="flex-shrink-0">
<Activity className="h-8 w-8 text-blue-500" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-muted-foreground">Active Connections</p>
<p className="text-2xl font-bold text-foreground">
{stats?.activeConnections || 0}
</p>
</div>
</div>
</div>
</div>
{/* Resource Usage */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<h3 className="text-lg font-semibold text-foreground mb-4">Memory Usage</h3>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Used</span>
<span className="text-foreground">
{stats ? formatBytes(stats.memoryUsage.used) : '0 MB'}
</span>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-primary h-2 rounded-full transition-all duration-300"
style={{
width: stats ? `${(stats.memoryUsage.used / stats.memoryUsage.total) * 100}%` : '0%'
}}
/>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Total</span>
<span className="text-foreground">
{stats ? formatBytes(stats.memoryUsage.total) : '0 MB'}
</span>
</div>
</div>
</div>
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<h3 className="text-lg font-semibold text-foreground mb-4">Disk Usage</h3>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Used</span>
<span className="text-foreground">
{stats ? formatBytes(stats.diskUsage.used) : '0 MB'}
</span>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-success h-2 rounded-full transition-all duration-300"
style={{
width: stats ? `${(stats.diskUsage.used / stats.diskUsage.total) * 100}%` : '0%'
}}
/>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Total</span>
<span className="text-foreground">
{stats ? formatBytes(stats.diskUsage.total) : '0 MB'}
</span>
</div>
</div>
</div>
</div>
{/* Recent Activity */}
<div className="rounded-lg border border-border bg-card shadow-sm">
<div className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-4">Recent Activity</h3>
<div className="space-y-4">
{stats?.recentActivity?.length ? (
stats.recentActivity.map((activity) => {
const Icon = getActivityIcon(activity.type);
return (
<div key={activity.id} className="flex items-center space-x-3 p-3 rounded-md bg-muted/50">
<Icon className={`h-5 w-5 ${getActivityColor(activity.type)}`} />
<div className="flex-1">
<p className="text-sm text-foreground">{activity.message}</p>
<p className="text-xs text-muted-foreground">
{new Date(activity.timestamp).toLocaleString()}
</p>
</div>
</div>
);
})
) : (
<div className="text-center py-8">
<Activity className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">No recent activity</p>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default Dashboard;