Add all files
This commit is contained in:
278
extensions/Servers/Proxmox/Services/CoreRouterClient.php
Normal file
278
extensions/Servers/Proxmox/Services/CoreRouterClient.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Servers\Proxmox\Services;
|
||||
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CoreRouterClient
|
||||
{
|
||||
public function listStates(int $limit = null): array
|
||||
{
|
||||
$queryParams = [];
|
||||
|
||||
// Add limit parameter if specified or use config default
|
||||
// Increase default limit significantly to ensure we capture all connections
|
||||
$stateLimit = $limit ?? config('corerouter.state_limit', 1000);
|
||||
if ($stateLimit > 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), '<!DOCTYPE'))) {
|
||||
$statusCode = $response->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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user