refactor: implement firewall logs loading via API on page load, enhancing performance and user experience
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Paymenter\Extensions\Others\UptimeLab\Models\UptimeServer;
|
||||||
|
|
||||||
|
class UptimeServerSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$servers = [
|
||||||
|
[
|
||||||
|
'name' => 'Web Server 01',
|
||||||
|
'ip_address' => '192.168.1.10',
|
||||||
|
'server_type' => 'ip',
|
||||||
|
'location' => 'Jakarta, Indonesia',
|
||||||
|
'is_active' => true,
|
||||||
|
'status' => 'online',
|
||||||
|
'uptime_percentage' => 99.8,
|
||||||
|
'last_online' => now(),
|
||||||
|
'last_offline' => now()->subHours(6),
|
||||||
|
'last_check' => now(),
|
||||||
|
'response_time' => 15,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Main Database Server',
|
||||||
|
'ip_address' => '192.168.1.20',
|
||||||
|
'server_type' => 'ip',
|
||||||
|
'location' => 'Surabaya, Indonesia',
|
||||||
|
'is_active' => true,
|
||||||
|
'status' => 'offline',
|
||||||
|
'uptime_percentage' => 100.0,
|
||||||
|
'last_online' => now()->subHours(5),
|
||||||
|
'last_offline' => now(),
|
||||||
|
'last_check' => now(),
|
||||||
|
'response_time' => null,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'API Gateway',
|
||||||
|
'ip_address' => 'api.example.com',
|
||||||
|
'server_type' => 'subdomain',
|
||||||
|
'location' => 'Bandung, Indonesia',
|
||||||
|
'is_active' => true,
|
||||||
|
'status' => 'online',
|
||||||
|
'uptime_percentage' => 99.9,
|
||||||
|
'last_online' => now()->subHours(6),
|
||||||
|
'last_offline' => now()->subHours(5),
|
||||||
|
'last_check' => now(),
|
||||||
|
'response_time' => 25,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Load Balancer',
|
||||||
|
'ip_address' => '192.168.1.40',
|
||||||
|
'server_type' => 'ip',
|
||||||
|
'location' => 'Jakarta, Indonesia',
|
||||||
|
'is_active' => true,
|
||||||
|
'status' => 'online',
|
||||||
|
'uptime_percentage' => 99.99,
|
||||||
|
'last_online' => now(),
|
||||||
|
'last_offline' => now()->subHours(6),
|
||||||
|
'last_check' => now(),
|
||||||
|
'response_time' => 8,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($servers as $serverData) {
|
||||||
|
UptimeServer::create($serverData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ class Firewall extends Component
|
|||||||
$this->loadFirewallOptions();
|
$this->loadFirewallOptions();
|
||||||
$this->loadFirewallRules();
|
$this->loadFirewallRules();
|
||||||
$this->loadNetworkAdapters();
|
$this->loadNetworkAdapters();
|
||||||
$this->loadFirewallLogs();
|
// Note: loadFirewallLogs() is now called only once via API on page load
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadAvailableIps(): void
|
protected function loadAvailableIps(): void
|
||||||
@@ -797,6 +797,27 @@ class Firewall extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public method to load firewall logs - called only once on page load via API
|
||||||
|
*/
|
||||||
|
#[\Livewire\Attributes\Computed]
|
||||||
|
public function firewallLogs()
|
||||||
|
{
|
||||||
|
return $this->loadFirewallLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch firewall logs via API call - returns logs as JSON
|
||||||
|
*/
|
||||||
|
public function fetchFirewallLogs(): array
|
||||||
|
{
|
||||||
|
$this->loadFirewallLogs();
|
||||||
|
return [
|
||||||
|
'logs' => $this->firewallLogs,
|
||||||
|
'error' => $this->logError,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function loadFirewallLogs(): void
|
public function loadFirewallLogs(): void
|
||||||
{
|
{
|
||||||
$this->logError = '';
|
$this->logError = '';
|
||||||
|
|||||||
@@ -303,94 +303,116 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(!empty($logError))
|
<div id="firewall-logs-container"
|
||||||
<div class="bg-warning/10 border border-warning/30 rounded-lg p-4 mb-4">
|
x-data="{
|
||||||
<div class="flex items-start gap-3">
|
logs: [],
|
||||||
<x-ri-error-warning-line class="size-5 text-warning flex-shrink-0 mt-0.5" />
|
loading: true,
|
||||||
<div class="flex-1">
|
error: '',
|
||||||
<p class="text-sm text-warning/90">{{ $logError }}</p>
|
searchTerm: '',
|
||||||
</div>
|
sortField: 'timestamp_iso',
|
||||||
</div>
|
sortDirection: 'desc',
|
||||||
</div>
|
async initializeLogs() {
|
||||||
@endif
|
// Only load logs once on page load
|
||||||
|
if (window.firewallLogsLoaded) {
|
||||||
|
this.logs = window.firewallLogsData || [];
|
||||||
|
this.loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call Livewire method to fetch logs
|
||||||
|
const result = await @this.call('fetchFirewallLogs');
|
||||||
|
window.firewallLogsData = result.logs || [];
|
||||||
|
window.firewallLogsLoaded = true;
|
||||||
|
this.logs = window.firewallLogsData;
|
||||||
|
this.error = result.error || '';
|
||||||
|
} catch (err) {
|
||||||
|
this.error = 'Failed to load firewall logs: ' + (err.message || 'Unknown error');
|
||||||
|
console.error('Error loading firewall logs:', err);
|
||||||
|
this.logs = [];
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sort(field) {
|
||||||
|
if (this.sortField === field) {
|
||||||
|
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
this.sortField = field;
|
||||||
|
this.sortDirection = 'asc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get filteredLogs() {
|
||||||
|
const term = this.searchTerm.trim().toLowerCase();
|
||||||
|
let entries = this.logs;
|
||||||
|
|
||||||
@once
|
if (term) {
|
||||||
<script>
|
entries = entries.filter((log) => {
|
||||||
if (typeof window.firewallLogsData === 'undefined') {
|
return Object.values(log).some((value) => {
|
||||||
window.firewallLogsData = @js($firewallLogs);
|
if (value === null || value === undefined) {
|
||||||
window.firewallLogsLoaded = true;
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
return String(value).toLowerCase().includes(term);
|
||||||
@endonce
|
|
||||||
|
|
||||||
@if(count($firewallLogs) > 0 || !empty($firewallLogs))
|
|
||||||
<div
|
|
||||||
x-data="{
|
|
||||||
logs: window.firewallLogsData || [],
|
|
||||||
searchTerm: '',
|
|
||||||
sortField: 'timestamp_iso',
|
|
||||||
sortDirection: 'desc',
|
|
||||||
sort(field) {
|
|
||||||
if (this.sortField === field) {
|
|
||||||
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
||||||
} else {
|
|
||||||
this.sortField = field;
|
|
||||||
this.sortDirection = 'asc';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get filteredLogs() {
|
|
||||||
const term = this.searchTerm.trim().toLowerCase();
|
|
||||||
let entries = this.logs;
|
|
||||||
|
|
||||||
if (term) {
|
|
||||||
entries = entries.filter((log) => {
|
|
||||||
return Object.values(log).some((value) => {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return String(value).toLowerCase().includes(term);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dir = this.sortDirection === 'asc' ? 1 : -1;
|
||||||
|
const field = this.sortField;
|
||||||
|
|
||||||
|
return entries.slice().sort((a, b) => {
|
||||||
|
let aVal = a[field] ?? '';
|
||||||
|
let bVal = b[field] ?? '';
|
||||||
|
|
||||||
|
if (field === 'timestamp_iso') {
|
||||||
|
const aTime = aVal ? new Date(aVal).getTime() : 0;
|
||||||
|
const bTime = bVal ? new Date(bVal).getTime() : 0;
|
||||||
|
return (aTime - bTime) * dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dir = this.sortDirection === 'asc' ? 1 : -1;
|
if (!Number.isNaN(parseFloat(aVal)) && !Number.isNaN(parseFloat(bVal))) {
|
||||||
const field = this.sortField;
|
return (parseFloat(aVal) - parseFloat(bVal)) * dir;
|
||||||
|
}
|
||||||
|
|
||||||
return entries.slice().sort((a, b) => {
|
return String(aVal).localeCompare(String(bVal)) * dir;
|
||||||
let aVal = a[field] ?? '';
|
});
|
||||||
let bVal = b[field] ?? '';
|
},
|
||||||
|
}"
|
||||||
if (field === 'timestamp_iso') {
|
@load="initializeLogs()"
|
||||||
const aTime = aVal ? new Date(aVal).getTime() : 0;
|
>
|
||||||
const bTime = bVal ? new Date(bVal).getTime() : 0;
|
<div x-show="loading" x-cloak class="text-center py-8 text-base/60">
|
||||||
return (aTime - bTime) * dir;
|
<div class="animate-spin inline-block">
|
||||||
}
|
<x-ri-loader-4-line class="size-8 mx-auto mb-3 opacity-50" />
|
||||||
|
|
||||||
if (!Number.isNaN(parseFloat(aVal)) && !Number.isNaN(parseFloat(bVal))) {
|
|
||||||
return (parseFloat(aVal) - parseFloat(bVal)) * dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(aVal).localeCompare(String(bVal)) * dir;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
class="space-y-4"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
|
||||||
<div class="relative w-full md:w-72">
|
|
||||||
<x-ri-search-line class="size-4 absolute left-3 top-1/2 -translate-y-1/2 text-base/40" />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
x-model="searchTerm"
|
|
||||||
placeholder="Search logs..."
|
|
||||||
class="firewall-log-search-input w-full pr-4 py-2 border border-neutral rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/40"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-base/50">
|
<p>Loading firewall logs...</p>
|
||||||
<span x-text="`${filteredLogs.length} / ${logs.length} results`"></span>
|
</div>
|
||||||
|
|
||||||
|
<div x-show="error" x-cloak class="bg-error/10 border border-error/30 rounded-lg p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<x-ri-error-warning-line class="size-5 text-error flex-shrink-0 mt-0.5" />
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm text-error/90" x-text="error"></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template x-if="!loading && !error && logs.length > 0">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
||||||
|
<div class="relative w-full md:w-72">
|
||||||
|
<x-ri-search-line class="size-4 absolute left-3 top-1/2 -translate-y-1/2 text-base/40" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="searchTerm"
|
||||||
|
placeholder="Search logs..."
|
||||||
|
class="firewall-log-search-input w-full pr-4 py-2 border border-neutral rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-base/50">
|
||||||
|
<span x-text="`${filteredLogs.length} / ${logs.length} results`"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full border-collapse">
|
<table class="w-full border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -993,14 +1015,17 @@
|
|||||||
<x-ri-file-list-3-line class="size-12 mx-auto mb-3 opacity-50" />
|
<x-ri-file-list-3-line class="size-12 mx-auto mb-3 opacity-50" />
|
||||||
<p>No logs matched your search.</p>
|
<p>No logs matched your search.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@elseif(empty($logError))
|
</template>
|
||||||
<div class="text-center py-8 text-base/60">
|
|
||||||
<x-ri-file-list-3-line class="size-12 mx-auto mb-3 opacity-50" />
|
<template x-if="!loading && !error && logs.length === 0">
|
||||||
<p>No firewall logs available</p>
|
<div class="text-center py-8 text-base/60">
|
||||||
<p class="text-xs mt-2">Ensure firewall logging is enabled in Proxmox (Datacenter > Firewall > Options)</p>
|
<x-ri-file-list-3-line class="size-12 mx-auto mb-3 opacity-50" />
|
||||||
</div>
|
<p>No firewall logs available</p>
|
||||||
@endif
|
<p class="text-xs mt-2">Ensure firewall logging is enabled in Proxmox (Datacenter > Firewall > Options)</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="bg-background-secondary border border-neutral/20 rounded-lg p-8 text-center">
|
<div class="bg-background-secondary border border-neutral/20 rounded-lg p-8 text-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user