mirror of
https://github.com/praveentcom/openproxy.git
synced 2026-06-07 07:03:57 +02:00
feat: add Next.js metrics dashboard for real-time visualization
Add a lightweight Next.js dashboard to visualize OpenProxy metrics in real-time. The dashboard provides comprehensive insights into LLM API usage, costs, and performance. Features: - Real-time metrics overview (requests, tokens, costs, response times) - Model breakdown with usage statistics - Hourly trends visualization with charts - Recent requests table with detailed information - Auto-refresh every 30 seconds - Configurable time ranges (1h, 6h, 24h, 7d) Technical details: - Built with Next.js 14 and React 18 - Uses Recharts for data visualization - Connects directly to PostgreSQL database - Runs on port 3008 by default - TypeScript for type safety - Minimal dependencies for lightweight deployment The dashboard complements the proxy server by providing a user-friendly interface for monitoring and analyzing LLM API usage patterns.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
const TABLE_NAME = process.env.DATABASE_TABLE || 'llm_proxy';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const hours = parseInt(searchParams.get('hours') || '24', 10);
|
||||
const limit = parseInt(searchParams.get('limit') || '100', 10);
|
||||
|
||||
try {
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
// Get summary statistics
|
||||
const summaryQuery = `
|
||||
SELECT
|
||||
COUNT(*) as total_requests,
|
||||
SUM(total_tokens) as total_tokens_used,
|
||||
SUM(total_cost) as total_cost,
|
||||
AVG(response_time) as avg_response_time,
|
||||
COUNT(DISTINCT model) as unique_models,
|
||||
COUNT(DISTINCT client_ip) as unique_clients
|
||||
FROM ${TABLE_NAME}
|
||||
WHERE timestamp >= NOW() - INTERVAL '${hours} hours'
|
||||
`;
|
||||
const summaryResult = await client.query(summaryQuery);
|
||||
const summary = summaryResult.rows[0];
|
||||
|
||||
// Get recent requests
|
||||
const recentQuery = `
|
||||
SELECT
|
||||
request_id,
|
||||
timestamp,
|
||||
model,
|
||||
prompt_tokens,
|
||||
completion_tokens,
|
||||
total_tokens,
|
||||
total_cost,
|
||||
response_time,
|
||||
response_status,
|
||||
client_ip,
|
||||
stream
|
||||
FROM ${TABLE_NAME}
|
||||
WHERE timestamp >= NOW() - INTERVAL '${hours} hours'
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ${limit}
|
||||
`;
|
||||
const recentResult = await client.query(recentQuery);
|
||||
const recentRequests = recentResult.rows;
|
||||
|
||||
// Get model breakdown
|
||||
const modelQuery = `
|
||||
SELECT
|
||||
model,
|
||||
COUNT(*) as request_count,
|
||||
SUM(total_tokens) as total_tokens,
|
||||
SUM(total_cost) as total_cost,
|
||||
AVG(response_time) as avg_response_time
|
||||
FROM ${TABLE_NAME}
|
||||
WHERE timestamp >= NOW() - INTERVAL '${hours} hours'
|
||||
GROUP BY model
|
||||
ORDER BY request_count DESC
|
||||
`;
|
||||
const modelResult = await client.query(modelQuery);
|
||||
const modelBreakdown = modelResult.rows;
|
||||
|
||||
// Get hourly trends
|
||||
const trendsQuery = `
|
||||
SELECT
|
||||
DATE_TRUNC('hour', timestamp) as hour,
|
||||
COUNT(*) as requests,
|
||||
SUM(total_tokens) as tokens,
|
||||
SUM(total_cost) as cost,
|
||||
AVG(response_time) as avg_response_time
|
||||
FROM ${TABLE_NAME}
|
||||
WHERE timestamp >= NOW() - INTERVAL '${hours} hours'
|
||||
GROUP BY hour
|
||||
ORDER BY hour ASC
|
||||
`;
|
||||
const trendsResult = await client.query(trendsQuery);
|
||||
const hourlyTrends = trendsResult.rows;
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
summary: {
|
||||
totalRequests: parseInt(summary.total_requests || '0'),
|
||||
totalTokens: parseInt(summary.total_tokens_used || '0'),
|
||||
totalCost: parseFloat(summary.total_cost || '0'),
|
||||
avgResponseTime: parseFloat(summary.avg_response_time || '0'),
|
||||
uniqueModels: parseInt(summary.unique_models || '0'),
|
||||
uniqueClients: parseInt(summary.unique_clients || '0'),
|
||||
},
|
||||
recentRequests,
|
||||
modelBreakdown,
|
||||
hourlyTrends,
|
||||
},
|
||||
timeRange: `${hours} hours`,
|
||||
});
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Database error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to fetch metrics',
|
||||
details: error instanceof Error ? error.message : 'Unknown error'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user