258 lines
7.9 KiB
PHP
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',
|
|
];
|
|
}
|
|
}
|