feat: add pagination for firewall logs in Proxmox component, enhancing log management and user experience with navigation controls

This commit is contained in:
2025-12-04 19:47:51 -05:00
parent 42fcad4854
commit 0adfdb3741
2 changed files with 170 additions and 8 deletions

View File

@@ -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 = [];

View File

@@ -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" />