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

333 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Jabali\Pages;
use App\Filament\Concerns\HasPageTour;
use App\Models\AuditLog;
use App\Models\UserResourceUsage;
use App\Services\Agent\AgentClient;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
class Logs extends Page implements HasActions, HasForms
{
use HasPageTour;
use InteractsWithActions;
use InteractsWithForms;
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
public static function getNavigationLabel(): string
{
return __('Logs & Statistics');
}
protected static ?int $navigationSort = 13;
protected static ?string $slug = 'logs';
protected string $view = 'filament.jabali.pages.logs';
protected ?AgentClient $agent = null;
#[Url]
public ?string $selectedDomain = null;
#[Url(as: 'tab')]
public string $activeTab = 'logs';
public string $logType = 'access';
public int $logLines = 100;
public string $logContent = '';
public array $logInfo = [];
public bool $statsGenerated = false;
public string $statsUrl = '';
public array $domains = [];
public function getTitle(): string|Htmlable
{
return __('Logs & Statistics');
}
public function mount(): void
{
$this->loadDomains();
$this->activeTab = $this->normalizeTab($this->activeTab);
if (! empty($this->domains) && ! $this->selectedDomain) {
$this->selectedDomain = $this->domains[0]['domain'] ?? null;
}
if ($this->selectedDomain) {
$this->loadLogs();
}
}
public function updatedActiveTab(): void
{
$this->activeTab = $this->normalizeTab($this->activeTab);
if ($this->activeTab === 'logs' && $this->selectedDomain) {
$this->loadLogs();
}
}
public function setTab(string $tab): void
{
$this->activeTab = $this->normalizeTab($tab);
}
protected function normalizeTab(?string $tab): string
{
return match ($tab) {
'logs', 'usage', 'activity', 'stats' => (string) $tab,
default => 'logs',
};
}
protected function getAgent(): AgentClient
{
if ($this->agent === null) {
$this->agent = new AgentClient;
}
return $this->agent;
}
protected function getUsername(): string
{
return Auth::user()->username ?? Auth::user()->name ?? 'unknown';
}
protected function loadDomains(): void
{
try {
$result = $this->getAgent()->send('domain.list', [
'username' => $this->getUsername(),
]);
$this->domains = ($result['success'] ?? false) ? ($result['domains'] ?? []) : [];
} catch (\Throwable $exception) {
$this->domains = [];
}
}
public function getDomainOptions(): array
{
$options = [];
foreach ($this->domains as $domain) {
$d = $domain['domain'] ?? $domain;
$options[$d] = $d;
}
return $options;
}
public function updatedSelectedDomain(): void
{
$this->statsGenerated = false;
$this->statsUrl = '';
$this->loadLogs();
}
public function setLogType(string $type): void
{
$this->logType = $type;
$this->loadLogs();
}
public function loadLogs(): void
{
if (! $this->selectedDomain) {
$this->logContent = '';
$this->logInfo = [];
return;
}
try {
$result = $this->getAgent()->send('logs.tail', [
'username' => $this->getUsername(),
'domain' => $this->selectedDomain,
'type' => $this->logType,
'lines' => $this->logLines,
]);
if ($result['success'] ?? false) {
$this->logContent = $result['content'] ?? '';
$this->logInfo = [
'file_size' => $this->formatBytes($result['file_size'] ?? 0),
'last_modified' => $result['last_modified'] ?? '',
'lines' => $result['lines'] ?? 0,
];
} else {
$this->logContent = '';
$this->logInfo = [];
}
} catch (\Exception $e) {
$this->logContent = '';
$this->logInfo = [];
}
}
public function refreshLogs(): void
{
$this->loadLogs();
Notification::make()
->title(__('Logs refreshed'))
->success()
->send();
}
public function getUsageChartData(): array
{
$start = now()->subDays(29)->startOfDay();
$end = now()->endOfDay();
$records = UserResourceUsage::query()
->where('user_id', Auth::id())
->whereBetween('captured_at', [$start, $end])
->get();
$labels = [];
for ($i = 0; $i < 30; $i++) {
$labels[] = $start->copy()->addDays($i)->format('Y-m-d');
}
$index = array_flip($labels);
$metrics = ['disk_bytes', 'database_bytes', 'mail_bytes', 'bandwidth_bytes'];
$values = [];
foreach ($metrics as $metric) {
$values[$metric] = array_fill(0, count($labels), 0);
}
foreach ($records as $record) {
$date = $record->captured_at?->format('Y-m-d');
if (! $date || ! isset($index[$date])) {
continue;
}
$idx = $index[$date];
$metric = $record->metric;
if (! isset($values[$metric])) {
continue;
}
if ($metric === 'bandwidth_bytes') {
$values[$metric][$idx] += (int) $record->value;
} else {
$values[$metric][$idx] = max($values[$metric][$idx], (int) $record->value);
}
}
$toGb = fn (int $bytes) => round($bytes / 1024 / 1024 / 1024, 2);
return [
'labels' => $labels,
'series' => [
['name' => __('Disk'), 'data' => array_map($toGb, $values['disk_bytes'])],
['name' => __('Databases'), 'data' => array_map($toGb, $values['database_bytes'])],
['name' => __('Mail'), 'data' => array_map($toGb, $values['mail_bytes'])],
['name' => __('Bandwidth'), 'data' => array_map($toGb, $values['bandwidth_bytes'])],
],
];
}
public function getActivityLogs()
{
return AuditLog::query()
->where('user_id', Auth::id())
->latest()
->limit(50)
->get();
}
public function generateStats(): void
{
if (! $this->selectedDomain) {
Notification::make()
->title(__('No domain selected'))
->danger()
->send();
return;
}
try {
$result = $this->getAgent()->send('logs.goaccess', [
'username' => $this->getUsername(),
'domain' => $this->selectedDomain,
'period' => 'all',
]);
if ($result['success'] ?? false) {
$this->statsGenerated = true;
$this->statsUrl = 'https://'.$this->selectedDomain.($result['report_url'] ?? '/stats/report.html');
Notification::make()
->title(__('Statistics generated'))
->body(__('Report generated with :lines log entries', ['lines' => number_format($result['log_lines'] ?? 0)]))
->success()
->send();
} else {
Notification::make()
->title(__('Error generating statistics'))
->body($result['error'] ?? 'Unknown error')
->danger()
->send();
}
} catch (\Exception $e) {
Notification::make()
->title(__('Error'))
->body($e->getMessage())
->danger()
->send();
}
}
protected function getHeaderActions(): array
{
return [
$this->getTourAction(),
Action::make('generateStats')
->label(__('Generate Statistics'))
->icon('heroicon-o-chart-bar')
->color('primary')
->visible(fn () => $this->selectedDomain !== null && $this->activeTab === 'stats')
->action(fn () => $this->generateStats()),
Action::make('refreshLogs')
->label(__('Refresh'))
->icon('heroicon-o-arrow-path')
->color('gray')
->visible(fn () => $this->selectedDomain !== null && $this->activeTab === 'logs')
->action(fn () => $this->refreshLogs()),
];
}
protected function formatBytes(int $bytes, int $precision = 2): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision).' '.$units[$pow];
}
}