- Any-LLM Gateway setup with Docker Compose - Laravel 11 admin interface with Livewire - Dashboard with usage statistics and charts - Gateway Users management with budget tracking - API Keys management with revocation - Budget templates with assignment - Usage Logs with filtering and CSV export - Model Pricing management with calculator - PostgreSQL database integration - Complete authentication system for admins
1331 lines
32 KiB
Markdown
1331 lines
32 KiB
Markdown
# 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
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
use Illuminate\Notifications\Notifiable;
|
|
|
|
class Admin extends Authenticatable
|
|
{
|
|
use Notifiable;
|
|
|
|
protected $fillable = [
|
|
'name',
|
|
'email',
|
|
'password',
|
|
];
|
|
|
|
protected $hidden = [
|
|
'password',
|
|
'remember_token',
|
|
];
|
|
|
|
protected $casts = [
|
|
'email_verified_at' => 'datetime',
|
|
'password' => 'hashed',
|
|
];
|
|
}
|
|
```
|
|
|
|
### 4.2 GatewayUser Model
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class GatewayUser extends Model
|
|
{
|
|
protected $table = 'users';
|
|
protected $primaryKey = 'user_id';
|
|
public $incrementing = false;
|
|
protected $keyType = 'string';
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'alias',
|
|
'spend',
|
|
'budget_id',
|
|
'blocked',
|
|
'metadata',
|
|
'budget_started_at',
|
|
'next_budget_reset_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'spend' => '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
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class ApiKey extends Model
|
|
{
|
|
protected $primaryKey = 'id';
|
|
public $incrementing = false;
|
|
protected $keyType = 'string';
|
|
|
|
protected $fillable = [
|
|
'id',
|
|
'key_hash',
|
|
'key_name',
|
|
'user_id',
|
|
'last_used_at',
|
|
'expires_at',
|
|
'is_active',
|
|
'metadata',
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_active' => '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
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class UsageLog extends Model
|
|
{
|
|
protected $primaryKey = 'id';
|
|
public $incrementing = false;
|
|
protected $keyType = 'string';
|
|
public $timestamps = false;
|
|
|
|
protected $fillable = [
|
|
'id',
|
|
'api_key_id',
|
|
'user_id',
|
|
'timestamp',
|
|
'model',
|
|
'provider',
|
|
'endpoint',
|
|
'prompt_tokens',
|
|
'completion_tokens',
|
|
'total_tokens',
|
|
'cost',
|
|
'status',
|
|
'error_message',
|
|
];
|
|
|
|
protected $casts = [
|
|
'timestamp' => '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
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class Budget extends Model
|
|
{
|
|
protected $primaryKey = 'budget_id';
|
|
public $incrementing = false;
|
|
protected $keyType = 'string';
|
|
|
|
protected $fillable = [
|
|
'budget_id',
|
|
'max_budget',
|
|
'budget_duration_sec',
|
|
];
|
|
|
|
protected $casts = [
|
|
'max_budget' => '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
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class ModelPricing extends Model
|
|
{
|
|
protected $table = 'model_pricing';
|
|
protected $primaryKey = 'model_key';
|
|
public $incrementing = false;
|
|
protected $keyType = 'string';
|
|
|
|
protected $fillable = [
|
|
'model_key',
|
|
'input_price_per_million',
|
|
'output_price_per_million',
|
|
];
|
|
|
|
protected $casts = [
|
|
'input_price_per_million' => '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
|
|
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\UsageLog;
|
|
use App\Models\GatewayUser;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class StatisticsService
|
|
{
|
|
public function getDashboardStats()
|
|
{
|
|
return [
|
|
'total_users' => 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
|
|
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Services\StatisticsService;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function __construct(
|
|
private StatisticsService $statsService
|
|
) {}
|
|
|
|
public function index()
|
|
{
|
|
$stats = $this->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
|
|
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\GatewayUser;
|
|
use App\Services\StatisticsService;
|
|
use Illuminate\Http\Request;
|
|
|
|
class GatewayUserController extends Controller
|
|
{
|
|
public function __construct(
|
|
private StatisticsService $statsService
|
|
) {}
|
|
|
|
public function index()
|
|
{
|
|
$users = GatewayUser::with('budget')
|
|
->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
|
|
<?php
|
|
|
|
namespace App\Http\Livewire\Dashboard;
|
|
|
|
use Livewire\Component;
|
|
use App\Services\StatisticsService;
|
|
|
|
class StatsOverview extends Component
|
|
{
|
|
public $refreshInterval = 30000; // 30 seconds
|
|
|
|
public function render()
|
|
{
|
|
$stats = app(StatisticsService::class)->getDashboardStats();
|
|
|
|
return view('livewire.dashboard.stats-overview', [
|
|
'stats' => $stats
|
|
]);
|
|
}
|
|
|
|
public function refresh()
|
|
{
|
|
// Livewire will automatically re-render
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.2 Usage Chart Component
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Http\Livewire\Dashboard;
|
|
|
|
use Livewire\Component;
|
|
use App\Services\StatisticsService;
|
|
|
|
class UsageChart extends Component
|
|
{
|
|
public $days = 30;
|
|
public $chartType = 'requests'; // requests, cost, tokens
|
|
|
|
public function render()
|
|
{
|
|
$data = app(StatisticsService::class)->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
|
|
<!-- resources/views/layouts/app.blade.php -->
|
|
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Any-LLM Admin - @yield('title')</title>
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
@livewireStyles
|
|
</head>
|
|
<body class="bg-gray-100">
|
|
<div class="min-h-screen">
|
|
<!-- Navigation -->
|
|
@include('layouts.navigation')
|
|
|
|
<!-- Page Content -->
|
|
<main class="py-10">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
@yield('content')
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
@livewireScripts
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### 8.2 Dashboard View
|
|
```blade
|
|
<!-- resources/views/dashboard.blade.php -->
|
|
@extends('layouts.app')
|
|
|
|
@section('title', 'Dashboard')
|
|
|
|
@section('content')
|
|
<div class="space-y-6">
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<div class="text-sm text-gray-600">Total Users</div>
|
|
<div class="text-3xl font-bold text-gray-900">{{ $stats['total_users'] }}</div>
|
|
</div>
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<div class="text-sm text-gray-600">Requests Today</div>
|
|
<div class="text-3xl font-bold text-blue-600">{{ number_format($stats['total_requests_today']) }}</div>
|
|
</div>
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<div class="text-sm text-gray-600">Spend Today</div>
|
|
<div class="text-3xl font-bold text-green-600">${{ number_format($stats['total_spend_today'], 2) }}</div>
|
|
</div>
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<div class="text-sm text-gray-600">Tokens Today</div>
|
|
<div class="text-3xl font-bold text-purple-600">{{ number_format($stats['total_tokens_today']) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Usage Chart -->
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<h3 class="text-lg font-semibold mb-4">Usage Trend (Last 30 Days)</h3>
|
|
<canvas id="usageChart"></canvas>
|
|
</div>
|
|
|
|
<!-- Provider Stats & Top Users -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<h3 class="text-lg font-semibold mb-4">Usage by Provider</h3>
|
|
<canvas id="providerChart"></canvas>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<h3 class="text-lg font-semibold mb-4">Top Users</h3>
|
|
<div class="space-y-3">
|
|
@foreach($topUsers as $user)
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<div class="font-medium">{{ $user->alias ?? $user->user_id }}</div>
|
|
<div class="text-sm text-gray-500">{{ number_format($user->usage_logs_count) }} requests</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="font-semibold text-green-600">${{ number_format($user->usage_logs_sum_cost ?? 0, 2) }}</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script>
|
|
// Chart.js initialization
|
|
const usageCtx = document.getElementById('usageChart').getContext('2d');
|
|
new Chart(usageCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: @json($dailyUsage->pluck('date')),
|
|
datasets: [{
|
|
label: 'Requests',
|
|
data: @json($dailyUsage->pluck('requests')),
|
|
borderColor: 'rgb(59, 130, 246)',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: true
|
|
}
|
|
});
|
|
|
|
const providerCtx = document.getElementById('providerChart').getContext('2d');
|
|
new Chart(providerCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: @json($providerStats->pluck('provider')),
|
|
datasets: [{
|
|
data: @json($providerStats->pluck('count')),
|
|
backgroundColor: [
|
|
'rgb(59, 130, 246)',
|
|
'rgb(16, 185, 129)',
|
|
'rgb(249, 115, 22)',
|
|
'rgb(168, 85, 247)',
|
|
]
|
|
}]
|
|
}
|
|
});
|
|
</script>
|
|
@endpush
|
|
@endsection
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Routes
|
|
|
|
### 9.1 Web Routes
|
|
```php
|
|
<?php
|
|
|
|
use Illuminate\Support\Facades\Route;
|
|
use App\Http\Controllers\DashboardController;
|
|
use App\Http\Controllers\GatewayUserController;
|
|
use App\Http\Controllers\ApiKeyController;
|
|
use App\Http\Controllers\BudgetController;
|
|
use App\Http\Controllers\UsageLogController;
|
|
use App\Http\Controllers\ModelPricingController;
|
|
|
|
// Authentication Routes (Laravel Breeze)
|
|
require __DIR__.'/auth.php';
|
|
|
|
// Protected Admin Routes
|
|
Route::middleware(['auth'])->group(function () {
|
|
// Dashboard
|
|
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
|
|
|
// Gateway Users
|
|
Route::resource('gateway-users', GatewayUserController::class);
|
|
Route::post('gateway-users/{id}/toggle-block', [GatewayUserController::class, 'toggleBlock'])
|
|
->name('gateway-users.toggle-block');
|
|
|
|
// API Keys
|
|
Route::resource('api-keys', ApiKeyController::class)->except(['edit', 'update']);
|
|
Route::post('api-keys/{id}/revoke', [ApiKeyController::class, 'revoke'])
|
|
->name('api-keys.revoke');
|
|
|
|
// Budgets
|
|
Route::resource('budgets', BudgetController::class');
|
|
|
|
// Usage Logs
|
|
Route::get('usage-logs', [UsageLogController::class, 'index'])->name('usage-logs.index');
|
|
Route::get('usage-logs/export', [UsageLogController::class, 'export'])->name('usage-logs.export');
|
|
|
|
// Model Pricing
|
|
Route::resource('model-pricing', ModelPricingController::class);
|
|
|
|
// API for Charts (AJAX)
|
|
Route::prefix('api')->group(function () {
|
|
Route::get('stats/daily-usage', [DashboardController::class, 'dailyUsage']);
|
|
Route::get('stats/provider-breakdown', [DashboardController::class, 'providerBreakdown']);
|
|
Route::get('stats/model-usage', [DashboardController::class, 'modelUsage']);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Installation & Setup
|
|
|
|
### 10.1 Voraussetzungen
|
|
- PHP 8.2+
|
|
- Composer
|
|
- Node.js & NPM
|
|
- PostgreSQL Client
|
|
|
|
### 10.2 Installation Steps
|
|
|
|
```bash
|
|
# 1. Laravel Projekt erstellen
|
|
composer create-project laravel/laravel any-llm-admin
|
|
cd any-llm-admin
|
|
|
|
# 2. Zusätzliche Packages installieren
|
|
composer require livewire/livewire
|
|
composer require laravel/breeze --dev
|
|
|
|
# 3. Breeze mit Livewire installieren
|
|
php artisan breeze:install livewire
|
|
npm install && npm run build
|
|
|
|
# 4. .env konfigurieren
|
|
cat > .env << 'EOF'
|
|
APP_NAME="Any-LLM Admin"
|
|
APP_URL=http://localhost:8001
|
|
|
|
DB_CONNECTION=pgsql
|
|
DB_HOST=localhost
|
|
DB_PORT=5432
|
|
DB_DATABASE=gateway
|
|
DB_USERNAME=gateway
|
|
DB_PASSWORD=gateway
|
|
EOF
|
|
|
|
# 5. Admin Migration erstellen
|
|
php artisan make:migration create_admins_table
|
|
|
|
# 6. Models erstellen
|
|
php artisan make:model GatewayUser
|
|
php artisan make:model ApiKey
|
|
php artisan make:model UsageLog
|
|
php artisan make:model Budget
|
|
php artisan make:model BudgetResetLog
|
|
php artisan make:model ModelPricing
|
|
|
|
# 7. Services erstellen
|
|
php artisan make:class Services/StatisticsService
|
|
|
|
# 8. Controllers erstellen
|
|
php artisan make:controller DashboardController
|
|
php artisan make:controller GatewayUserController --resource
|
|
php artisan make:controller ApiKeyController --resource
|
|
php artisan make:controller BudgetController --resource
|
|
php artisan make:controller UsageLogController
|
|
php artisan make:controller ModelPricingController --resource
|
|
|
|
# 9. Livewire Components erstellen
|
|
php artisan make:livewire Dashboard/StatsOverview
|
|
php artisan make:livewire Dashboard/UsageChart
|
|
php artisan make:livewire GatewayUsers/Index
|
|
php artisan make:livewire ApiKeys/Index
|
|
|
|
# 10. Migration für Admins ausführen
|
|
php artisan migrate
|
|
|
|
# 11. Admin User erstellen
|
|
php artisan tinker
|
|
>>> \App\Models\Admin::create([
|
|
... 'name' => 'Admin',
|
|
... 'email' => 'admin@example.com',
|
|
... 'password' => bcrypt('password123')
|
|
... ]);
|
|
|
|
# 12. Development Server starten
|
|
php artisan serve --port=8001
|
|
```
|
|
|
|
### 10.3 Admin Migration
|
|
```php
|
|
<?php
|
|
|
|
use Illuminate\Database\Migrations\Migration;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
return new class extends Migration
|
|
{
|
|
public function up(): void
|
|
{
|
|
Schema::create('admins', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('name');
|
|
$table->string('email')->unique();
|
|
$table->timestamp('email_verified_at')->nullable();
|
|
$table->string('password');
|
|
$table->rememberToken();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
public function down(): void
|
|
{
|
|
Schema::dropIfExists('admins');
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Features & Funktionalität
|
|
|
|
### 11.1 Dashboard
|
|
✅ **Übersicht-Statistiken**
|
|
- Total Users / Active Users
|
|
- Requests Today
|
|
- Spend Today / This Month
|
|
- Tokens Today
|
|
|
|
✅ **Visualisierungen**
|
|
- Daily Usage Chart (Line Chart)
|
|
- Provider Breakdown (Doughnut Chart)
|
|
- Model Usage (Bar Chart)
|
|
- Cost Trends
|
|
|
|
✅ **Quick Actions**
|
|
- Top Users Widget
|
|
- Recent Activity Feed
|
|
- Alerts (Budget Warnings)
|
|
|
|
### 11.2 Gateway Users Management
|
|
✅ **Liste aller Users**
|
|
- Sortierbar nach Spend, Requests, Created Date
|
|
- Filter: Active / Blocked / All
|
|
- Suche nach User ID / Alias
|
|
|
|
✅ **User Detail Page**
|
|
- Übersicht (Spend, Requests, Tokens)
|
|
- Associated API Keys
|
|
- Budget Information
|
|
- 30-Day Usage Chart
|
|
- Recent Activity Log
|
|
|
|
✅ **User Actions**
|
|
- Create New User
|
|
- Edit User (Alias, Budget)
|
|
- Block / Unblock User
|
|
- Delete User (mit Confirmation)
|
|
|
|
### 11.3 API Keys Management
|
|
✅ **Liste aller Keys**
|
|
- Masked Keys anzeigen
|
|
- Status: Active / Expired / Revoked
|
|
- Last Used Date
|
|
- Associated User
|
|
|
|
✅ **Key Actions**
|
|
- Create Virtual Key
|
|
- Revoke Key
|
|
- Set Expiration Date
|
|
- View Key Details
|
|
|
|
### 11.4 Budgets Management
|
|
✅ **Budget Templates**
|
|
- Daily / Weekly / Monthly Budgets
|
|
- Custom Duration
|
|
- No Limit Option
|
|
|
|
✅ **Budget Assignment**
|
|
- Assign to Users
|
|
- Bulk Assignment
|
|
- Auto-Reset Configuration
|
|
|
|
### 11.5 Usage Logs
|
|
✅ **Comprehensive Logging**
|
|
- Filter by Date Range
|
|
- Filter by User / Provider / Model
|
|
- Export to CSV / Excel
|
|
- Real-time Updates (Livewire)
|
|
|
|
✅ **Log Details**
|
|
- Request Metadata
|
|
- Token Counts
|
|
- Cost Calculation
|
|
- Error Messages (if failed)
|
|
|
|
### 11.6 Model Pricing
|
|
✅ **Pricing Management**
|
|
- Add New Model Pricing
|
|
- Update Existing Prices
|
|
- Bulk Import from CSV
|
|
- Cost Calculator Tool
|
|
|
|
---
|
|
|
|
## 12. Security & Best Practices
|
|
|
|
### 12.1 Authentication
|
|
```php
|
|
// auth.php guard configuration
|
|
'guards' => [
|
|
'web' => [
|
|
'driver' => 'session',
|
|
'provider' => 'admins',
|
|
],
|
|
],
|
|
|
|
'providers' => [
|
|
'admins' => [
|
|
'driver' => 'eloquent',
|
|
'model' => App\Models\Admin::class,
|
|
],
|
|
],
|
|
```
|
|
|
|
### 12.2 Middleware
|
|
- `auth` - Alle Admin Routes
|
|
- `throttle:60,1` - Rate Limiting
|
|
- CSRF Protection (automatisch)
|
|
|
|
### 12.3 Database Security
|
|
- Read-only Connection für Statistics
|
|
- Prepared Statements (Eloquent default)
|
|
- Input Validation & Sanitization
|
|
|
|
---
|
|
|
|
## 13. Testing Strategy
|
|
|
|
### 13.1 Feature Tests
|
|
```php
|
|
// tests/Feature/DashboardTest.php
|
|
public function test_admin_can_view_dashboard()
|
|
{
|
|
$admin = Admin::factory()->create();
|
|
|
|
$response = $this->actingAs($admin)
|
|
->get('/dashboard');
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertSee('Total Users');
|
|
}
|
|
```
|
|
|
|
### 13.2 Unit Tests
|
|
```php
|
|
// tests/Unit/StatisticsServiceTest.php
|
|
public function test_dashboard_stats_calculation()
|
|
{
|
|
$stats = app(StatisticsService::class)->getDashboardStats();
|
|
|
|
$this->assertIsArray($stats);
|
|
$this->assertArrayHasKey('total_users', $stats);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Deployment Checklist
|
|
|
|
### 14.1 Production Setup
|
|
```bash
|
|
# 1. Umgebung vorbereiten
|
|
composer install --optimize-autoloader --no-dev
|
|
npm ci && npm run build
|
|
|
|
# 2. .env für Production
|
|
APP_ENV=production
|
|
APP_DEBUG=false
|
|
APP_KEY=[generieren mit php artisan key:generate]
|
|
|
|
# 3. Optimierungen
|
|
php artisan config:cache
|
|
php artisan route:cache
|
|
php artisan view:cache
|
|
php artisan event:cache
|
|
|
|
# 4. Queue Worker (optional)
|
|
php artisan queue:work --tries=3
|
|
```
|
|
|
|
### 14.2 Server Requirements
|
|
- PHP 8.2+ (php-fpm)
|
|
- Nginx / Apache
|
|
- PostgreSQL Client
|
|
- Supervisor (für Queue Workers)
|
|
- SSL Certificate (Let's Encrypt)
|
|
|
|
---
|
|
|
|
## 15. Erweiterungsmöglichkeiten
|
|
|
|
### 15.1 Geplante Features
|
|
- [ ] Email Notifications (Budget Alerts)
|
|
- [ ] API Rate Limiting per User
|
|
- [ ] Advanced Analytics Dashboard
|
|
- [ ] Multi-Admin with Roles
|
|
- [ ] Audit Log
|
|
- [ ] Webhook Support
|
|
- [ ] Slack Integration
|
|
- [ ] Cost Forecasting
|
|
|
|
### 15.2 Integration Options
|
|
- Stripe für Billing
|
|
- Sentry für Error Tracking
|
|
- DataDog für Monitoring
|
|
- Grafana für Advanced Charts
|
|
|
|
---
|
|
|
|
## 16. Maintenance
|
|
|
|
### 16.1 Backup Strategy
|
|
```bash
|
|
# Database Backup
|
|
pg_dump -U gateway gateway > backup_$(date +%Y%m%d).sql
|
|
|
|
# Automated Backup
|
|
0 2 * * * /usr/bin/pg_dump -U gateway gateway > /backups/gateway_$(date +\%Y\%m\%d).sql
|
|
```
|
|
|
|
### 16.2 Log Rotation
|
|
```bash
|
|
# Laravel Log Rotation
|
|
php artisan log:clear --keep=30
|
|
|
|
# Usage Logs Archival (älter als 90 Tage)
|
|
DELETE FROM usage_logs WHERE timestamp < NOW() - INTERVAL '90 days';
|
|
```
|
|
|
|
---
|
|
|
|
## 17. Support & Documentation
|
|
|
|
### 17.1 Admin Handbuch
|
|
- Benutzer-Onboarding Guide
|
|
- Troubleshooting Common Issues
|
|
- API Key Best Practices
|
|
- Budget Configuration Examples
|
|
|
|
### 17.2 Developer Documentation
|
|
- Code Style Guide (PSR-12)
|
|
- Component Structure
|
|
- Adding New Features
|
|
- Testing Guidelines
|
|
|
|
---
|
|
|
|
**Ende des Implementierungskonzepts**
|
|
|
|
Nächste Schritte:
|
|
1. Repository aufsetzen
|
|
2. Models & Migrations implementieren
|
|
3. Dashboard UI aufbauen
|
|
4. Testing durchführen
|
|
5. Production Deployment
|