dashboard.htmlβ’20 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Token Saver MCP - Developer Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #6B5B95; /* Solid color instead of gradient for better compression */
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
/* text-shadow removed for better compression */
}
.dashboard {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto auto;
gap: 20px;
}
.card.full-width {
grid-column: 1 / -1;
}
.card {
background: white;
border-radius: 10px;
padding: 20px;
border: 1px solid #ddd; /* Simple border instead of shadow */
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card h2 {
color: #333;
margin-bottom: 15px;
font-size: 1.3em;
border-bottom: 2px solid #6B5B95;
padding-bottom: 10px;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
animation: pulse 2s infinite;
}
.status-active {
background: #10b981;
}
.status-inactive {
background: #ef4444;
}
.status-warning {
background: #f59e0b;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.metric {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.metric:last-child {
border-bottom: none;
}
.metric-label {
color: #666;
font-size: 0.9em;
}
.metric-value {
color: #333;
font-weight: bold;
font-family: 'Courier New', monospace;
}
.activity-log {
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.85em;
background: #f8f9fa;
padding: 10px;
border-radius: 5px;
}
.log-entry {
padding: 4px 0;
border-bottom: 1px solid #e0e0e0;
}
.log-time {
color: #666;
margin-right: 10px;
}
.log-method {
color: #6B5B95;
font-weight: bold;
}
.refresh-btn {
background: #6B5B95;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
transition: background 0.3s ease;
}
.refresh-btn:hover {
background: #5a4a8a;
}
.error-message {
background: #fee;
color: #c00;
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
.tools-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
margin-top: 10px;
}
@media (max-width: 1200px) {
.tools-grid {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 900px) {
.tools-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 600px) {
.tools-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.tool-badge {
background: #f0f0f0;
padding: 5px 10px;
border-radius: 20px;
font-size: 0.85em;
text-align: center;
}
.tool-badge.lsp {
background: #e0f2fe;
color: #0369a1;
}
.tool-badge.cdp {
background: #fef3c7;
color: #92400e;
}
.tool-badge.helper {
background: #f0fdf4;
color: #166534;
}
.tool-badge.system {
background: #e0e7ff;
color: #3730a3;
}
.chart-container {
height: 200px;
margin-top: 15px;
position: relative;
}
.workspace-info {
background: #f8f9fa;
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
.workspace-path {
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: #666;
word-break: break-all;
}
</style>
</head>
<body>
<div class="container">
<h1>π Token Saver MCP Dashboard</h1>
<div class="dashboard">
<!-- Top Row: Server Status | Request Metrics | Token Savings -->
<!-- Server Status Card -->
<div class="card">
<h2>Server Status</h2>
<div class="metric">
<span class="metric-label">Status</span>
<span class="metric-value">
<span class="status-indicator status-active"></span>
<span id="server-status">Active</span>
</span>
</div>
<div class="metric">
<span class="metric-label">Port</span>
<span class="metric-value" id="server-port">9527</span>
</div>
<div class="metric">
<span class="metric-label">Uptime</span>
<span class="metric-value" id="server-uptime">0h 0m</span>
</div>
<div class="metric">
<span class="metric-label">Mode</span>
<span class="metric-value" id="server-mode">Sessionless</span>
</div>
<div class="workspace-info">
<div class="metric-label">Workspace</div>
<div class="workspace-path" id="workspace-path">Loading...</div>
</div>
</div>
<!-- Request Metrics Card -->
<div class="card">
<h2>Request Metrics</h2>
<div class="metric">
<span class="metric-label">Total Requests</span>
<span class="metric-value" id="total-requests">0</span>
</div>
<div class="metric">
<span class="metric-label">Success Rate</span>
<span class="metric-value" id="success-rate">100%</span>
</div>
<div class="metric">
<span class="metric-label">Avg Response Time</span>
<span class="metric-value" id="avg-response-time">0ms</span>
</div>
<div class="metric">
<span class="metric-label">Active Connections</span>
<span class="metric-value" id="active-connections">0</span>
</div>
<div class="metric">
<span class="metric-label">Errors (24h)</span>
<span class="metric-value" id="error-count">0</span>
</div>
<button class="refresh-btn" onclick="refreshMetrics()">Refresh</button>
</div>
<!-- Token Savings Card -->
<div class="card">
<h2>Token Savings</h2>
<div class="metric">
<span class="metric-label">Total Saved</span>
<span class="metric-value" id="total-tokens-saved">0</span>
</div>
<div class="metric">
<span class="metric-label">Avg per Request</span>
<span class="metric-value" id="avg-tokens-saved">0</span>
</div>
<div class="metric">
<span class="metric-label">Last 24h</span>
<span class="metric-value" id="tokens-saved-24h">0</span>
</div>
<div class="metric">
<span class="metric-label">Cost Saved</span>
<span class="metric-value" id="cost-saved">$0.00</span>
</div>
<div class="metric">
<span class="metric-label">Efficiency</span>
<span class="metric-value" id="efficiency">100x</span>
</div>
</div>
<!-- Middle Row: Available Tools (full-width) -->
<!-- Available Tools Card -->
<div class="card full-width">
<h2>Available Tools</h2>
<div class="metric">
<span class="metric-label">LSP Tools</span>
<span class="metric-value" id="lsp-tool-count">15</span>
</div>
<div class="metric">
<span class="metric-label">CDP Tools</span>
<span class="metric-value" id="cdp-tool-count">8</span>
</div>
<div class="metric">
<span class="metric-label">Helper Tools</span>
<span class="metric-value" id="helper-tool-count">5</span>
</div>
<div class="metric">
<span class="metric-label">System Tools</span>
<span class="metric-value" id="system-tool-count">4</span>
</div>
<div class="tools-grid" id="tools-list">
<!-- Tools will be populated here -->
</div>
</div>
<!-- Bottom Row: Recent Activity | Response Time Graph | Most Used Tools -->
<!-- Recent Activity Card -->
<div class="card">
<h2>Recent Activity</h2>
<div class="activity-log" id="activity-log">
<!-- Activity entries will be added here -->
</div>
</div>
<!-- Performance Chart Card -->
<div class="card">
<h2>Response Time (Last 50 requests)</h2>
<div class="chart-container">
<canvas id="performance-chart"></canvas>
</div>
</div>
<!-- Popular Tools Card -->
<div class="card">
<h2>Most Used Tools (24h)</h2>
<div id="popular-tools">
<!-- Will be populated with tool usage stats -->
</div>
</div>
</div>
</div>
<script>
let eventSource = null;
let startTime = Date.now();
let responseTimeHistory = [];
async function fetchMetrics() {
try {
const response = await fetch('/metrics');
const data = await response.json();
updateDashboard(data);
} catch (error) {
console.error('Failed to fetch metrics:', error);
document.getElementById('server-status').textContent = 'Error';
document.querySelector('.status-indicator').className = 'status-indicator status-inactive';
}
}
async function fetchWorkspaceInfo() {
try {
const response = await fetch('/workspace-info');
const data = await response.json();
document.getElementById('workspace-path').textContent = data.workspacePath || 'No workspace';
document.getElementById('server-port').textContent = data.port || '9527';
} catch (error) {
console.error('Failed to fetch workspace info:', error);
}
}
async function fetchTools() {
try {
const response = await fetch('/available-tools');
const data = await response.json();
const toolsList = document.getElementById('tools-list');
toolsList.innerHTML = '';
data.tools.forEach(tool => {
const badge = document.createElement('div');
badge.className = `tool-badge ${tool.displayCategory || tool.category}`;
badge.textContent = tool.callCount > 0
? `${tool.name} (${tool.callCount})`
: tool.name;
toolsList.appendChild(badge);
});
document.getElementById('lsp-tool-count').textContent =
data.tools.filter(t => t.category === 'lsp').length;
document.getElementById('cdp-tool-count').textContent =
data.tools.filter(t => t.category === 'cdp').length;
document.getElementById('helper-tool-count').textContent =
data.tools.filter(t => t.category === 'helper').length;
document.getElementById('system-tool-count').textContent =
data.tools.filter(t => t.category === 'system').length;
} catch (error) {
console.error('Failed to fetch tools:', error);
}
}
function updateDashboard(data) {
// Update metrics
document.getElementById('total-requests').textContent = data.totalRequests || 0;
document.getElementById('success-rate').textContent =
Math.round((data.successRate || 0) * 100) + '%';
document.getElementById('avg-response-time').textContent =
Math.round(data.avgResponseTime || 0) + 'ms';
document.getElementById('active-connections').textContent = data.activeConnections || 0;
document.getElementById('error-count').textContent = data.errorCount || 0;
// Update token savings metrics
document.getElementById('total-tokens-saved').textContent =
(data.totalTokensSaved || 0).toLocaleString();
document.getElementById('avg-tokens-saved').textContent =
Math.round(data.avgTokensSaved || 0).toLocaleString();
document.getElementById('tokens-saved-24h').textContent =
(data.tokensSaved24h || 0).toLocaleString();
// Calculate cost saved (assuming $0.01 per 1000 tokens)
const costSaved = ((data.totalTokensSaved || 0) / 1000) * 0.01;
document.getElementById('cost-saved').textContent =
'$' + costSaved.toFixed(2);
// Calculate efficiency
const efficiency = data.efficiency || 100;
document.getElementById('efficiency').textContent = efficiency + 'x';
// Update uptime
const uptime = Date.now() - startTime;
const hours = Math.floor(uptime / 3600000);
const minutes = Math.floor((uptime % 3600000) / 60000);
document.getElementById('server-uptime').textContent = `${hours}h ${minutes}m`;
// Update activity log
if (data.recentActivity) {
const logContainer = document.getElementById('activity-log');
logContainer.innerHTML = data.recentActivity.map(entry => `
<div class="log-entry">
<span class="log-time">${new Date(entry.timestamp).toLocaleTimeString()}</span>
<span class="log-method">${entry.method}</span>
${entry.tool ? `<span>${entry.tool}</span>` : ''}
<span>${entry.status}</span>
</div>
`).join('');
}
// Update popular tools
if (data.popularTools) {
const popularTools = document.getElementById('popular-tools');
popularTools.innerHTML = data.popularTools.map((tool, index) => `
<div class="metric">
<span class="metric-label">${index + 1}. ${tool.name}</span>
<span class="metric-value">${tool.count} calls</span>
</div>
`).join('');
}
// Update response time history for chart
if (data.responseTimeHistory) {
responseTimeHistory = data.responseTimeHistory;
updateChart();
}
}
function updateChart() {
const canvas = document.getElementById('performance-chart');
const ctx = canvas.getContext('2d');
const width = canvas.offsetWidth;
const height = canvas.offsetHeight;
canvas.width = width;
canvas.height = height;
if (responseTimeHistory.length === 0) return;
const maxTime = Math.max(...responseTimeHistory, 100);
const step = width / Math.max(responseTimeHistory.length - 1, 1);
ctx.strokeStyle = '#667eea';
ctx.lineWidth = 2;
ctx.beginPath();
responseTimeHistory.forEach((time, index) => {
const x = index * step;
const y = height - (time / maxTime) * height;
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
}
function connectSSE() {
eventSource = new EventSource('/dashboard-events');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
updateDashboard(data);
};
eventSource.onerror = function(error) {
console.error('SSE connection error:', error);
setTimeout(connectSSE, 5000); // Reconnect after 5 seconds
};
}
function refreshMetrics() {
fetchMetrics();
fetchTools();
}
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
fetchMetrics();
fetchWorkspaceInfo();
fetchTools();
connectSSE();
// Auto-refresh every 5 seconds
setInterval(() => {
fetchMetrics();
fetchTools(); // Also refresh tools to update call counts
}, 5000);
});
</script>
</body>
</html>