Implement pagination for network analysis, statistics history, and task history in Proxmox statistics view
This commit is contained in:
@@ -48,6 +48,22 @@ class Statistics extends Component
|
|||||||
public int $networkAnalysisRetryCount = 0;
|
public int $networkAnalysisRetryCount = 0;
|
||||||
public int $networkAnalysisMaxRetries = 3;
|
public int $networkAnalysisMaxRetries = 3;
|
||||||
|
|
||||||
|
// Network Analysis Pagination
|
||||||
|
public int $connectionsPerPage = 25;
|
||||||
|
public int $connectionsCurrentPage = 1;
|
||||||
|
public int $topConnectionsPerPage = 25;
|
||||||
|
public int $topConnectionsCurrentPage = 1;
|
||||||
|
public int $topPortsPerPage = 25;
|
||||||
|
public int $topPortsCurrentPage = 1;
|
||||||
|
|
||||||
|
// Statistics History Pagination
|
||||||
|
public int $statisticsPerPage = 25;
|
||||||
|
public int $statisticsCurrentPage = 1;
|
||||||
|
|
||||||
|
// Task History Pagination
|
||||||
|
public int $taskHistoryPerPage = 25;
|
||||||
|
public int $taskHistoryCurrentPage = 1;
|
||||||
|
|
||||||
public array $networkAnalysis = [
|
public array $networkAnalysis = [
|
||||||
'connections' => [],
|
'connections' => [],
|
||||||
'topConnections' => [],
|
'topConnections' => [],
|
||||||
@@ -377,16 +393,22 @@ class Statistics extends Component
|
|||||||
'sample_connections' => array_slice($connections, 0, 3),
|
'sample_connections' => array_slice($connections, 0, 3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Store all connections without limit - pagination handles display
|
||||||
$analysis = [
|
$analysis = [
|
||||||
'connections' => array_slice($connections, 0, 100),
|
'connections' => $connections,
|
||||||
'topConnections' => array_slice($topConnections, 0, 10),
|
'topConnections' => $topConnections,
|
||||||
'topPorts' => array_slice($topPorts, 0, 10),
|
'topPorts' => $topPorts,
|
||||||
'security' => $securityWarnings,
|
'security' => $securityWarnings,
|
||||||
'lastUpdated' => now()->toDateTimeString(),
|
'lastUpdated' => now()->toDateTimeString(),
|
||||||
'error' => null,
|
'error' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->networkAnalysis = $analysis;
|
$this->networkAnalysis = $analysis;
|
||||||
|
|
||||||
|
// Reset pagination to page 1 when data is refreshed
|
||||||
|
$this->connectionsCurrentPage = 1;
|
||||||
|
$this->topConnectionsCurrentPage = 1;
|
||||||
|
$this->topPortsCurrentPage = 1;
|
||||||
|
|
||||||
// Persist the computed analysis for a short period to speed up subsequent loads
|
// Persist the computed analysis for a short period to speed up subsequent loads
|
||||||
Cache::put($cacheKey, $analysis, now()->addSeconds(30));
|
Cache::put($cacheKey, $analysis, now()->addSeconds(30));
|
||||||
@@ -446,6 +468,297 @@ class Statistics extends Component
|
|||||||
$this->networkAnalysisLoading = false;
|
$this->networkAnalysisLoading = false;
|
||||||
$this->loadNetworkAnalysis();
|
$this->loadNetworkAnalysis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Network Analysis Pagination Methods
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get paginated connections for display
|
||||||
|
*/
|
||||||
|
public function getPaginatedConnectionsProperty(): array
|
||||||
|
{
|
||||||
|
$connections = $this->networkAnalysis['connections'] ?? [];
|
||||||
|
$total = count($connections);
|
||||||
|
$offset = ($this->connectionsCurrentPage - 1) * $this->connectionsPerPage;
|
||||||
|
|
||||||
|
return array_slice($connections, $offset, $this->connectionsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total pages for connections
|
||||||
|
*/
|
||||||
|
public function getConnectionsTotalPagesProperty(): int
|
||||||
|
{
|
||||||
|
$total = count($this->networkAnalysis['connections'] ?? []);
|
||||||
|
return max(1, (int) ceil($total / $this->connectionsPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get paginated top connections for display
|
||||||
|
*/
|
||||||
|
public function getPaginatedTopConnectionsProperty(): array
|
||||||
|
{
|
||||||
|
$connections = $this->networkAnalysis['topConnections'] ?? [];
|
||||||
|
$offset = ($this->topConnectionsCurrentPage - 1) * $this->topConnectionsPerPage;
|
||||||
|
|
||||||
|
return array_slice($connections, $offset, $this->topConnectionsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total pages for top connections
|
||||||
|
*/
|
||||||
|
public function getTopConnectionsTotalPagesProperty(): int
|
||||||
|
{
|
||||||
|
$total = count($this->networkAnalysis['topConnections'] ?? []);
|
||||||
|
return max(1, (int) ceil($total / $this->topConnectionsPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get paginated top ports for display
|
||||||
|
*/
|
||||||
|
public function getPaginatedTopPortsProperty(): array
|
||||||
|
{
|
||||||
|
$ports = $this->networkAnalysis['topPorts'] ?? [];
|
||||||
|
$offset = ($this->topPortsCurrentPage - 1) * $this->topPortsPerPage;
|
||||||
|
|
||||||
|
return array_slice($ports, $offset, $this->topPortsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total pages for top ports
|
||||||
|
*/
|
||||||
|
public function getTopPortsTotalPagesProperty(): int
|
||||||
|
{
|
||||||
|
$total = count($this->networkAnalysis['topPorts'] ?? []);
|
||||||
|
return max(1, (int) ceil($total / $this->topPortsPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset page to 1 when perPage changes for connections
|
||||||
|
*/
|
||||||
|
public function updatedConnectionsPerPage(): void
|
||||||
|
{
|
||||||
|
$this->connectionsCurrentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset page to 1 when perPage changes for top connections
|
||||||
|
*/
|
||||||
|
public function updatedTopConnectionsPerPage(): void
|
||||||
|
{
|
||||||
|
$this->topConnectionsCurrentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset page to 1 when perPage changes for top ports
|
||||||
|
*/
|
||||||
|
public function updatedTopPortsPerPage(): void
|
||||||
|
{
|
||||||
|
$this->topPortsCurrentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a specific page for connections
|
||||||
|
*/
|
||||||
|
public function goToConnectionsPage(int $page): void
|
||||||
|
{
|
||||||
|
$this->connectionsCurrentPage = max(1, min($page, $this->connectionsTotalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to previous page for connections
|
||||||
|
*/
|
||||||
|
public function previousConnectionsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToConnectionsPage($this->connectionsCurrentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to next page for connections
|
||||||
|
*/
|
||||||
|
public function nextConnectionsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToConnectionsPage($this->connectionsCurrentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a specific page for top connections
|
||||||
|
*/
|
||||||
|
public function goToTopConnectionsPage(int $page): void
|
||||||
|
{
|
||||||
|
$this->topConnectionsCurrentPage = max(1, min($page, $this->topConnectionsTotalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to previous page for top connections
|
||||||
|
*/
|
||||||
|
public function previousTopConnectionsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToTopConnectionsPage($this->topConnectionsCurrentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to next page for top connections
|
||||||
|
*/
|
||||||
|
public function nextTopConnectionsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToTopConnectionsPage($this->topConnectionsCurrentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a specific page for top ports
|
||||||
|
*/
|
||||||
|
public function goToTopPortsPage(int $page): void
|
||||||
|
{
|
||||||
|
$this->topPortsCurrentPage = max(1, min($page, $this->topPortsTotalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to previous page for top ports
|
||||||
|
*/
|
||||||
|
public function previousTopPortsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToTopPortsPage($this->topPortsCurrentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to next page for top ports
|
||||||
|
*/
|
||||||
|
public function nextTopPortsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToTopPortsPage($this->topPortsCurrentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Statistics History Pagination Methods
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all statistics for pagination (raw data)
|
||||||
|
*/
|
||||||
|
public function getAllStatisticsProperty(): \Illuminate\Database\Eloquent\Collection
|
||||||
|
{
|
||||||
|
return ServerStatistics::where('server_id', $this->server->id)
|
||||||
|
->orderBy('DateTime', 'desc')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get paginated statistics for display
|
||||||
|
*/
|
||||||
|
public function getPaginatedStatisticsProperty(): array
|
||||||
|
{
|
||||||
|
$statistics = $this->allStatistics;
|
||||||
|
$offset = ($this->statisticsCurrentPage - 1) * $this->statisticsPerPage;
|
||||||
|
|
||||||
|
return $statistics->slice($offset, $this->statisticsPerPage)->values()->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total pages for statistics
|
||||||
|
*/
|
||||||
|
public function getStatisticsTotalPagesProperty(): int
|
||||||
|
{
|
||||||
|
$total = $this->allStatistics->count();
|
||||||
|
return max(1, (int) ceil($total / $this->statisticsPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset page to 1 when perPage changes for statistics
|
||||||
|
*/
|
||||||
|
public function updatedStatisticsPerPage(): void
|
||||||
|
{
|
||||||
|
$this->statisticsCurrentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a specific page for statistics
|
||||||
|
*/
|
||||||
|
public function goToStatisticsPage(int $page): void
|
||||||
|
{
|
||||||
|
$this->statisticsCurrentPage = max(1, min($page, $this->statisticsTotalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to previous page for statistics
|
||||||
|
*/
|
||||||
|
public function previousStatisticsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToStatisticsPage($this->statisticsCurrentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to next page for statistics
|
||||||
|
*/
|
||||||
|
public function nextStatisticsPage(): void
|
||||||
|
{
|
||||||
|
$this->goToStatisticsPage($this->statisticsCurrentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Task History Pagination Methods
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all task history for pagination (raw data)
|
||||||
|
*/
|
||||||
|
public function getAllTaskHistoryProperty(): array
|
||||||
|
{
|
||||||
|
return $this->taskHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get paginated task history for display
|
||||||
|
*/
|
||||||
|
public function getPaginatedTaskHistoryProperty(): array
|
||||||
|
{
|
||||||
|
$tasks = $this->allTaskHistory;
|
||||||
|
$offset = ($this->taskHistoryCurrentPage - 1) * $this->taskHistoryPerPage;
|
||||||
|
|
||||||
|
return array_slice($tasks, $offset, $this->taskHistoryPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total pages for task history
|
||||||
|
*/
|
||||||
|
public function getTaskHistoryTotalPagesProperty(): int
|
||||||
|
{
|
||||||
|
$total = count($this->allTaskHistory);
|
||||||
|
return max(1, (int) ceil($total / $this->taskHistoryPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset page to 1 when perPage changes for task history
|
||||||
|
*/
|
||||||
|
public function updatedTaskHistoryPerPage(): void
|
||||||
|
{
|
||||||
|
$this->taskHistoryCurrentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a specific page for task history
|
||||||
|
*/
|
||||||
|
public function goToTaskHistoryPage(int $page): void
|
||||||
|
{
|
||||||
|
$this->taskHistoryCurrentPage = max(1, min($page, $this->taskHistoryTotalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to previous page for task history
|
||||||
|
*/
|
||||||
|
public function previousTaskHistoryPage(): void
|
||||||
|
{
|
||||||
|
$this->goToTaskHistoryPage($this->taskHistoryCurrentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to next page for task history
|
||||||
|
*/
|
||||||
|
public function nextTaskHistoryPage(): void
|
||||||
|
{
|
||||||
|
$this->goToTaskHistoryPage($this->taskHistoryCurrentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract IP address from field (handles "IP[port]" and "IP:port" formats)
|
* Extract IP address from field (handles "IP[port]" and "IP:port" formats)
|
||||||
@@ -797,7 +1110,7 @@ class Statistics extends Component
|
|||||||
{
|
{
|
||||||
return ServerStatistics::where('server_id', $this->server->id)
|
return ServerStatistics::where('server_id', $this->server->id)
|
||||||
->orderBy('DateTime', 'desc')
|
->orderBy('DateTime', 'desc')
|
||||||
->paginate(50);
|
->paginate($this->statisticsPerPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteStatistic($statisticId)
|
public function deleteStatistic($statisticId)
|
||||||
|
|||||||
@@ -897,11 +897,31 @@
|
|||||||
<template id="statistics-history-export" type="application/json">
|
<template id="statistics-history-export" type="application/json">
|
||||||
@json($statisticsExportRows)
|
@json($statisticsExportRows)
|
||||||
</template>
|
</template>
|
||||||
@if($this->statistics->hasPages())
|
<!-- Statistics History Pagination -->
|
||||||
<div class="p-4 border-t border-neutral">
|
<div class="p-4 border-t border-neutral">
|
||||||
{{ $this->statistics->links() }}
|
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<!-- Per Page Selector -->
|
||||||
|
<div class="flex items-center gap-2 text-sm">
|
||||||
|
<span class="text-base/70">Show</span>
|
||||||
|
<select wire:model.live="statisticsPerPage"
|
||||||
|
class="bg-background border border-neutral rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</select>
|
||||||
|
<span class="text-base/70">entries</span>
|
||||||
|
<span class="text-base/50 ml-2">({{ $this->statistics->total() }} total)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Links -->
|
||||||
|
@if($this->statistics->hasPages())
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{{ $this->statistics->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Task History Section -->
|
<!-- Task History Section -->
|
||||||
@@ -2160,12 +2180,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-neutral/40">
|
<tbody class="divide-y divide-neutral/40">
|
||||||
@foreach($networkAnalysis['connections'] as $index => $connection)
|
@foreach($this->paginatedConnections as $index => $connection)
|
||||||
@php
|
@php
|
||||||
// Use raw bytes value from backend for reliable numeric sorting
|
// Use raw bytes value from backend for reliable numeric sorting
|
||||||
$bytesForSort = (int)($connection['bytes'] ?? 0);
|
$bytesForSort = (int)($connection['bytes'] ?? 0);
|
||||||
@endphp
|
@endphp
|
||||||
<tr wire:key="conn-{{ $index }}"
|
<tr wire:key="conn-{{ $connectionsCurrentPage }}-{{ $index }}"
|
||||||
class="hover:bg-background-secondary/50 transition-colors table-row"
|
class="hover:bg-background-secondary/50 transition-colors table-row"
|
||||||
data-direction="{{ strtolower($connection['direction'] ?? '') }}"
|
data-direction="{{ strtolower($connection['direction'] ?? '') }}"
|
||||||
data-protocol="{{ strtolower($connection['protocol'] ?? '') }}"
|
data-protocol="{{ strtolower($connection['protocol'] ?? '') }}"
|
||||||
@@ -2561,6 +2581,98 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Connections Pagination Controls --}}
|
||||||
|
@if(count($networkAnalysis['connections']) > 0)
|
||||||
|
<div class="p-4 border-t border-neutral flex flex-col sm:flex-row items-center justify-between gap-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="connectionsPerPage" 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 {{ (($connectionsCurrentPage - 1) * $connectionsPerPage) + 1 }} - {{ min($connectionsCurrentPage * $connectionsPerPage, count($networkAnalysis['connections'])) }} of {{ count($networkAnalysis['connections']) }} connections
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{{-- First page --}}
|
||||||
|
<button type="button"
|
||||||
|
wire:click="goToConnectionsPage(1)"
|
||||||
|
@if($connectionsCurrentPage === 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="previousConnectionsPage"
|
||||||
|
@if($connectionsCurrentPage === 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
|
||||||
|
$connectionsTotalPages = $this->connectionsTotalPages;
|
||||||
|
$startPage = max(1, $connectionsCurrentPage - 2);
|
||||||
|
$endPage = min($connectionsTotalPages, $connectionsCurrentPage + 2);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($startPage > 1)
|
||||||
|
<button type="button" wire:click="goToConnectionsPage(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="goToConnectionsPage({{ $i }})"
|
||||||
|
class="px-3 py-1 border rounded text-sm transition-colors {{ $connectionsCurrentPage === $i ? 'bg-primary text-white border-primary' : 'border-neutral hover:bg-background-secondary' }}">
|
||||||
|
{{ $i }}
|
||||||
|
</button>
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
@if($endPage < $connectionsTotalPages)
|
||||||
|
@if($endPage < $connectionsTotalPages - 1)
|
||||||
|
<span class="px-2 text-base/60">...</span>
|
||||||
|
@endif
|
||||||
|
<button type="button" wire:click="goToConnectionsPage({{ $connectionsTotalPages }})" class="px-3 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors text-sm">{{ $connectionsTotalPages }}</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next page --}}
|
||||||
|
<button type="button"
|
||||||
|
wire:click="nextConnectionsPage"
|
||||||
|
@if($connectionsCurrentPage === $connectionsTotalPages) 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="goToConnectionsPage({{ $connectionsTotalPages }})"
|
||||||
|
@if($connectionsCurrentPage === $connectionsTotalPages) 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>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -2617,7 +2729,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-neutral/30">
|
<tbody class="divide-y divide-neutral/30">
|
||||||
@foreach($networkAnalysis['topConnections'] as $index => $row)
|
@foreach($this->paginatedTopConnections as $index => $row)
|
||||||
@php
|
@php
|
||||||
$endpointIP = $row['endpoint'] ?? '—';
|
$endpointIP = $row['endpoint'] ?? '—';
|
||||||
$port = $row['port'] ?? 0;
|
$port = $row['port'] ?? 0;
|
||||||
@@ -2625,7 +2737,7 @@
|
|||||||
$bytes = $row['bytes'] ?? 0;
|
$bytes = $row['bytes'] ?? 0;
|
||||||
$bytesForSort = $bytes;
|
$bytesForSort = $bytes;
|
||||||
@endphp
|
@endphp
|
||||||
<tr class="hover:bg-background-secondary/60 table-row"
|
<tr wire:key="top-conn-{{ $topConnectionsCurrentPage }}-{{ $index }}" class="hover:bg-background-secondary/60 table-row"
|
||||||
data-endpoint="{{ strtolower($endpointIP) }}"
|
data-endpoint="{{ strtolower($endpointIP) }}"
|
||||||
data-port="{{ $port }}"
|
data-port="{{ $port }}"
|
||||||
data-connections="{{ $connections }}"
|
data-connections="{{ $connections }}"
|
||||||
@@ -2792,6 +2904,98 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Top Connections Pagination Controls --}}
|
||||||
|
@if(count($networkAnalysis['topConnections']) > 0)
|
||||||
|
<div class="p-4 border-t border-neutral flex flex-col sm:flex-row items-center justify-between gap-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="topConnectionsPerPage" 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 {{ (($topConnectionsCurrentPage - 1) * $topConnectionsPerPage) + 1 }} - {{ min($topConnectionsCurrentPage * $topConnectionsPerPage, count($networkAnalysis['topConnections'])) }} of {{ count($networkAnalysis['topConnections']) }} peers
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{{-- First page --}}
|
||||||
|
<button type="button"
|
||||||
|
wire:click="goToTopConnectionsPage(1)"
|
||||||
|
@if($topConnectionsCurrentPage === 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="previousTopConnectionsPage"
|
||||||
|
@if($topConnectionsCurrentPage === 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
|
||||||
|
$topConnectionsTotalPages = $this->topConnectionsTotalPages;
|
||||||
|
$startPage = max(1, $topConnectionsCurrentPage - 2);
|
||||||
|
$endPage = min($topConnectionsTotalPages, $topConnectionsCurrentPage + 2);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($startPage > 1)
|
||||||
|
<button type="button" wire:click="goToTopConnectionsPage(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="goToTopConnectionsPage({{ $i }})"
|
||||||
|
class="px-3 py-1 border rounded text-sm transition-colors {{ $topConnectionsCurrentPage === $i ? 'bg-primary text-white border-primary' : 'border-neutral hover:bg-background-secondary' }}">
|
||||||
|
{{ $i }}
|
||||||
|
</button>
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
@if($endPage < $topConnectionsTotalPages)
|
||||||
|
@if($endPage < $topConnectionsTotalPages - 1)
|
||||||
|
<span class="px-2 text-base/60">...</span>
|
||||||
|
@endif
|
||||||
|
<button type="button" wire:click="goToTopConnectionsPage({{ $topConnectionsTotalPages }})" class="px-3 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors text-sm">{{ $topConnectionsTotalPages }}</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next page --}}
|
||||||
|
<button type="button"
|
||||||
|
wire:click="nextTopConnectionsPage"
|
||||||
|
@if($topConnectionsCurrentPage === $topConnectionsTotalPages) 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="goToTopConnectionsPage({{ $topConnectionsTotalPages }})"
|
||||||
|
@if($topConnectionsCurrentPage === $topConnectionsTotalPages) 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>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -2842,14 +3046,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-neutral/30">
|
<tbody class="divide-y divide-neutral/30">
|
||||||
@foreach($networkAnalysis['topPorts'] as $index => $row)
|
@foreach($this->paginatedTopPorts as $index => $row)
|
||||||
@php
|
@php
|
||||||
$port = $row['port'] ?? 0;
|
$port = $row['port'] ?? 0;
|
||||||
$connections = $row['connections'] ?? 0;
|
$connections = $row['connections'] ?? 0;
|
||||||
$bytes = $row['bytes'] ?? 0;
|
$bytes = $row['bytes'] ?? 0;
|
||||||
$bytesForSort = $bytes;
|
$bytesForSort = $bytes;
|
||||||
@endphp
|
@endphp
|
||||||
<tr class="hover:bg-background-secondary/60 table-row"
|
<tr wire:key="top-port-{{ $topPortsCurrentPage }}-{{ $index }}" class="hover:bg-background-secondary/60 table-row"
|
||||||
data-port="{{ $port }}"
|
data-port="{{ $port }}"
|
||||||
data-connections="{{ $connections }}"
|
data-connections="{{ $connections }}"
|
||||||
data-traffic="{{ $bytesForSort }}">
|
data-traffic="{{ $bytesForSort }}">
|
||||||
@@ -2913,6 +3117,98 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Top Ports Pagination Controls --}}
|
||||||
|
@if(count($networkAnalysis['topPorts']) > 0)
|
||||||
|
<div class="p-4 border-t border-neutral flex flex-col sm:flex-row items-center justify-between gap-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="topPortsPerPage" 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 {{ (($topPortsCurrentPage - 1) * $topPortsPerPage) + 1 }} - {{ min($topPortsCurrentPage * $topPortsPerPage, count($networkAnalysis['topPorts'])) }} of {{ count($networkAnalysis['topPorts']) }} ports
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{{-- First page --}}
|
||||||
|
<button type="button"
|
||||||
|
wire:click="goToTopPortsPage(1)"
|
||||||
|
@if($topPortsCurrentPage === 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="previousTopPortsPage"
|
||||||
|
@if($topPortsCurrentPage === 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
|
||||||
|
$topPortsTotalPages = $this->topPortsTotalPages;
|
||||||
|
$startPage = max(1, $topPortsCurrentPage - 2);
|
||||||
|
$endPage = min($topPortsTotalPages, $topPortsCurrentPage + 2);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($startPage > 1)
|
||||||
|
<button type="button" wire:click="goToTopPortsPage(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="goToTopPortsPage({{ $i }})"
|
||||||
|
class="px-3 py-1 border rounded text-sm transition-colors {{ $topPortsCurrentPage === $i ? 'bg-primary text-white border-primary' : 'border-neutral hover:bg-background-secondary' }}">
|
||||||
|
{{ $i }}
|
||||||
|
</button>
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
@if($endPage < $topPortsTotalPages)
|
||||||
|
@if($endPage < $topPortsTotalPages - 1)
|
||||||
|
<span class="px-2 text-base/60">...</span>
|
||||||
|
@endif
|
||||||
|
<button type="button" wire:click="goToTopPortsPage({{ $topPortsTotalPages }})" class="px-3 py-1 border border-neutral rounded hover:bg-background-secondary transition-colors text-sm">{{ $topPortsTotalPages }}</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next page --}}
|
||||||
|
<button type="button"
|
||||||
|
wire:click="nextTopPortsPage"
|
||||||
|
@if($topPortsCurrentPage === $topPortsTotalPages) 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="goToTopPortsPage({{ $topPortsTotalPages }})"
|
||||||
|
@if($topPortsCurrentPage === $topPortsTotalPages) 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>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user