|
|
|
@@ -1,15 +1,29 @@
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
import { BarChart3, Activity, Clock, Cpu, Server, CheckCircle, Zap, RefreshCw, Layers, Compass, ArrowUpRight } from 'lucide-react';
|
|
|
|
import { BarChart3, Activity, Clock, Zap, Server, CheckCircle, XCircle, RefreshCw, Layers, ArrowUpRight, TrendingUp, Gauge } from 'lucide-react';
|
|
|
|
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar, PieChart, Pie, Cell } from 'recharts';
|
|
|
|
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar, PieChart, Pie, Cell, CartesianGrid } from 'recharts';
|
|
|
|
import { ServerStats } from '../types';
|
|
|
|
import { ServerStats } from '../types';
|
|
|
|
|
|
|
|
|
|
|
|
interface AnalyticsTabProps {
|
|
|
|
interface AnalyticsTabProps {
|
|
|
|
stats: ServerStats | null;
|
|
|
|
stats: ServerStats | null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const CATEGORY_COLORS: Record<string, string> = {
|
|
|
|
|
|
|
|
web: '#4f46e5',
|
|
|
|
|
|
|
|
code: '#0ea5e9',
|
|
|
|
|
|
|
|
data: '#10b981',
|
|
|
|
|
|
|
|
media: '#f59e0b',
|
|
|
|
|
|
|
|
memory: '#a855f7',
|
|
|
|
|
|
|
|
network: '#ec4899',
|
|
|
|
|
|
|
|
dev: '#6366f1',
|
|
|
|
|
|
|
|
custom: '#94a3b8',
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const PIE_COLORS = ['#4f46e5', '#0ea5e9', '#10b981', '#f59e0b', '#a855f7', '#ec4899', '#6366f1', '#94a3b8'];
|
|
|
|
|
|
|
|
|
|
|
|
export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
|
|
|
|
export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
|
|
|
|
const [routerStats, setRouterStats] = useState<any>(null);
|
|
|
|
const [routerStats, setRouterStats] = useState<any>(null);
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
|
|
|
|
|
|
const [lastUpdated, setLastUpdated] = useState<string>('');
|
|
|
|
|
|
|
|
|
|
|
|
const fetchRouterStats = async () => {
|
|
|
|
const fetchRouterStats = async () => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
@@ -18,6 +32,7 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
|
|
|
|
if (res.ok) {
|
|
|
|
if (res.ok) {
|
|
|
|
const data = await res.json();
|
|
|
|
const data = await res.json();
|
|
|
|
setRouterStats(data);
|
|
|
|
setRouterStats(data);
|
|
|
|
|
|
|
|
setLastUpdated(new Date().toLocaleTimeString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Failed to fetch router stats:', err);
|
|
|
|
console.error('Failed to fetch router stats:', err);
|
|
|
|
@@ -32,179 +47,224 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
}, []);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const totalToolCalls = routerStats?.totalToolCalls || stats?.mcpRequests || 142;
|
|
|
|
const totalCalls = routerStats?.totalRoutedCalls ?? 0;
|
|
|
|
const avgLatency = routerStats?.avgLatencyMs || stats?.avgLatencyMs || 4;
|
|
|
|
const totalSuccess = routerStats?.totalSuccess ?? 0;
|
|
|
|
|
|
|
|
const totalErrors = routerStats?.totalErrors ?? 0;
|
|
|
|
|
|
|
|
const successRate = totalCalls > 0 ? Math.round((totalSuccess / totalCalls) * 100) : 0;
|
|
|
|
|
|
|
|
const errorRate = totalCalls > 0 ? Math.round((totalErrors / totalCalls) * 100) : 0;
|
|
|
|
|
|
|
|
const avgLatency = routerStats?.avgLatencyMs ?? stats?.avgLatencyMs ?? 0;
|
|
|
|
|
|
|
|
|
|
|
|
const topToolsData = routerStats?.topTools && routerStats.topTools.length > 0
|
|
|
|
const toolMetrics = routerStats?.toolMetrics || [];
|
|
|
|
? routerStats.topTools.map((t: any) => ({ name: t.toolName, calls: t.callCount }))
|
|
|
|
const topTools = toolMetrics.slice(0, 8).map((t: any) => ({ name: t.toolName, calls: t.totalCalls }));
|
|
|
|
: [
|
|
|
|
const categoryData = (routerStats?.categoryBreakdown || []).map((c: any) => ({ name: c.category, value: c.totalCalls }));
|
|
|
|
{ name: 'verify_and_refine_loop', calls: 38 },
|
|
|
|
const serverData = (routerStats?.serverBreakdown || []).slice(0, 10);
|
|
|
|
{ name: 'diff_patch_validator', calls: 31 },
|
|
|
|
const recentExecutions = routerStats?.recentExecutions || [];
|
|
|
|
{ name: 'tree_of_thought_brancher', calls: 27 },
|
|
|
|
const trafficData = (routerStats?.trafficByHour || []).map((b: any) => ({
|
|
|
|
{ name: 'code_ast_inspector', calls: 24 },
|
|
|
|
hour: b.hour.slice(11, 16),
|
|
|
|
{ name: 'web_scrape_markdown', calls: 19 },
|
|
|
|
calls: b.count,
|
|
|
|
];
|
|
|
|
success: b.success,
|
|
|
|
|
|
|
|
errors: b.errors,
|
|
|
|
|
|
|
|
latency: b.avgLatencyMs,
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const trafficData = [
|
|
|
|
const fmtHour = (iso: string) => new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
|
|
{ time: '10:00', requests: 12, mcp: 8, rest: 4, latency: 5 },
|
|
|
|
|
|
|
|
{ time: '10:05', requests: 19, mcp: 14, rest: 5, latency: 4 },
|
|
|
|
const EmptyState = ({ message }: { message: string }) => (
|
|
|
|
{ time: '10:10', requests: 34, mcp: 28, rest: 6, latency: 6 },
|
|
|
|
<div className="h-full flex flex-col items-center justify-center text-center text-slate-400 py-10">
|
|
|
|
{ time: '10:15', requests: 27, mcp: 20, rest: 7, latency: 4 },
|
|
|
|
<Activity className="h-8 w-8 mb-2 opacity-40" />
|
|
|
|
{ time: '10:20', requests: 45, mcp: 36, rest: 9, latency: 5 },
|
|
|
|
<p className="text-xs font-medium">{message}</p>
|
|
|
|
{ time: '10:25', requests: 52, mcp: 42, rest: 10, latency: 3 },
|
|
|
|
</div>
|
|
|
|
{ time: '10:30', requests: stats?.totalRequests || 68, mcp: stats?.mcpRequests || 54, rest: stats?.restRequests || 14, latency: avgLatency },
|
|
|
|
);
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className="space-y-6">
|
|
|
|
<div className="space-y-6">
|
|
|
|
{/* Header Banner */}
|
|
|
|
{/* Header */}
|
|
|
|
<div className="bg-gradient-to-r from-indigo-950 via-slate-900 to-indigo-950 rounded-3xl p-6 sm:p-8 text-white shadow-xl border border-slate-800 relative overflow-hidden flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
|
|
|
<div className="bg-gradient-to-r from-indigo-950 via-slate-900 to-indigo-950 rounded-3xl p-6 sm:p-8 text-white shadow-xl border border-slate-800 relative overflow-hidden flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
|
|
|
<div className="relative z-10 max-w-3xl space-y-2">
|
|
|
|
<div className="relative z-10 max-w-3xl space-y-2">
|
|
|
|
<span className="inline-flex items-center space-x-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
|
|
|
|
<span className="inline-flex items-center space-x-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
|
|
|
|
<BarChart3 className="h-3.5 w-3.5" />
|
|
|
|
<BarChart3 className="h-3.5 w-3.5" />
|
|
|
|
<span>Live MCP Router & Telemetry Engine</span>
|
|
|
|
<span>Live MCP Router & Telemetry Engine</span>
|
|
|
|
</span>
|
|
|
|
</span>
|
|
|
|
<h2 className="text-2xl sm:text-3xl font-black tracking-tight">Real-Time Router Performance & Tool Audit</h2>
|
|
|
|
<h2 className="text-2xl sm:text-3xl font-black tracking-tight">Router Performance & Tool Audit</h2>
|
|
|
|
<p className="text-slate-300 text-xs sm:text-sm leading-relaxed">
|
|
|
|
<p className="text-slate-300 text-xs sm:text-sm leading-relaxed">
|
|
|
|
Monitor zero-key algorithmic tool loops, remote Road Sign dispatch latencies, and agent invocation trends across the universal MCP Source of Truth.
|
|
|
|
Real invocation traffic, latency, and success rates across {stats?.totalMCPServersCount ?? 0} registered MCP servers. {lastUpdated && <span className="text-indigo-300 font-mono">Last sync {lastUpdated}</span>}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
<button
|
|
|
|
onClick={fetchRouterStats}
|
|
|
|
onClick={fetchRouterStats}
|
|
|
|
disabled={loading}
|
|
|
|
disabled={loading}
|
|
|
|
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-bold rounded-xl border border-slate-700 transition flex items-center space-x-2 shrink-0"
|
|
|
|
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-bold rounded-xl border border-slate-700 transition flex items-center space-x-2 shrink-0"
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} />
|
|
|
|
<RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} />
|
|
|
|
<span>Refresh Metrics</span>
|
|
|
|
<span>Refresh</span>
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Top Metrics Cards */}
|
|
|
|
{/* Metric Cards */}
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
|
|
<div className="grid grid-cols-2 lg:grid-cols-6 gap-4">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
|
|
|
|
<div className="flex items-center justify-between text-slate-500">
|
|
|
|
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Total Calls</span><Activity className="h-4 w-4 text-indigo-600" /></div>
|
|
|
|
<span className="text-xs font-bold uppercase tracking-wider">Total MCP Requests</span>
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">{totalCalls}</div>
|
|
|
|
<Activity className="h-4 w-4 text-indigo-600" />
|
|
|
|
<div className="text-[10px] text-slate-400">routed tool invocations</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
|
|
|
|
{stats?.totalRequests || totalToolCalls}
|
|
|
|
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Success</span><CheckCircle className="h-4 w-4 text-emerald-600" /></div>
|
|
|
|
</div>
|
|
|
|
<div className="text-2xl font-black text-emerald-600 font-mono">{successRate}%</div>
|
|
|
|
<div className="text-[11px] text-slate-500">
|
|
|
|
<div className="text-[10px] text-slate-400">{totalSuccess} succeeded</div>
|
|
|
|
JSON-RPC & SSE Protocol Traffic
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Errors</span><XCircle className="h-4 w-4 text-rose-600" /></div>
|
|
|
|
|
|
|
|
<div className="text-2xl font-black text-rose-600 font-mono">{errorRate}%</div>
|
|
|
|
|
|
|
|
<div className="text-[10px] text-slate-400">{totalErrors} failed</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Avg Latency</span><Clock className="h-4 w-4 text-amber-500" /></div>
|
|
|
|
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">{avgLatency}<span className="text-xs font-normal text-slate-400">ms</span></div>
|
|
|
|
|
|
|
|
<div className="text-[10px] text-slate-400">per routed call</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Active Tools</span><Zap className="h-4 w-4 text-amber-500" /></div>
|
|
|
|
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">{stats?.registeredToolsCount ?? 0}</div>
|
|
|
|
|
|
|
|
<div className="text-[10px] text-slate-400">across active servers</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Servers</span><Server className="h-4 w-4 text-purple-600" /></div>
|
|
|
|
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">{stats?.activeMCPServersCount ?? 0}<span className="text-xs font-normal text-slate-400">/{stats?.totalMCPServersCount ?? 0}</span></div>
|
|
|
|
|
|
|
|
<div className="text-[10px] text-slate-400">active / registered</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Traffic + Category */}
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
|
|
|
|
<div className="lg:col-span-2 bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
|
|
|
|
|
|
|
|
<h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
|
|
|
|
|
|
|
|
<TrendingUp className="h-5 w-5 text-indigo-600" />
|
|
|
|
|
|
|
|
<span>Request Volume & Latency (per hour)</span>
|
|
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
{trafficData.length === 0 ? <EmptyState message="No traffic recorded yet — run a tool or workflow to populate this chart." /> : (
|
|
|
|
|
|
|
|
<div className="h-64 w-full">
|
|
|
|
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
|
|
|
|
<AreaChart data={trafficData}>
|
|
|
|
|
|
|
|
<defs>
|
|
|
|
|
|
|
|
<linearGradient id="colorCalls" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
|
|
|
<stop offset="5%" stopColor="#4f46e5" stopOpacity={0.85}/>
|
|
|
|
|
|
|
|
<stop offset="95%" stopColor="#4f46e5" stopOpacity={0}/>
|
|
|
|
|
|
|
|
</linearGradient>
|
|
|
|
|
|
|
|
</defs>
|
|
|
|
|
|
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
|
|
|
|
|
|
|
<XAxis dataKey="hour" stroke="#94a3b8" fontSize={11} />
|
|
|
|
|
|
|
|
<YAxis stroke="#94a3b8" fontSize={11} />
|
|
|
|
|
|
|
|
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
|
|
|
|
|
|
|
|
<Area type="monotone" dataKey="calls" name="Calls" stroke="#4f46e5" fillOpacity={1} fill="url(#colorCalls)" />
|
|
|
|
|
|
|
|
<Area type="monotone" dataKey="errors" name="Errors" stroke="#f43f5e" fill="none" />
|
|
|
|
|
|
|
|
</AreaChart>
|
|
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
|
|
|
|
<div className="flex items-center justify-between text-slate-500">
|
|
|
|
<h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
|
|
|
|
<span className="text-xs font-bold uppercase tracking-wider">Aggregated Tools</span>
|
|
|
|
<Layers className="h-5 w-5 text-emerald-600" />
|
|
|
|
<Zap className="h-4 w-4 text-amber-500" />
|
|
|
|
<span>Traffic by Category</span>
|
|
|
|
</div>
|
|
|
|
</h3>
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">
|
|
|
|
{categoryData.length === 0 ? <EmptyState message="No category traffic yet." /> : (
|
|
|
|
{stats?.registeredToolsCount || 27}
|
|
|
|
<div className="h-64 w-full">
|
|
|
|
</div>
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
<div className="text-[11px] text-emerald-600 font-medium">
|
|
|
|
<PieChart>
|
|
|
|
Across {stats?.activeMCPServersCount || 4} Servers & Road Signs
|
|
|
|
<Pie data={categoryData} dataKey="value" nameKey="name" cx="50%" cy="50%" innerRadius={50} outerRadius={80} paddingAngle={2}>
|
|
|
|
</div>
|
|
|
|
{categoryData.map((_: any, i: number) => <Cell key={i} fill={PIE_COLORS[i % PIE_COLORS.length]} />)}
|
|
|
|
</div>
|
|
|
|
</Pie>
|
|
|
|
|
|
|
|
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2">
|
|
|
|
</PieChart>
|
|
|
|
<div className="flex items-center justify-between text-slate-500">
|
|
|
|
</ResponsiveContainer>
|
|
|
|
<span className="text-xs font-bold uppercase tracking-wider">Router Dispatch Latency</span>
|
|
|
|
</div>
|
|
|
|
<Clock className="h-4 w-4 text-emerald-600" />
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">
|
|
|
|
{categoryData.map((c: any, i: number) => (
|
|
|
|
{avgLatency} <span className="text-xs font-normal text-slate-500">ms</span>
|
|
|
|
<span key={i} className="inline-flex items-center space-x-1 text-[10px] text-slate-600">
|
|
|
|
</div>
|
|
|
|
<span className="w-2 h-2 rounded-full" style={{ background: PIE_COLORS[i % PIE_COLORS.length] }} />
|
|
|
|
<div className="text-[11px] text-emerald-600 font-semibold">
|
|
|
|
<span className="font-medium">{c.name}</span>
|
|
|
|
Sub-millisecond local in-memory routing
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2">
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-500">
|
|
|
|
|
|
|
|
<span className="text-xs font-bold uppercase tracking-wider">Server Memory</span>
|
|
|
|
|
|
|
|
<Server className="h-4 w-4 text-purple-600" />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="text-2xl font-black text-slate-900 font-mono">
|
|
|
|
|
|
|
|
{stats?.memoryUsageMb || 34} <span className="text-xs font-normal text-slate-500">MB</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="text-[11px] text-slate-500">
|
|
|
|
|
|
|
|
Node.js Runtime Container Heap
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Traffic Charts */}
|
|
|
|
{/* Top Tools + Server Breakdown */}
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
|
{/* Request Time Series */}
|
|
|
|
|
|
|
|
<div className="lg:col-span-2 bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
|
|
|
|
|
|
|
|
<h3 className="text-sm sm:text-base font-bold text-slate-900 flex items-center space-x-2">
|
|
|
|
|
|
|
|
<Activity className="h-5 w-5 text-indigo-600" />
|
|
|
|
|
|
|
|
<span>MCP Request Volume & Routing Latency Trend</span>
|
|
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="h-64 w-full">
|
|
|
|
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
|
|
|
|
<AreaChart data={trafficData}>
|
|
|
|
|
|
|
|
<defs>
|
|
|
|
|
|
|
|
<linearGradient id="colorMcp" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
|
|
|
<stop offset="5%" stopColor="#4f46e5" stopOpacity={0.8}/>
|
|
|
|
|
|
|
|
<stop offset="95%" stopColor="#4f46e5" stopOpacity={0}/>
|
|
|
|
|
|
|
|
</linearGradient>
|
|
|
|
|
|
|
|
</defs>
|
|
|
|
|
|
|
|
<XAxis dataKey="time" stroke="#94a3b8" fontSize={11} />
|
|
|
|
|
|
|
|
<YAxis stroke="#94a3b8" fontSize={11} />
|
|
|
|
|
|
|
|
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
|
|
|
|
|
|
|
|
<Area type="monotone" dataKey="mcp" name="MCP JSON-RPC Calls" stroke="#4f46e5" fillOpacity={1} fill="url(#colorMcp)" />
|
|
|
|
|
|
|
|
</AreaChart>
|
|
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Top Tools Bar Chart */}
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
|
|
|
|
<h3 className="text-sm sm:text-base font-bold text-slate-900 flex items-center space-x-2">
|
|
|
|
<h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
|
|
|
|
<Zap className="h-5 w-5 text-emerald-600" />
|
|
|
|
<Zap className="h-5 w-5 text-emerald-600" />
|
|
|
|
<span>Most Invoked Tools</span>
|
|
|
|
<span>Most Invoked Tools</span>
|
|
|
|
</h3>
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
{topTools.length === 0 ? <EmptyState message="No tool invocations recorded yet." /> : (
|
|
|
|
|
|
|
|
<div className="h-64 w-full">
|
|
|
|
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
|
|
|
|
<BarChart data={topTools} layout="vertical">
|
|
|
|
|
|
|
|
<XAxis type="number" stroke="#94a3b8" fontSize={10} allowDecimals={false} />
|
|
|
|
|
|
|
|
<YAxis dataKey="name" type="category" stroke="#94a3b8" fontSize={10} width={150} />
|
|
|
|
|
|
|
|
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
|
|
|
|
|
|
|
|
<Bar dataKey="calls" name="Invocations" fill="#10b981" radius={[0, 6, 6, 0]} />
|
|
|
|
|
|
|
|
</BarChart>
|
|
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="h-64 w-full">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
<h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
|
|
|
|
<BarChart data={topToolsData} layout="vertical">
|
|
|
|
<Server className="h-5 w-5 text-purple-600" />
|
|
|
|
<XAxis type="number" stroke="#94a3b8" fontSize={10} />
|
|
|
|
<span>Traffic by Server</span>
|
|
|
|
<YAxis dataKey="name" type="category" stroke="#94a3b8" fontSize={10} width={120} />
|
|
|
|
</h3>
|
|
|
|
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
|
|
|
|
{serverData.length === 0 ? <EmptyState message="No server traffic yet." /> : (
|
|
|
|
<Bar dataKey="calls" name="Tool Invocations" fill="#10b981" radius={[0, 6, 6, 0]} />
|
|
|
|
<div className="space-y-3 max-h-64 overflow-y-auto pr-1">
|
|
|
|
</BarChart>
|
|
|
|
{serverData.map((s: any) => {
|
|
|
|
</ResponsiveContainer>
|
|
|
|
const pct = totalCalls > 0 ? (s.totalCalls / totalCalls) * 100 : 0;
|
|
|
|
</div>
|
|
|
|
return (
|
|
|
|
|
|
|
|
<div key={s.serverName}>
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between text-xs mb-1">
|
|
|
|
|
|
|
|
<span className="font-semibold text-slate-700 truncate mr-2">{s.serverName}</span>
|
|
|
|
|
|
|
|
<span className="text-slate-400 font-mono shrink-0">{s.totalCalls} · {s.avgLatencyMs}ms</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
|
|
|
|
|
|
|
|
<div className="h-full rounded-full" style={{ width: `${pct}%`, background: '#6366f1' }} />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Recent Telemetry Executions */}
|
|
|
|
{/* Live Execution Stream */}
|
|
|
|
{routerStats?.recentExecutions && routerStats.recentExecutions.length > 0 && (
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-3">
|
|
|
|
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-3">
|
|
|
|
<h3 className="text-sm font-bold text-slate-900 uppercase tracking-wider flex items-center space-x-2">
|
|
|
|
<h3 className="text-sm font-bold text-slate-900 uppercase tracking-wider">
|
|
|
|
<Gauge className="h-4 w-4 text-indigo-600" />
|
|
|
|
Live Tool Call Stream Log ({routerStats.recentExecutions.length})
|
|
|
|
<span>Live Tool Call Stream</span>
|
|
|
|
</h3>
|
|
|
|
{recentExecutions.length > 0 && <span className="text-slate-400 font-mono text-xs normal-case">({recentExecutions.length} recent)</span>}
|
|
|
|
<div className="divide-y divide-slate-100 overflow-hidden">
|
|
|
|
</h3>
|
|
|
|
{routerStats.recentExecutions.map((log: any, idx: number) => (
|
|
|
|
{recentExecutions.length === 0 ? (
|
|
|
|
|
|
|
|
<p className="text-xs text-slate-400 py-4 text-center">No executions recorded yet — traffic will stream here in real time.</p>
|
|
|
|
|
|
|
|
) : (
|
|
|
|
|
|
|
|
<div className="divide-y divide-slate-100 max-h-80 overflow-y-auto">
|
|
|
|
|
|
|
|
{recentExecutions.map((log: any, idx: number) => (
|
|
|
|
<div key={idx} className="py-2.5 flex items-center justify-between text-xs font-mono">
|
|
|
|
<div key={idx} className="py-2.5 flex items-center justify-between text-xs font-mono">
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
<div className="flex items-center space-x-2 min-w-0">
|
|
|
|
<span className={`w-2 h-2 rounded-full ${log.success ? 'bg-emerald-500' : 'bg-rose-500'}`} />
|
|
|
|
<span className={`w-2 h-2 rounded-full shrink-0 ${log.success ? 'bg-emerald-500' : 'bg-rose-500'}`} />
|
|
|
|
<span className="font-bold text-slate-900">{log.toolName}</span>
|
|
|
|
<span className="font-bold text-slate-900 truncate">{log.toolName}</span>
|
|
|
|
|
|
|
|
<span className="text-slate-400 truncate hidden sm:inline">{log.serverName}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-4 text-slate-500">
|
|
|
|
<div className="flex items-center space-x-4 text-slate-500 shrink-0">
|
|
|
|
<span>{log.durationMs}ms</span>
|
|
|
|
<span className={log.durationMs > 5000 ? 'text-amber-600' : ''}>{log.durationMs}ms</span>
|
|
|
|
<span>{new Date(log.timestamp).toLocaleTimeString()}</span>
|
|
|
|
<span>{fmtHour(log.timestamp)}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|