-
Notifications
You must be signed in to change notification settings - Fork 168
Closed
Feature
Copy link
Labels
enhancementNew feature or requestNew feature or requestfrontendFrontend development (HTML, CSS, JavaScript)Frontend development (HTML, CSS, JavaScript)good first issueGood for newcomersGood for newcomershelp wantedExtra attention is neededExtra attention is neededtriageIssues / Features awaiting triageIssues / Features awaiting triage
Milestone
Description
Enhance Metrics Tab UI with Virtual Servers and Top 5 Performance Tables
Priority: Medium (UI/Analytics Enhancement)
Description:
The Metrics tab in the Admin UI needs to be enhanced to provide more comprehensive analytics. Currently missing virtual servers in the metrics display, and the top performers section needs to be expanded to show detailed tables with performance statistics for each entity type (tools, resources, prompts, gateways, servers).
Current State:
- Metrics display shows basic counts and aggregate statistics
- Top performers section exists but shows limited information (just name and execution count)
- Virtual servers are not included in metrics
- No detailed performance data in the top performers section

Requested Enhancements:
-
Add Virtual Servers to Metrics Display
- Include virtual servers alongside tools, resources, prompts, gateways
- Show the same metrics as other entities (executions, response times, success rates)
-
Enhanced Top 5 Tables for Each Entity Type
- Replace simple lists with detailed tables
- Include performance statistics:
- Number of calls (executions)
- Average response time
- Success rate
- Last execution time
- Make tables responsive and visually appealing
Suggested Implementation:
- Backend Changes (
mcpgateway/admin/metrics.py
or equivalent):
@router.get("/admin/metrics")
async def get_aggregated_metrics(db: Session = Depends(get_db)):
metrics = {
"tools": await get_tools_metrics(db),
"resources": await get_resources_metrics(db),
"prompts": await get_prompts_metrics(db),
"gateways": await get_gateways_metrics(db),
"servers": await get_servers_metrics(db), # Virtual servers
"topPerformers": {
"tools": await get_top_tools(db, limit=5),
"resources": await get_top_resources(db, limit=5),
"prompts": await get_top_prompts(db, limit=5),
"gateways": await get_top_gateways(db, limit=5),
"servers": await get_top_servers(db, limit=5)
}
}
return metrics
async def get_top_tools(db: Session, limit: int = 5):
"""Get top performing tools with detailed metrics"""
return db.query(
Tool.name,
Tool.id,
func.count(ToolExecution.id).label('execution_count'),
func.avg(ToolExecution.response_time).label('avg_response_time'),
func.sum(case((ToolExecution.success == True, 1), else_=0)).label('successful_count'),
func.max(ToolExecution.created_at).label('last_execution')
).join(
ToolExecution, Tool.id == ToolExecution.tool_id
).group_by(
Tool.id
).order_by(
desc('execution_count')
).limit(limit).all()
- Frontend Changes (
admin.js
- enhance thecreateTopPerformersSection
function):
function createEnhancedTopPerformersSection(topData) {
try {
const section = document.createElement('div');
section.className = 'bg-white rounded-lg shadow p-6 dark:bg-gray-800';
const title = document.createElement('h3');
title.className = 'text-lg font-medium mb-4 dark:text-gray-200';
title.textContent = 'Top Performers';
section.appendChild(title);
const tabsContainer = document.createElement('div');
tabsContainer.className = 'border-b border-gray-200 dark:border-gray-700';
const tabList = document.createElement('nav');
tabList.className = '-mb-px flex space-x-8';
tabList.setAttribute('aria-label', 'Tabs');
// Create tabs for each entity type
const entityTypes = ['tools', 'resources', 'prompts', 'gateways', 'servers'];
entityTypes.forEach((type, index) => {
if (topData[type] && Array.isArray(topData[type])) {
const tab = createTab(type, index === 0);
tabList.appendChild(tab);
}
});
tabsContainer.appendChild(tabList);
section.appendChild(tabsContainer);
// Create content panels
const contentContainer = document.createElement('div');
contentContainer.className = 'mt-4';
entityTypes.forEach((type, index) => {
if (topData[type] && Array.isArray(topData[type])) {
const panel = createTopPerformersTable(type, topData[type], index === 0);
contentContainer.appendChild(panel);
}
});
section.appendChild(contentContainer);
return section;
} catch (error) {
console.error('Error creating enhanced top performers section:', error);
return document.createElement('div');
}
}
function createTopPerformersTable(entityType, data, isActive) {
const panel = document.createElement('div');
panel.id = `top-${entityType}-panel`;
panel.className = `${isActive ? '' : 'hidden'}`;
if (data.length === 0) {
const emptyState = document.createElement('p');
emptyState.className = 'text-gray-500 dark:text-gray-400 text-center py-4';
emptyState.textContent = `No ${entityType} data available`;
panel.appendChild(emptyState);
return panel;
}
// Create responsive table wrapper
const tableWrapper = document.createElement('div');
tableWrapper.className = 'overflow-x-auto';
const table = document.createElement('table');
table.className = 'min-w-full divide-y divide-gray-200 dark:divide-gray-700';
// Table header
const thead = document.createElement('thead');
thead.className = 'bg-gray-50 dark:bg-gray-700';
const headerRow = document.createElement('tr');
const headers = ['Rank', 'Name', 'Executions', 'Avg Response Time', 'Success Rate', 'Last Used'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider';
th.textContent = headerText;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Table body
const tbody = document.createElement('tbody');
tbody.className = 'bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700';
data.forEach((item, index) => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
// Rank
const rankCell = document.createElement('td');
rankCell.className = 'px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100';
const rankBadge = document.createElement('span');
rankBadge.className = `inline-flex items-center justify-center w-6 h-6 rounded-full ${
index === 0 ? 'bg-yellow-400 text-yellow-900' :
index === 1 ? 'bg-gray-300 text-gray-900' :
index === 2 ? 'bg-orange-400 text-orange-900' :
'bg-gray-100 text-gray-600'
}`;
rankBadge.textContent = index + 1;
rankCell.appendChild(rankBadge);
row.appendChild(rankCell);
// Name
const nameCell = document.createElement('td');
nameCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100';
nameCell.textContent = item.name || 'Unknown';
row.appendChild(nameCell);
// Executions
const execCell = document.createElement('td');
execCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300';
execCell.textContent = formatNumber(item.execution_count || item.executions || 0);
row.appendChild(execCell);
// Avg Response Time
const avgTimeCell = document.createElement('td');
avgTimeCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300';
const avgTime = item.avg_response_time || item.avgResponseTime;
avgTimeCell.textContent = avgTime ? `${Math.round(avgTime)}ms` : 'N/A';
row.appendChild(avgTimeCell);
// Success Rate
const successCell = document.createElement('td');
successCell.className = 'px-6 py-4 whitespace-nowrap text-sm';
const successRate = calculateSuccessRate(item);
const successBadge = document.createElement('span');
successBadge.className = `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
successRate >= 95 ? 'bg-green-100 text-green-800' :
successRate >= 80 ? 'bg-yellow-100 text-yellow-800' :
'bg-red-100 text-red-800'
}`;
successBadge.textContent = `${successRate}%`;
successCell.appendChild(successBadge);
row.appendChild(successCell);
// Last Used
const lastUsedCell = document.createElement('td');
lastUsedCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300';
lastUsedCell.textContent = formatLastUsed(item.last_execution || item.lastExecution);
row.appendChild(lastUsedCell);
tbody.appendChild(row);
});
table.appendChild(tbody);
tableWrapper.appendChild(table);
panel.appendChild(tableWrapper);
return panel;
}
// Helper functions
function createTab(type, isActive) {
const tab = document.createElement('a');
tab.href = '#';
tab.className = `${
isActive
? 'border-indigo-500 text-indigo-600 dark:text-indigo-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
} whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm capitalize`;
tab.textContent = type;
tab.onclick = (e) => {
e.preventDefault();
showTopPerformerTab(type);
};
return tab;
}
function showTopPerformerTab(activeType) {
const entityTypes = ['tools', 'resources', 'prompts', 'gateways', 'servers'];
entityTypes.forEach(type => {
const panel = document.getElementById(`top-${type}-panel`);
const tab = document.querySelector(`a[onclick*="showTopPerformerTab('${type}')"]`);
if (panel) {
panel.classList.toggle('hidden', type !== activeType);
}
if (tab) {
if (type === activeType) {
tab.classList.add('border-indigo-500', 'text-indigo-600', 'dark:text-indigo-400');
tab.classList.remove('border-transparent', 'text-gray-500');
} else {
tab.classList.remove('border-indigo-500', 'text-indigo-600', 'dark:text-indigo-400');
tab.classList.add('border-transparent', 'text-gray-500');
}
}
});
}
function calculateSuccessRate(item) {
const total = item.execution_count || item.executions || 0;
const successful = item.successful_count || item.successfulExecutions || 0;
return total > 0 ? Math.round((successful / total) * 100) : 0;
}
function formatNumber(num) {
return new Intl.NumberFormat().format(num);
}
function formatLastUsed(timestamp) {
if (!timestamp) return 'Never';
const date = new Date(timestamp);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins} min ago`;
if (diffMins < 1440) return `${Math.floor(diffMins / 60)} hours ago`;
if (diffMins < 10080) return `${Math.floor(diffMins / 1440)} days ago`;
return date.toLocaleDateString();
}
- Add Virtual Servers to Metrics Cards:
// In displayMetrics function, ensure servers are included
if (data.servers) {
const serversCard = createMetricsCard('Virtual Servers', data.servers);
metricsContainer.appendChild(serversCard);
}
Visual Design Considerations:
- Use consistent color coding for performance indicators
- Make tables responsive on mobile devices
- Add hover effects for better interactivity
- Use loading skeletons while data is being fetched
- Consider adding export functionality for metrics data
Testing Requirements:
- Verify all entity types display correctly in top performers
- Test with various data scenarios (no data, single item, many items)
- Ensure responsive design works on mobile devices
- Test dark mode appearance
- Verify performance with large datasets
- Test tab switching functionality
Acceptance Criteria:
- Virtual servers appear in main metrics display
- Top 5 tables show for all entity types (tools, resources, prompts, gateways, servers)
- Each table displays: rank, name, executions, avg response time, success rate, last used
- Tables are responsive and work on mobile devices
- Tab navigation works smoothly between entity types
- Performance badges show appropriate colors based on success rates
- Empty states display when no data is available
- Dark mode styling is consistent throughout
Performance Considerations:
- Implement pagination if lists grow beyond 5 items
- Consider caching metrics data with appropriate TTL
- Use database indexes on frequently queried columns
- Batch API calls where possible
Future Enhancements:
- Add time range filters (last hour, day, week, month)
- Include sparkline charts for trend visualization
- Add drill-down functionality to see detailed metrics
- Export metrics to CSV/PDF
- Real-time updates using WebSocket connections
Related Issues:
- General metrics endpoint optimization
- Database query performance for aggregations
- UI performance with large datasets
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestfrontendFrontend development (HTML, CSS, JavaScript)Frontend development (HTML, CSS, JavaScript)good first issueGood for newcomersGood for newcomershelp wantedExtra attention is neededExtra attention is neededtriageIssues / Features awaiting triageIssues / Features awaiting triage