Files
jabali-panel/resources/views/filament/jabali/pages/logs.blade.php
2026-01-27 23:38:27 +02:00

279 lines
14 KiB
PHP

<x-filament-panels::page>
@php
$tabs = [
'logs' => ['label' => __('Logs'), 'icon' => 'heroicon-o-document-text'],
'stats' => ['label' => __('Statistics'), 'icon' => 'heroicon-o-chart-bar'],
'usage' => ['label' => __('Resource Usage'), 'icon' => 'heroicon-o-chart-pie'],
'activity' => ['label' => __('Activity Log'), 'icon' => 'heroicon-o-clipboard-document-list'],
];
@endphp
<nav class="fi-tabs flex max-w-full gap-x-1 overflow-x-auto mx-auto rounded-xl bg-white p-2 shadow-sm ring-1 ring-gray-950/5 dark:bg-white/5 dark:ring-white/10" role="tablist">
@foreach($tabs as $key => $tab)
<button
type="button"
role="tab"
aria-selected="{{ $activeTab === $key ? 'true' : 'false' }}"
wire:click="setTab('{{ $key }}')"
@class([
'fi-tabs-item group flex items-center gap-x-2 rounded-lg px-3 py-2 text-sm font-medium outline-none transition duration-75',
'fi-active bg-gray-50 dark:bg-white/5' => $activeTab === $key,
'hover:bg-gray-50 focus-visible:bg-gray-50 dark:hover:bg-white/5 dark:focus-visible:bg-white/5' => $activeTab !== $key,
])
>
<x-filament::icon
:icon="$tab['icon']"
@class([
'fi-tabs-item-icon h-5 w-5 shrink-0 transition duration-75',
'text-primary-600 dark:text-primary-400' => $activeTab === $key,
'text-gray-400 group-hover:text-gray-500 group-focus-visible:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-400 dark:group-focus-visible:text-gray-400' => $activeTab !== $key,
])
/>
<span @class([
'fi-tabs-item-label transition duration-75',
'text-primary-600 dark:text-primary-400' => $activeTab === $key,
'text-gray-500 group-hover:text-gray-700 group-focus-visible:text-gray-700 dark:text-gray-400 dark:group-hover:text-gray-200 dark:group-focus-visible:text-gray-200' => $activeTab !== $key,
])>
{{ $tab['label'] }}
</span>
</button>
@endforeach
</nav>
@if(in_array($activeTab, ['logs', 'stats'], true))
@if(count($this->getDomainOptions()) > 0)
<x-filament::section icon="heroicon-o-globe-alt" icon-color="primary" class="mt-4">
<x-slot name="heading">
{{ __('Select Domain') }}
</x-slot>
<x-slot name="description">
{{ __('Choose the domain you want to view logs for.') }}
</x-slot>
<div class="max-w-md">
<x-filament::input.wrapper>
<x-filament::input.select wire:model.live="selectedDomain">
@foreach($this->getDomainOptions() as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</x-filament::input.select>
</x-filament::input.wrapper>
</div>
</x-filament::section>
@if($selectedDomain)
@if($activeTab === 'stats')
@if($statsGenerated)
<x-filament::section icon="heroicon-o-check-circle" icon-color="success" class="mt-4">
<x-slot name="heading">
{{ __('Statistics Report Ready') }}
</x-slot>
<x-slot name="description">
{{ __('Traffic analysis report has been generated for :domain', ['domain' => $selectedDomain]) }}
</x-slot>
<x-filament::button
:href="$statsUrl"
tag="a"
target="_blank"
icon="heroicon-o-arrow-top-right-on-square"
>
{{ __('View Report') }}
</x-filament::button>
</x-filament::section>
@endif
@endif
@if($activeTab === 'logs')
<x-filament::section icon="heroicon-o-document-text" class="mt-4">
<x-slot name="heading">
{{ __('Log Viewer') }}
</x-slot>
<x-slot name="description">
{{ __('Viewing :type for :domain', ['type' => $logType === 'access' ? __('access log') : __('error log'), 'domain' => $selectedDomain]) }}
@if(!empty($logInfo))
({{ $logInfo['file_size'] ?? '' }}, {{ __(':lines lines', ['lines' => $logInfo['lines'] ?? 100]) }})
@endif
</x-slot>
<div class="flex flex-wrap items-center gap-2 gap-y-2 mb-4">
<x-filament::button
wire:click="setLogType('access')"
:color="$logType === 'access' ? 'primary' : 'gray'"
:outlined="$logType !== 'access'"
size="sm"
icon="heroicon-o-arrow-right-circle"
>
{{ __('Access Log') }}
</x-filament::button>
<x-filament::button
wire:click="setLogType('error')"
:color="$logType === 'error' ? 'danger' : 'gray'"
:outlined="$logType !== 'error'"
size="sm"
icon="heroicon-o-exclamation-triangle"
>
{{ __('Error Log') }}
</x-filament::button>
</div>
@if($logContent)
<div class="fi-input-wrp rounded-lg shadow-sm ring-1 ring-gray-950/10 dark:ring-white/20 overflow-hidden">
<textarea
readonly
rows="25"
class="w-full border-0 bg-gray-50 dark:bg-gray-900 text-gray-700 dark:text-gray-300 resize-none focus:ring-0"
style="font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;"
>{{ $logContent }}</textarea>
</div>
@else
<x-filament::section compact>
<x-slot name="heading">
{{ __('No Log Entries') }}
</x-slot>
<x-slot name="description">
{{ $logType === 'access'
? __('No access log entries found. Logs will appear after visitors access your site.')
: __('No error log entries found. This is good - your site has no errors.') }}
</x-slot>
</x-filament::section>
@endif
</x-filament::section>
@endif
@endif
@else
<x-filament::section class="mt-4">
<div class="flex flex-col items-center justify-center py-12">
<div class="mb-4 rounded-full bg-gray-100 p-3 dark:bg-gray-500/20">
<x-filament::icon icon="heroicon-o-globe-alt" class="h-6 w-6 text-gray-500 dark:text-gray-400" />
</div>
<h3 class="text-base font-semibold text-gray-950 dark:text-white">
{{ __('No Domains Yet') }}
</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ __('Add a domain first to view logs and statistics.') }}
</p>
<div class="mt-6">
<x-filament::button href="{{ route('filament.jabali.pages.domains') }}" tag="a">
{{ __('Add Domain') }}
</x-filament::button>
</div>
</div>
</x-filament::section>
@endif
@endif
@if($activeTab === 'usage')
@php($usageData = $this->getUsageChartData())
<x-filament::section class="mt-4" icon="heroicon-o-chart-pie">
<x-slot name="heading">{{ __('Resource Usage (Last 30 Days)') }}</x-slot>
<x-slot name="description">{{ __('Historical usage snapshots collected hourly.') }}</x-slot>
<div
x-data="{
chart: null,
init() {
const data = @js($usageData);
const isDemo = Boolean(data.demo);
const boot = () => {
const element = this.$refs.chart ?? this.$el;
if (!window.echarts || !element) {
return false;
}
if (this.chart) {
this.chart.dispose();
}
this.chart = window.echarts.init(element);
this.chart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: data.series.map(s => s.name) },
grid: { left: '3%', right: '3%', bottom: 50, containLabel: true },
xAxis: {
type: 'category',
data: data.labels,
axisLabel: {
formatter: (value) => value.slice(5),
margin: 12,
},
},
yAxis: {
type: 'value',
axisLabel: { formatter: '{value} GB' },
},
series: data.series.map((series) => ({
name: series.name,
type: 'line',
smooth: true,
areaStyle: {},
data: series.data,
})),
});
window.addEventListener('resize', () => this.chart?.resize());
requestAnimationFrame(() => this.chart?.resize());
setTimeout(() => this.chart?.resize(), 150);
return true;
};
if (!boot()) {
const interval = setInterval(() => {
if (boot()) {
clearInterval(interval);
}
}, 200);
}
},
}"
x-init="init"
class="w-full"
wire:ignore
>
<div x-ref="chart" class="h-80 w-full" style="height: 320px;"></div>
</div>
</x-filament::section>
@endif
@if($activeTab === 'activity')
<x-filament::section class="mt-4" icon="heroicon-o-clipboard-document-list">
<x-slot name="heading">{{ __('Activity Log') }}</x-slot>
<x-slot name="description">{{ __('Recent actions performed in your account.') }}</x-slot>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Time') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Category') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Action') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Description') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('IP') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@forelse($this->getActivityLogs() as $log)
<tr>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">{{ $log->created_at?->format('Y-m-d H:i') }}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">{{ $log->category }}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">{{ $log->action }}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">{{ $log->description }}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">{{ $log->ip_address }}</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-6 text-center text-sm text-gray-500">
{{ __('No activity recorded yet.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</x-filament::section>
@endif
<x-filament-actions::modals />
</x-filament-panels::page>