Files
jabali-panel/app/Filament/Admin/Pages/ResourceUsage.php
2026-01-27 23:38:27 +02:00

258 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Admin\Pages;
use App\Models\User;
use App\Models\UserResourceUsage;
use BackedEnum;
use Filament\Forms\Components\Select;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;
class ResourceUsage extends Page implements HasForms
{
use InteractsWithForms;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChartBar;
protected static ?int $navigationSort = 14;
protected static ?string $slug = 'resource-usage';
protected string $view = 'filament.admin.pages.resource-usage';
public array $usageFormData = [];
public array $chartData = [];
public array $performanceChartData = [];
public bool $hasPerformanceData = false;
public ?int $cpuLimitPercent = null;
public ?float $memoryLimitGb = null;
public array $cpuStats = [];
public array $memoryStats = [];
public array $summary = [];
public function getTitle(): string|Htmlable
{
return __('Resource Usage Reports');
}
public static function getNavigationLabel(): string
{
return __('Resource Usage');
}
public function mount(): void
{
$defaultUser = User::query()->where('is_admin', false)->orderBy('name')->first();
$this->usageFormData = [
'user_id' => $defaultUser?->id,
];
$this->loadUsage();
}
protected function getForms(): array
{
return ['usageForm'];
}
public function usageForm(Schema $schema): Schema
{
return $schema
->statePath('usageFormData')
->schema([
Section::make(__('Filters'))
->schema([
Select::make('user_id')
->label(__('User'))
->options($this->getUserOptions())
->searchable()
->preload()
->live()
->afterStateUpdated(function () {
$this->loadUsage();
})
->required(),
])
->columns(1),
]);
}
protected function getUserOptions(): array
{
return User::query()
->where('is_admin', false)
->orderBy('name')
->pluck('name', 'id')
->toArray();
}
protected function loadUsage(): void
{
$userId = $this->usageFormData['user_id'] ?? null;
$userId = $userId ? (int) $userId : null;
if (! $userId) {
$this->chartData = [];
$this->summary = [];
return;
}
$user = User::query()->with('hostingPackage')->find($userId);
$package = $user?->hostingPackage;
$this->cpuLimitPercent = $package?->cpu_limit_percent ?: null;
$memoryLimitMb = $package?->memory_limit_mb ?: null;
$this->memoryLimitGb = $memoryLimitMb ? $this->bytesToGb((int) ($memoryLimitMb * 1024 * 1024)) : null;
$usageMetrics = ['disk_bytes', 'mail_bytes', 'database_bytes', 'bandwidth_bytes'];
$performanceMetrics = ['cpu_percent', 'memory_bytes', 'disk_io_bytes'];
$metrics = array_merge($usageMetrics, $performanceMetrics);
$start = now()->subDays(30);
$records = UserResourceUsage::query()
->where('user_id', $userId)
->whereIn('metric', $metrics)
->where('captured_at', '>=', $start)
->orderBy('captured_at')
->get();
$labels = $records
->map(fn ($record) => $record->captured_at->format('Y-m-d H:00'))
->unique()
->values();
$grouped = $records->groupBy('metric')->map(function ($collection) {
return $collection->keyBy(fn ($item) => $item->captured_at->format('Y-m-d H:00'));
});
$series = [];
foreach ($usageMetrics as $metric) {
$series[$metric] = [];
foreach ($labels as $label) {
$value = $grouped[$metric][$label]->value ?? 0;
$series[$metric][] = $this->bytesToGb((int) $value);
}
}
$this->chartData = [
'labels' => $labels->toArray(),
'disk' => $series['disk_bytes'],
'mail' => $series['mail_bytes'],
'database' => $series['database_bytes'],
'bandwidth' => $series['bandwidth_bytes'],
];
$performanceSeries = [];
foreach ($performanceMetrics as $metric) {
$performanceSeries[$metric] = [];
foreach ($labels as $label) {
$value = $grouped[$metric][$label]->value ?? 0;
if ($metric === 'cpu_percent') {
$performanceSeries[$metric][] = (int) $value;
} elseif ($metric === 'memory_bytes') {
$performanceSeries[$metric][] = $this->bytesToGb((int) $value);
} else {
$performanceSeries[$metric][] = $this->bytesToMb((int) $value);
}
}
}
$performanceRecords = $records->whereIn('metric', $performanceMetrics);
$this->hasPerformanceData = $performanceRecords->isNotEmpty();
$this->performanceChartData = [
'labels' => $labels->toArray(),
'cpu' => $performanceSeries['cpu_percent'],
'memory' => $performanceSeries['memory_bytes'],
'disk_io' => $performanceSeries['disk_io_bytes'],
];
$this->cpuStats = $this->buildPercentageStats($performanceRecords->where('metric', 'cpu_percent')->pluck('value'));
$this->memoryStats = $this->buildBytesStats($performanceRecords->where('metric', 'memory_bytes')->pluck('value'));
$this->summary = [
'disk' => $this->formatMetric($userId, 'disk_bytes'),
'mail' => $this->formatMetric($userId, 'mail_bytes'),
'database' => $this->formatMetric($userId, 'database_bytes'),
'bandwidth' => $this->formatMetric($userId, 'bandwidth_bytes'),
];
}
protected function formatMetric(int $userId, string $metric): string
{
$latest = UserResourceUsage::query()
->where('user_id', $userId)
->where('metric', $metric)
->latest('captured_at')
->value('value');
if ($latest === null) {
return __('No data');
}
return number_format($this->bytesToGb((int) $latest), 2).' GB';
}
protected function bytesToGb(int $bytes): float
{
return round($bytes / 1024 / 1024 / 1024, 4);
}
protected function bytesToMb(int $bytes): float
{
return round($bytes / 1024 / 1024, 2);
}
/**
* @param \Illuminate\Support\Collection<int, int|float> $values
* @return array{avg: ?string, max: ?string}
*/
protected function buildPercentageStats($values): array
{
if ($values->isEmpty()) {
return [
'avg' => null,
'max' => null,
];
}
return [
'avg' => number_format((float) $values->avg(), 1).'%',
'max' => number_format((float) $values->max(), 1).'%',
];
}
/**
* @param \Illuminate\Support\Collection<int, int|float> $values
* @return array{avg: ?string, max: ?string}
*/
protected function buildBytesStats($values): array
{
if ($values->isEmpty()) {
return [
'avg' => null,
'max' => null,
];
}
return [
'avg' => number_format($this->bytesToGb((int) $values->avg()), 2).' GB',
'max' => number_format($this->bytesToGb((int) $values->max()), 2).' GB',
];
}
}