- Fixed database relationships: LlmRequest now properly uses gateway_user_id instead of user_id - Updated Models: GatewayUser and LlmRequest relationships corrected - Removed User->llmRequests relationship (admin users don't have LLM requests) - Simplified Dashboard: Now shows Gateway User statistics instead of admin users - Removed obsolete Budgets management pages (budgets handled directly in gateway_users) - Removed User Budgets admin section (redundant with gateway_users management) - Fixed view errors: Added null-checks for user_id in keys views - Updated navigation: Removed Budget and User Budget links - Updated routes: Cleaned up unused BudgetController and UserManagementController routes - Simplified StatisticsService: Focus on gateway_users and basic metrics only
227 lines
11 KiB
PHP
227 lines
11 KiB
PHP
<x-app-layout>
|
|
<x-slot name="header">
|
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
|
{{ __('Dashboard') }} - LLM Gateway
|
|
</h2>
|
|
</x-slot>
|
|
|
|
<div class="py-12">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<!-- Total Gateway Users -->
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-600">Gateway Users</p>
|
|
<p class="text-3xl font-bold text-gray-900">
|
|
{{ number_format($stats['total_gateway_users']) }}
|
|
</p>
|
|
<p class="text-xs text-green-600 mt-1">
|
|
{{ $stats['active_gateway_users'] }} active
|
|
</p>
|
|
</div>
|
|
<div class="text-blue-500">
|
|
<svg class="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Requests Today -->
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-600">Requests Today</p>
|
|
<p class="text-3xl font-bold text-blue-600">
|
|
{{ number_format($stats['total_requests_today']) }}
|
|
</p>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
{{ number_format($stats['total_requests_month']) }} this month
|
|
</p>
|
|
</div>
|
|
<div class="text-blue-500">
|
|
<svg class="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Spend Today -->
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-600">Spend Today</p>
|
|
<p class="text-3xl font-bold text-green-600">
|
|
${{ number_format($stats['total_spend_today'], 2) }}
|
|
</p>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
${{ number_format($stats['total_spend_month'], 2) }} this month
|
|
</p>
|
|
</div>
|
|
<div class="text-green-500">
|
|
<svg class="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tokens Today -->
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-600">Tokens Today</p>
|
|
<p class="text-3xl font-bold text-purple-600">
|
|
{{ number_format($stats['total_tokens_today']) }}
|
|
</p>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
Avg: ${{ number_format($stats['avg_cost_per_request'], 4) }}/req
|
|
</p>
|
|
</div>
|
|
<div class="text-purple-500">
|
|
<svg class="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Daily Usage Chart -->
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Daily Usage (Last 30 Days)</h3>
|
|
<canvas id="dailyUsageChart" height="80"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Provider Statistics -->
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Provider Statistics (Last 30 Days)</h3>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Provider
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Requests
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Total Cost
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Total Tokens
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
@forelse($providerStats as $provider)
|
|
<tr>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
|
|
{{ $provider->provider }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
{{ number_format($provider->count) }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
${{ number_format($provider->total_cost, 2) }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
{{ number_format($provider->total_tokens) }}
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="4" class="px-6 py-4 text-center text-sm text-gray-500">
|
|
No data available yet
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
// Daily Usage Chart
|
|
const dailyUsageCtx = document.getElementById('dailyUsageChart').getContext('2d');
|
|
const dailyUsageData = @json($dailyUsage);
|
|
|
|
new Chart(dailyUsageCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: dailyUsageData.map(d => d.date),
|
|
datasets: [{
|
|
label: 'Requests',
|
|
data: dailyUsageData.map(d => d.requests),
|
|
borderColor: 'rgb(59, 130, 246)',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
tension: 0.1,
|
|
yAxisID: 'y',
|
|
}, {
|
|
label: 'Cost ($)',
|
|
data: dailyUsageData.map(d => d.cost),
|
|
borderColor: 'rgb(34, 197, 94)',
|
|
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
|
tension: 0.1,
|
|
yAxisID: 'y1',
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
},
|
|
scales: {
|
|
y: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: 'Requests'
|
|
}
|
|
},
|
|
y1: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: 'Cost ($)'
|
|
},
|
|
grid: {
|
|
drawOnChartArea: false,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
@endpush
|
|
</x-app-layout>
|