# Laravel Verwaltungsoberfläche für Any-LLM Gateway ## Implementierungskonzept --- ## 1. Projekt-Übersicht ### 1.1 Ziel Vollständige Laravel-Verwaltungsoberfläche für das Any-LLM Gateway mit: - Benutzer-Login und Authentifizierung - Dashboard mit Statistiken und Analysen - Verwaltung von Users, API Keys, Budgets - Detaillierte Nutzungsberichte - Monitoring und Alerts ### 1.2 Tech Stack - **Backend**: Laravel 11.x - **Frontend**: Livewire 3.x + Alpine.js + Tailwind CSS - **Charts**: Chart.js / ApexCharts - **Datenbank**: PostgreSQL (existing Gateway DB) - **Authentication**: Laravel Breeze mit Livewire --- ## 2. Datenbankstruktur (Existing Gateway DB) ### 2.1 Tabellen-Übersicht ``` ┌─────────────────┐ │ users │ ← Gateway Users (API Consumers) └────────┬────────┘ │ ┌────┴────┬─────────────┬──────────────┐ │ │ │ │ ┌───▼────┐ ┌─▼────────┐ ┌──▼───────┐ ┌──▼──────────┐ │api_keys│ │usage_logs│ │ budgets │ │budget_reset │ └────────┘ └──────────┘ └──────────┘ │ _logs │ └─────────────┘ ``` ### 2.2 Tabellen-Details #### `users` - Gateway API Users ```sql - user_id (PK) VARCHAR - alias VARCHAR - spend DOUBLE - budget_id (FK) VARCHAR - blocked BOOLEAN - created_at TIMESTAMP - updated_at TIMESTAMP - metadata JSON - budget_started_at TIMESTAMP - next_budget_reset_at TIMESTAMP ``` #### `api_keys` - Virtual Keys ```sql - id (PK) VARCHAR - key_hash VARCHAR (UNIQUE) - key_name VARCHAR - user_id (FK) VARCHAR - created_at TIMESTAMP - last_used_at TIMESTAMP - expires_at TIMESTAMP - is_active BOOLEAN - metadata JSON ``` #### `usage_logs` - Request Tracking ```sql - id (PK) VARCHAR - api_key_id (FK) VARCHAR - user_id (FK) VARCHAR - timestamp TIMESTAMP (INDEXED) - model VARCHAR - provider VARCHAR - endpoint VARCHAR - prompt_tokens INT - completion_tokens INT - total_tokens INT - cost DOUBLE - status VARCHAR - error_message VARCHAR ``` #### `budgets` - Budget Definitions ```sql - budget_id (PK) VARCHAR - max_budget DOUBLE - created_at TIMESTAMP - updated_at TIMESTAMP - budget_duration_sec INT ``` #### `model_pricing` - Model Costs ```sql - model_key (PK) VARCHAR - input_price_per_million DOUBLE - output_price_per_million DOUBLE - created_at TIMESTAMP - updated_at TIMESTAMP ``` --- ## 3. Laravel Projektstruktur ### 3.1 Verzeichnisstruktur ``` any-llm-admin/ ├── app/ │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── DashboardController.php │ │ │ ├── GatewayUserController.php │ │ │ ├── ApiKeyController.php │ │ │ ├── BudgetController.php │ │ │ ├── UsageLogController.php │ │ │ └── ModelPricingController.php │ │ ├── Livewire/ │ │ │ ├── Dashboard/ │ │ │ │ ├── StatsOverview.php │ │ │ │ ├── UsageChart.php │ │ │ │ ├── TopUsers.php │ │ │ │ └── RecentActivity.php │ │ │ ├── GatewayUsers/ │ │ │ │ ├── Index.php │ │ │ │ ├── Create.php │ │ │ │ ├── Edit.php │ │ │ │ └── Show.php │ │ │ ├── ApiKeys/ │ │ │ │ ├── Index.php │ │ │ │ ├── Create.php │ │ │ │ └── Revoke.php │ │ │ └── Budgets/ │ │ │ ├── Index.php │ │ │ ├── Create.php │ │ │ └── Edit.php │ │ └── Middleware/ │ │ └── EnsureAdmin.php │ ├── Models/ │ │ ├── Admin.php (Laravel Auth User) │ │ ├── GatewayUser.php (Gateway users table) │ │ ├── ApiKey.php │ │ ├── UsageLog.php │ │ ├── Budget.php │ │ ├── BudgetResetLog.php │ │ └── ModelPricing.php │ └── Services/ │ ├── StatisticsService.php │ ├── BudgetService.php │ └── GatewayApiService.php ├── database/ │ ├── migrations/ │ │ └── 2025_11_15_000001_create_admins_table.php │ └── seeders/ │ └── AdminSeeder.php ├── resources/ │ ├── views/ │ │ ├── layouts/ │ │ │ ├── app.blade.php │ │ │ ├── navigation.blade.php │ │ │ └── guest.blade.php │ │ ├── dashboard.blade.php │ │ ├── gateway-users/ │ │ ├── api-keys/ │ │ ├── budgets/ │ │ ├── usage-logs/ │ │ └── model-pricing/ │ └── js/ │ └── charts.js └── routes/ ├── web.php └── api.php ``` --- ## 4. Laravel Models ### 4.1 Admin Model (Laravel Auth) ```php 'datetime', 'password' => 'hashed', ]; } ``` ### 4.2 GatewayUser Model ```php 'double', 'blocked' => 'boolean', 'metadata' => 'array', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'budget_started_at' => 'datetime', 'next_budget_reset_at' => 'datetime', ]; // Relationships public function apiKeys() { return $this->hasMany(ApiKey::class, 'user_id', 'user_id'); } public function usageLogs() { return $this->hasMany(UsageLog::class, 'user_id', 'user_id'); } public function budget() { return $this->belongsTo(Budget::class, 'budget_id', 'budget_id'); } public function budgetResetLogs() { return $this->hasMany(BudgetResetLog::class, 'user_id', 'user_id'); } // Scopes public function scopeActive($query) { return $query->where('blocked', false); } public function scopeBlocked($query) { return $query->where('blocked', true); } // Accessors public function getSpendFormattedAttribute() { return '$' . number_format($this->spend, 2); } public function getTotalRequestsAttribute() { return $this->usageLogs()->count(); } public function getTotalTokensAttribute() { return $this->usageLogs()->sum('total_tokens'); } } ``` ### 4.3 ApiKey Model ```php 'boolean', 'metadata' => 'array', 'created_at' => 'datetime', 'last_used_at' => 'datetime', 'expires_at' => 'datetime', ]; // Relationships public function gatewayUser() { return $this->belongsTo(GatewayUser::class, 'user_id', 'user_id'); } public function usageLogs() { return $this->hasMany(UsageLog::class, 'api_key_id', 'id'); } // Scopes public function scopeActive($query) { return $query->where('is_active', true) ->where(function ($q) { $q->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }); } public function scopeExpired($query) { return $query->whereNotNull('expires_at') ->where('expires_at', '<=', now()); } // Accessors public function getMaskedKeyAttribute() { return 'gw-' . substr($this->id, 0, 8) . '...' . substr($this->id, -8); } public function getIsExpiredAttribute() { return $this->expires_at && $this->expires_at->isPast(); } } ``` ### 4.4 UsageLog Model ```php 'datetime', 'prompt_tokens' => 'integer', 'completion_tokens' => 'integer', 'total_tokens' => 'integer', 'cost' => 'double', ]; // Relationships public function gatewayUser() { return $this->belongsTo(GatewayUser::class, 'user_id', 'user_id'); } public function apiKey() { return $this->belongsTo(ApiKey::class, 'api_key_id', 'id'); } // Scopes public function scopeSuccess($query) { return $query->where('status', 'success'); } public function scopeFailed($query) { return $query->where('status', '!=', 'success'); } public function scopeToday($query) { return $query->whereDate('timestamp', today()); } public function scopeDateRange($query, $start, $end) { return $query->whereBetween('timestamp', [$start, $end]); } // Accessors public function getCostFormattedAttribute() { return $this->cost ? '$' . number_format($this->cost, 4) : 'N/A'; } } ``` ### 4.5 Budget Model ```php 'double', 'budget_duration_sec' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; // Relationships public function gatewayUsers() { return $this->hasMany(GatewayUser::class, 'budget_id', 'budget_id'); } public function resetLogs() { return $this->hasMany(BudgetResetLog::class, 'budget_id', 'budget_id'); } // Accessors public function getMaxBudgetFormattedAttribute() { return '$' . number_format($this->max_budget, 2); } public function getDurationHumanAttribute() { if (!$this->budget_duration_sec) return 'No limit'; $days = floor($this->budget_duration_sec / 86400); $hours = floor(($this->budget_duration_sec % 86400) / 3600); return "{$days}d {$hours}h"; } } ``` ### 4.6 ModelPricing Model ```php 'double', 'output_price_per_million' => 'double', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; // Accessors public function getInputPriceFormattedAttribute() { return '$' . number_format($this->input_price_per_million, 2) . '/M'; } public function getOutputPriceFormattedAttribute() { return '$' . number_format($this->output_price_per_million, 2) . '/M'; } } ``` --- ## 5. Services ### 5.1 StatisticsService ```php GatewayUser::count(), 'active_users' => GatewayUser::active()->count(), 'total_requests_today' => UsageLog::today()->count(), 'total_spend_today' => UsageLog::today()->sum('cost'), 'total_tokens_today' => UsageLog::today()->sum('total_tokens'), ]; } public function getUsageByProvider($days = 30) { return UsageLog::selectRaw('provider, COUNT(*) as count, SUM(cost) as total_cost') ->where('timestamp', '>=', now()->subDays($days)) ->groupBy('provider') ->get(); } public function getUsageByModel($days = 30) { return UsageLog::selectRaw('model, COUNT(*) as count, SUM(total_tokens) as tokens') ->where('timestamp', '>=', now()->subDays($days)) ->groupBy('model') ->orderByDesc('count') ->limit(10) ->get(); } public function getDailyUsageChart($days = 30) { return UsageLog::selectRaw('DATE(timestamp) as date, COUNT(*) as requests, SUM(cost) as cost') ->where('timestamp', '>=', now()->subDays($days)) ->groupBy('date') ->orderBy('date') ->get(); } public function getTopUsers($limit = 10) { return GatewayUser::withCount('usageLogs') ->withSum('usageLogs', 'cost') ->orderByDesc('usage_logs_sum_cost') ->limit($limit) ->get(); } public function getRecentActivity($limit = 20) { return UsageLog::with(['gatewayUser', 'apiKey']) ->orderByDesc('timestamp') ->limit($limit) ->get(); } public function getUserStatistics($userId, $days = 30) { $stats = UsageLog::where('user_id', $userId) ->where('timestamp', '>=', now()->subDays($days)) ->selectRaw(' COUNT(*) as total_requests, SUM(prompt_tokens) as total_prompt_tokens, SUM(completion_tokens) as total_completion_tokens, SUM(total_tokens) as total_tokens, SUM(cost) as total_cost, AVG(total_tokens) as avg_tokens_per_request ') ->first(); return $stats; } } ``` --- ## 6. Controllers ### 6.1 DashboardController ```php statsService->getDashboardStats(); $dailyUsage = $this->statsService->getDailyUsageChart(30); $topUsers = $this->statsService->getTopUsers(5); $providerStats = $this->statsService->getUsageByProvider(30); return view('dashboard', compact( 'stats', 'dailyUsage', 'topUsers', 'providerStats' )); } } ``` ### 6.2 GatewayUserController ```php withCount('apiKeys') ->withCount('usageLogs') ->paginate(20); return view('gateway-users.index', compact('users')); } public function show($userId) { $user = GatewayUser::with(['apiKeys', 'budget']) ->findOrFail($userId); $stats = $this->statsService->getUserStatistics($userId, 30); $recentLogs = $user->usageLogs() ->orderByDesc('timestamp') ->limit(50) ->get(); return view('gateway-users.show', compact('user', 'stats', 'recentLogs')); } // ... weitere CRUD Methoden } ``` --- ## 7. Livewire Components ### 7.1 Dashboard Stats Overview ```php getDashboardStats(); return view('livewire.dashboard.stats-overview', [ 'stats' => $stats ]); } public function refresh() { // Livewire will automatically re-render } } ``` ### 7.2 Usage Chart Component ```php getDailyUsageChart($this->days); return view('livewire.dashboard.usage-chart', [ 'chartData' => $data ]); } public function updatedDays() { // Chart will automatically update } } ``` --- ## 8. Views Structure ### 8.1 Dashboard Layout ```blade