cached($this->applicationCacheKey($applicationId), function () use ($applicationId) { $application = $this->get('/application.one', [ 'applicationId' => $applicationId, ]); $appName = Arr::get($application, 'appName') ?? Arr::get($application, 'name'); $monitoring = []; if ($appName) { $monitoring = $this->optionalGet('/application.readAppMonitoring', [ 'appName' => $appName, ]); } return [ 'application' => $application, 'monitoring' => $monitoring, 'appName' => $appName, ]; }, 30); } public function projects(): array { return $this->cached($this->projectsCacheKey(), function () { $projects = $this->get('/project.all'); return is_array($projects) ? $projects : []; }, 60); } public function findProject(string $projectIdOrName): ?array { $projects = $this->projects(); foreach ($projects as $project) { if (Arr::get($project, 'projectId') === $projectIdOrName) { return $project; } } foreach ($projects as $project) { if ( Arr::get($project, 'name') === $projectIdOrName || Arr::get($project, 'projectName') === $projectIdOrName ) { return $project; } } return null; } public function recentDeployments(string $applicationId, int $limit = 5): array { return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) { $deployments = $this->get('/deployment.all', [ 'applicationId' => $applicationId, ]); if (! is_array($deployments)) { return []; } return array_slice($deployments, 0, $limit); }, 60); } public function reloadApplication(string $applicationId, ?Authenticatable $actor = null): array { $status = $this->applicationStatus($applicationId); $appName = Arr::get($status, 'appName'); if (! $appName) { throw new \RuntimeException('Dokploy application name is required to reload the service.'); } $response = $this->dispatchAction( $applicationId, 'reload', [ 'applicationId' => $applicationId, 'appName' => $appName, ], fn (array $payload) => $this->post('/application.reload', $payload), $actor, ); $this->forgetApplicationCaches($applicationId); return $response; } public function redeployApplication(string $applicationId, ?Authenticatable $actor = null): array { $response = $this->dispatchAction( $applicationId, 'redeploy', [ 'applicationId' => $applicationId, ], fn (array $payload) => $this->post('/application.redeploy', $payload), $actor, ); $this->forgetApplicationCaches($applicationId); return $response; } public function composeStatus(string $composeId): array { return $this->cached($this->composeCacheKey($composeId), function () use ($composeId) { $compose = $this->get('/compose.one', [ 'composeId' => $composeId, ]); $services = $this->optionalGet('/compose.loadServices', [ 'composeId' => $composeId, 'type' => 'cache', ]); return [ 'compose' => $compose, 'services' => $services, ]; }, 30); } public function composeDeployments(string $composeId, int $limit = 5): array { return $this->cached($this->composeDeploymentsCacheKey($composeId), function () use ($composeId, $limit) { $deployments = $this->get('/deployment.allByCompose', [ 'composeId' => $composeId, ]); if (! is_array($deployments)) { return []; } return array_slice($deployments, 0, $limit); }, 60); } public function redeployCompose(string $composeId, ?Authenticatable $actor = null): array { $response = $this->dispatchAction( $composeId, 'compose.redeploy', [ 'composeId' => $composeId, ], fn (array $payload) => $this->post('/compose.redeploy', $payload), $actor, ); $this->forgetComposeCaches($composeId); return $response; } public function deployCompose(string $composeId, ?Authenticatable $actor = null): array { $response = $this->dispatchAction( $composeId, 'compose.deploy', [ 'composeId' => $composeId, ], fn (array $payload) => $this->post('/compose.deploy', $payload), $actor, ); $this->forgetComposeCaches($composeId); return $response; } public function stopCompose(string $composeId, ?Authenticatable $actor = null): array { $response = $this->dispatchAction( $composeId, 'compose.stop', [ 'composeId' => $composeId, ], fn (array $payload) => $this->post('/compose.stop', $payload), $actor, ); $this->forgetComposeCaches($composeId); return $response; } protected function cached(string $key, callable $callback, int $seconds): mixed { return Cache::remember($key, now()->addSeconds($seconds), $callback); } protected function optionalGet(string $path, array $query = []): array { try { return $this->get($path, $query); } catch (\Throwable $exception) { Log::warning('[Dokploy] Optional GET failed', [ 'path' => $path, 'message' => $exception->getMessage(), ]); return []; } } protected function get(string $path, array $query = []): array { $response = $this->request()->get($this->normalizePath($path), $query); if ($response->failed()) { $this->logFailure('GET', $path, $response); throw new RequestException($response); } return $response->json() ?? []; } protected function post(string $path, array $payload = []): Response { $response = $this->request()->post($this->normalizePath($path), $payload); if ($response->failed()) { $this->logFailure('POST', $path, $response); throw new RequestException($response); } return $response; } protected function request(): PendingRequest { $baseUrl = config('dokploy.api.base_url'); $token = config('dokploy.api.token'); $timeout = config('dokploy.api.timeout', 10); if (! $baseUrl || ! $token) { throw new \RuntimeException('Dokploy API is not configured.'); } return $this->http ->baseUrl($baseUrl) ->timeout($timeout) ->acceptJson() ->withHeaders([ 'x-api-key' => $token, ]); } protected function normalizePath(string $path): string { return '/'.ltrim($path, '/'); } protected function logFailure(string $method, string $path, Response $response): void { Log::error('[Dokploy] API request failed', [ 'method' => $method, 'path' => $path, 'status' => $response->status(), 'body' => $response->body(), ]); } protected function dispatchAction( string $targetId, string $action, array $payload, callable $callback, ?Authenticatable $actor = null, ): array { try { $response = $callback($payload); $body = $response->json() ?? []; $status = $response->status(); } catch (\Throwable $exception) { $this->logAction($targetId, $action, $payload, [ 'error' => $exception->getMessage(), ], null, $actor); throw $exception; } $this->logAction($targetId, $action, $payload, $body, $status, $actor); return $body; } protected function logAction( string $targetId, string $action, array $payload, array $response, ?int $status, ?Authenticatable $actor = null, ): void { InfrastructureActionLog::create([ 'user_id' => $actor?->getAuthIdentifier() ?? auth()->id(), 'service_id' => $targetId, 'action' => $action, 'payload' => $payload, 'response' => $response, 'status_code' => $status, ]); } protected function applicationCacheKey(string $applicationId): string { return "dokploy.application.{$applicationId}"; } protected function deploymentCacheKey(string $applicationId): string { return "dokploy.deployments.{$applicationId}"; } protected function composeCacheKey(string $composeId): string { return "dokploy.compose.{$composeId}"; } protected function composeDeploymentsCacheKey(string $composeId): string { return "dokploy.compose.deployments.{$composeId}"; } protected function projectsCacheKey(): string { return 'dokploy.projects'; } protected function forgetApplicationCaches(string $applicationId): void { Cache::forget($this->applicationCacheKey($applicationId)); Cache::forget($this->deploymentCacheKey($applicationId)); } protected function forgetComposeCaches(string $composeId): void { Cache::forget($this->composeCacheKey($composeId)); Cache::forget($this->composeDeploymentsCacheKey($composeId)); } }