From 96d2a1a512c4f9ea298db4876be24ace250f0f17 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:49:04 +0100 Subject: [PATCH 01/32] fix: edge case where executions could be null --- app/Livewire/Project/Database/BackupExecutions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php index f91b8bfaf..ce168a352 100644 --- a/app/Livewire/Project/Database/BackupExecutions.php +++ b/app/Livewire/Project/Database/BackupExecutions.php @@ -83,8 +83,10 @@ class BackupExecutions extends Component public function refreshBackupExecutions(): void { - if ($this->backup) { - $this->executions = $this->backup->executions()->get(); + if ($this->backup && $this->backup->exists) { + $this->executions = $this->backup->executions()->get()->toArray(); + } else { + $this->executions = []; } } From b09f0043d14c7f04e8b0e656ec8bce6b53a3bf4d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 11:54:45 +0100 Subject: [PATCH 02/32] fix: restrict jobs on cloud fix: restrict sentinel endpoint --- app/Console/Kernel.php | 82 +++++++++++++++++++++++++------- app/Jobs/PushServerUpdateJob.php | 6 +++ routes/api.php | 21 +++++++- 3 files changed, 89 insertions(+), 20 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 2ed3ee454..ff997667a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -91,7 +91,11 @@ class Kernel extends ConsoleKernel private function pullImages(): void { - $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); + if (isCloud()) { + $servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); + } else { + $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); + } foreach ($servers as $server) { if ($server->isSentinelEnabled()) { $this->scheduleInstance->job(function () use ($server) { @@ -124,7 +128,7 @@ class Kernel extends ConsoleKernel private function checkResources(): void { if (isCloud()) { - $servers = $this->allServers->whereHas('team.subscription')->get(); + $servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->get(); $own = Team::find(0)->servers; $servers = $servers->merge($own); } else { @@ -175,22 +179,43 @@ class Kernel extends ConsoleKernel if ($scheduled_backups->isEmpty()) { return; } + $finalScheduledBackups = collect(); foreach ($scheduled_backups as $scheduled_backup) { - if (is_null(data_get($scheduled_backup, 'database'))) { + if (blank(data_get($scheduled_backup, 'database'))) { $scheduled_backup->delete(); continue; } - $server = $scheduled_backup->server(); + if (blank($server)) { + $scheduled_backup->delete(); - if (is_null($server)) { continue; } + if ($server->isFunctional() === false) { + continue; + } + if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false) { + continue; + } + $finalScheduledBackups->push($scheduled_backup); + } + $own = Team::find(0)->servers; + $finalScheduledBackups = $finalScheduledBackups->merge($own); + + foreach ($finalScheduledBackups as $scheduled_backup) { if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; } + + $server = $scheduled_backup->server(); + $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); + + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } + $this->scheduleInstance->job(new DatabaseBackupJob( backup: $scheduled_backup ))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer(); @@ -203,34 +228,55 @@ class Kernel extends ConsoleKernel if ($scheduled_tasks->isEmpty()) { return; } + $finalScheduledTasks = collect(); foreach ($scheduled_tasks as $scheduled_task) { $service = $scheduled_task->service; $application = $scheduled_task->application; - if (! $application && ! $service) { + $server = $scheduled_task->server(); + if (blank($server)) { $scheduled_task->delete(); continue; } - if ($application) { - if (str($application->status)->contains('running') === false) { - continue; - } - } - if ($service) { - if (str($service->status)->contains('running') === false) { - continue; - } - } - $server = $scheduled_task->server(); - if (! $server) { + if ($server->isFunctional() === false) { continue; } + if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false) { + continue; + } + + if (! $service && ! $application) { + $scheduled_task->delete(); + + continue; + } + + if ($application && str($application->status)->contains('running') === false) { + continue; + } + if ($service && str($service->status)->contains('running') === false) { + continue; + } + + $finalScheduledTasks->push($scheduled_task); + } + + $own = Team::find(0)->servers; + $finalScheduledTasks = $finalScheduledTasks->merge($own); + + foreach ($finalScheduledTasks as $scheduled_task) { + $server = $scheduled_task->server(); if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; } + $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); + + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } $this->scheduleInstance->job(new ScheduledTaskJob( task: $scheduled_task ))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer(); diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 24f8d1e6b..93b203fcb 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -19,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; @@ -68,6 +69,11 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue public bool $foundLogDrainContainer = false; + public function middleware(): array + { + return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + } + public function backoff(): int { return isDev() ? 1 : 3; diff --git a/routes/api.php b/routes/api.php index 9ad64c40c..fd983d370 100644 --- a/routes/api.php +++ b/routes/api.php @@ -132,18 +132,35 @@ Route::group([ 'prefix' => 'v1', ], function () { Route::post('/sentinel/push', function () { + // return response()->json(['message' => 'temporary unavailable'], 503); $token = request()->header('Authorization'); if (! $token) { return response()->json(['message' => 'Unauthorized'], 401); } $naked_token = str_replace('Bearer ', '', $token); - $decrypted = decrypt($naked_token); - $decrypted_token = json_decode($decrypted, true); + try { + $decrypted = decrypt($naked_token); + $decrypted_token = json_decode($decrypted, true); + } catch (\Exception $e) { + return response()->json(['message' => 'Invalid token'], 401); + } $server_uuid = data_get($decrypted_token, 'server_uuid'); + if (! $server_uuid) { + return response()->json(['message' => 'Invalid token'], 401); + } $server = Server::where('uuid', $server_uuid)->first(); if (! $server) { return response()->json(['message' => 'Server not found'], 404); } + + if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + if ($server->isFunctional() === false) { + return response()->json(['message' => 'Server is not functional'], 401); + } + if ($server->settings->sentinel_token !== $naked_token) { return response()->json(['message' => 'Unauthorized'], 401); } From b7d18fc28cfccb4bdec36674e19e5053854928f5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 12:04:59 +0100 Subject: [PATCH 03/32] fix: getcontainer status should timeout after 30s --- app/Models/Server.php | 6 +++--- bootstrap/helpers/remoteProcess.php | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 8d11e23a9..cc6bff2cf 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -656,9 +656,9 @@ $schema://$host { $containers = collect([]); $containerReplicates = collect([]); if ($this->isSwarm()) { - $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false); + $containers = instant_remote_process_with_timeout(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false); $containers = format_docker_command_output_to_json($containers); - $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this, false); + $containerReplicates = instant_remote_process_with_timeout(["docker service ls --format '{{json .}}'"], $this, false); if ($containerReplicates) { $containerReplicates = format_docker_command_output_to_json($containerReplicates); foreach ($containerReplicates as $containerReplica) { @@ -682,7 +682,7 @@ $schema://$host { } } } else { - $containers = instant_remote_process(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false); + $containers = instant_remote_process_with_timeout(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false); $containers = format_docker_command_output_to_json($containers); $containerReplicates = collect([]); } diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index c7dd2cb83..d1cb93d9a 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -71,6 +71,31 @@ function instant_scp(string $source, string $dest, Server $server, $throwError = return $output === 'null' ? null : $output; } +function instant_remote_process_with_timeout(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string +{ + $command = $command instanceof Collection ? $command->toArray() : $command; + if ($server->isNonRoot() && ! $no_sudo) { + $command = parseCommandsByLineForSudo(collect($command), $server); + } + $command_string = implode("\n", $command); + + // $start_time = microtime(true); + $sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string); + $process = Process::timeout(30)->run($sshCommand); + // $end_time = microtime(true); + + // $execution_time = ($end_time - $start_time) * 1000; // Convert to milliseconds + // ray('SSH command execution time:', $execution_time.' ms')->orange(); + + $output = trim($process->output()); + $exitCode = $process->exitCode(); + + if ($exitCode !== 0) { + return $throwError ? excludeCertainErrors($process->errorOutput(), $exitCode) : null; + } + + return $output === 'null' ? null : $output; +} function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string { $command = $command instanceof Collection ? $command->toArray() : $command; From 3111cdd15becde6cf2d37559cf1a022348a8830f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 12:08:22 +0100 Subject: [PATCH 04/32] fix: enable response for temporary unavailability in sentinel push endpoint --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index fd983d370..ed77f77ab 100644 --- a/routes/api.php +++ b/routes/api.php @@ -132,7 +132,7 @@ Route::group([ 'prefix' => 'v1', ], function () { Route::post('/sentinel/push', function () { - // return response()->json(['message' => 'temporary unavailable'], 503); + return response()->json(['message' => 'temporary unavailable'], 503); $token = request()->header('Authorization'); if (! $token) { return response()->json(['message' => 'Unauthorized'], 401); From 5c8b26d446a280ed32c58d2635198364433cc062 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 12:31:20 +0100 Subject: [PATCH 05/32] fix --- app/Console/Kernel.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ff997667a..c48944f79 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -201,9 +201,6 @@ class Kernel extends ConsoleKernel $finalScheduledBackups->push($scheduled_backup); } - $own = Team::find(0)->servers; - $finalScheduledBackups = $finalScheduledBackups->merge($own); - foreach ($finalScheduledBackups as $scheduled_backup) { if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; @@ -264,9 +261,6 @@ class Kernel extends ConsoleKernel $finalScheduledTasks->push($scheduled_task); } - $own = Team::find(0)->servers; - $finalScheduledTasks = $finalScheduledTasks->merge($own); - foreach ($finalScheduledTasks as $scheduled_task) { $server = $scheduled_task->server(); if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { From f748fba3f7f3843d5e0ac7751d713d2e457dc356 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 12:34:20 +0100 Subject: [PATCH 06/32] fix: use timeout in cleanup resources --- app/Console/Commands/CleanupStuckedResources.php | 5 +++++ app/Jobs/CleanupHelperContainersJob.php | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index def3d5a2c..0b5eef84d 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -39,6 +39,11 @@ class CleanupStuckedResources extends Command $servers = Server::all()->filter(function ($server) { return $server->isFunctional(); }); + if (isCloud()) { + $servers = $servers->filter(function ($server) { + return data_get($server->team->subscription, 'stripe_invoice_paid', false) === true; + }); + } foreach ($servers as $server) { CleanupHelperContainersJob::dispatch($server); } diff --git a/app/Jobs/CleanupHelperContainersJob.php b/app/Jobs/CleanupHelperContainersJob.php index f185ab781..0e1fcb4d7 100644 --- a/app/Jobs/CleanupHelperContainersJob.php +++ b/app/Jobs/CleanupHelperContainersJob.php @@ -20,11 +20,11 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S public function handle(): void { try { - $containers = instant_remote_process(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false); + $containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false); $containerIds = collect(json_decode($containers))->pluck('ID'); if ($containerIds->count() > 0) { foreach ($containerIds as $containerId) { - instant_remote_process(['docker container rm -f '.$containerId], $this->server, false); + instant_remote_process_with_timeout(['docker container rm -f '.$containerId], $this->server, false); } } } catch (\Throwable $e) { From 9048efaf74c35ff722e873ad7a5306b94ecfb1ae Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 13:03:47 +0100 Subject: [PATCH 07/32] fix: add timeout to sentinel process checks for improved reliability --- app/Jobs/CheckAndStartSentinelJob.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Jobs/CheckAndStartSentinelJob.php b/app/Jobs/CheckAndStartSentinelJob.php index 788db89ea..304b2a15c 100644 --- a/app/Jobs/CheckAndStartSentinelJob.php +++ b/app/Jobs/CheckAndStartSentinelJob.php @@ -24,7 +24,7 @@ class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue $latestVersion = get_latest_sentinel_version(); // Check if sentinel is running - $sentinelFound = instant_remote_process(['docker inspect coolify-sentinel'], $this->server, false); + $sentinelFound = instant_remote_process_with_timeout(['docker inspect coolify-sentinel'], $this->server, false, 10); $sentinelFoundJson = json_decode($sentinelFound, true); $sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited'); if ($sentinelStatus !== 'running') { @@ -33,7 +33,7 @@ class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue return; } // If sentinel is running, check if it needs an update - $runningVersion = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false); + $runningVersion = instant_remote_process_with_timeout(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false); if (empty($runningVersion)) { $runningVersion = '0.0.0'; } From cc61f48f274cfbeaafa6c8159aa53b9355a2c652 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 14:23:33 +0100 Subject: [PATCH 08/32] refactor: remove unused tags method from ApplicationDeploymentJob --- app/Jobs/ApplicationDeploymentJob.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 6b677fa0e..bd9149040 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -233,11 +233,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } } - public function tags(): array - { - return ['server:'.gethostname()]; - } - public function handle(): void { $this->application_deployment_queue->update([ From 5fda3842033965fa24950102100c1919851befad Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 14:23:49 +0100 Subject: [PATCH 09/32] update packages --- composer.json | 4 +- composer.lock | 629 +++++++++++++++++++++++--------------------------- 2 files changed, 293 insertions(+), 340 deletions(-) diff --git a/composer.json b/composer.json index b8dc354c3..3a06a5beb 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "league/flysystem-sftp-v3": "^3.0", "livewire/livewire": "^3.5", "log1x/laravel-webfonts": "^1.0", - "lorisleiva/laravel-actions": "^2.7", + "lorisleiva/laravel-actions": "^2.8", "nubs/random-name-generator": "^2.2", "phpseclib/phpseclib": "^3.0", "pion/laravel-chunk-upload": "^1.5", @@ -121,4 +121,4 @@ "@php artisan key:generate --ansi" ] } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 3fbe72afb..7bb7f24e0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "871067cb42e6347ca53ff36e81ac5079", + "content-hash": "85a775fb1a4b9ea329d8d893f43621c2", "packages": [ { "name": "3sidedcube/laravel-redoc", @@ -287,16 +287,16 @@ }, { "name": "amphp/dns", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/amphp/dns.git", - "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7" + "reference": "166c43737cef1b77782c648a9d9ed11ee0c9859f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/dns/zipball/758266b0ea7470e2e42cd098493bc6d6c7100cf7", - "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7", + "url": "https://api.github.com/repos/amphp/dns/zipball/166c43737cef1b77782c648a9d9ed11ee0c9859f", + "reference": "166c43737cef1b77782c648a9d9ed11ee0c9859f", "shasum": "" }, "require": { @@ -304,9 +304,10 @@ "amphp/byte-stream": "^2", "amphp/cache": "^2", "amphp/parser": "^1", - "amphp/windows-registry": "^1.0.1", + "amphp/process": "^2", "daverandom/libdns": "^2.0.2", "ext-filter": "*", + "ext-json": "*", "php": ">=8.1", "revolt/event-loop": "^1 || ^0.2" }, @@ -363,7 +364,7 @@ ], "support": { "issues": "https://github.com/amphp/dns/issues", - "source": "https://github.com/amphp/dns/tree/v2.2.0" + "source": "https://github.com/amphp/dns/tree/v2.3.0" }, "funding": [ { @@ -371,20 +372,20 @@ "type": "github" } ], - "time": "2024-06-02T19:54:12+00:00" + "time": "2024-12-21T01:15:34+00:00" }, { "name": "amphp/parallel", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "9777db1460d1535bc2a843840684fb1205225b87" + "reference": "5113111de02796a782f5d90767455e7391cca190" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/9777db1460d1535bc2a843840684fb1205225b87", - "reference": "9777db1460d1535bc2a843840684fb1205225b87", + "url": "https://api.github.com/repos/amphp/parallel/zipball/5113111de02796a782f5d90767455e7391cca190", + "reference": "5113111de02796a782f5d90767455e7391cca190", "shasum": "" }, "require": { @@ -447,7 +448,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.3.0" + "source": "https://github.com/amphp/parallel/tree/v2.3.1" }, "funding": [ { @@ -455,7 +456,7 @@ "type": "github" } ], - "time": "2024-09-14T19:16:14+00:00" + "time": "2024-12-21T01:56:09+00:00" }, { "name": "amphp/parser", @@ -871,58 +872,6 @@ ], "time": "2024-08-03T19:31:26+00:00" }, - { - "name": "amphp/windows-registry", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/amphp/windows-registry.git", - "reference": "0d569e8f256cca974e3842b6e78b4e434bf98306" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/windows-registry/zipball/0d569e8f256cca974e3842b6e78b4e434bf98306", - "reference": "0d569e8f256cca974e3842b6e78b4e434bf98306", - "shasum": "" - }, - "require": { - "amphp/byte-stream": "^2", - "amphp/process": "^2", - "php": ">=8.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "^2", - "psalm/phar": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Amp\\WindowsRegistry\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "Windows Registry Reader.", - "support": { - "issues": "https://github.com/amphp/windows-registry/issues", - "source": "https://github.com/amphp/windows-registry/tree/v1.0.1" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-01-30T23:01:51+00:00" - }, { "name": "aws/aws-crt-php", "version": "v1.2.7", @@ -979,16 +928,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.334.3", + "version": "3.336.12", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04" + "reference": "a173ab3af8d9186d266e4937d8254597f36a9e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04", - "reference": "6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a173ab3af8d9186d266e4937d8254597f36a9e15", + "reference": "a173ab3af8d9186d266e4937d8254597f36a9e15", "shasum": "" }, "require": { @@ -1071,9 +1020,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.334.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.336.12" }, - "time": "2024-12-10T19:41:55+00:00" + "time": "2025-01-09T19:04:34+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1869,16 +1818,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.2", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" + "reference": "b115554301161fa21467629f1e1391c1936de517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", - "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", + "reference": "b115554301161fa21467629f1e1391c1936de517", "shasum": "" }, "require": { @@ -1924,7 +1873,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" }, "funding": [ { @@ -1932,7 +1881,7 @@ "type": "github" } ], - "time": "2023-10-06T06:47:41+00:00" + "time": "2024-12-27T00:36:43+00:00" }, { "name": "firebase/php-jwt", @@ -2725,16 +2674,16 @@ }, { "name": "laravel/framework", - "version": "v11.35.0", + "version": "v11.37.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f1a7aaa3c1235b7a95ccaa58db90e0cd9d8c3fcc" + "reference": "6cb103d2024b087eae207654b3f4b26646119ba5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f1a7aaa3c1235b7a95ccaa58db90e0cd9d8c3fcc", - "reference": "f1a7aaa3c1235b7a95ccaa58db90e0cd9d8c3fcc", + "url": "https://api.github.com/repos/laravel/framework/zipball/6cb103d2024b087eae207654b3f4b26646119ba5", + "reference": "6cb103d2024b087eae207654b3f4b26646119ba5", "shasum": "" }, "require": { @@ -2755,7 +2704,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.2.1", + "league/commonmark": "^2.6", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", @@ -2770,7 +2719,7 @@ "symfony/console": "^7.0.3", "symfony/error-handler": "^7.0.3", "symfony/finder": "^7.0.3", - "symfony/http-foundation": "^7.0.3", + "symfony/http-foundation": "^7.2.0", "symfony/http-kernel": "^7.0.3", "symfony/mailer": "^7.0.3", "symfony/mime": "^7.0.3", @@ -2784,7 +2733,6 @@ "voku/portable-ascii": "^2.0.2" }, "conflict": { - "mockery/mockery": "1.6.8", "tightenco/collect": "<5.5.33" }, "provide": { @@ -2936,20 +2884,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-12-10T16:09:29+00:00" + "time": "2025-01-02T20:10:21+00:00" }, { "name": "laravel/horizon", - "version": "v5.30.0", + "version": "v5.30.1", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "37d1f29daa7500fcd170d5c45b98b592fcaab95a" + "reference": "77177646679ef2f2acf71d4d4b16036d18002040" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/37d1f29daa7500fcd170d5c45b98b592fcaab95a", - "reference": "37d1f29daa7500fcd170d5c45b98b592fcaab95a", + "url": "https://api.github.com/repos/laravel/horizon/zipball/77177646679ef2f2acf71d4d4b16036d18002040", + "reference": "77177646679ef2f2acf71d4d4b16036d18002040", "shasum": "" }, "require": { @@ -3014,9 +2962,9 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.30.0" + "source": "https://github.com/laravel/horizon/tree/v5.30.1" }, - "time": "2024-12-06T18:58:00+00:00" + "time": "2024-12-13T14:08:51+00:00" }, { "name": "laravel/pail", @@ -3054,13 +3002,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - }, "laravel": { "providers": [ "Laravel\\Pail\\PailServiceProvider" ] + }, + "branch-alias": { + "dev-main": "1.x-dev" } }, "autoload": { @@ -3157,16 +3105,16 @@ }, { "name": "laravel/sanctum", - "version": "v4.0.6", + "version": "v4.0.7", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "9e069e36d90b1e1f41886efa0fe9800a6b354694" + "reference": "698064236a46df016e64a7eb059b1414e0b281df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/9e069e36d90b1e1f41886efa0fe9800a6b354694", - "reference": "9e069e36d90b1e1f41886efa0fe9800a6b354694", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/698064236a46df016e64a7eb059b1414e0b281df", + "reference": "698064236a46df016e64a7eb059b1414e0b281df", "shasum": "" }, "require": { @@ -3217,20 +3165,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2024-11-26T21:18:33+00:00" + "time": "2024-12-11T16:40:21+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.0", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "0d8d3d8086984996df86596a86dea60398093a81" + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/0d8d3d8086984996df86596a86dea60398093a81", - "reference": "0d8d3d8086984996df86596a86dea60398093a81", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/613b2d4998f85564d40497e05e89cb6d9bd1cbe8", + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8", "shasum": "" }, "require": { @@ -3278,20 +3226,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-11-19T01:38:44+00:00" + "time": "2024-12-16T15:26:28+00:00" }, { "name": "laravel/socialite", - "version": "v5.16.0", + "version": "v5.16.1", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf" + "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", - "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", + "url": "https://api.github.com/repos/laravel/socialite/zipball/4e5be83c0b3ecf81b2ffa47092e917d1f79dce71", + "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71", "shasum": "" }, "require": { @@ -3301,7 +3249,7 @@ "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "league/oauth1-client": "^1.10.1", + "league/oauth1-client": "^1.11", "php": "^7.2|^8.0", "phpseclib/phpseclib": "^3.0" }, @@ -3350,7 +3298,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-09-03T09:46:57+00:00" + "time": "2024-12-11T16:43:51+00:00" }, { "name": "laravel/tinker", @@ -3446,13 +3394,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - }, "laravel": { "providers": [ "Laravel\\Ui\\UiServiceProvider" ] + }, + "branch-alias": { + "dev-master": "4.x-dev" } }, "autoload": { @@ -3556,16 +3504,16 @@ }, { "name": "league/commonmark", - "version": "2.6.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d150f911e0079e90ae3c106734c93137c184f932" + "reference": "d990688c91cedfb69753ffc2512727ec646df2ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932", - "reference": "d150f911e0079e90ae3c106734c93137c184f932", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad", + "reference": "d990688c91cedfb69753ffc2512727ec646df2ad", "shasum": "" }, "require": { @@ -3659,7 +3607,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T15:34:16+00:00" + "time": "2024-12-29T14:10:59+00:00" }, { "name": "league/config", @@ -4287,16 +4235,16 @@ }, { "name": "livewire/livewire", - "version": "v3.5.17", + "version": "v3.5.18", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "7bbf80d93db9b866776bf957ca6229364bca8d87" + "reference": "62f0fa6b340a467c25baa590a567d9a134b357da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/7bbf80d93db9b866776bf957ca6229364bca8d87", - "reference": "7bbf80d93db9b866776bf957ca6229364bca8d87", + "url": "https://api.github.com/repos/livewire/livewire/zipball/62f0fa6b340a467c25baa590a567d9a134b357da", + "reference": "62f0fa6b340a467c25baa590a567d9a134b357da", "shasum": "" }, "require": { @@ -4351,7 +4299,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.17" + "source": "https://github.com/livewire/livewire/tree/v3.5.18" }, "funding": [ { @@ -4359,7 +4307,7 @@ "type": "github" } ], - "time": "2024-12-06T13:41:21+00:00" + "time": "2024-12-23T15:05:02+00:00" }, { "name": "log1x/laravel-webfonts", @@ -4425,16 +4373,16 @@ }, { "name": "lorisleiva/laravel-actions", - "version": "v2.8.4", + "version": "v2.8.5", "source": { "type": "git", "url": "https://github.com/lorisleiva/laravel-actions.git", - "reference": "5a168bfdd3b75dd6ff259019d4aeef784bbd5403" + "reference": "ae6f5e8dc1f450a0879f73059242e5834b2dbdec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/5a168bfdd3b75dd6ff259019d4aeef784bbd5403", - "reference": "5a168bfdd3b75dd6ff259019d4aeef784bbd5403", + "url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/ae6f5e8dc1f450a0879f73059242e5834b2dbdec", + "reference": "ae6f5e8dc1f450a0879f73059242e5834b2dbdec", "shasum": "" }, "require": { @@ -4489,7 +4437,7 @@ ], "support": { "issues": "https://github.com/lorisleiva/laravel-actions/issues", - "source": "https://github.com/lorisleiva/laravel-actions/tree/v2.8.4" + "source": "https://github.com/lorisleiva/laravel-actions/tree/v2.8.5" }, "funding": [ { @@ -4497,7 +4445,7 @@ "type": "github" } ], - "time": "2024-09-10T09:57:29+00:00" + "time": "2024-12-19T15:58:09+00:00" }, { "name": "lorisleiva/lody", @@ -4525,12 +4473,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Lorisleiva\\Lody\\LodyServiceProvider" - ], "aliases": { "Lody": "Lorisleiva\\Lody\\Lody" - } + }, + "providers": [ + "Lorisleiva\\Lody\\LodyServiceProvider" + ] } }, "autoload": { @@ -4742,16 +4690,16 @@ }, { "name": "nesbot/carbon", - "version": "3.8.2", + "version": "3.8.4", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947", - "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", "shasum": "" }, "require": { @@ -4783,10 +4731,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -4796,6 +4740,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -4844,7 +4792,7 @@ "type": "tidelift" } ], - "time": "2024-11-07T17:46:48+00:00" + "time": "2024-12-27T09:25:35+00:00" }, { "name": "nette/schema", @@ -4996,16 +4944,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -5048,9 +4996,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "nubs/random-name-generator", @@ -5797,16 +5745,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.42", + "version": "3.0.43", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98" + "reference": "709ec107af3cb2f385b9617be72af8cf62441d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98", - "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02", + "reference": "709ec107af3cb2f385b9617be72af8cf62441d02", "shasum": "" }, "require": { @@ -5887,7 +5835,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.43" }, "funding": [ { @@ -5903,7 +5851,7 @@ "type": "tidelift" } ], - "time": "2024-09-16T03:06:04+00:00" + "time": "2024-12-14T21:12:59+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -6073,16 +6021,16 @@ }, { "name": "poliander/cron", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/poliander/cron.git", - "reference": "213c477b3d9d6fcf8f0944298f481c1649a92b3b" + "reference": "68baf899189d0a68611b9575fc62642144877d80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/poliander/cron/zipball/213c477b3d9d6fcf8f0944298f481c1649a92b3b", - "reference": "213c477b3d9d6fcf8f0944298f481c1649a92b3b", + "url": "https://api.github.com/repos/poliander/cron/zipball/68baf899189d0a68611b9575fc62642144877d80", + "reference": "68baf899189d0a68611b9575fc62642144877d80", "shasum": "" }, "require": { @@ -6111,9 +6059,9 @@ "homepage": "https://github.com/poliander/cron", "support": { "issues": "https://github.com/poliander/cron/issues", - "source": "https://github.com/poliander/cron/tree/3.2.0" + "source": "https://github.com/poliander/cron/tree/3.2.1" }, - "time": "2024-11-22T08:35:47+00:00" + "time": "2024-12-21T05:57:05+00:00" }, { "name": "pragmarx/google2fa", @@ -6757,16 +6705,16 @@ }, { "name": "pusher/pusher-php-server", - "version": "7.2.6", + "version": "7.2.7", "source": { "type": "git", "url": "https://github.com/pusher/pusher-http-php.git", - "reference": "d89e9997191d18fb0fe03a956fa3ccfe0af524ea" + "reference": "148b0b5100d000ed57195acdf548a2b1b38ee3f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/d89e9997191d18fb0fe03a956fa3ccfe0af524ea", - "reference": "d89e9997191d18fb0fe03a956fa3ccfe0af524ea", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/148b0b5100d000ed57195acdf548a2b1b38ee3f7", + "reference": "148b0b5100d000ed57195acdf548a2b1b38ee3f7", "shasum": "" }, "require": { @@ -6812,9 +6760,9 @@ ], "support": { "issues": "https://github.com/pusher/pusher-http-php/issues", - "source": "https://github.com/pusher/pusher-http-php/tree/7.2.6" + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.7" }, - "time": "2024-10-18T12:04:31+00:00" + "time": "2025-01-06T10:56:20+00:00" }, { "name": "ralouphie/getallheaders", @@ -7070,13 +7018,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - }, "laravel": { "providers": [ "Resend\\Laravel\\ResendServiceProvider" ] + }, + "branch-alias": { + "dev-main": "1.x-dev" } }, "autoload": { @@ -7330,16 +7278,16 @@ }, { "name": "sentry/sentry-laravel", - "version": "4.10.1", + "version": "4.10.2", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "1c007fb111ff00f02efba2aca022310dae412c3a" + "reference": "0e2e5bc4311da51349487afcf67b8fca937f6d94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/1c007fb111ff00f02efba2aca022310dae412c3a", - "reference": "1c007fb111ff00f02efba2aca022310dae412c3a", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/0e2e5bc4311da51349487afcf67b8fca937f6d94", + "reference": "0e2e5bc4311da51349487afcf67b8fca937f6d94", "shasum": "" }, "require": { @@ -7403,7 +7351,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.10.1" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.10.2" }, "funding": [ { @@ -7415,7 +7363,7 @@ "type": "custom" } ], - "time": "2024-11-24T11:02:20+00:00" + "time": "2024-12-17T11:38:58+00:00" }, { "name": "socialiteproviders/authentik", @@ -7469,22 +7417,22 @@ }, { "name": "socialiteproviders/manager", - "version": "v4.7.0", + "version": "v4.8.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "ab0691b82cec77efd90154c78f1854903455c82f" + "reference": "e93acc38f8464cc775a2b8bf09df311d1fdfefcb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/ab0691b82cec77efd90154c78f1854903455c82f", - "reference": "ab0691b82cec77efd90154c78f1854903455c82f", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/e93acc38f8464cc775a2b8bf09df311d1fdfefcb", + "reference": "e93acc38f8464cc775a2b8bf09df311d1fdfefcb", "shasum": "" }, "require": { "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", "laravel/socialite": "^5.5", - "php": "^8.0" + "php": "^8.1" }, "require-dev": { "mockery/mockery": "^1.2", @@ -7539,7 +7487,7 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2024-11-10T01:56:18+00:00" + "time": "2025-01-03T09:40:37+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -7832,16 +7780,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.17.0", + "version": "1.18.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85" + "reference": "8332205b90d17164913244f4a8e13ab7e6761d29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/9ab30fd24f677e5aa370ea4cf6b41c517d16cf85", - "reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/8332205b90d17164913244f4a8e13ab7e6761d29", + "reference": "8332205b90d17164913244f4a8e13ab7e6761d29", "shasum": "" }, "require": { @@ -7880,7 +7828,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.17.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.18.0" }, "funding": [ { @@ -7888,7 +7836,7 @@ "type": "github" } ], - "time": "2024-12-09T16:29:14+00:00" + "time": "2024-12-30T13:13:39+00:00" }, { "name": "spatie/laravel-ray", @@ -8106,16 +8054,16 @@ }, { "name": "spatie/php-structure-discoverer", - "version": "2.2.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/spatie/php-structure-discoverer.git", - "reference": "271542206169d95dd2ffe346ddf11f37672553a2" + "reference": "e2b39ba0baaf05d1300c5467e7ee8a6439324827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/271542206169d95dd2ffe346ddf11f37672553a2", - "reference": "271542206169d95dd2ffe346ddf11f37672553a2", + "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/e2b39ba0baaf05d1300c5467e7ee8a6439324827", + "reference": "e2b39ba0baaf05d1300c5467e7ee8a6439324827", "shasum": "" }, "require": { @@ -8174,7 +8122,7 @@ ], "support": { "issues": "https://github.com/spatie/php-structure-discoverer/issues", - "source": "https://github.com/spatie/php-structure-discoverer/tree/2.2.0" + "source": "https://github.com/spatie/php-structure-discoverer/tree/2.2.1" }, "funding": [ { @@ -8182,7 +8130,7 @@ "type": "github" } ], - "time": "2024-08-29T10:43:45+00:00" + "time": "2024-12-16T13:29:18+00:00" }, { "name": "spatie/ray", @@ -8333,16 +8281,16 @@ }, { "name": "stripe/stripe-php", - "version": "v16.3.0", + "version": "v16.4.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "48af6bc64ca8157b3fdce100e856069963bac466" + "reference": "4aa86099f888db9368f5f778f29feb14e6294dfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/48af6bc64ca8157b3fdce100e856069963bac466", - "reference": "48af6bc64ca8157b3fdce100e856069963bac466", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/4aa86099f888db9368f5f778f29feb14e6294dfb", + "reference": "4aa86099f888db9368f5f778f29feb14e6294dfb", "shasum": "" }, "require": { @@ -8386,9 +8334,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v16.3.0" + "source": "https://github.com/stripe/stripe-php/tree/v16.4.0" }, - "time": "2024-11-20T23:30:16+00:00" + "time": "2024-12-18T23:42:15+00:00" }, { "name": "symfony/clock", @@ -8641,12 +8589,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -8864,12 +8812,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -8922,16 +8870,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { @@ -8966,7 +8914,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.0" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -8982,20 +8930,20 @@ "type": "tidelift" } ], - "time": "2024-10-23T06:56:12+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744" + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e88a66c3997859532bc2ddd6dd8f35aba2711744", - "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62d1a43796ca3fea3f83a8470dfe63a4af3bc588", + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588", "shasum": "" }, "require": { @@ -9044,7 +8992,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.2" }, "funding": [ { @@ -9060,20 +9008,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T18:58:46+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.1", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97" + "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d8ae58eecae44c8e66833e76cc50a4ad3c002d97", - "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3c432966bd8c7ec7429663105f5a02d7e75b4306", + "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306", "shasum": "" }, "require": { @@ -9158,7 +9106,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.1" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.2" }, "funding": [ { @@ -9174,7 +9122,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T12:09:10+00:00" + "time": "2024-12-31T14:59:40+00:00" }, { "name": "symfony/mailer", @@ -9512,8 +9460,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10372,12 +10320,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -10433,16 +10381,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "696f418b0d722a4225e1c3d95489d262971ca924" + "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/696f418b0d722a4225e1c3d95489d262971ca924", - "reference": "696f418b0d722a4225e1c3d95489d262971ca924", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df", + "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df", "shasum": "" }, "require": { @@ -10475,7 +10423,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.2.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.2.2" }, "funding": [ { @@ -10491,7 +10439,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-12-18T14:28:33+00:00" }, { "name": "symfony/string", @@ -10582,16 +10530,16 @@ }, { "name": "symfony/translation", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5" + "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/dc89e16b44048ceecc879054e5b7f38326ab6cc5", - "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5", + "url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", + "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", "shasum": "" }, "require": { @@ -10657,7 +10605,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.0" + "source": "https://github.com/symfony/translation/tree/v7.2.2" }, "funding": [ { @@ -10673,7 +10621,7 @@ "type": "tidelift" } ], - "time": "2024-11-12T20:47:56+00:00" + "time": "2024-12-07T08:18:10+00:00" }, { "name": "symfony/translation-contracts", @@ -10694,12 +10642,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -10984,31 +10932,33 @@ }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "v2.2.7", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb" + "reference": "0d72ac1c00084279c1816675284073c5a337c20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/83ee6f38df0a63106a9e4536e3060458b74ccedb", - "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", + "reference": "0d72ac1c00084279c1816675284073c5a337c20d", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -11031,9 +10981,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.2.7" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" }, - "time": "2023-12-08T13:03:43+00:00" + "time": "2024-12-21T16:25:41+00:00" }, { "name": "visus/cuid2", @@ -11711,16 +11661,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.14.9", + "version": "v3.14.10", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "2e805a6bd4e1aa83774316bb062703c65d0691ef" + "reference": "56b9bd235e3fe62e250124804009ce5bab97cc63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/2e805a6bd4e1aa83774316bb062703c65d0691ef", - "reference": "2e805a6bd4e1aa83774316bb062703c65d0691ef", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/56b9bd235e3fe62e250124804009ce5bab97cc63", + "reference": "56b9bd235e3fe62e250124804009ce5bab97cc63", "shasum": "" }, "require": { @@ -11779,7 +11729,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.9" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.10" }, "funding": [ { @@ -11791,20 +11741,20 @@ "type": "github" } ], - "time": "2024-11-25T14:51:20+00:00" + "time": "2024-12-23T10:10:42+00:00" }, { "name": "brianium/paratest", - "version": "v7.6.3", + "version": "v7.7.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "ae3c9f1aeda7daa374c904b35ece8f574f56d176" + "reference": "4fb3f73bc5a4c3146bac2850af7dc72435a32daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/ae3c9f1aeda7daa374c904b35ece8f574f56d176", - "reference": "ae3c9f1aeda7daa374c904b35ece8f574f56d176", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4fb3f73bc5a4c3146bac2850af7dc72435a32daf", + "reference": "4fb3f73bc5a4c3146bac2850af7dc72435a32daf", "shasum": "" }, "require": { @@ -11815,12 +11765,12 @@ "fidry/cpu-core-counter": "^1.2.0", "jean85/pretty-package-versions": "^2.1.0", "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-code-coverage": "^11.0.8", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.5.0", + "phpunit/phpunit": "^11.5.1", "sebastian/environment": "^7.2.0", - "symfony/console": "^6.4.14 || ^7.2.0", + "symfony/console": "^6.4.14 || ^7.2.1", "symfony/process": "^6.4.14 || ^7.2.0" }, "require-dev": { @@ -11872,7 +11822,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.6.3" + "source": "https://github.com/paratestphp/paratest/tree/v7.7.0" }, "funding": [ { @@ -11884,7 +11834,7 @@ "type": "paypal" } ], - "time": "2024-12-10T13:59:28+00:00" + "time": "2024-12-11T14:50:44+00:00" }, { "name": "fakerphp/faker", @@ -12206,16 +12156,16 @@ }, { "name": "laravel/pint", - "version": "v1.18.3", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "cef51821608239040ab841ad6e1c6ae502ae3026" + "reference": "8169513746e1bac70c85d6ea1524d9225d4886f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026", - "reference": "cef51821608239040ab841ad6e1c6ae502ae3026", + "url": "https://api.github.com/repos/laravel/pint/zipball/8169513746e1bac70c85d6ea1524d9225d4886f0", + "reference": "8169513746e1bac70c85d6ea1524d9225d4886f0", "shasum": "" }, "require": { @@ -12226,10 +12176,10 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.65.0", - "illuminate/view": "^10.48.24", - "larastan/larastan": "^2.9.11", - "laravel-zero/framework": "^10.4.0", + "friendsofphp/php-cs-fixer": "^3.66.0", + "illuminate/view": "^10.48.25", + "larastan/larastan": "^2.9.12", + "laravel-zero/framework": "^10.48.25", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.17.0", "pestphp/pest": "^2.36.0" @@ -12268,7 +12218,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-11-26T15:34:00+00:00" + "time": "2024-12-30T16:20:10+00:00" }, { "name": "laravel/telescope", @@ -12341,16 +12291,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.23.4", + "version": "v1.23.5", "source": { "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca" + "url": "https://github.com/php-debugbar/php-debugbar.git", + "reference": "eeabd61a1f19ba5dcd5ac4585a477130ee03ce25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca", - "reference": "0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/eeabd61a1f19ba5dcd5ac4585a477130ee03ce25", + "reference": "eeabd61a1f19ba5dcd5ac4585a477130ee03ce25", "shasum": "" }, "require": { @@ -12402,10 +12352,10 @@ "debugbar" ], "support": { - "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.4" + "issues": "https://github.com/php-debugbar/php-debugbar/issues", + "source": "https://github.com/php-debugbar/php-debugbar/tree/v1.23.5" }, - "time": "2024-12-05T10:36:51+00:00" + "time": "2024-12-15T19:20:42+00:00" }, { "name": "mockery/mockery", @@ -12649,31 +12599,31 @@ }, { "name": "pestphp/pest", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "9688b83a3d7d0acdda21c01b8aeb933ec9fcd556" + "reference": "bf3178473dcaa53b0458f21dfdb271306ea62512" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/9688b83a3d7d0acdda21c01b8aeb933ec9fcd556", - "reference": "9688b83a3d7d0acdda21c01b8aeb933ec9fcd556", + "url": "https://api.github.com/repos/pestphp/pest/zipball/bf3178473dcaa53b0458f21dfdb271306ea62512", + "reference": "bf3178473dcaa53b0458f21dfdb271306ea62512", "shasum": "" }, "require": { - "brianium/paratest": "^7.6.2", + "brianium/paratest": "^7.7.0", "nunomaduro/collision": "^8.5.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.5.0" + "phpunit/phpunit": "^11.5.1" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.0", + "phpunit/phpunit": ">11.5.1", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, @@ -12745,7 +12695,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.7.0" + "source": "https://github.com/pestphp/pest/tree/v3.7.1" }, "funding": [ { @@ -12757,7 +12707,7 @@ "type": "github" } ], - "time": "2024-12-10T11:54:49+00:00" + "time": "2024-12-12T11:52:01+00:00" }, { "name": "pestphp/pest-plugin", @@ -13157,16 +13107,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.12", + "version": "1.12.15", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" + "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1", + "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1", "shasum": "" }, "require": { @@ -13211,7 +13161,7 @@ "type": "github" } ], - "time": "2024-11-28T22:13:23+00:00" + "time": "2025-01-05T16:40:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -13538,16 +13488,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.0", + "version": "11.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7" + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0569902506a6c0878930b87ea79ec3b50ea563f7", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a", + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a", "shasum": "" }, "require": { @@ -13619,7 +13569,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.0" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1" }, "funding": [ { @@ -13635,7 +13585,7 @@ "type": "tidelift" } ], - "time": "2024-12-06T05:57:38+00:00" + "time": "2024-12-11T10:52:48+00:00" }, { "name": "sebastian/cli-parser", @@ -13696,23 +13646,23 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { @@ -13741,7 +13691,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" }, "funding": [ { @@ -13749,7 +13699,7 @@ "type": "github" } ], - "time": "2024-07-03T04:44:28+00:00" + "time": "2024-12-12T09:59:06+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -13809,16 +13759,16 @@ }, { "name": "sebastian/comparator", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" + "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", - "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115", + "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115", "shasum": "" }, "require": { @@ -13831,6 +13781,9 @@ "require-dev": { "phpunit/phpunit": "^11.4" }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, "type": "library", "extra": { "branch-alias": { @@ -13874,7 +13827,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0" }, "funding": [ { @@ -13882,7 +13835,7 @@ "type": "github" } ], - "time": "2024-10-31T05:30:08+00:00" + "time": "2025-01-06T10:28:19+00:00" }, { "name": "sebastian/complexity", @@ -14976,23 +14929,23 @@ }, { "name": "symfony/http-client", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "955e43336aff03df1e8a8e17daefabb0127a313b" + "reference": "339ba21476eb184290361542f732ad12c97591ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/955e43336aff03df1e8a8e17daefabb0127a313b", - "reference": "955e43336aff03df1e8a8e17daefabb0127a313b", + "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", + "reference": "339ba21476eb184290361542f732ad12c97591ec", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.3|^3.5.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -15051,7 +15004,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.0" + "source": "https://github.com/symfony/http-client/tree/v7.2.2" }, "funding": [ { @@ -15067,20 +15020,20 @@ "type": "tidelift" } ], - "time": "2024-11-29T08:22:02+00:00" + "time": "2024-12-30T18:35:15+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.1", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -15129,7 +15082,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -15145,7 +15098,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T12:02:18+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", From b8f1ded9f872f490422609f011351c6c687f2c2f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 14:28:46 +0100 Subject: [PATCH 10/32] horizon manage command --- app/Console/Commands/HorizonManage.php | 127 ++++++++++++++++++ .../CustomJobRepositoryInterface.php | 24 ++++ app/Providers/HorizonServiceProvider.php | 45 +++---- app/Repositories/CustomJobRepository.php | 71 ++++++++++ 4 files changed, 238 insertions(+), 29 deletions(-) create mode 100644 app/Console/Commands/HorizonManage.php create mode 100644 app/Contracts/CustomJobRepositoryInterface.php create mode 100644 app/Repositories/CustomJobRepository.php diff --git a/app/Console/Commands/HorizonManage.php b/app/Console/Commands/HorizonManage.php new file mode 100644 index 000000000..611b516b0 --- /dev/null +++ b/app/Console/Commands/HorizonManage.php @@ -0,0 +1,127 @@ + 'Pending Jobs', + 'running' => 'Running Jobs', + 'workers' => 'Workers', + 'failed' => 'Failed Jobs', + 'failed-delete' => 'Failed Jobs - Delete', + ] + ); + + if ($action === 'pending') { + $pendingJobs = app(JobRepository::class)->getPending(); + $pendingJobsTable = []; + if (count($pendingJobs) === 0) { + $this->info('No pending jobs found.'); + + return; + } + foreach ($pendingJobs as $pendingJob) { + $pendingJobsTable[] = [ + 'id' => $pendingJob->id, + 'name' => $pendingJob->name, + 'status' => $pendingJob->status, + 'reserved_at' => $pendingJob->reserved_at ? now()->parse($pendingJob->reserved_at)->format('Y-m-d H:i:s') : null, + ]; + } + table($pendingJobsTable); + } + + if ($action === 'failed') { + $failedJobs = app(JobRepository::class)->getFailed(); + $failedJobsTable = []; + if (count($failedJobs) === 0) { + $this->info('No failed jobs found.'); + + return; + } + foreach ($failedJobs as $failedJob) { + $failedJobsTable[] = [ + 'id' => $failedJob->id, + 'name' => $failedJob->name, + 'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null, + ]; + } + table($failedJobsTable); + } + + if ($action === 'failed-delete') { + $failedJobs = app(JobRepository::class)->getFailed(); + $failedJobsTable = []; + foreach ($failedJobs as $failedJob) { + $failedJobsTable[] = [ + 'id' => $failedJob->id, + 'name' => $failedJob->name, + 'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null, + ]; + } + app(MetricsRepository::class)->clear(); + if (count($failedJobsTable) === 0) { + $this->info('No failed jobs found.'); + + return; + } + $jobIds = multiselect( + label: 'Which job to delete?', + options: collect($failedJobsTable)->mapWithKeys(fn ($job) => [$job['id'] => $job['id'].' - '.$job['name']])->toArray(), + ); + foreach ($jobIds as $jobId) { + Artisan::queue('horizon:forget', ['id' => $jobId]); + } + } + + if ($action === 'running') { + $redisJobRepository = app(CustomJobRepository::class); + $runningJobs = $redisJobRepository->getReservedJobs(); + $runningJobsTable = []; + if (count($runningJobs) === 0) { + $this->info('No running jobs found.'); + + return; + } + dump($runningJobs); + foreach ($runningJobs as $runningJob) { + $runningJobsTable[] = [ + 'id' => $runningJob->id, + 'name' => $runningJob->name, + ]; + } + table($runningJobsTable); + } + + if ($action === 'workers') { + $redisJobRepository = app(CustomJobRepository::class); + $workers = $redisJobRepository->getHorizonWorkers(); + $workersTable = []; + foreach ($workers as $worker) { + $workersTable[] = [ + 'name' => $worker->name, + ]; + } + table($workersTable); + } + } +} diff --git a/app/Contracts/CustomJobRepositoryInterface.php b/app/Contracts/CustomJobRepositoryInterface.php new file mode 100644 index 000000000..689b1897f --- /dev/null +++ b/app/Contracts/CustomJobRepositoryInterface.php @@ -0,0 +1,24 @@ +app->singleton(JobRepository::class, CustomJobRepository::class); + $this->app->singleton(CustomJobRepositoryInterface::class, CustomJobRepository::class); + } + + /** + * Bootstrap services. */ public function boot(): void { - parent::boot(); - - // Horizon::routeSmsNotificationsTo('15556667777'); - // Horizon::routeMailNotificationsTo('example@example.com'); - // Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel'); - - Horizon::night(); - } - - /** - * Register the Horizon gate. - * - * This gate determines who can access Horizon in non-local environments. - */ - protected function gate(): void - { - Gate::define('viewHorizon', function ($user) { - $root_user = User::find(0); - - return in_array($user->email, [ - $root_user->email, - ]); - }); + // } } diff --git a/app/Repositories/CustomJobRepository.php b/app/Repositories/CustomJobRepository.php new file mode 100644 index 000000000..ef492e3ba --- /dev/null +++ b/app/Repositories/CustomJobRepository.php @@ -0,0 +1,71 @@ +all(); + } + + public function getReservedJobs(): Collection + { + return $this->getJobsByStatus('reserved'); + } + + /** + * Get all jobs with a specific status. + */ + public function getJobsByStatus(string $status, ?string $worker = null): Collection + { + $jobs = new Collection; + + $this->getRecent()->each(function ($job) use ($jobs, $status, $worker) { + if ($job->status === $status) { + if ($worker) { + dump($job); + if ($job->worker !== $worker) { + return; + } + } + $jobs->push($job); + } + }); + + return $jobs; + } + + /** + * Get the count of jobs with a specific status. + */ + public function countJobsByStatus(string $status): int + { + return $this->getJobsByStatus($status)->count(); + } + + /** + * Get jobs that have been running longer than a specified duration in seconds. + */ + public function getLongRunningJobs(int $seconds): Collection + { + $jobs = new Collection; + + $this->getRecent()->each(function ($job) use ($jobs, $seconds) { + if ($job->status === 'reserved' && + isset($job->reserved_at) && + (time() - strtotime($job->reserved_at)) > $seconds) { + $jobs->push($job); + } + }); + + return $jobs; + } +} From 765e1ea04ba7ad61f09b78f669de6decf2be56a5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 15:39:22 +0100 Subject: [PATCH 11/32] feat: add horizon server details to queue --- app/Console/Commands/HorizonManage.php | 16 ++++++++-- app/Jobs/ApplicationDeploymentJob.php | 5 +++- .../Project/Application/DeploymentNavbar.php | 1 + app/Providers/HorizonServiceProvider.php | 21 ++++++++++++- app/Repositories/CustomJobRepository.php | 10 +++++++ ...35244_add_horizon_job_details_to_queue.php | 30 +++++++++++++++++++ 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php diff --git a/app/Console/Commands/HorizonManage.php b/app/Console/Commands/HorizonManage.php index 611b516b0..a03c41a7b 100644 --- a/app/Console/Commands/HorizonManage.php +++ b/app/Console/Commands/HorizonManage.php @@ -7,6 +7,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Contracts\MetricsRepository; +use Laravel\Horizon\Repositories\RedisJobRepository; use function Laravel\Prompts\multiselect; use function Laravel\Prompts\select; @@ -14,7 +15,7 @@ use function Laravel\Prompts\table; class HorizonManage extends Command { - protected $signature = 'horizon:manage '; + protected $signature = 'horizon:manage'; protected $description = 'Manage horizon'; @@ -28,6 +29,7 @@ class HorizonManage extends Command 'workers' => 'Workers', 'failed' => 'Failed Jobs', 'failed-delete' => 'Failed Jobs - Delete', + 'purge-queues' => 'Purge Queues', ] ); @@ -102,11 +104,11 @@ class HorizonManage extends Command return; } - dump($runningJobs); foreach ($runningJobs as $runningJob) { $runningJobsTable[] = [ 'id' => $runningJob->id, 'name' => $runningJob->name, + 'reserved_at' => $runningJob->reserved_at ? now()->parse($runningJob->reserved_at)->format('Y-m-d H:i:s') : null, ]; } table($runningJobsTable); @@ -123,5 +125,15 @@ class HorizonManage extends Command } table($workersTable); } + + if ($action === 'purge-queues') { + $getQueues = app(CustomJobRepository::class)->getQueues(); + $queueName = select( + label: 'Which queue to purge?', + options: $getQueues, + ); + $redisJobRepository = app(RedisJobRepository::class); + $redisJobRepository->purge($queueName); + } } } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index bd9149040..1b92f817b 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -237,6 +237,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue { $this->application_deployment_queue->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, + 'horizon_job_worker' => gethostname(), ]); if ($this->server->isFunctional() === false) { $this->application_deployment_queue->addLogEntry('Server is not functional.'); @@ -2389,10 +2390,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); queue_next_deployment($this->application); // If the deployment is cancelled by the user, don't update the status if ( - $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value + $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && + $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value ) { $this->application_deployment_queue->update([ 'status' => $status, + 'horizon_job_status' => $status, ]); } if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) { diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php index 6a6fa2482..8210a12a1 100644 --- a/app/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Livewire/Project/Application/DeploymentNavbar.php @@ -84,6 +84,7 @@ class DeploymentNavbar extends Component $this->application_deployment_queue->update([ 'current_process_id' => null, 'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value, + 'horizon_job_status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value, ]); next_after_cancel($server); } diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index a3ca3fc98..e89528d4c 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -3,9 +3,12 @@ namespace App\Providers; use App\Contracts\CustomJobRepositoryInterface; +use App\Models\ApplicationDeploymentQueue; use App\Repositories\CustomJobRepository; +use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; use Laravel\Horizon\Contracts\JobRepository; +use Laravel\Horizon\Events\JobReserved; class HorizonServiceProvider extends ServiceProvider { @@ -23,6 +26,22 @@ class HorizonServiceProvider extends ServiceProvider */ public function boot(): void { - // + // Event::listen(function (JobReserved $event) { + // $payload = $event->payload->decoded; + // $jobName = $payload['displayName']; + // if ($jobName === 'App\Jobs\ApplicationDeploymentJob') { + // $tags = $payload['tags']; + + // $deploymentQueueId = collect($tags)->first(function ($tag) { + // return str_contains($tag, 'App\Models\ApplicationDeploymentQueue'); + // }); + // $deploymentQueueId = explode(':', $deploymentQueueId)[1]; + // $deploymentQueue = ApplicationDeploymentQueue::find($deploymentQueueId); + // $deploymentQueue->update([ + // 'horizon_job_status' => 'reserved', + // ]); + // } + // }); + } } diff --git a/app/Repositories/CustomJobRepository.php b/app/Repositories/CustomJobRepository.php index ef492e3ba..3d67cdd1a 100644 --- a/app/Repositories/CustomJobRepository.php +++ b/app/Repositories/CustomJobRepository.php @@ -68,4 +68,14 @@ class CustomJobRepository extends RedisJobRepository implements CustomJobReposit return $jobs; } + + public function getQueues(): array + { + $queues = $this->connection()->keys('queue:*'); + $queues = array_map(function ($queue) { + return explode(':', $queue)[2]; + }, $queues); + + return $queues; + } } diff --git a/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php b/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php new file mode 100644 index 000000000..e6d4693a7 --- /dev/null +++ b/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php @@ -0,0 +1,30 @@ +string('horizon_job_status')->nullable(); + $table->string('horizon_job_worker')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('horizon_job_status'); + $table->dropColumn('horizon_job_worker'); + }); + } +}; From a3648901ed88f0147f01633f88bc4a4c99e87494 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 15:48:23 +0100 Subject: [PATCH 12/32] feat: enhance horizon:manage command with worker restart check - Added a new option `--can-i-restart-this-worker` to the `horizon:manage` command. - Implemented logic to check if the current worker can be restarted based on running jobs in the ApplicationDeploymentQueue. - Refactored the command to include a new method `canIRestartThisWorker` for better code organization. - Removed unnecessary dump statement from the CustomJobRepository. --- app/Console/Commands/HorizonManage.php | 28 +++++++++++++++++++++++- app/Repositories/CustomJobRepository.php | 1 - 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/HorizonManage.php b/app/Console/Commands/HorizonManage.php index a03c41a7b..de4a5a264 100644 --- a/app/Console/Commands/HorizonManage.php +++ b/app/Console/Commands/HorizonManage.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Models\ApplicationDeploymentQueue; use App\Repositories\CustomJobRepository; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; @@ -15,17 +16,21 @@ use function Laravel\Prompts\table; class HorizonManage extends Command { - protected $signature = 'horizon:manage'; + protected $signature = 'horizon:manage {--can-i-restart-this-worker}'; protected $description = 'Manage horizon'; public function handle() { + if ($this->option('can-i-restart-this-worker')) { + return $this->canIRestartThisWorker(); + } $action = select( label: 'What to do?', options: [ 'pending' => 'Pending Jobs', 'running' => 'Running Jobs', + 'can-i-restart-this-worker' => 'Can I restart this worker?', 'workers' => 'Workers', 'failed' => 'Failed Jobs', 'failed-delete' => 'Failed Jobs - Delete', @@ -33,6 +38,16 @@ class HorizonManage extends Command ] ); + if ($action === 'can-i-restart-this-worker') { + $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('horizon_job_status', 'reserved')->get(); + $count = $runningJobs->count(); + if ($count > 0) { + return false; + } + + return true; + } + if ($action === 'pending') { $pendingJobs = app(JobRepository::class)->getPending(); $pendingJobsTable = []; @@ -136,4 +151,15 @@ class HorizonManage extends Command $redisJobRepository->purge($queueName); } } + + public function canIRestartThisWorker() + { + $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('horizon_job_status', 'reserved')->get(); + $count = $runningJobs->count(); + if ($count > 0) { + return false; + } + + return true; + } } diff --git a/app/Repositories/CustomJobRepository.php b/app/Repositories/CustomJobRepository.php index 3d67cdd1a..518be844f 100644 --- a/app/Repositories/CustomJobRepository.php +++ b/app/Repositories/CustomJobRepository.php @@ -31,7 +31,6 @@ class CustomJobRepository extends RedisJobRepository implements CustomJobReposit $this->getRecent()->each(function ($job) use ($jobs, $status, $worker) { if ($job->status === $status) { if ($worker) { - dump($job); if ($job->worker !== $worker) { return; } From 02400added03b3fc7368fb0138c74da52c6801e0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 18:27:48 +0100 Subject: [PATCH 13/32] fix: horizon job checker --- app/Console/Commands/HorizonManage.php | 40 ++++++++++++++----- app/Jobs/ApplicationDeploymentJob.php | 1 - .../Project/Application/Deployment/Show.php | 18 +++++++-- .../Project/Application/DeploymentNavbar.php | 1 - app/Models/ApplicationDeploymentQueue.php | 9 +++++ app/Providers/HorizonServiceProvider.php | 32 +++++++-------- app/Repositories/CustomJobRepository.php | 5 +++ bootstrap/helpers/shared.php | 11 +++++ ...35244_add_horizon_job_details_to_queue.php | 4 +- 9 files changed, 87 insertions(+), 34 deletions(-) diff --git a/app/Console/Commands/HorizonManage.php b/app/Console/Commands/HorizonManage.php index de4a5a264..9acc16ed6 100644 --- a/app/Console/Commands/HorizonManage.php +++ b/app/Console/Commands/HorizonManage.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Enums\ApplicationDeploymentStatus; use App\Models\ApplicationDeploymentQueue; use App\Repositories\CustomJobRepository; use Illuminate\Console\Command; @@ -13,24 +14,31 @@ use Laravel\Horizon\Repositories\RedisJobRepository; use function Laravel\Prompts\multiselect; use function Laravel\Prompts\select; use function Laravel\Prompts\table; +use function Laravel\Prompts\text; class HorizonManage extends Command { - protected $signature = 'horizon:manage {--can-i-restart-this-worker}'; + protected $signature = 'horizon:manage {--can-i-restart-this-worker} {--job-status=}'; protected $description = 'Manage horizon'; public function handle() { if ($this->option('can-i-restart-this-worker')) { - return $this->canIRestartThisWorker(); + return $this->isThereAJobInProgress(); } + + if ($this->option('job-status')) { + return $this->getJobStatus($this->option('job-status')); + } + $action = select( label: 'What to do?', options: [ 'pending' => 'Pending Jobs', 'running' => 'Running Jobs', 'can-i-restart-this-worker' => 'Can I restart this worker?', + 'job-status' => 'Job Status', 'workers' => 'Workers', 'failed' => 'Failed Jobs', 'failed-delete' => 'Failed Jobs - Delete', @@ -39,13 +47,13 @@ class HorizonManage extends Command ); if ($action === 'can-i-restart-this-worker') { - $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('horizon_job_status', 'reserved')->get(); - $count = $runningJobs->count(); - if ($count > 0) { - return false; - } + $this->isThereAJobInProgress(); + } - return true; + if ($action === 'job-status') { + $jobId = text('Which job to check?'); + $jobStatus = $this->getJobStatus($jobId); + $this->info('Job Status: '.$jobStatus); } if ($action === 'pending') { @@ -152,14 +160,24 @@ class HorizonManage extends Command } } - public function canIRestartThisWorker() + public function isThereAJobInProgress() { - $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('horizon_job_status', 'reserved')->get(); + $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); $count = $runningJobs->count(); - if ($count > 0) { + if ($count === 0) { return false; } return true; } + + public function getJobStatus(string $jobId) + { + $jobFound = app(JobRepository::class)->getJobs([$jobId]); + if ($jobFound->isEmpty()) { + return 'unknown'; + } + + return $jobFound->first()->status; + } } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 1b92f817b..309201653 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2395,7 +2395,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); ) { $this->application_deployment_queue->update([ 'status' => $status, - 'horizon_job_status' => $status, ]); } if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) { diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index 04170fa28..fadc0ed5e 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -14,6 +14,8 @@ class Show extends Component public string $deployment_uuid; + public string $horizon_job_status; + public $isKeepAliveOn = true; protected $listeners = ['refreshQueue']; @@ -52,7 +54,9 @@ class Show extends Component } $this->application = $application; $this->application_deployment_queue = $application_deployment_queue; + $this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus(); $this->deployment_uuid = $deploymentUuid; + $this->isKeepAliveOn(); } public function refreshQueue() @@ -60,13 +64,21 @@ class Show extends Component $this->application_deployment_queue->refresh(); } + private function isKeepAliveOn() + { + if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') { + $this->isKeepAliveOn = false; + } else { + $this->isKeepAliveOn = true; + } + } + public function polling() { $this->dispatch('deploymentFinished'); $this->application_deployment_queue->refresh(); - if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') { - $this->isKeepAliveOn = false; - } + $this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus(); + $this->isKeepAliveOn(); } public function getLogLinesProperty() diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php index 8210a12a1..6a6fa2482 100644 --- a/app/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Livewire/Project/Application/DeploymentNavbar.php @@ -84,7 +84,6 @@ class DeploymentNavbar extends Component $this->application_deployment_queue->update([ 'current_process_id' => null, 'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value, - 'horizon_job_status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value, ]); next_after_cancel($server); } diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index c261c30c6..f6a88a692 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -70,6 +70,15 @@ class ApplicationDeploymentQueue extends Model return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null; } + public function getHorizonJobStatus() + { + if (! $this->horizon_job_id) { + return 'unknown'; + } + + return getJobStatus($this->horizon_job_id); + } + public function commitMessage() { if (empty($this->commit_message) || is_null($this->commit_message)) { diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index e89528d4c..efe17cd7e 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -26,22 +26,22 @@ class HorizonServiceProvider extends ServiceProvider */ public function boot(): void { - // Event::listen(function (JobReserved $event) { - // $payload = $event->payload->decoded; - // $jobName = $payload['displayName']; - // if ($jobName === 'App\Jobs\ApplicationDeploymentJob') { - // $tags = $payload['tags']; - - // $deploymentQueueId = collect($tags)->first(function ($tag) { - // return str_contains($tag, 'App\Models\ApplicationDeploymentQueue'); - // }); - // $deploymentQueueId = explode(':', $deploymentQueueId)[1]; - // $deploymentQueue = ApplicationDeploymentQueue::find($deploymentQueueId); - // $deploymentQueue->update([ - // 'horizon_job_status' => 'reserved', - // ]); - // } - // }); + Event::listen(function (JobReserved $event) { + $payload = $event->payload->decoded; + $jobName = $payload['displayName']; + if ($jobName === 'App\Jobs\ApplicationDeploymentJob') { + $tags = $payload['tags']; + $id = $payload['id']; + $deploymentQueueId = collect($tags)->first(function ($tag) { + return str_contains($tag, 'App\Models\ApplicationDeploymentQueue'); + }); + $deploymentQueueId = explode(':', $deploymentQueueId)[1]; + $deploymentQueue = ApplicationDeploymentQueue::find($deploymentQueueId); + $deploymentQueue->update([ + 'horizon_job_id' => $id, + ]); + } + }); } } diff --git a/app/Repositories/CustomJobRepository.php b/app/Repositories/CustomJobRepository.php index 518be844f..af299895d 100644 --- a/app/Repositories/CustomJobRepository.php +++ b/app/Repositories/CustomJobRepository.php @@ -21,6 +21,11 @@ class CustomJobRepository extends RedisJobRepository implements CustomJobReposit return $this->getJobsByStatus('reserved'); } + public function getJobStatus(string $jobId): string + { + return $this->connection()->get('job:'.$jobId.':status'); + } + /** * Get all jobs with a specific status. */ diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7aa411cd1..74220a1f9 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -41,6 +41,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Support\Stringable; +use Laravel\Horizon\Contracts\JobRepository; use Lcobucci\JWT\Encoding\ChainedFormatter; use Lcobucci\JWT\Encoding\JoseEncoder; use Lcobucci\JWT\Signer\Hmac\Sha256; @@ -4069,3 +4070,13 @@ function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp 'port' => $providerInfo['port'], ]; } + +function getJobStatus(string $jobId) +{ + $jobFound = app(JobRepository::class)->getJobs([$jobId]); + if ($jobFound->isEmpty()) { + return 'unknown'; + } + + return $jobFound->first()->status; +} diff --git a/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php b/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php index e6d4693a7..8ce5821ef 100644 --- a/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php +++ b/database/migrations/2025_01_10_135244_add_horizon_job_details_to_queue.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('application_deployment_queues', function (Blueprint $table) { - $table->string('horizon_job_status')->nullable(); + $table->string('horizon_job_id')->nullable(); $table->string('horizon_job_worker')->nullable(); }); } @@ -23,7 +23,7 @@ return new class extends Migration public function down(): void { Schema::table('application_deployment_queues', function (Blueprint $table) { - $table->dropColumn('horizon_job_status'); + $table->dropColumn('horizon_job_id'); $table->dropColumn('horizon_job_worker'); }); } From db079c0c7d4d0ef435ae917d7a7e73a836580c15 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 18:34:16 +0100 Subject: [PATCH 14/32] refactor: improve deployment status check in isAnyDeploymentInprogress function - Updated the isAnyDeploymentInprogress function to check for running jobs based on the current hostname. - Enhanced the logic to return true if any job status is 'unknown' and to provide a clearer output regarding the number of deployments in progress. - Modified the cloud_upgrade.sh script to loop until the deployment status check confirms no ongoing deployments before proceeding with the upgrade. --- bootstrap/helpers/shared.php | 23 ++++++++++++++++------- scripts/cloud_upgrade.sh | 5 +---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 74220a1f9..17e17b7fa 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1258,14 +1258,23 @@ function get_public_ips() function isAnyDeploymentInprogress() { - // Only use it in the deployment script - $count = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS, ApplicationDeploymentStatus::QUEUED])->count(); - if ($count > 0) { - echo "There are $count deployments in progress. Exiting...\n"; - exit(1); + + $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); + $horizonJobIds = []; + foreach ($runningJobs as $runningJob) { + $horizonJobId = getJobStatus($runningJob->horizon_job_id); + if ($horizonJobId === 'unknown') { + return true; + } + $horizonJobIds[] = $runningJob->horizon_job_id; } - echo "No deployments in progress.\n"; - exit(0); + if (count($horizonJobIds) === 0) { + echo "No deployments in progress.\n"; + exit(0); + } + $horizonJobIds = collect($horizonJobIds)->unique()->toArray(); + echo 'There are '.count($horizonJobIds)." deployments in progress.\n"; + exit(1); } function isBase64Encoded($strValue) diff --git a/scripts/cloud_upgrade.sh b/scripts/cloud_upgrade.sh index 8bab73b98..4cb326cbb 100644 --- a/scripts/cloud_upgrade.sh +++ b/scripts/cloud_upgrade.sh @@ -3,7 +3,4 @@ export IMAGE=$1 docker system prune -af docker compose pull read -p "Press Enter to update Coolify to $IMAGE..." last_version -docker compose logs -f +while ! (docker exec coolify sh -c "php artisan tinker --execute='isAnyDeploymentInprogress()'" && docker compose up --remove-orphans --force-recreate -d --wait && echo $IMAGE > last_version); do sleep 1; done \ No newline at end of file From 925af53d3dc9e2d5b0d45ab5c44b1bd64d35b8c8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 19:00:18 +0100 Subject: [PATCH 15/32] horizon gate --- app/Providers/HorizonServiceProvider.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index efe17cd7e..44d5593ea 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -4,8 +4,10 @@ namespace App\Providers; use App\Contracts\CustomJobRepositoryInterface; use App\Models\ApplicationDeploymentQueue; +use App\Models\User; use App\Repositories\CustomJobRepository; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobReserved; @@ -42,6 +44,16 @@ class HorizonServiceProvider extends ServiceProvider ]); } }); + } + protected function gate(): void + { + Gate::define('viewHorizon', function ($user) { + $root_user = User::find(0); + + return in_array($user->email, [ + $root_user->email, + ]); + }); } } From a0fc46dee49cc870d49ae0d81ca1d0a2b98ee220 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 19:15:57 +0100 Subject: [PATCH 16/32] refactor: extend HorizonServiceProvider from HorizonApplicationServiceProvider - Updated HorizonServiceProvider to extend from HorizonApplicationServiceProvider for improved functionality. - Added a call to the parent boot method to ensure proper initialization. - Cleaned up the gate method by removing unnecessary whitespace. --- app/Providers/HorizonServiceProvider.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index 44d5593ea..2a459e74a 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -8,11 +8,11 @@ use App\Models\User; use App\Repositories\CustomJobRepository; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Gate; -use Illuminate\Support\ServiceProvider; use Laravel\Horizon\Contracts\JobRepository; use Laravel\Horizon\Events\JobReserved; +use Laravel\Horizon\HorizonApplicationServiceProvider; -class HorizonServiceProvider extends ServiceProvider +class HorizonServiceProvider extends HorizonApplicationServiceProvider { /** * Register services. @@ -28,6 +28,7 @@ class HorizonServiceProvider extends ServiceProvider */ public function boot(): void { + parent::boot(); Event::listen(function (JobReserved $event) { $payload = $event->payload->decoded; $jobName = $payload['displayName']; From 7582d7dd8b6a3a2b72b9cbd0bbeb034cce25b366 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 19:53:13 +0100 Subject: [PATCH 17/32] refactor: streamline job status retrieval and clean up repository interface - Simplified the job status retrieval process by consolidating logic into a single `getJobStatus` function. - Removed redundant checks and methods from the `CustomJobRepositoryInterface` and `CustomJobRepository`. - Updated the `getHorizonJobStatus` method in `ApplicationDeploymentQueue` to directly utilize the new `getJobStatus` function. - Enhanced the `isThereAJobInProgress` method to improve clarity and maintainability. --- app/Console/Commands/HorizonManage.php | 7 +--- .../CustomJobRepositoryInterface.php | 5 --- app/Models/ApplicationDeploymentQueue.php | 4 -- app/Repositories/CustomJobRepository.php | 38 +------------------ bootstrap/helpers/shared.php | 9 +++-- 5 files changed, 9 insertions(+), 54 deletions(-) diff --git a/app/Console/Commands/HorizonManage.php b/app/Console/Commands/HorizonManage.php index 9acc16ed6..ca2da147c 100644 --- a/app/Console/Commands/HorizonManage.php +++ b/app/Console/Commands/HorizonManage.php @@ -173,11 +173,6 @@ class HorizonManage extends Command public function getJobStatus(string $jobId) { - $jobFound = app(JobRepository::class)->getJobs([$jobId]); - if ($jobFound->isEmpty()) { - return 'unknown'; - } - - return $jobFound->first()->status; + return getJobStatus($jobId); } } diff --git a/app/Contracts/CustomJobRepositoryInterface.php b/app/Contracts/CustomJobRepositoryInterface.php index 689b1897f..1fbd71f46 100644 --- a/app/Contracts/CustomJobRepositoryInterface.php +++ b/app/Contracts/CustomJobRepositoryInterface.php @@ -16,9 +16,4 @@ interface CustomJobRepositoryInterface extends JobRepository * Get the count of jobs with a specific status. */ public function countJobsByStatus(string $status): int; - - /** - * Get jobs that have been running longer than a specified duration in seconds. - */ - public function getLongRunningJobs(int $seconds): Collection; } diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index f6a88a692..95c78d725 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -72,10 +72,6 @@ class ApplicationDeploymentQueue extends Model public function getHorizonJobStatus() { - if (! $this->horizon_job_id) { - return 'unknown'; - } - return getJobStatus($this->horizon_job_id); } diff --git a/app/Repositories/CustomJobRepository.php b/app/Repositories/CustomJobRepository.php index af299895d..502dd252b 100644 --- a/app/Repositories/CustomJobRepository.php +++ b/app/Repositories/CustomJobRepository.php @@ -21,25 +21,12 @@ class CustomJobRepository extends RedisJobRepository implements CustomJobReposit return $this->getJobsByStatus('reserved'); } - public function getJobStatus(string $jobId): string - { - return $this->connection()->get('job:'.$jobId.':status'); - } - - /** - * Get all jobs with a specific status. - */ - public function getJobsByStatus(string $status, ?string $worker = null): Collection + public function getJobsByStatus(string $status): Collection { $jobs = new Collection; - $this->getRecent()->each(function ($job) use ($jobs, $status, $worker) { + $this->getRecent()->each(function ($job) use ($jobs, $status) { if ($job->status === $status) { - if ($worker) { - if ($job->worker !== $worker) { - return; - } - } $jobs->push($job); } }); @@ -47,32 +34,11 @@ class CustomJobRepository extends RedisJobRepository implements CustomJobReposit return $jobs; } - /** - * Get the count of jobs with a specific status. - */ public function countJobsByStatus(string $status): int { return $this->getJobsByStatus($status)->count(); } - /** - * Get jobs that have been running longer than a specified duration in seconds. - */ - public function getLongRunningJobs(int $seconds): Collection - { - $jobs = new Collection; - - $this->getRecent()->each(function ($job) use ($jobs, $seconds) { - if ($job->status === 'reserved' && - isset($job->reserved_at) && - (time() - strtotime($job->reserved_at)) > $seconds) { - $jobs->push($job); - } - }); - - return $jobs; - } - public function getQueues(): array { $queues = $this->connection()->keys('queue:*'); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 17e17b7fa..fd7e63b75 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1262,8 +1262,8 @@ function isAnyDeploymentInprogress() $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); $horizonJobIds = []; foreach ($runningJobs as $runningJob) { - $horizonJobId = getJobStatus($runningJob->horizon_job_id); - if ($horizonJobId === 'unknown') { + $horizonJobStatus = getJobStatus($runningJob->horizon_job_id); + if ($horizonJobStatus === 'unknown') { return true; } $horizonJobIds[] = $runningJob->horizon_job_id; @@ -4080,8 +4080,11 @@ function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp ]; } -function getJobStatus(string $jobId) +function getJobStatus(?string $jobId = null) { + if (blank($jobId)) { + return 'unknown'; + } $jobFound = app(JobRepository::class)->getJobs([$jobId]); if ($jobFound->isEmpty()) { return 'unknown'; From 3a3219394d1815e1e58078676f436e946b5a3e2e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 20:07:01 +0100 Subject: [PATCH 18/32] refactor: enhance ApplicationDeploymentJob and HorizonServiceProvider for improved job handling - Removed the private property for application deployment queue ID in ApplicationDeploymentJob and utilized constructor property promotion. - Added a tags method in ApplicationDeploymentJob to properly identify the worker running the job. - Updated HorizonServiceProvider to handle cases where the deployment queue ID is blank, preventing potential errors during job processing. - Cleaned up the isAnyDeploymentInprogress function by removing unnecessary whitespace. --- app/Jobs/ApplicationDeploymentJob.php | 13 ++++++++----- app/Providers/HorizonServiceProvider.php | 3 +++ bootstrap/helpers/shared.php | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 309201653..538c2eca0 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -43,8 +43,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue public static int $batch_counter = 0; - private int $application_deployment_queue_id; - private bool $newVersionIsHealthy = false; private ApplicationDeploymentQueue $application_deployment_queue; @@ -166,16 +164,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue public $tries = 1; - public function __construct(int $application_deployment_queue_id) + public function tags() + { + // Do not remove this one, it needs to properly identify which worker is running the job + return ['App\Models\ApplicationDeploymentQueue:'.$this->application_deployment_queue_id]; + } + + public function __construct(public int $application_deployment_queue_id) { $this->onQueue('high'); - $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); + $this->application_deployment_queue = ApplicationDeploymentQueue::find($this->application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); $this->build_args = collect([]); - $this->application_deployment_queue_id = $application_deployment_queue_id; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; $this->pull_request_id = $this->application_deployment_queue->pull_request_id; $this->commit = $this->application_deployment_queue->commit; diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index 2a459e74a..0caa3a3a9 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -38,6 +38,9 @@ class HorizonServiceProvider extends HorizonApplicationServiceProvider $deploymentQueueId = collect($tags)->first(function ($tag) { return str_contains($tag, 'App\Models\ApplicationDeploymentQueue'); }); + if (blank($deploymentQueueId)) { + return; + } $deploymentQueueId = explode(':', $deploymentQueueId)[1]; $deploymentQueue = ApplicationDeploymentQueue::find($deploymentQueueId); $deploymentQueue->update([ diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index fd7e63b75..32b7463de 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1258,7 +1258,6 @@ function get_public_ips() function isAnyDeploymentInprogress() { - $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); $horizonJobIds = []; foreach ($runningJobs as $runningJob) { From b724c85b0f12e18dc1efee8d27e6ecfb3bb5dc63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:08:15 +0000 Subject: [PATCH 19/32] chore(deps): bump nesbot/carbon from 3.8.2 to 3.8.4 Bumps [nesbot/carbon](https://github.com/CarbonPHP/carbon) from 3.8.2 to 3.8.4. - [Release notes](https://github.com/CarbonPHP/carbon/releases) - [Commits](https://github.com/CarbonPHP/carbon/compare/3.8.2...3.8.4) --- updated-dependencies: - dependency-name: nesbot/carbon dependency-type: indirect ... Signed-off-by: dependabot[bot] --- composer.lock | 80 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/composer.lock b/composer.lock index 3fbe72afb..ac32fdc11 100644 --- a/composer.lock +++ b/composer.lock @@ -4742,16 +4742,16 @@ }, { "name": "nesbot/carbon", - "version": "3.8.2", + "version": "3.8.4", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947", - "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", "shasum": "" }, "require": { @@ -4783,10 +4783,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -4796,6 +4792,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -4844,7 +4844,7 @@ "type": "tidelift" } ], - "time": "2024-11-07T17:46:48+00:00" + "time": "2024-12-27T09:25:35+00:00" }, { "name": "nette/schema", @@ -8641,12 +8641,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -8864,12 +8864,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -10372,12 +10372,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -10582,16 +10582,16 @@ }, { "name": "symfony/translation", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5" + "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/dc89e16b44048ceecc879054e5b7f38326ab6cc5", - "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5", + "url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", + "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", "shasum": "" }, "require": { @@ -10657,7 +10657,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.0" + "source": "https://github.com/symfony/translation/tree/v7.2.2" }, "funding": [ { @@ -10673,7 +10673,7 @@ "type": "tidelift" } ], - "time": "2024-11-12T20:47:56+00:00" + "time": "2024-12-07T08:18:10+00:00" }, { "name": "symfony/translation-contracts", @@ -10694,12 +10694,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -15071,16 +15071,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.5.1", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -15129,7 +15129,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -15145,7 +15145,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T12:02:18+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", @@ -15259,12 +15259,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } From 70db50def60a3f8e73da5dae5276b1f4ec8491ed Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 Jan 2025 20:27:49 +0100 Subject: [PATCH 20/32] fix: update response message for sentinel push route - Removed the temporary unavailability message from the /sentinel/push route to allow for proper handling of authorization without misleading responses. - This change improves the clarity of the API response when the authorization token is not provided. --- routes/api.php | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index 246b98d1f..b0ee6a6c9 100644 --- a/routes/api.php +++ b/routes/api.php @@ -133,7 +133,6 @@ Route::group([ 'prefix' => 'v1', ], function () { Route::post('/sentinel/push', function () { - return response()->json(['message' => 'temporary unavailable'], 503); $token = request()->header('Authorization'); if (! $token) { return response()->json(['message' => 'Unauthorized'], 401); From 18f03dc1738b03ad6f233c9bf0752d30dbc4b44a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sun, 12 Jan 2025 17:26:28 +0100 Subject: [PATCH 21/32] fix: create destination properly --- app/Livewire/Destination/New/Docker.php | 4 +--- app/Models/StandaloneDocker.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php index 337f1d067..0e60025e5 100644 --- a/app/Livewire/Destination/New/Docker.php +++ b/app/Livewire/Destination/New/Docker.php @@ -83,9 +83,7 @@ class Docker extends Component ]); } } - $connectProxyToDockerNetworks = connectProxyToNetworks($this->selectedServer); - instant_remote_process($connectProxyToDockerNetworks, $this->selectedServer, false); - $this->dispatch('reloadWindow'); + $this->redirect(route('destination.show', $docker->uuid)); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 1ef6ff587..9db6a2d29 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -6,6 +6,19 @@ class StandaloneDocker extends BaseModel { protected $guarded = []; + protected static function boot() + { + parent::boot(); + static::created(function ($newStandaloneDocker) { + $server = $newStandaloneDocker->server; + instant_remote_process([ + "docker network inspect $newStandaloneDocker->network >/dev/null 2>&1 || docker network create --driver overlay --attachable $newStandaloneDocker->network >/dev/null", + ], $server, false); + $connectProxyToDockerNetworks = connectProxyToNetworks($server); + instant_remote_process($connectProxyToDockerNetworks, $server, false); + }); + } + public function applications() { return $this->morphMany(Application::class, 'destination'); From 7eaf5d73f6c6c7ee0a73fd925e2899e5a37905de Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sun, 12 Jan 2025 18:45:15 +0100 Subject: [PATCH 22/32] refactor: improve checkbox component accessibility and styling --- .../views/components/forms/checkbox.blade.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index 39704a122..29c97735f 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -11,20 +11,21 @@ ])
$fullWidth, + 'dark:hover:bg-coolgray-100 cursor-pointer' => !$disabled, ])> -