feat: implement caching for network analysis to improve performance and reduce load times
This commit is contained in:
@@ -154,23 +154,45 @@ class Statistics extends Component
|
||||
{
|
||||
Log::debug('Network Analysis: loadNetworkAnalysis() called', [
|
||||
'current_loading_state' => $this->networkAnalysisLoading,
|
||||
'has_last_updated' => !empty($this->networkAnalysis['lastUpdated']),
|
||||
'has_error' => !empty($this->networkAnalysis['error']),
|
||||
'has_last_updated' => ! empty($this->networkAnalysis['lastUpdated']),
|
||||
'has_error' => ! empty($this->networkAnalysis['error']),
|
||||
]);
|
||||
|
||||
// Prevent duplicate loads
|
||||
if ($this->networkAnalysisLoading) {
|
||||
Log::debug('Network Analysis: Already loading, skipping');
|
||||
|
||||
// Use a short-lived cache to avoid repeatedly performing heavy analysis
|
||||
$cacheKey = 'proxmox_network_analysis_' . $this->server->id;
|
||||
|
||||
// If we already have a fairly recent result, reuse it immediately
|
||||
$cachedAnalysis = Cache::get($cacheKey);
|
||||
if (is_array($cachedAnalysis) && ! empty($cachedAnalysis['lastUpdated']) && empty($this->networkAnalysis['error'])) {
|
||||
Log::debug('Network Analysis: served from cache', [
|
||||
'cache_key' => $cacheKey,
|
||||
'last_updated' => $cachedAnalysis['lastUpdated'] ?? null,
|
||||
]);
|
||||
|
||||
$this->networkAnalysis = $cachedAnalysis;
|
||||
$this->networkAnalysisLoading = false;
|
||||
|
||||
// Reset retry count when we successfully hydrate from cache
|
||||
$this->networkAnalysisRetryCount = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->coreRouterEnabled()) {
|
||||
|
||||
// Prevent duplicate loads while a request is already in-flight
|
||||
if ($this->networkAnalysisLoading) {
|
||||
Log::debug('Network Analysis: Already loading, skipping new request');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->coreRouterEnabled()) {
|
||||
$this->networkAnalysis['error'] = 'Core Router API is not configured.';
|
||||
$this->networkAnalysis['connections'] = [];
|
||||
$this->networkAnalysis['topConnections'] = [];
|
||||
$this->networkAnalysis['topPorts'] = [];
|
||||
$this->networkAnalysis['security'] = [];
|
||||
$this->networkAnalysisLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,6 +204,7 @@ class Statistics extends Component
|
||||
$this->networkAnalysis['topPorts'] = [];
|
||||
$this->networkAnalysis['security'] = [];
|
||||
$this->networkAnalysisLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,20 +213,20 @@ class Statistics extends Component
|
||||
|
||||
try {
|
||||
$client = app(CoreRouterClient::class);
|
||||
|
||||
|
||||
// Fetch all firewall states (no API-level filtering)
|
||||
$allStates = $client->listStates();
|
||||
|
||||
|
||||
// Normalize server IPs for matching
|
||||
$normalizedServerIps = array_map(function($ip) {
|
||||
$normalizedServerIps = array_map(static function ($ip) {
|
||||
return strtolower(trim($ip));
|
||||
}, $serverIps);
|
||||
|
||||
// Sample a few states to debug extraction
|
||||
|
||||
// Sample a few states to debug extraction (kept minimal for performance)
|
||||
$sampleStatesForDebug = [];
|
||||
foreach (array_slice($allStates, 0, 10) as $sampleState) {
|
||||
$rawSrc = (string)($sampleState['source'] ?? '');
|
||||
$rawDst = (string)($sampleState['destination'] ?? '');
|
||||
$rawSrc = (string) ($sampleState['source'] ?? '');
|
||||
$rawDst = (string) ($sampleState['destination'] ?? '');
|
||||
$sampleStatesForDebug[] = [
|
||||
'raw_source' => $rawSrc,
|
||||
'raw_destination' => $rawDst,
|
||||
@@ -212,35 +235,35 @@ class Statistics extends Component
|
||||
'protocol' => $sampleState['protocol'] ?? 'N/A',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Log::debug('Core Router filtering states', [
|
||||
'total_states' => count($allStates),
|
||||
'server_ips' => $serverIps,
|
||||
'normalized_server_ips' => $normalizedServerIps,
|
||||
'sample_states_extraction' => $sampleStatesForDebug,
|
||||
]);
|
||||
|
||||
|
||||
// Filter states: keep only where source OR destination matches a server IP
|
||||
$filteredStates = [];
|
||||
$sampleChecks = [];
|
||||
foreach ($allStates as $state) {
|
||||
$rawSource = (string)($state['source'] ?? '');
|
||||
$rawDestination = (string)($state['destination'] ?? '');
|
||||
|
||||
$rawSource = (string) ($state['source'] ?? '');
|
||||
$rawDestination = (string) ($state['destination'] ?? '');
|
||||
|
||||
// Extract IP from "IP[port]" or "IP:port" format
|
||||
$sourceIp = $this->extractIpFromField($rawSource);
|
||||
$destinationIp = $this->extractIpFromField($rawDestination);
|
||||
|
||||
|
||||
$sourceNormalized = strtolower(trim($sourceIp));
|
||||
$destNormalized = strtolower(trim($destinationIp));
|
||||
|
||||
|
||||
$matches = false;
|
||||
$matchReason = '';
|
||||
|
||||
|
||||
// Normalize raw fields for case-insensitive matching
|
||||
$rawSourceLower = strtolower($rawSource);
|
||||
$rawDestinationLower = strtolower($rawDestination);
|
||||
|
||||
|
||||
// Check exact match
|
||||
foreach ($normalizedServerIps as $serverIp) {
|
||||
if ($sourceNormalized === $serverIp) {
|
||||
@@ -265,10 +288,10 @@ class Statistics extends Component
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($matches) {
|
||||
$filteredStates[] = $state;
|
||||
|
||||
|
||||
// Log first few matches for debugging
|
||||
if (count($sampleChecks) < 5) {
|
||||
$sampleChecks[] = [
|
||||
@@ -284,41 +307,10 @@ class Statistics extends Component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also search for specific target IPs in raw states for debugging (check ALL states)
|
||||
$targetIp = '2a0b:4e07:8:1::2:10';
|
||||
$targetIpLower = strtolower($targetIp);
|
||||
$targetIpv4 = '45.154.99.28';
|
||||
$targetIpv4Lower = strtolower($targetIpv4);
|
||||
$statesWithTargetIp = [];
|
||||
foreach ($allStates as $state) {
|
||||
$rawSrc = (string)($state['source'] ?? '');
|
||||
$rawDst = (string)($state['destination'] ?? '');
|
||||
$rawSrcLower = strtolower($rawSrc);
|
||||
$rawDstLower = strtolower($rawDst);
|
||||
|
||||
// Check both IPv6 and IPv4 target IPs
|
||||
if (stripos($rawSrcLower, $targetIpLower) !== false ||
|
||||
stripos($rawDstLower, $targetIpLower) !== false ||
|
||||
stripos($rawSrcLower, $targetIpv4Lower) !== false ||
|
||||
stripos($rawDstLower, $targetIpv4Lower) !== false) {
|
||||
$statesWithTargetIp[] = [
|
||||
'id' => $state['id'] ?? null,
|
||||
'source' => $rawSrc,
|
||||
'destination' => $rawDst,
|
||||
'protocol' => $state['protocol'] ?? 'N/A',
|
||||
'extracted_source' => $this->extractIpFromField($rawSrc),
|
||||
'extracted_destination' => $this->extractIpFromField($rawDst),
|
||||
];
|
||||
if (count($statesWithTargetIp) >= 20) break; // Increase limit for better debugging
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Log::debug('Core Router filtered states result', [
|
||||
'filtered_count' => count($filteredStates),
|
||||
'sample_matches' => $sampleChecks,
|
||||
'target_ip' => $targetIp,
|
||||
'states_with_target_ip_in_raw' => $statesWithTargetIp,
|
||||
]);
|
||||
|
||||
// Format connections
|
||||
@@ -333,7 +325,7 @@ class Statistics extends Component
|
||||
'sample_connections' => array_slice($connections, 0, 3),
|
||||
]);
|
||||
|
||||
$this->networkAnalysis = [
|
||||
$analysis = [
|
||||
'connections' => array_slice($connections, 0, 100),
|
||||
'topConnections' => array_slice($topConnections, 0, 10),
|
||||
'topPorts' => array_slice($topPorts, 0, 10),
|
||||
@@ -341,33 +333,41 @@ class Statistics extends Component
|
||||
'lastUpdated' => now()->toDateTimeString(),
|
||||
'error' => null,
|
||||
];
|
||||
|
||||
|
||||
$this->networkAnalysis = $analysis;
|
||||
|
||||
// Persist the computed analysis for a short period to speed up subsequent loads
|
||||
Cache::put($cacheKey, $analysis, now()->addSeconds(30));
|
||||
|
||||
// Reset retry count on successful load
|
||||
$this->networkAnalysisRetryCount = 0;
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
|
||||
|
||||
$errorMessage = $e->getMessage();
|
||||
$is502Error = str_contains($errorMessage, 'HTTP 502') || str_contains($errorMessage, '(HTTP 502)');
|
||||
|
||||
|
||||
// Always initialize error arrays first
|
||||
$this->networkAnalysis['connections'] = [];
|
||||
$this->networkAnalysis['topConnections'] = [];
|
||||
$this->networkAnalysis['topPorts'] = [];
|
||||
$this->networkAnalysis['security'] = [];
|
||||
|
||||
|
||||
// On failure, do not leave any stale cache behind for this server
|
||||
Cache::forget($cacheKey);
|
||||
|
||||
// Check if it's a 502 error and we haven't exceeded max retries
|
||||
if ($is502Error && $this->networkAnalysisRetryCount < $this->networkAnalysisMaxRetries) {
|
||||
$this->networkAnalysisRetryCount++;
|
||||
|
||||
|
||||
Log::warning('Core Router API returned 502 error, will retry', [
|
||||
'retry_count' => $this->networkAnalysisRetryCount,
|
||||
'max_retries' => $this->networkAnalysisMaxRetries,
|
||||
]);
|
||||
|
||||
|
||||
// Set error message indicating retry
|
||||
$this->networkAnalysis['error'] = 'Temporary server error (HTTP 502). Retrying... (Attempt ' . $this->networkAnalysisRetryCount . '/' . $this->networkAnalysisMaxRetries . ')';
|
||||
|
||||
|
||||
// Dispatch event to retry after a delay
|
||||
$this->dispatch('network-analysis-502-retry', [
|
||||
'retryCount' => $this->networkAnalysisRetryCount,
|
||||
@@ -378,7 +378,7 @@ class Statistics extends Component
|
||||
$this->networkAnalysis['error'] = 'Unable to load network analysis: ' . $errorMessage;
|
||||
}
|
||||
} finally {
|
||||
// Always clear loading state in finally block
|
||||
// Always clear loading state in finally block so UI can recover
|
||||
$this->networkAnalysisLoading = false;
|
||||
}
|
||||
}
|
||||
@@ -388,9 +388,10 @@ class Statistics extends Component
|
||||
*/
|
||||
public function retryNetworkAnalysis(): void
|
||||
{
|
||||
// Reset loading state before retry
|
||||
// Simple helper that can be invoked from Livewire/JS if needed.
|
||||
// We intentionally avoid blocking sleep here; the browser-side
|
||||
// retry logic already introduces a delay between attempts.
|
||||
$this->networkAnalysisLoading = false;
|
||||
sleep(2); // Wait 2 seconds before retry
|
||||
$this->loadNetworkAnalysis();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user