feat: add pagination for firewall logs in Proxmox component, enhancing log management and user experience with navigation controls
This commit is contained in:
@@ -157,6 +157,10 @@ class Firewall extends Component
|
||||
public array $firewallLogs = [];
|
||||
public int $logLimit = 1000;
|
||||
public string $logError = '';
|
||||
|
||||
// Pagination for logs
|
||||
public int $currentPage = 1;
|
||||
public int $perPage = 50;
|
||||
|
||||
// Available IPs for rules
|
||||
public array $availableIps = [];
|
||||
@@ -1239,6 +1243,50 @@ class Firewall extends Component
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LOG PAGINATION ====================
|
||||
|
||||
public function getPaginatedLogsProperty(): array
|
||||
{
|
||||
$reversedLogs = array_reverse($this->firewallLogs);
|
||||
$offset = ($this->currentPage - 1) * $this->perPage;
|
||||
return array_slice($reversedLogs, $offset, $this->perPage);
|
||||
}
|
||||
|
||||
public function getTotalPagesProperty(): int
|
||||
{
|
||||
return (int)ceil(count($this->firewallLogs) / $this->perPage);
|
||||
}
|
||||
|
||||
public function goToPage(int $page): void
|
||||
{
|
||||
$totalPages = $this->getTotalPagesProperty();
|
||||
if ($page >= 1 && $page <= $totalPages) {
|
||||
$this->currentPage = $page;
|
||||
}
|
||||
}
|
||||
|
||||
public function previousPage(): void
|
||||
{
|
||||
if ($this->currentPage > 1) {
|
||||
$this->currentPage--;
|
||||
}
|
||||
}
|
||||
|
||||
public function nextPage(): void
|
||||
{
|
||||
if ($this->currentPage < $this->getTotalPagesProperty()) {
|
||||
$this->currentPage++;
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedPerPage(): void
|
||||
{
|
||||
// Reset to page 1 when changing per-page value
|
||||
$this->currentPage = 1;
|
||||
}
|
||||
|
||||
// ==================== LOG PARSING ====================
|
||||
|
||||
protected function parseFirewallLogs(array $rawLogs): array
|
||||
{
|
||||
$parsed = [];
|
||||
|
||||
@@ -730,7 +730,36 @@
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold">Firewall Logs</h3>
|
||||
<button type="button" wire:click="loadFirewallLogs" wire:loading.attr="disabled" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium flex items-center gap-2">
|
||||
<x-ri-refresh-line class="size-4" wire:loading.class="animate-spin" wire:target="loadFirewallLogs" />
|
||||
<span wire:loading.remove wire:target="loadFirewallLogs">
|
||||
<x-ri-refresh-line class="size-4" />
|
||||
</span>
|
||||
<div wire:loading wire:target="loadFirewallLogs" class="loader-container">
|
||||
<style>
|
||||
.loader-container {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.loader {
|
||||
width: 16px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
background:
|
||||
conic-gradient(from 135deg at top, currentColor 90deg, #0000 0) 0 calc(50% - 1.5px)/6px 3px,
|
||||
radial-gradient(farthest-side at bottom left, #0000 calc(100% - 2px), currentColor calc(100% - 1.5px) 99%, #0000) top right/50% 50% content-box content-box,
|
||||
radial-gradient(farthest-side at top, #0000 calc(100% - 2px), currentColor calc(100% - 1.5px) 99%, #0000) bottom/100% 50% content-box content-box;
|
||||
background-repeat: no-repeat;
|
||||
animation: loader-spin 1s infinite linear;
|
||||
}
|
||||
@keyframes loader-spin {
|
||||
100% { transform: rotate(1turn); }
|
||||
}
|
||||
</style>
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
<span wire:loading.remove wire:target="loadFirewallLogs">Refresh</span>
|
||||
<span wire:loading wire:target="loadFirewallLogs">Loading...</span>
|
||||
</button>
|
||||
@@ -759,7 +788,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(array_slice(array_reverse($firewallLogs), 0, 50) as $log)
|
||||
@foreach($this->paginatedLogs as $log)
|
||||
<tr class="border-b border-neutral/10 hover:bg-background-secondary/30 transition-colors">
|
||||
<td class="py-3 px-4 text-xs text-base/70">{{ $log['timestamp'] ?? '-' }}</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
@@ -933,12 +962,97 @@
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if(count($firewallLogs) > 50)
|
||||
<div class="mt-4 text-center text-sm text-base/60">
|
||||
Showing latest 50 of {{ count($firewallLogs) }} log entries (most recent first)
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Pagination Controls --}}
|
||||
<div class="mt-6 flex items-center justify-between border-t border-neutral/20 pt-4">
|
||||
{{-- Records per page selector --}}
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-sm text-base/70">Show:</label>
|
||||
<select wire:model.live="perPage" class="px-3 py-1.5 border border-neutral rounded-lg bg-background text-sm">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="250">250</option>
|
||||
</select>
|
||||
<span class="text-sm text-base/60">per page</span>
|
||||
</div>
|
||||
|
||||
{{-- Page info and navigation --}}
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm text-base/60">
|
||||
Showing {{ (($currentPage - 1) * $perPage) + 1 }} - {{ min($currentPage * $perPage, count($firewallLogs)) }} of {{ count($firewallLogs) }} logs
|
||||
</span>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
{{-- First page --}}
|
||||
<button type="button"
|
||||
wire:click="goToPage(1)"
|
||||
@if($currentPage === 1) disabled @endif
|
||||
class="px-2 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="First page">
|
||||
<x-ri-arrow-left-double-line class="size-4" />
|
||||
</button>
|
||||
|
||||
{{-- Previous page --}}
|
||||
<button type="button"
|
||||
wire:click="previousPage"
|
||||
@if($currentPage === 1) disabled @endif
|
||||
class="px-2 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Previous page">
|
||||
<x-ri-arrow-left-s-line class="size-4" />
|
||||
</button>
|
||||
|
||||
{{-- Page numbers --}}
|
||||
@php
|
||||
$totalPages = $this->totalPages;
|
||||
$startPage = max(1, $currentPage - 2);
|
||||
$endPage = min($totalPages, $currentPage + 2);
|
||||
@endphp
|
||||
|
||||
@if($startPage > 1)
|
||||
<button type="button" wire:click="goToPage(1)" class="px-3 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors text-sm">1</button>
|
||||
@if($startPage > 2)
|
||||
<span class="px-2 text-base/60">...</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@for($i = $startPage; $i <= $endPage; $i++)
|
||||
<button type="button"
|
||||
wire:click="goToPage({{ $i }})"
|
||||
class="px-3 py-1 border rounded text-sm transition-colors {{ $currentPage === $i ? 'bg-primary text-white border-primary' : 'border-neutral hover:bg-background-secondary' }}">
|
||||
{{ $i }}
|
||||
</button>
|
||||
@endfor
|
||||
|
||||
@if($endPage < $totalPages)
|
||||
@if($endPage < $totalPages - 1)
|
||||
<span class="px-2 text-base/60">...</span>
|
||||
@endif
|
||||
<button type="button" wire:click="goToPage({{ $totalPages }})" class="px-3 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors text-sm">{{ $totalPages }}</button>
|
||||
@endif
|
||||
|
||||
{{-- Next page --}}
|
||||
<button type="button"
|
||||
wire:click="nextPage"
|
||||
@if($currentPage === $totalPages) disabled @endif
|
||||
class="px-2 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Next page">
|
||||
<x-ri-arrow-right-s-line class="size-4" />
|
||||
</button>
|
||||
|
||||
{{-- Last page --}}
|
||||
<button type="button"
|
||||
wire:click="goToPage({{ $totalPages }})"
|
||||
@if($currentPage === $totalPages) disabled @endif
|
||||
class="px-2 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Last page">
|
||||
<x-ri-arrow-right-double-line class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="text-center py-12 text-base/60">
|
||||
<x-ri-file-list-3-line class="size-16 mx-auto mb-4 opacity-30" />
|
||||
|
||||
Reference in New Issue
Block a user