0) { $queryParams['limit'] = $stateLimit; } // Normal flow: fetch all states try { $response = $this->request('/firewall/states', $queryParams); } catch (\RuntimeException $e) { // Try alternative endpoint path if standard fails if (str_contains($e->getMessage(), '404')) { try { $response = $this->request('/firewall/state/list', $queryParams); } catch (\RuntimeException $e2) { // Re-throw with original error if alternative also fails throw $e; } } else { throw $e; } } // The API returns data directly in the 'data' field as an array // Response structure: {"code":200,"status":"ok","data":[...],"_links":{...}} $data = Arr::get($response, 'data', []); // Check for pagination links - if there are more results, fetch them $links = Arr::get($response, '_links', []); $allStates = is_array($data) ? $data : []; // Handle pagination if present (data might be nested in items) if (isset($allStates['items']) && is_array($allStates['items'])) { $allStates = $allStates['items']; } // Fetch ALL available states by continuing pagination until no more pages // Continue pagination if: // 1. There's a next link, OR // 2. We got exactly the requested limit (suggests there might be more) $pageCount = 1; $hasNextLink = isset($links['next']) && !empty($links['next']); $gotFullLimit = count($allStates) >= $stateLimit; $maxPages = 1000; // Safety limit to prevent infinite loops (should never reach this) while (($hasNextLink || $gotFullLimit) && $pageCount < $maxPages) { try { // Extract offset and limit from next URL or increment offset $offset = count($allStates); $nextQueryParams = array_merge($queryParams, ['offset' => $offset, 'limit' => $stateLimit]); $nextResponse = $this->request('/firewall/states', $nextQueryParams); $nextData = Arr::get($nextResponse, 'data', []); if (isset($nextData['items']) && is_array($nextData['items'])) { $nextData = $nextData['items']; } if (is_array($nextData) && !empty($nextData)) { $allStates = array_merge($allStates, $nextData); $links = Arr::get($nextResponse, '_links', []); $hasNextLink = isset($links['next']) && !empty($links['next']); $gotFullLimit = count($nextData) >= $stateLimit; $pageCount++; // Log progress every 10 pages to track fetching if ($pageCount % 10 === 0) { \Log::debug('Core Router API pagination progress', [ 'pages_fetched' => $pageCount, 'total_states_so_far' => count($allStates), 'has_next_link' => $hasNextLink, ]); } } else { // No more data, stop pagination break; } } catch (\Throwable $e) { \Log::warning('Core Router API pagination failed', [ 'page' => $pageCount + 1, 'error' => $e->getMessage(), ]); break; } } // Log a warning if we hit the safety limit (should never happen) if ($pageCount >= $maxPages) { \Log::warning('Core Router API pagination reached safety limit', [ 'pages' => $pageCount, 'total_states' => count($allStates), ]); } // Log pagination results if ($pageCount > 1) { \Log::debug('Core Router API fetched all states via pagination', [ 'pages_fetched' => $pageCount, 'total_states' => count($allStates), 'final_page_count' => $pageCount, ]); } else { \Log::debug('Core Router API fetched states (single page)', [ 'total_states' => count($allStates), ]); } // Ensure we return an array return is_array($allStates) ? $allStates : []; } public function gatewayStatus(): array { // Try alternative endpoint paths - gateway status endpoint may vary $endpoints = [ '/diagnostics/gateways/status', '/diagnostics/gateway/status', '/diagnostics/gateways', ]; foreach ($endpoints as $endpoint) { try { $response = $this->requestOptional($endpoint); if ($response !== null) { // The API returns data directly in the 'data' field as an array $data = Arr::get($response, 'data', []); // Handle pagination if present (data might be nested in items) if (isset($data['items']) && is_array($data['items'])) { $data = $data['items']; } return is_array($data) ? $data : []; } } catch (\Throwable $e) { // Continue to next endpoint continue; } } // If all endpoints fail, return empty array (gateway data is optional) return []; } /** * Make an optional API request that returns null on 404 instead of throwing */ protected function requestOptional(string $endpoint, array $query = []): ?array { try { return $this->request($endpoint, $query); } catch (\RuntimeException $e) { // Return null for 404 errors (endpoint not found) if (str_contains($e->getMessage(), '404') || str_contains($e->getMessage(), 'not found')) { return null; } // Re-throw other errors throw $e; } } protected function request(string $endpoint, array $query = []): array { $baseUrl = rtrim((string) config('corerouter.base_url'), '/'); if (!$baseUrl) { throw new \RuntimeException('Core Router API base URL is not configured.'); } // Ensure endpoint starts with / $endpoint = '/' . ltrim($endpoint, '/'); // If base_url doesn't include /api/v2, add it to the endpoint if (!str_contains($baseUrl, '/api/v2')) { $endpoint = '/api/v2' . $endpoint; } $fullUrl = $baseUrl . $endpoint; $response = $this->http() ->get($fullUrl, $query); // Check if response is HTML (indicates wrong URL or auth failure) $contentType = $response->header('Content-Type', ''); $body = $response->body(); if (str_contains($contentType, 'text/html') || (strlen($body) > 0 && str_starts_with(trim($body), 'status(); if ($statusCode === 404) { throw new \RuntimeException( "Core Router API endpoint not found (404). " . "Please verify the base URL includes /api/v2 (e.g., https://your-corerouter/api/v2). " . "Attempted URL: {$fullUrl}" ); } elseif ($statusCode === 401 || $statusCode === 403) { throw new \RuntimeException( "Core Router API authentication failed ({$statusCode}). " . "Please verify your API credentials (API key or username/password) are correct." ); } else { throw new \RuntimeException( "Core Router API returned an unexpected response (HTTP {$statusCode}). " . "This may indicate the API is not properly configured or accessible. " . "Attempted URL: {$fullUrl}" ); } } if ($response->failed()) { $jsonMessage = $response->json('message'); $statusCode = $response->status(); if ($jsonMessage) { throw new \RuntimeException("Core Router API request failed (HTTP {$statusCode}): {$jsonMessage}"); } else { throw new \RuntimeException( "Core Router API request failed (HTTP {$statusCode}). " . "Attempted URL: {$fullUrl}. " . "Response: " . substr($body, 0, 200) ); } } $jsonResponse = $response->json(); // Validate response structure if (!is_array($jsonResponse)) { throw new \RuntimeException( "Core Router API returned invalid response format. " . "Expected JSON array/object, got: " . gettype($jsonResponse) . ". " . "Attempted URL: {$fullUrl}" ); } return $jsonResponse; } protected function http(): PendingRequest { $apiKey = (string) config('corerouter.api_key'); $username = (string) config('corerouter.username'); $password = (string) config('corerouter.password'); $request = Http::timeout((int) config('corerouter.timeout', 10)) ->acceptJson() ->withOptions([ 'verify' => (bool) config('corerouter.verify_ssl', true), ]); if ($apiKey) { $request = $request->withHeaders([ 'X-API-Key' => $apiKey, ]); } elseif ($username && $password) { $request = $request->withBasicAuth($username, $password); } else { throw new \RuntimeException('Core Router API credentials are not configured.'); } return $request; } }