diff --git a/README.md b/README.md index 8868bcea6..dac48d127 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # About the Project -Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. +Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else. @@ -40,21 +40,21 @@ Special thanks to our biggest sponsors! ### Special Sponsors -![image](https://github.com/user-attachments/assets/c95a07df-7c5a-4e77-a35a-81f25fcbece1) +![image](https://github.com/user-attachments/assets/726fb63e-c3b8-4260-b3ac-06780605ec5d) * [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. * [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions. * [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities. +* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform. * [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies. * [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution. * [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks. * [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. +* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. * [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management. -* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions. * [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies. * [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. * [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. -* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses. * [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. * [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. * [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. @@ -63,6 +63,7 @@ Special thanks to our biggest sponsors! * [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses. * [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly. * [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. +* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - Fast web hosting provider. ## Github Sponsors ($40+) @@ -90,7 +91,11 @@ Special thanks to our biggest sponsors! Paweł Pierścionek Michael Mazurczak Formbricks -Adith Suhas +StartupFame +Jonas Jaeger +JP +Evercam +Web3 Career ## Organizations @@ -142,10 +147,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri # Core Maintainers -| Andras Bacsai | Peak | +| Andras Bacsai | 🏔️ Peak | |------------|------------| -| Andras Bacsai | Peak Labs | -| | | +| Andras Bacsai | peaklabs-dev | +| | | # Repo Activity diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index cab7e45f0..642b4ba45 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -10,6 +10,8 @@ class StopApplication { use AsAction; + public string $jobQueue = 'high'; + public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true) { try { diff --git a/app/Actions/CoolifyTask/PrepareCoolifyTask.php b/app/Actions/CoolifyTask/PrepareCoolifyTask.php index 6676b7937..3f76a2e3c 100644 --- a/app/Actions/CoolifyTask/PrepareCoolifyTask.php +++ b/app/Actions/CoolifyTask/PrepareCoolifyTask.php @@ -3,7 +3,6 @@ namespace App\Actions\CoolifyTask; use App\Data\CoolifyTaskArgs; -use App\Enums\ActivityTypes; use App\Jobs\CoolifyTask; use Spatie\Activitylog\Models\Activity; @@ -47,11 +46,7 @@ class PrepareCoolifyTask call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data, ); - if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) { - dispatch($job)->onQueue('high'); - } else { - dispatch($job); - } + dispatch($job); $this->activity->refresh(); return $this->activity; diff --git a/app/Actions/Database/StartDatabase.php b/app/Actions/Database/StartDatabase.php index 869a88521..e2fa6fc87 100644 --- a/app/Actions/Database/StartDatabase.php +++ b/app/Actions/Database/StartDatabase.php @@ -16,6 +16,8 @@ class StartDatabase { use AsAction; + public string $jobQueue = 'high'; + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database) { $server = $database->destination->server; @@ -49,7 +51,7 @@ class StartDatabase break; } if ($database->is_public && $database->public_port) { - StartDatabaseProxy::dispatch($database)->onQueue('high'); + StartDatabaseProxy::dispatch($database); } return $activity; diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index d7a3bc697..3ddf6c036 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -18,6 +18,8 @@ class StartDatabaseProxy { use AsAction; + public string $jobQueue = 'high'; + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database) { $internalPort = null; diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php index 0a166d24a..9ee794351 100644 --- a/app/Actions/Database/StopDatabaseProxy.php +++ b/app/Actions/Database/StopDatabaseProxy.php @@ -18,6 +18,8 @@ class StopDatabaseProxy { use AsAction; + public string $jobQueue = 'high'; + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database) { $server = data_get($database, 'destination.server'); diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index a08056837..706356930 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -7,7 +7,6 @@ use App\Actions\Shared\ComplexStatusCheck; use App\Models\ApplicationPreview; use App\Models\Server; use App\Models\ServiceDatabase; -use App\Notifications\Container\ContainerRestarted; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Lorisleiva\Actions\Concerns\AsAction; @@ -16,6 +15,8 @@ class GetContainersStatus { use AsAction; + public string $jobQueue = 'high'; + public $applications; public ?Collection $containers; @@ -178,7 +179,7 @@ class GetContainersStatus })->first(); if (! $foundTcpProxy) { StartDatabaseProxy::run($database); - // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server)); } } } else { diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index 51303d87a..6c8dd5234 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -30,7 +30,7 @@ class CheckProxy if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) { return false; } - ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(false); + ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); if (! $uptime) { throw new \Exception($error); } diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index 1b8e6c9a6..0349ead89 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -9,6 +9,8 @@ class CleanupDocker { use AsAction; + public string $jobQueue = 'high'; + public function handle(Server $server) { $settings = instanceSettings(); diff --git a/app/Actions/Server/ServerCheck.php b/app/Actions/Server/ServerCheck.php index 1dae03fd9..75b8501f3 100644 --- a/app/Actions/Server/ServerCheck.php +++ b/app/Actions/Server/ServerCheck.php @@ -51,7 +51,6 @@ class ServerCheck $containerReplicates = null; $this->isSentinel = true; - } else { ['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers(); // ServerStorageCheckJob::dispatch($this->server); @@ -130,10 +129,10 @@ class ServerCheck if ($foundLogDrainContainer) { $status = data_get($foundLogDrainContainer, 'State.Status'); if ($status !== 'running') { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } else { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } @@ -148,7 +147,6 @@ class ServerCheck } else { $labels = Arr::undot(data_get($container, 'Config.Labels')); } - } $managed = data_get($labels, 'coolify.managed'); if (! $managed) { @@ -259,7 +257,7 @@ class ServerCheck })->first(); if (! $foundTcpProxy) { StartDatabaseProxy::run($database); - // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server)); } } } diff --git a/app/Actions/Server/StartLogDrain.php b/app/Actions/Server/StartLogDrain.php index 1997b58d6..0d28a0099 100644 --- a/app/Actions/Server/StartLogDrain.php +++ b/app/Actions/Server/StartLogDrain.php @@ -9,6 +9,8 @@ class StartLogDrain { use AsAction; + public string $jobQueue = 'high'; + public function handle(Server $server) { if ($server->settings->is_logdrain_newrelic_enabled) { diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index d57a4fe46..be9b4062c 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -29,9 +29,9 @@ class UpdateCoolify if (! $this->server) { return; } - CleanupDocker::dispatch($this->server)->onQueue('high'); + CleanupDocker::dispatch($this->server); $this->latestVersion = get_latest_version_of_coolify(); - $this->currentVersion = config('version'); + $this->currentVersion = config('constants.coolify.version'); if (! $manual_update) { if (! $settings->is_auto_update_enabled) { return; diff --git a/app/Actions/Server/ValidateServer.php b/app/Actions/Server/ValidateServer.php index d0a4cd6be..55b37a77c 100644 --- a/app/Actions/Server/ValidateServer.php +++ b/app/Actions/Server/ValidateServer.php @@ -9,6 +9,8 @@ class ValidateServer { use AsAction; + public string $jobQueue = 'high'; + public ?string $uptime = null; public ?string $error = null; diff --git a/app/Actions/Service/RestartService.php b/app/Actions/Service/RestartService.php index 1b6a5c32c..4151ea947 100644 --- a/app/Actions/Service/RestartService.php +++ b/app/Actions/Service/RestartService.php @@ -9,6 +9,8 @@ class RestartService { use AsAction; + public string $jobQueue = 'high'; + public function handle(Service $service) { StopService::run($service); diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index 82de066d7..1dfaf6c49 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -10,6 +10,8 @@ class StartService { use AsAction; + public string $jobQueue = 'high'; + public function handle(Service $service) { $service->saveComposeConfigs(); diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 046d94ced..95b08b437 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -10,6 +10,8 @@ class StopService { use AsAction; + public string $jobQueue = 'high'; + public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true) { try { diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php index cda4ca84f..f0e0e7fa0 100644 --- a/app/Console/Commands/Emails.php +++ b/app/Console/Commands/Emails.php @@ -187,7 +187,7 @@ class Emails extends Command 'team_id' => 0, ]); } - $this->mail = (new BackupSuccess($backup, $db))->toMail(); + // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail(); $this->sendEmail(); break; // case 'invitation-link': diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 57bbe896b..216262819 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -200,7 +200,7 @@ class Init extends Command private function restore_coolify_db_backup() { - if (version_compare('4.0.0-beta.179', config('version'), '<=')) { + if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) { try { $database = StandalonePostgresql::withTrashed()->find(0); if ($database && $database->trashed()) { @@ -228,7 +228,7 @@ class Init extends Command private function send_alive_signal() { $id = config('app.id'); - $version = config('version'); + $version = config('constants.coolify.version'); $settings = instanceSettings(); $do_not_track = data_get($settings, 'do_not_track'); if ($do_not_track == true) { @@ -264,7 +264,7 @@ class Init extends Command private function replace_slash_in_environment_name() { - if (version_compare('4.0.0-beta.298', config('version'), '<=')) { + if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) { $environments = Environment::all(); foreach ($environments as $environment) { if (str_contains($environment->name, '/')) { diff --git a/app/Console/Commands/ServicesDelete.php b/app/Console/Commands/ServicesDelete.php index 1e5d5808c..b5a74166a 100644 --- a/app/Console/Commands/ServicesDelete.php +++ b/app/Console/Commands/ServicesDelete.php @@ -96,7 +96,7 @@ class ServicesDelete extends Command if (! $confirmed) { break; } - DeleteResourceJob::dispatch($toDelete)->onQueue('high'); + DeleteResourceJob::dispatch($toDelete); } } } @@ -122,7 +122,7 @@ class ServicesDelete extends Command if (! $confirmed) { return; } - DeleteResourceJob::dispatch($toDelete)->onQueue('high'); + DeleteResourceJob::dispatch($toDelete); } } } @@ -148,7 +148,7 @@ class ServicesDelete extends Command if (! $confirmed) { return; } - DeleteResourceJob::dispatch($toDelete)->onQueue('high'); + DeleteResourceJob::dispatch($toDelete); } } } diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php index 1559e5f6d..b45707c5c 100644 --- a/app/Console/Commands/ServicesGenerate.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -20,7 +20,10 @@ class ServicesGenerate extends Command public function handle(): int { - $serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml'))) + $serviceTemplatesJson = collect(array_merge( + glob(base_path('templates/compose/*.yaml')), + glob(base_path('templates/compose/*.yml')) + )) ->mapWithKeys(function ($file): array { $file = basename($file); $parsed = $this->processFile($file); @@ -68,7 +71,7 @@ class ServicesGenerate extends Command 'slogan' => $data->get('slogan', str($file)->headline()), 'compose' => $compose, 'tags' => $tags, - 'logo' => $data->get('logo', 'svgs/coolify.png'), + 'logo' => $data->get('logo', 'svgs/default.webp'), 'minversion' => $data->get('minversion', '0.0.0'), ]; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index e113dbe9a..19d22ae21 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -50,7 +50,7 @@ class Kernel extends ConsoleKernel $this->instanceTimezone = config('app.timezone'); } - $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly(); + // $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly(); if (isDev()) { // Instance Jobs @@ -132,7 +132,7 @@ class Kernel extends ConsoleKernel } foreach ($servers as $server) { - $serverTimezone = $server->settings->server_timezone; + $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); // Sentinel check $lastSentinelUpdate = $server->sentinel_updated_at; @@ -141,8 +141,12 @@ class Kernel extends ConsoleKernel if (validate_timezone($serverTimezone) === false) { $serverTimezone = config('app.timezone'); } - $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); - // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer(); + if (isCloud()) { + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); + } else { + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); + } + // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer(); // Check storage usage every 10 minutes if Sentinel does not activated $this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); @@ -154,7 +158,7 @@ class Kernel extends ConsoleKernel } // Cleanup multiplexed connections every hour - $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer(); + // $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer(); // Temporary solution until we have better memory management for Sentinel if ($server->isSentinelEnabled()) { diff --git a/app/Events/DatabaseProxyStopped.php b/app/Events/DatabaseProxyStopped.php index b457dc6a0..96b35a5ca 100644 --- a/app/Events/DatabaseProxyStopped.php +++ b/app/Events/DatabaseProxyStopped.php @@ -18,7 +18,7 @@ class DatabaseProxyStopped implements ShouldBroadcast public function __construct($teamId = null) { if (is_null($teamId)) { - $teamId = Auth::user()->currentTeam()->id ?? null; + $teamId = Auth::user()?->currentTeam()?->id ?? null; } if (is_null($teamId)) { throw new \Exception('Team id is null'); diff --git a/app/Helpers/SshMultiplexingHelper.php b/app/Helpers/SshMultiplexingHelper.php index 0e840c3ce..8da476b9e 100644 --- a/app/Helpers/SshMultiplexingHelper.php +++ b/app/Helpers/SshMultiplexingHelper.php @@ -21,17 +21,14 @@ class SshMultiplexingHelper ]; } - public static function ensureMultiplexedConnection(Server $server) + public static function ensureMultiplexedConnection(Server $server): bool { if (! self::isMultiplexingEnabled()) { - return; + return false; } $sshConfig = self::serverSshConfiguration($server); $muxSocket = $sshConfig['muxFilename']; - $sshKeyLocation = $sshConfig['sshKeyLocation']; - - self::validateSshKey($sshKeyLocation); $checkCommand = "ssh -O check -o ControlPath=$muxSocket "; if (data_get($server, 'settings.is_cloudflare_tunnel')) { @@ -41,16 +38,17 @@ class SshMultiplexingHelper $process = Process::run($checkCommand); if ($process->exitCode() !== 0) { - self::establishNewMultiplexedConnection($server); + return self::establishNewMultiplexedConnection($server); } + + return true; } - public static function establishNewMultiplexedConnection(Server $server) + public static function establishNewMultiplexedConnection(Server $server): bool { $sshConfig = self::serverSshConfiguration($server); $sshKeyLocation = $sshConfig['sshKeyLocation']; $muxSocket = $sshConfig['muxFilename']; - $connectionTimeout = config('constants.ssh.connection_timeout'); $serverInterval = config('constants.ssh.server_interval'); $muxPersistTime = config('constants.ssh.mux_persist_time'); @@ -60,15 +58,14 @@ class SshMultiplexingHelper if (data_get($server, 'settings.is_cloudflare_tunnel')) { $establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" '; } - $establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval); $establishCommand .= "{$server->user}@{$server->ip}"; - $establishProcess = Process::run($establishCommand); - if ($establishProcess->exitCode() !== 0) { - throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput()); + return false; } + + return true; } public static function removeMuxFile(Server $server) @@ -97,9 +94,8 @@ class SshMultiplexingHelper if ($server->isIpv6()) { $scp_command .= '-6 '; } - if (self::isMultiplexingEnabled()) { + if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) { $scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; - self::ensureMultiplexedConnection($server); } if (data_get($server, 'settings.is_cloudflare_tunnel')) { @@ -120,6 +116,9 @@ class SshMultiplexingHelper $sshConfig = self::serverSshConfiguration($server); $sshKeyLocation = $sshConfig['sshKeyLocation']; + + self::validateSshKey($server->privateKey); + $muxSocket = $sshConfig['muxFilename']; $timeout = config('constants.ssh.command_timeout'); @@ -127,9 +126,8 @@ class SshMultiplexingHelper $ssh_command = "timeout $timeout ssh "; - if (self::isMultiplexingEnabled()) { + if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) { $ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; - self::ensureMultiplexedConnection($server); } if (data_get($server, 'settings.is_cloudflare_tunnel')) { @@ -154,13 +152,14 @@ class SshMultiplexingHelper return config('constants.ssh.mux_enabled') && ! config('constants.coolify.is_windows_docker_desktop'); } - private static function validateSshKey(string $sshKeyLocation): void + private static function validateSshKey(PrivateKey $privateKey): void { - $checkKeyCommand = "ls $sshKeyLocation 2>/dev/null"; + $keyLocation = $privateKey->getKeyLocation(); + $checkKeyCommand = "ls $keyLocation 2>/dev/null"; $keyCheckProcess = Process::run($checkKeyCommand); if ($keyCheckProcess->exitCode() !== 0) { - throw new \RuntimeException("SSH key file not accessible: $sshKeyLocation"); + $privateKey->storeInFileSystem(); } } diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 500db3922..614208c78 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1224,7 +1224,7 @@ class ApplicationsController extends Controller $service->name = "service-$service->uuid"; $service->parse(isNew: true); if ($instantDeploy) { - StartService::dispatch($service)->onQueue('high'); + StartService::dispatch($service); } return response()->json(serializeApiResponse([ @@ -1379,7 +1379,7 @@ class ApplicationsController extends Controller deleteVolumes: $request->query->get('delete_volumes', true), dockerCleanup: $request->query->get('docker_cleanup', true), deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) - )->onQueue('high'); + ); return response()->json([ 'message' => 'Application deletion request queued.', @@ -1591,16 +1591,32 @@ class ApplicationsController extends Controller } $domains = $request->domains; if ($request->has('domains') && $server->isProxyShouldRun()) { - $errors = []; + $uuid = $request->uuid; $fqdn = $request->domains; $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); $fqdn = str($fqdn)->replaceStart(',', '')->trim(); - $application->fqdn = $fqdn; - if (! $application->settings->is_container_label_readonly_enabled) { - $customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); - $application->custom_labels = base64_encode($customLabels); + $errors = []; + $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + $domain = trim($domain); + if (filter_var($domain, FILTER_VALIDATE_URL) === false || !preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) { + $errors[] = 'Invalid domain: '.$domain; + } + return $domain; + }); + if (count($errors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'domains' => 'One of the domain is already used.', + ], + ], 422); } - $request->offsetUnset('domains'); } $dockerComposeDomainsJson = collect(); @@ -2523,7 +2539,7 @@ class ApplicationsController extends Controller if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } - StopApplication::dispatch($application)->onQueue('high'); + StopApplication::dispatch($application); return response()->json( [ @@ -2811,3 +2827,30 @@ class ApplicationsController extends Controller } } } + + $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + $errors = []; + $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + if (filter_var($domain, FILTER_VALIDATE_URL) === false) { + $errors[] = 'Invalid domain: ' . $domain; + } + + return str($domain)->trim()->lower(); + }); + if (count($errors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'domains' => 'One of the domain is already used.', + ], + ], 422); + } + } + } +} diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index eaa542a83..9366e6300 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -497,9 +497,9 @@ class DatabasesController extends Controller $database->update($request->all()); if ($whatToDoWithDatabaseProxy === 'start') { - StartDatabaseProxy::dispatch($database)->onQueue('high'); + StartDatabaseProxy::dispatch($database); } elseif ($whatToDoWithDatabaseProxy === 'stop') { - StopDatabaseProxy::dispatch($database)->onQueue('high'); + StopDatabaseProxy::dispatch($database); } return response()->json([ @@ -1151,7 +1151,7 @@ class DatabasesController extends Controller } $database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); $payload = [ @@ -1206,7 +1206,7 @@ class DatabasesController extends Controller } $database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1264,7 +1264,7 @@ class DatabasesController extends Controller } $database = create_standalone_mysql($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1320,7 +1320,7 @@ class DatabasesController extends Controller } $database = create_standalone_redis($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1357,7 +1357,7 @@ class DatabasesController extends Controller removeUnnecessaryFieldsFromRequest($request); $database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } return response()->json(serializeApiResponse([ @@ -1406,7 +1406,7 @@ class DatabasesController extends Controller } $database = create_standalone_keydb($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1442,7 +1442,7 @@ class DatabasesController extends Controller removeUnnecessaryFieldsFromRequest($request); $database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1500,7 +1500,7 @@ class DatabasesController extends Controller } $database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1557,7 +1557,8 @@ class DatabasesController extends Controller ] ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1593,7 +1594,7 @@ class DatabasesController extends Controller deleteVolumes: $request->query->get('delete_volumes', true), dockerCleanup: $request->query->get('docker_cleanup', true), deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) - )->onQueue('high'); + ); return response()->json([ 'message' => 'Database deletion request queued.', @@ -1632,9 +1633,11 @@ class DatabasesController extends Controller type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Database starting request queued.'], - ]) + ] + ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1666,7 +1669,7 @@ class DatabasesController extends Controller if (str($database->status)->contains('running')) { return response()->json(['message' => 'Database is already running.'], 400); } - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); return response()->json( [ @@ -1708,9 +1711,11 @@ class DatabasesController extends Controller type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'], - ]) + ] + ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1742,7 +1747,7 @@ class DatabasesController extends Controller if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) { return response()->json(['message' => 'Database is already stopped.'], 400); } - StopDatabase::dispatch($database)->onQueue('high'); + StopDatabase::dispatch($database); return response()->json( [ @@ -1784,9 +1789,11 @@ class DatabasesController extends Controller type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'], - ]) + ] + ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1815,7 +1822,7 @@ class DatabasesController extends Controller if (! $database) { return response()->json(['message' => 'Database not found.'], 404); } - RestartDatabase::dispatch($database)->onQueue('high'); + RestartDatabase::dispatch($database); return response()->json( [ diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 59b199d87..666dc55a5 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -307,7 +307,7 @@ class DeployController extends Controller break; default: // Database resource - StartDatabase::dispatch($resource)->onQueue('high'); + StartDatabase::dispatch($resource); $resource->update([ 'started_at' => now(), ]); diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index b35c72116..303e6535d 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -37,7 +37,7 @@ class OtherController extends Controller )] public function version(Request $request) { - return response(config('version')); + return response(config('constants.coolify.version')); } #[OA\Get( diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index cbee00642..8c13b1a01 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -550,7 +550,7 @@ class ServersController extends Controller 'is_build_server' => $request->is_build_server, ]); if ($request->instant_validate) { - ValidateServer::dispatch($server)->onQueue('high'); + ValidateServer::dispatch($server); } return response()->json([ @@ -567,6 +567,9 @@ class ServersController extends Controller ['bearerAuth' => []], ], tags: ['Servers'], + parameters: [ + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')), + ], requestBody: new OA\RequestBody( required: true, description: 'Server updated.', @@ -596,8 +599,7 @@ class ServersController extends Controller new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( - type: 'array', - items: new OA\Items(ref: '#/components/schemas/Server') + ref: '#/components/schemas/Server' ) ), ]), @@ -675,11 +677,11 @@ class ServersController extends Controller ]); } if ($request->instant_validate) { - ValidateServer::dispatch($server)->onQueue('high'); + ValidateServer::dispatch($server); } return response()->json([ - + 'uuid' => $server->uuid, ])->setStatusCode(201); } @@ -813,7 +815,7 @@ class ServersController extends Controller if (! $server) { return response()->json(['message' => 'Server not found.'], 404); } - ValidateServer::dispatch($server)->onQueue('high'); + ValidateServer::dispatch($server); return response()->json(['message' => 'Validation started.']); } diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index bdb5612ad..bf90322e2 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -342,7 +342,7 @@ class ServicesController extends Controller } $service->parse(isNew: true); if ($instantDeploy) { - StartService::dispatch($service)->onQueue('high'); + StartService::dispatch($service); } $domains = $service->applications()->get()->pluck('fqdn')->sort(); $domains = $domains->map(function ($domain) { @@ -487,7 +487,7 @@ class ServicesController extends Controller deleteVolumes: $request->query->get('delete_volumes', true), dockerCleanup: $request->query->get('docker_cleanup', true), deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) - )->onQueue('high'); + ); return response()->json([ 'message' => 'Service deletion request queued.', @@ -1076,7 +1076,7 @@ class ServicesController extends Controller if (str($service->status())->contains('running')) { return response()->json(['message' => 'Service is already running.'], 400); } - StartService::dispatch($service)->onQueue('high'); + StartService::dispatch($service); return response()->json( [ @@ -1154,7 +1154,7 @@ class ServicesController extends Controller if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) { return response()->json(['message' => 'Service is already stopped.'], 400); } - StopService::dispatch($service)->onQueue('high'); + StopService::dispatch($service); return response()->json( [ @@ -1229,7 +1229,7 @@ class ServicesController extends Controller if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - RestartService::dispatch($service)->onQueue('high'); + RestartService::dispatch($service); return response()->json( [ diff --git a/app/Http/Controllers/Webhook/Stripe.php b/app/Http/Controllers/Webhook/Stripe.php index e94209b23..83ba16699 100644 --- a/app/Http/Controllers/Webhook/Stripe.php +++ b/app/Http/Controllers/Webhook/Stripe.php @@ -3,21 +3,26 @@ namespace App\Http\Controllers\Webhook; use App\Http\Controllers\Controller; -use App\Jobs\ServerLimitCheckJob; -use App\Jobs\SubscriptionInvoiceFailedJob; -use App\Models\Subscription; -use App\Models\Team; +use App\Jobs\StripeProcessJob; use App\Models\Webhook; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; class Stripe extends Controller { + protected $webhook; + public function events(Request $request) { try { + $webhookSecret = config('subscription.stripe_webhook_secret'); + $signature = $request->header('Stripe-Signature'); + $event = \Stripe\Webhook::constructEvent( + $request->getContent(), + $signature, + $webhookSecret + ); if (app()->isDownForMaintenance()) { $epoch = now()->valueOf(); $data = [ @@ -33,241 +38,17 @@ class Stripe extends Controller $json = json_encode($data); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json); - return; + return response('Webhook received. Cool cool cool cool cool.', 200); } - $webhookSecret = config('subscription.stripe_webhook_secret'); - $signature = $request->header('Stripe-Signature'); - $excludedPlans = config('subscription.stripe_excluded_plans'); - $event = \Stripe\Webhook::constructEvent( - $request->getContent(), - $signature, - $webhookSecret - ); - $webhook = Webhook::create([ + $this->webhook = Webhook::create([ 'type' => 'stripe', 'payload' => $request->getContent(), ]); - $type = data_get($event, 'type'); - $data = data_get($event, 'data.object'); - switch ($type) { - case 'radar.early_fraud_warning.created': - $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); - $id = data_get($data, 'id'); - $charge = data_get($data, 'charge'); - if ($charge) { - $stripe->refunds->create(['charge' => $charge]); - } - $pi = data_get($data, 'payment_intent'); - $piData = $stripe->paymentIntents->retrieve($pi, []); - $customerId = data_get($piData, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - $subscriptionId = data_get($subscription, 'stripe_subscription_id'); - $stripe->subscriptions->cancel($subscriptionId, []); - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); - send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}"); - } else { - send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + StripeProcessJob::dispatch($event); - return response("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}", 400); - } - break; - case 'checkout.session.completed': - $clientReferenceId = data_get($data, 'client_reference_id'); - if (is_null($clientReferenceId)) { - send_internal_notification('Checkout session completed without client reference id.'); - break; - } - $userId = Str::before($clientReferenceId, ':'); - $teamId = Str::after($clientReferenceId, ':'); - $subscriptionId = data_get($data, 'subscription'); - $customerId = data_get($data, 'customer'); - $team = Team::find($teamId); - $found = $team->members->where('id', $userId)->first(); - if (! $found->isAdmin()) { - send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); - - return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.", 400); - } - $subscription = Subscription::where('team_id', $teamId)->first(); - if ($subscription) { - // send_internal_notification('Old subscription activated for team: '.$teamId); - $subscription->update([ - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => true, - ]); - } else { - // send_internal_notification('New subscription for team: '.$teamId); - Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => true, - ]); - } - break; - case 'invoice.paid': - $customerId = data_get($data, 'customer'); - $planId = data_get($data, 'lines.data.0.plan.id'); - if (Str::contains($excludedPlans, $planId)) { - // send_internal_notification('Subscription excluded.'); - break; - } - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - $subscription->update([ - 'stripe_invoice_paid' => true, - ]); - } else { - return response("No subscription found for customer: {$customerId}", 400); - } - break; - case 'invoice.payment_failed': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - // send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); - - return response('No subscription found in Coolify.'); - } - $team = data_get($subscription, 'team'); - if (! $team) { - // send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); - - return response('No team found in Coolify.'); - } - if (! $subscription->stripe_invoice_paid) { - SubscriptionInvoiceFailedJob::dispatch($team); - // send_internal_notification('Invoice payment failed: '.$customerId); - } else { - // send_internal_notification('Invoice payment failed but already paid: '.$customerId); - } - break; - case 'payment_intent.payment_failed': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - // send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); - - return response('No subscription found in Coolify.'); - } - if ($subscription->stripe_invoice_paid) { - // send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); - - return; - } - send_internal_notification('Subscription payment failed for customer: '.$customerId); - break; - case 'customer.subscription.created': - $customerId = data_get($data, 'customer'); - $subscriptionId = data_get($data, 'id'); - $teamId = data_get($data, 'metadata.team_id'); - $userId = data_get($data, 'metadata.user_id'); - if (! $teamId || ! $userId) { - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - return response("Subscription already exists for customer: {$customerId}", 200); - } - - return response('No team id or user id found', 400); - } - $team = Team::find($teamId); - $found = $team->members->where('id', $userId)->first(); - if (! $found->isAdmin()) { - send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); - - return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.", 400); - } - $subscription = Subscription::where('team_id', $teamId)->first(); - if ($subscription) { - return response("Subscription already exists for team: {$teamId}", 200); - } else { - Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => false, - ]); - - return response('Subscription created'); - } - case 'customer.subscription.updated': - $teamId = data_get($data, 'metadata.team_id'); - $userId = data_get($data, 'metadata.user_id'); - $customerId = data_get($data, 'customer'); - $status = data_get($data, 'status'); - $subscriptionId = data_get($data, 'items.data.0.subscription'); - $planId = data_get($data, 'items.data.0.plan.id'); - if (Str::contains($excludedPlans, $planId)) { - // send_internal_notification('Subscription excluded.'); - break; - } - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - if ($status === 'incomplete_expired') { - return response('Subscription incomplete expired', 200); - } - if ($teamId) { - $subscription = Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => false, - ]); - } else { - return response('No subscription and team id found', 400); - } - } - $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); - $feedback = data_get($data, 'cancellation_details.feedback'); - $comment = data_get($data, 'cancellation_details.comment'); - $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); - if (str($lookup_key)->contains('dynamic')) { - $quantity = data_get($data, 'items.data.0.quantity', 2); - $team = data_get($subscription, 'team'); - if ($team) { - $team->update([ - 'custom_server_limit' => $quantity, - ]); - } - ServerLimitCheckJob::dispatch($team); - } - $subscription->update([ - 'stripe_feedback' => $feedback, - 'stripe_comment' => $comment, - 'stripe_plan_id' => $planId, - 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, - ]); - if ($status === 'paused' || $status === 'incomplete_expired') { - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); - } - if ($feedback) { - $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; - if ($comment) { - $reason .= ' with comment: \''.$comment."'"; - } - } - break; - case 'customer.subscription.deleted': - // End subscription - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); - $team = data_get($subscription, 'team'); - $team?->subscriptionEnded(); - break; - default: - // Unhandled event type - } + return response('Webhook received. Cool cool cool cool cool.', 200); } catch (Exception $e) { - if ($type !== 'payment_intent.payment_failed') { - send_internal_notification("Subscription webhook ($type) failed: ".$e->getMessage()); - } - $webhook->update([ + $this->webhook->update([ 'status' => 'failed', 'failure_reason' => $e->getMessage(), ]); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index cd89f55f3..41909fa30 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -166,6 +166,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue public function __construct(int $application_deployment_queue_id) { + $this->onQueue('high'); + $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); @@ -349,8 +351,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function post_deployment() { if ($this->server->isProxyShouldRun()) { - GetContainersStatus::dispatch($this->server)->onQueue('high'); - // dispatch(new ContainerStatusJob($this->server)); + GetContainersStatus::dispatch($this->server); } $this->next(ApplicationDeploymentStatus::FINISHED->value); if ($this->pull_request_id !== 0) { @@ -462,7 +463,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id')); $this->save_environment_variables(); if (! is_null($this->env_filename)) { - $services = collect($composeFile['services']); + $services = collect(data_get($composeFile, 'services', [])); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; @@ -2399,7 +2400,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); } - $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); + //$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); } } diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index 2eefc4dd2..ef8e6efb6 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -25,7 +25,9 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue public ApplicationPreview $preview, public ProcessStatus $status, public ?string $deployment_uuid = null - ) {} + ) { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php index f2348118a..1d3a345e1 100644 --- a/app/Jobs/CheckForUpdatesJob.php +++ b/app/Jobs/CheckForUpdatesJob.php @@ -27,7 +27,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue $versions = $response->json(); $latest_version = data_get($versions, 'coolify.v4.version'); - $current_version = config('version'); + $current_version = config('constants.coolify.version'); if (version_compare($latest_version, $current_version, '>')) { // New version available diff --git a/app/Jobs/CoolifyTask.php b/app/Jobs/CoolifyTask.php index c3692c30b..49a5ba8dd 100755 --- a/app/Jobs/CoolifyTask.php +++ b/app/Jobs/CoolifyTask.php @@ -23,7 +23,10 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue public bool $ignore_errors, public $call_event_on_finish, public $call_event_data, - ) {} + ) { + + $this->onQueue('high'); + } /** * Execute the job. diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 94f185882..ee702202f 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -60,12 +60,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public function __construct($backup) { + $this->onQueue('high'); $this->backup = $backup; } public function handle(): void { try { + $databasesToBackup = null; + $this->team = Team::find($this->backup->team_id); if (! $this->team) { $this->backup->delete(); @@ -197,8 +200,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $databaseType = $this->database->type(); $databasesToBackup = data_get($this->backup, 'databases_to_backup'); } - - if (is_null($databasesToBackup)) { + if (blank($databasesToBackup)) { if (str($databaseType)->contains('postgres')) { $databasesToBackup = [$this->database->postgres_db]; } elseif (str($databaseType)->contains('mongodb')) { @@ -304,7 +306,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue if ($this->backup->save_s3) { $this->upload_to_s3(); } - $this->team?->notify(new BackupSuccess($this->backup, $this->database, $database)); + //$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database)); $this->backup_log->update([ 'status' => 'success', 'message' => $this->backup_output, @@ -319,12 +321,10 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue 'filename' => null, ]); } - send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage()); $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database)); } } } catch (\Throwable $e) { - send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage()); throw $e; } finally { if ($this->team) { diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 2442d5b06..8b9228e5f 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -35,7 +35,9 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue public bool $deleteVolumes = true, public bool $dockerCleanup = true, public bool $deleteConnectedNetworks = true - ) {} + ) { + $this->onQueue('high'); + } public function handle() { @@ -87,7 +89,6 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue $this->resource?->delete_connected_networks($this->resource->uuid); } } catch (\Throwable $e) { - send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage()); throw $e; } finally { $this->resource->forceDelete(); diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php index cfc0c5a94..b92886d38 100644 --- a/app/Jobs/PullHelperImageJob.php +++ b/app/Jobs/PullHelperImageJob.php @@ -16,7 +16,10 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 1000; - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + $this->onQueue('high'); + } public function handle(): void { diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php index bde5e6c7a..45c536e06 100644 --- a/app/Jobs/PullTemplatesFromCDN.php +++ b/app/Jobs/PullTemplatesFromCDN.php @@ -17,7 +17,10 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue public $timeout = 10; - public function __construct() {} + public function __construct() + { + $this->onQueue('high'); + } public function handle(): void { diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 9822ca071..24f8d1e6b 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -360,7 +360,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue private function checkLogDrainContainer() { if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } } diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 7bfc29af3..00575e187 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -40,6 +40,8 @@ class ScheduledTaskJob implements ShouldQueue public function __construct($task) { + $this->onQueue('high'); + $this->task = $task; if ($service = $task->service()->first()) { $this->resource = $service; diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php index 5b406f50f..99aeaeea2 100644 --- a/app/Jobs/SendMessageToDiscordJob.php +++ b/app/Jobs/SendMessageToDiscordJob.php @@ -32,7 +32,9 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public DiscordMessage $message, public string $webhookUrl - ) {} + ) { + $this->onQueue('high'); + } /** * Execute the job. diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index bf52b782f..85f4fc934 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -33,7 +33,9 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue public string $token, public string $chatId, public ?string $topicId = null, - ) {} + ) { + $this->onQueue('high'); + } /** * Execute the job. @@ -70,7 +72,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue } $response = Http::post($url, $payload); if ($response->failed()) { - throw new \Exception('Telegram notification failed with '.$response->status().' status code.'.$response->body()); + throw new \RuntimeException('Telegram notification failed with '.$response->status().' status code.'.$response->body()); } } } diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index 0d5e2fd36..49d8dfe08 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -31,7 +31,12 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; } - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + if (isDev()) { + $this->handle(); + } + } public function handle() { @@ -94,10 +99,10 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue if ($foundLogDrainContainer) { $status = data_get($foundLogDrainContainer, 'State.Status'); if ($status !== 'running') { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } else { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } } diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php index 769dfc004..58455df2f 100644 --- a/app/Jobs/ServerFilesFromServerJob.php +++ b/app/Jobs/ServerFilesFromServerJob.php @@ -16,7 +16,10 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {} + public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) + { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/ServerStorageSaveJob.php b/app/Jobs/ServerStorageSaveJob.php index 526cd5375..17a293f94 100644 --- a/app/Jobs/ServerStorageSaveJob.php +++ b/app/Jobs/ServerStorageSaveJob.php @@ -14,7 +14,10 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public LocalFileVolume $localFileVolume) {} + public function __construct(public LocalFileVolume $localFileVolume) + { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php new file mode 100644 index 000000000..00c9b6d18 --- /dev/null +++ b/app/Jobs/StripeProcessJob.php @@ -0,0 +1,246 @@ +onQueue('high'); + } + + public function handle(): void + { + try { + $excludedPlans = config('subscription.stripe_excluded_plans'); + + $type = data_get($this->event, 'type'); + $this->type = $type; + $data = data_get($this->event, 'data.object'); + switch ($type) { + case 'radar.early_fraud_warning.created': + $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); + $id = data_get($data, 'id'); + $charge = data_get($data, 'charge'); + if ($charge) { + $stripe->refunds->create(['charge' => $charge]); + } + $pi = data_get($data, 'payment_intent'); + $piData = $stripe->paymentIntents->retrieve($pi, []); + $customerId = data_get($piData, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + $subscriptionId = data_get($subscription, 'stripe_subscription_id'); + $stripe->subscriptions->cancel($subscriptionId, []); + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}"); + } else { + send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + throw new \RuntimeException("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + } + break; + case 'checkout.session.completed': + $clientReferenceId = data_get($data, 'client_reference_id'); + if (is_null($clientReferenceId)) { + send_internal_notification('Checkout session completed without client reference id.'); + break; + } + $userId = Str::before($clientReferenceId, ':'); + $teamId = Str::after($clientReferenceId, ':'); + $subscriptionId = data_get($data, 'subscription'); + $customerId = data_get($data, 'customer'); + $team = Team::find($teamId); + $found = $team->members->where('id', $userId)->first(); + if (! $found->isAdmin()) { + send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); + throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); + } + $subscription = Subscription::where('team_id', $teamId)->first(); + if ($subscription) { + send_internal_notification('Old subscription activated for team: '.$teamId); + $subscription->update([ + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => true, + ]); + } else { + send_internal_notification('New subscription for team: '.$teamId); + Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => true, + ]); + } + break; + case 'invoice.paid': + $customerId = data_get($data, 'customer'); + $planId = data_get($data, 'lines.data.0.plan.id'); + if (Str::contains($excludedPlans, $planId)) { + send_internal_notification('Subscription excluded.'); + break; + } + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + $subscription->update([ + 'stripe_invoice_paid' => true, + ]); + } else { + throw new \RuntimeException("No subscription found for customer: {$customerId}"); + } + break; + case 'invoice.payment_failed': + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found for customer: {$customerId}"); + } + $team = data_get($subscription, 'team'); + if (! $team) { + send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No team found in Coolify for customer: {$customerId}"); + } + if (! $subscription->stripe_invoice_paid) { + SubscriptionInvoiceFailedJob::dispatch($team); + send_internal_notification('Invoice payment failed: '.$customerId); + } else { + send_internal_notification('Invoice payment failed but already paid: '.$customerId); + } + break; + case 'payment_intent.payment_failed': + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}"); + } + if ($subscription->stripe_invoice_paid) { + send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); + + return; + } + send_internal_notification('Subscription payment failed for customer: '.$customerId); + break; + case 'customer.subscription.created': + $customerId = data_get($data, 'customer'); + $subscriptionId = data_get($data, 'id'); + $teamId = data_get($data, 'metadata.team_id'); + $userId = data_get($data, 'metadata.user_id'); + if (! $teamId || ! $userId) { + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + throw new \RuntimeException("Subscription already exists for customer: {$customerId}"); + } + throw new \RuntimeException('No team id or user id found'); + } + $team = Team::find($teamId); + $found = $team->members->where('id', $userId)->first(); + if (! $found->isAdmin()) { + send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); + throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); + } + $subscription = Subscription::where('team_id', $teamId)->first(); + if ($subscription) { + send_internal_notification("Subscription already exists for team: {$teamId}"); + throw new \RuntimeException("Subscription already exists for team: {$teamId}"); + } else { + Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => false, + ]); + } + case 'customer.subscription.updated': + $teamId = data_get($data, 'metadata.team_id'); + $userId = data_get($data, 'metadata.user_id'); + $customerId = data_get($data, 'customer'); + $status = data_get($data, 'status'); + $subscriptionId = data_get($data, 'items.data.0.subscription'); + $planId = data_get($data, 'items.data.0.plan.id'); + if (Str::contains($excludedPlans, $planId)) { + send_internal_notification('Subscription excluded.'); + break; + } + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + if ($status === 'incomplete_expired') { + send_internal_notification('Subscription incomplete expired'); + throw new \RuntimeException('Subscription incomplete expired'); + } + if ($teamId) { + $subscription = Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => false, + ]); + } else { + send_internal_notification('No subscription and team id found'); + throw new \RuntimeException('No subscription and team id found'); + } + } + $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); + $feedback = data_get($data, 'cancellation_details.feedback'); + $comment = data_get($data, 'cancellation_details.comment'); + $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); + if (str($lookup_key)->contains('dynamic')) { + $quantity = data_get($data, 'items.data.0.quantity', 2); + $team = data_get($subscription, 'team'); + if ($team) { + $team->update([ + 'custom_server_limit' => $quantity, + ]); + } + ServerLimitCheckJob::dispatch($team); + } + $subscription->update([ + 'stripe_feedback' => $feedback, + 'stripe_comment' => $comment, + 'stripe_plan_id' => $planId, + 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, + ]); + if ($status === 'paused' || $status === 'incomplete_expired') { + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + } + if ($feedback) { + $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; + if ($comment) { + $reason .= ' with comment: \''.$comment."'"; + } + } + break; + case 'customer.subscription.deleted': + // End subscription + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); + $team = data_get($subscription, 'team'); + $team?->subscriptionEnded(); + break; + default: + throw new \RuntimeException("Unhandled event type: {$type}"); + } + } catch (\Exception $e) { + send_internal_notification('StripeProcessJob error: '.$e->getMessage()); + } + } +} diff --git a/app/Jobs/SubscriptionInvoiceFailedJob.php b/app/Jobs/SubscriptionInvoiceFailedJob.php index aabeecef5..dc511f445 100755 --- a/app/Jobs/SubscriptionInvoiceFailedJob.php +++ b/app/Jobs/SubscriptionInvoiceFailedJob.php @@ -15,7 +15,10 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(protected Team $team) {} + public function __construct(protected Team $team) + { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/UpdateCoolifyJob.php b/app/Jobs/UpdateCoolifyJob.php index 1e5197b6f..f0e43cbc0 100644 --- a/app/Jobs/UpdateCoolifyJob.php +++ b/app/Jobs/UpdateCoolifyJob.php @@ -18,6 +18,11 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 600; + public function __construct() + { + $this->onQueue('high'); + } + public function handle(): void { try { diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index b6c799c4e..eadabba7c 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -172,13 +172,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== public function getProxyType() { - // Set Default Proxy Type $this->selectProxy(ProxyTypes::TRAEFIK->value); - // $proxyTypeSet = $this->createdServer->proxy->type; - // if (!$proxyTypeSet) { - // $this->currentState = 'select-proxy'; - // return; - // } $this->getProjects(); } @@ -189,7 +183,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== return; } - $this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey); + $this->createdPrivateKey = PrivateKey::where('team_id', currentTeam()->id)->where('id', $this->selectedExistingPrivateKey)->first(); $this->privateKey = $this->createdPrivateKey->private_key; $this->currentState = 'create-server'; } diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php index f86f42e34..337f1d067 100644 --- a/app/Livewire/Destination/New/Docker.php +++ b/app/Livewire/Destination/New/Docker.php @@ -35,7 +35,7 @@ class Docker extends Component $this->network = new Cuid2; $this->servers = Server::isUsable()->get(); if ($server_id) { - $this->selectedServer = $this->servers->find($server_id); + $this->selectedServer = $this->servers->find($server_id) ?: $this->servers->first(); $this->serverId = $this->selectedServer->id; } else { $this->selectedServer = $this->servers->first(); diff --git a/app/Livewire/NavbarDeleteTeam.php b/app/Livewire/NavbarDeleteTeam.php index cc5d78f60..e97cceb0d 100644 --- a/app/Livewire/NavbarDeleteTeam.php +++ b/app/Livewire/NavbarDeleteTeam.php @@ -3,7 +3,6 @@ namespace App\Livewire; use App\Models\InstanceSettings; -use Illuminate\Container\Attributes\Auth as AttributesAuth; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; @@ -32,7 +31,7 @@ class NavbarDeleteTeam extends Component $currentTeam->delete(); $currentTeam->members->each(function ($user) use ($currentTeam) { - if ($user->id === AttributesAuth::id()) { + if ($user->id === Auth::id()) { return; } $user->teams()->detach($currentTeam); diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index 56f07f3a9..c4e4aae14 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -73,6 +73,9 @@ class Email extends Component #[Validate(['nullable', 'string'])] public ?string $resendApiKey = null; + #[Validate(['nullable', 'email'])] + public ?string $testEmailAddress = null; + public function mount() { try { @@ -132,14 +135,21 @@ class Email extends Component } } - public function sendTestNotification() + public function sendTestEmail() { try { + $this->validate([ + 'testEmailAddress' => 'required|email', + ], [ + 'testEmailAddress.required' => 'Test email address is required.', + 'testEmailAddress.email' => 'Please enter a valid email address.', + ]); + $executed = RateLimiter::attempt( 'test-email:'.$this->team->id, $perMinute = 0, function () { - $this->team?->notify(new Test($this->emails)); + $this->team?->notify(new Test($this->testEmailAddress)); $this->dispatch('success', 'Test Email sent.'); }, $decaySeconds = 10, diff --git a/app/Livewire/Project/Application/Configuration.php b/app/Livewire/Project/Application/Configuration.php index d4ec8f581..cce3bdd39 100644 --- a/app/Livewire/Project/Application/Configuration.php +++ b/app/Livewire/Project/Application/Configuration.php @@ -16,24 +16,28 @@ class Configuration extends Component public function mount() { - $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); - if (! $project) { - return redirect()->route('dashboard'); + $this->application = Application::query() + ->whereHas('environment.project', function ($query) { + $query->where('team_id', currentTeam()->id) + ->where('uuid', request()->route('project_uuid')); + }) + ->whereHas('environment', function ($query) { + $query->where('name', request()->route('environment_name')); + }) + ->where('uuid', request()->route('application_uuid')) + ->with(['destination' => function ($query) { + $query->select('id', 'server_id'); + }]) + ->firstOrFail(); + + if ($this->application->destination && $this->application->destination->server_id) { + $this->servers = Server::ownedByCurrentTeam() + ->select('id', 'name') + ->where('id', '!=', $this->application->destination->server_id) + ->get(); + } else { + $this->servers = collect(); } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); - if (! $environment) { - return redirect()->route('dashboard'); - } - $application = $environment->applications->where('uuid', request()->route('application_uuid'))->first(); - if (! $application) { - return redirect()->route('dashboard'); - } - $this->application = $application; - $mainServer = $this->application->destination->server; - $servers = Server::ownedByCurrentTeam()->get(); - $this->servers = $servers->filter(function ($server) use ($mainServer) { - return $server->id != $mainServer->id; - }); } public function render() diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 1082b48cd..19a6145b7 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -36,7 +36,11 @@ class Heading extends Component public function mount() { - $this->parameters = get_route_parameters(); + $this->parameters = [ + 'project_uuid' => $this->application->project()->uuid, + 'environment_name' => $this->application->environment->name, + 'application_uuid' => $this->application->uuid, + ]; $lastDeployment = $this->application->get_last_successful_deployment(); $this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message'); $this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit')); @@ -45,13 +49,11 @@ class Heading extends Component public function check_status($showNotification = false) { if ($this->application->destination->server->isFunctional()) { - GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high'); + GetContainersStatus::dispatch($this->application->destination->server); } if ($showNotification) { $this->dispatch('success', 'Success', 'Application status updated.'); } - // Removed because it caused flickering - // $this->dispatch('configurationChanged'); } public function force_deploy_without_cache() diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index 3dedc11af..0b6d075a4 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -91,12 +91,16 @@ class Select extends Component { $services = get_service_templates(true); $services = collect($services)->map(function ($service, $key) { - $logo = data_get($service, 'logo', 'svgs/coolify.png'); + $default_logo = 'images/default.webp'; + $logo = data_get($service, 'logo', $default_logo); + $local_logo_path = public_path($logo); return [ 'name' => str($key)->headline(), 'logo' => asset($logo), - 'logo_github_url' => 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/a'.$logo, + 'logo_github_url' => file_exists($local_logo_path) + ? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo + : asset($default_logo), ] + (array) $service; })->all(); $gitBasedApplications = [ @@ -144,14 +148,14 @@ class Select extends Component 'id' => 'postgresql', 'name' => 'PostgreSQL', 'description' => 'PostgreSQL is an object-relational database known for its robustness, advanced features, and strong standards compliance.', - 'logo' => ' + 'logo' => ' ', ], [ 'id' => 'mysql', 'name' => 'MySQL', 'description' => 'MySQL is an open-source relational database management system. ', - 'logo' => ' + 'logo' => ' @@ -162,37 +166,37 @@ class Select extends Component 'id' => 'mariadb', 'name' => 'MariaDB', 'description' => 'MariaDB is a community-developed, commercially supported fork of the MySQL relational database management system, intended to remain free and open-source.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'redis', 'name' => 'Redis', 'description' => 'Redis is a source-available, in-memory storage, used as a distributed, in-memory key–value database, cache and message broker, with optional durability.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'keydb', 'name' => 'KeyDB', 'description' => 'KeyDB is a database that offers high performance, low latency, and scalability for various data structures and workloads.', - 'logo' => '
', + 'logo' => '
', ], [ 'id' => 'dragonfly', 'name' => 'Dragonfly', 'description' => 'Dragonfly DB is a drop-in Redis replacement that delivers 25x more throughput and 12x faster snapshotting than Redis.', - 'logo' => '
', + 'logo' => '
', ], [ 'id' => 'mongodb', 'name' => 'MongoDB', 'description' => 'MongoDB is a source-available, cross-platform, document-oriented database program.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'clickhouse', 'name' => 'ClickHouse', 'description' => 'ClickHouse is a column-oriented database that supports real-time analytics, business intelligence, observability, ML and GenAI, and more.', - 'logo' => '
', + 'logo' => '
', ], ]; diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php index 0852abebf..0650de9a0 100644 --- a/app/Livewire/Server/Advanced.php +++ b/app/Livewire/Server/Advanced.php @@ -22,7 +22,7 @@ class Advanced extends Component #[Validate('boolean')] public bool $forceDockerCleanup = false; - #[Validate('string')] + #[Validate(['string', 'required'])] public string $dockerCleanupFrequency = '*/10 * * * *'; #[Validate(['integer', 'min:1', 'max:99'])] @@ -78,7 +78,6 @@ class Advanced extends Component try { $this->syncData(true); $this->dispatch('success', 'Server updated.'); - // $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Server/LogDrains.php b/app/Livewire/Server/LogDrains.php index 6599149c4..edddfc755 100644 --- a/app/Livewire/Server/LogDrains.php +++ b/app/Livewire/Server/LogDrains.php @@ -49,33 +49,73 @@ class LogDrains extends Component } } - public function syncData(bool $toModel = false) + public function syncDataNewRelic(bool $toModel = false) + { + if ($toModel) { + $this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled; + $this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey; + $this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri; + } else { + $this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled; + $this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key; + $this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri; + } + } + + public function syncDataAxiom(bool $toModel = false) + { + if ($toModel) { + $this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled; + $this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName; + $this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey; + } else { + $this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled; + $this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name; + $this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key; + } + } + + public function syncDataCustom(bool $toModel = false) + { + if ($toModel) { + $this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled; + $this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig; + $this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser; + } else { + $this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled; + $this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config; + $this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser; + } + } + + public function syncData(bool $toModel = false, ?string $type = null) { if ($toModel) { $this->customValidation(); - $this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled; - $this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled; - $this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled; - - $this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey; - $this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri; - $this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName; - $this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey; - $this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig; - $this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser; - + if ($type === 'newrelic') { + $this->syncDataNewRelic($toModel); + } elseif ($type === 'axiom') { + $this->syncDataAxiom($toModel); + } elseif ($type === 'custom') { + $this->syncDataCustom($toModel); + } else { + $this->syncDataNewRelic($toModel); + $this->syncDataAxiom($toModel); + $this->syncDataCustom($toModel); + } $this->server->settings->save(); } else { - $this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled; - $this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled; - $this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled; - - $this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key; - $this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri; - $this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name; - $this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key; - $this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config; - $this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser; + if ($type === 'newrelic') { + $this->syncDataNewRelic($toModel); + } elseif ($type === 'axiom') { + $this->syncDataAxiom($toModel); + } elseif ($type === 'custom') { + $this->syncDataCustom($toModel); + } else { + $this->syncDataNewRelic($toModel); + $this->syncDataAxiom($toModel); + $this->syncDataCustom($toModel); + } } } @@ -136,7 +176,7 @@ class LogDrains extends Component public function submit(string $type) { try { - $this->syncData(true); + $this->syncData(true, $type); $this->dispatch('success', 'Settings saved.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index 55ba49867..31dd13c52 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -53,7 +53,7 @@ class Index extends Component #[Validate('string')] public string $auto_update_frequency; - #[Validate('string')] + #[Validate('string|required')] public string $update_check_frequency; #[Validate('required|string|timezone')] @@ -109,6 +109,13 @@ class Index extends Component public function instantSave($isSave = true) { + $this->validate(); + if ($this->settings->is_auto_update_enabled === true) { + $this->validate([ + 'auto_update_frequency' => ['required', 'string'], + ]); + } + $this->settings->fqdn = $this->fqdn; $this->settings->resale_license = $this->resale_license; $this->settings->public_port_min = $this->public_port_min; diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 17201ea6e..79801987b 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Visus\Cuid2\Cuid2; @@ -18,4 +19,18 @@ abstract class BaseModel extends Model } }); } + + public function name(): Attribute + { + return new Attribute( + get: fn () => sanitize_string($this->getRawOriginal('name')), + ); + } + + public function image(): Attribute + { + return new Attribute( + get: fn () => sanitize_string($this->getRawOriginal('image')), + ); + } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 08f23d7ab..96c57e63e 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -71,7 +71,7 @@ class EnvironmentVariable extends Model } } $environment_variable->update([ - 'version' => config('version'), + 'version' => config('constants.coolify.version'), ]); }); static::saving(function (EnvironmentVariable $environmentVariable) { diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index fab1f1de7..80015d87f 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -219,15 +219,14 @@ class PrivateKey extends BaseModel private static function fingerprintExists($fingerprint, $excludeId = null) { $query = self::query() - ->where('fingerprint', $fingerprint); + ->where('fingerprint', $fingerprint) + ->where('id', '!=', $excludeId); if (currentTeam()) { $query->where('team_id', currentTeam()->id); } - return $query - ->when($excludeId, fn ($query) => $query->where('id', '!=', $excludeId)) - ->exists(); + return $query->exists(); } public static function cleanupUnusedKeys() diff --git a/app/Models/Server.php b/app/Models/Server.php index 3df156a78..83b91b254 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -42,8 +42,7 @@ use Symfony\Component\Yaml\Yaml; 'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'], 'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'], 'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'], - 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'], - 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'], + 'settings' => ['$ref' => '#/components/schemas/ServerSetting'], ] )] @@ -814,7 +813,7 @@ $schema://$host { { return Attribute::make( get: function ($value) { - return preg_replace('/[^0-9]/', '', $value); + return (int) preg_replace('/[^0-9]/', '', $value); } ); } @@ -988,7 +987,7 @@ $schema://$host { public function status(): bool { - ['uptime' => $uptime] = $this->validateConnection(false); + ['uptime' => $uptime] = $this->validateConnection(); if ($uptime === false) { foreach ($this->applications() as $application) { $application->status = 'exited'; @@ -1040,7 +1039,7 @@ $schema://$host { $this->unreachable_notification_sent = false; $this->save(); $this->refresh(); - $this->team->notify(new Reachable($this)); + // $this->team->notify(new Reachable($this)); } public function sendUnreachableNotification() @@ -1048,21 +1047,17 @@ $schema://$host { $this->unreachable_notification_sent = true; $this->save(); $this->refresh(); - $this->team->notify(new Unreachable($this)); + // $this->team->notify(new Unreachable($this)); } - public function validateConnection(bool $isManualCheck = true, bool $justCheckingNewKey = false) + public function validateConnection(bool $justCheckingNewKey = false) { - config()->set('constants.ssh.mux_enabled', ! $isManualCheck); + config()->set('constants.ssh.mux_enabled', false); if ($this->skipServer()) { return ['uptime' => false, 'error' => 'Server skipped.']; } try { - // Make sure the private key is stored - if ($this->privateKey) { - $this->privateKey->storeInFileSystem(); - } instant_remote_process(['ls /'], $this); if ($this->settings->is_reachable === false) { $this->settings->is_reachable = true; diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index fc2c5a0f4..e078372e2 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -45,6 +45,8 @@ use OpenApi\Attributes as OA; 'wildcard_domain' => ['type' => 'string'], 'created_at' => ['type' => 'string'], 'updated_at' => ['type' => 'string'], + 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'], + 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'], ] )] class ServerSetting extends Model diff --git a/app/Models/Team.php b/app/Models/Team.php index e21aa3a25..6ba044349 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -127,6 +127,13 @@ class Team extends Model implements SendsDiscord, SendsEmail ]; } + public function name(): Attribute + { + return new Attribute( + get: fn () => sanitize_string($this->getRawOriginal('name')), + ); + } + public function getRecepients($notification) { $recipients = data_get($notification, 'emails', null); diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index 242980e00..ce1f99d77 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -4,18 +4,12 @@ namespace App\Notifications\Application; use App\Models\Application; use App\Models\ApplicationPreview; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class DeploymentFailed extends Notification implements ShouldQueue +class DeploymentFailed extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - public Application $application; public ?ApplicationPreview $preview = null; @@ -34,6 +28,7 @@ class DeploymentFailed extends Notification implements ShouldQueue public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) { + $this->onQueue('high'); $this->application = $application; $this->deployment_uuid = $deployment_uuid; $this->preview = $preview; diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 946a622ca..391601257 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -4,18 +4,12 @@ namespace App\Notifications\Application; use App\Models\Application; use App\Models\ApplicationPreview; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class DeploymentSuccess extends Notification implements ShouldQueue +class DeploymentSuccess extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - public Application $application; public ?ApplicationPreview $preview = null; @@ -34,6 +28,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) { + $this->onQueue('high'); $this->application = $application; $this->deployment_uuid = $deployment_uuid; $this->preview = $preview; diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 852c6b526..c757495cb 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -3,18 +3,12 @@ namespace App\Notifications\Application; use App\Models\Application; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class StatusChanged extends Notification implements ShouldQueue +class StatusChanged extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - public string $resource_name; public string $project_uuid; @@ -27,6 +21,7 @@ class StatusChanged extends Notification implements ShouldQueue public function __construct(public Application $resource) { + $this->onQueue('high'); $this->resource_name = data_get($resource, 'name'); $this->project_uuid = data_get($resource, 'environment.project.uuid'); $this->environment_name = data_get($resource, 'environment.name'); diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php index 86276fec9..df7040f8f 100644 --- a/app/Notifications/Channels/DiscordChannel.php +++ b/app/Notifications/Channels/DiscordChannel.php @@ -17,6 +17,6 @@ class DiscordChannel if (! $webhookUrl) { return; } - dispatch(new SendMessageToDiscordJob($message, $webhookUrl))->onQueue('high'); + SendMessageToDiscordJob::dispatch($message, $webhookUrl); } } diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php index b3d4e384b..958c46c21 100644 --- a/app/Notifications/Channels/TelegramChannel.php +++ b/app/Notifications/Channels/TelegramChannel.php @@ -41,6 +41,6 @@ class TelegramChannel if (! $telegramToken || ! $chatId || ! $message) { return; } - dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId))->onQueue('high'); + SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId); } } diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index 182a1f5fc..eb709535f 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -3,19 +3,16 @@ namespace App\Notifications\Container; use App\Models\Server; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ContainerRestarted extends Notification implements ShouldQueue +class ContainerRestarted extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public string $name, public Server $server, public ?string $url = null) {} + public function __construct(public string $name, public Server $server, public ?string $url = null) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index 33a55c65a..a73e984a0 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -3,19 +3,16 @@ namespace App\Notifications\Container; use App\Models\Server; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ContainerStopped extends Notification implements ShouldQueue +class ContainerStopped extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public string $name, public Server $server, public ?string $url = null) {} + public function __construct(public string $name, public Server $server, public ?string $url = null) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/CustomEmailNotification.php b/app/Notifications/CustomEmailNotification.php new file mode 100644 index 000000000..c3c89b30f --- /dev/null +++ b/app/Notifications/CustomEmailNotification.php @@ -0,0 +1,18 @@ +onQueue('high'); $this->name = $database->name; $this->frequency = $backup->frequency; } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 5128c8ed6..d8bab069b 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -3,26 +3,20 @@ namespace App\Notifications\Database; use App\Models\ScheduledDatabaseBackup; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class BackupSuccess extends Notification implements ShouldQueue +class BackupSuccess extends CustomEmailNotification { - use Queueable; - - public $backoff = 10; - - public $tries = 3; - public string $name; public string $frequency; public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name) { + $this->onQueue('high'); + $this->name = $database->name; $this->frequency = $backup->frequency; } diff --git a/app/Notifications/Dto/DiscordMessage.php b/app/Notifications/Dto/DiscordMessage.php index 856753dca..278bfd1b6 100644 --- a/app/Notifications/Dto/DiscordMessage.php +++ b/app/Notifications/Dto/DiscordMessage.php @@ -46,7 +46,7 @@ class DiscordMessage public function toPayload(): array { - $footerText = 'Coolify v'.config('version'); + $footerText = 'Coolify v'.config('constants.coolify.version'); if (isCloud()) { $footerText = 'Coolify Cloud'; } diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index 48e7d8340..dcfde7b5b 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -15,7 +15,10 @@ class GeneralNotification extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $message) {} + public function __construct(public string $message) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index c3501a8eb..701f61277 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -3,24 +3,17 @@ namespace App\Notifications\ScheduledTask; use App\Models\ScheduledTask; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class TaskFailed extends Notification implements ShouldQueue +class TaskFailed extends CustomEmailNotification { - use Queueable; - - public $backoff = 10; - - public $tries = 2; - public ?string $url = null; public function __construct(public ScheduledTask $task, public string $output) { + $this->onQueue('high'); if ($task->application) { $this->url = $task->application->failedTaskLink($task->uuid); } elseif ($task->service) { diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index 7ea1b84c2..2d007a262 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -5,18 +5,15 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Notifications\Notification; -class DockerCleanup extends Notification implements ShouldQueue +class DockerCleanup extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server, public string $message) {} + public function __construct(public Server $server, public string $message) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index a26c803ee..eabf8b334 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -6,19 +6,16 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ForceDisabled extends Notification implements ShouldQueue +class ForceDisabled extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 65b65a10c..0c21ed6b8 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -6,19 +6,16 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ForceEnabled extends Notification implements ShouldQueue +class ForceEnabled extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index e373abc03..7cec2e892 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -3,19 +3,16 @@ namespace App\Notifications\Server; use App\Models\Server; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class HighDiskUsage extends Notification implements ShouldQueue +class HighDiskUsage extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) {} + public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index 9b54501d9..44189c3b5 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -6,22 +6,17 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class Reachable extends Notification implements ShouldQueue +class Reachable extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - protected bool $isRateLimited = false; public function __construct(public Server $server) { + $this->onQueue('high'); $this->isRateLimited = isEmailRateLimited( limiterKey: 'server-reachable:'.$this->server->id, ); diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 5bc568e82..6fb792bdc 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -6,22 +6,17 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class Unreachable extends Notification implements ShouldQueue +class Unreachable extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - protected bool $isRateLimited = false; public function __construct(public Server $server) { + $this->onQueue('high'); $this->isRateLimited = isEmailRateLimited( limiterKey: 'server-unreachable:'.$this->server->id, ); diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index a43b1e153..64f9bb0a5 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -15,7 +15,10 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) {} + public function __construct(public ?string $emails = null) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index 6da2a6fcc..30ace99dc 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -6,23 +6,20 @@ use App\Models\Team; use App\Models\TeamInvitation; use App\Models\User; use App\Notifications\Channels\TransactionalEmailChannel; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; +use App\Notifications\CustomEmailNotification; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class InvitationLink extends Notification implements ShouldQueue +class InvitationLink extends CustomEmailNotification { - use Queueable; - - public $tries = 5; - public function via(): array { return [TransactionalEmailChannel::class]; } - public function __construct(public User $user) {} + public function __construct(public User $user) + { + $this->onQueue('high'); + } public function toMail(): MailMessage { diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index 64883a58e..eeb32a254 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -3,18 +3,15 @@ namespace App\Notifications\TransactionalEmails; use App\Notifications\Channels\EmailChannel; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; +use App\Notifications\CustomEmailNotification; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class Test extends Notification implements ShouldQueue +class Test extends CustomEmailNotification { - use Queueable; - - public $tries = 5; - - public function __construct(public string $emails) {} + public function __construct(public string $emails) + { + $this->onQueue('high'); + } public function via(): array { diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index 414dbf2ae..0bdebe7e4 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -17,11 +17,14 @@ class Checkbox extends Component public ?string $value = null, public ?string $label = null, public ?string $helper = null, + public string|bool|null $checked = false, public string|bool $instantSave = false, public bool $disabled = false, public string $defaultClass = 'dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed', ) { - // + if ($this->disabled) { + $this->defaultClass .= ' opacity-40'; + } } /** diff --git a/bootstrap/getVersion.php b/bootstrap/getVersion.php index a8329a319..8fa27a332 100644 --- a/bootstrap/getVersion.php +++ b/bootstrap/getVersion.php @@ -1,4 +1,10 @@ id, - ))->onQueue('high'); + ); } elseif (next_queuable($server_id, $application_id)) { - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, - ))->onQueue('high'); + ); } } function force_start_deployment(ApplicationDeploymentQueue $deployment) @@ -59,9 +59,9 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment) 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, - ))->onQueue('high'); + ); } function queue_next_deployment(Application $application) { @@ -72,9 +72,9 @@ function queue_next_deployment(Application $application) 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $next_found->id, - ))->onQueue('high'); + ); } } @@ -113,9 +113,9 @@ function next_after_cancel(?Server $server = null) 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $next->id, - ))->onQueue('high'); + ); } break; } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 302193131..8dd01a162 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -192,7 +192,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica { $labels = collect([]); $labels->push('coolify.managed=true'); - $labels->push('coolify.version='.config('version')); + $labels->push('coolify.version='.config('constants.coolify.version')); $labels->push('coolify.'.$type.'Id='.$id); $labels->push("coolify.type=$type"); $labels->push('coolify.name='.$name); @@ -227,13 +227,17 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) $MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first(); $MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first(); - if (str($MINIO_BROWSER_REDIRECT_URL->value)->isEmpty()) { - $MINIO_BROWSER_REDIRECT_URL?->update([ + if (is_null($MINIO_BROWSER_REDIRECT_URL) || is_null($MINIO_SERVER_URL)) { + return collect([]); + } + + if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) { + $MINIO_BROWSER_REDIRECT_URL->update([ 'value' => generateFqdn($server, 'console-'.$uuid, true), ]); } - if (str($MINIO_SERVER_URL->value)->isEmpty()) { - $MINIO_SERVER_URL?->update([ + if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) { + $MINIO_SERVER_URL->update([ 'value' => generateFqdn($server, 'minio-'.$uuid, true), ]); } @@ -246,13 +250,17 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) $LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first(); $LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first(); - if (str($LOGTO_ENDPOINT?->value)->isEmpty()) { - $LOGTO_ENDPOINT?->update([ + if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) { + return collect([]); + } + + if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) { + $LOGTO_ENDPOINT->update([ 'value' => generateFqdn($server, 'logto-'.$uuid), ]); } - if (str($LOGTO_ADMIN_ENDPOINT?->value)->isEmpty()) { - $LOGTO_ADMIN_ENDPOINT?->update([ + if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) { + $LOGTO_ADMIN_ENDPOINT->update([ 'value' => generateFqdn($server, 'logto-admin-'.$uuid), ]); } @@ -280,6 +288,10 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $host_without_www = str($host)->replace('www.', ''); $schema = $url->getScheme(); $port = $url->getPort(); + $handle = "handle_path"; + if ( ! $is_stripprefix_enabled){ + $handle = "handle"; + } if (is_null($port) && ! is_null($onlyPort)) { $port = $onlyPort; } @@ -290,12 +302,13 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $labels->push("caddy_{$loop}.header=-Server"); $labels->push("caddy_{$loop}.try_files={path} /index.html /index.php"); + if ($port) { - $labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams $port}}"); + $labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}"); } else { - $labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams}}"); + $labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams}}"); } - $labels->push("caddy_{$loop}.handle_path={$path}*"); + $labels->push("caddy_{$loop}.{$handle}={$path}*"); if ($is_gzip_enabled) { $labels->push("caddy_{$loop}.encode=zstd gzip"); } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index aab543a09..1acbede65 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -90,8 +90,28 @@ function metrics_dir(): string return base_configuration_dir().'/metrics'; } +function sanitize_string(string $input): string +{ + // Remove any HTML/PHP tags + $sanitized = strip_tags($input); + + // Convert special characters to HTML entities + $sanitized = htmlspecialchars($sanitized, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + // Remove any control characters + $sanitized = preg_replace('/[\x00-\x1F\x7F]/u', '', $sanitized); + + // Trim whitespace + $sanitized = trim($sanitized); + + return $sanitized; +} + function generate_readme_file(string $name, string $updated_at): string { + $name = sanitize_string($name); + $updated_at = sanitize_string($updated_at); + return "Resource name: $name\nLatest Deployment Date: $updated_at"; } @@ -945,6 +965,15 @@ function generateEnvValue(string $command, Service|Application|null $service = n case 'REALBASE64_32': $generatedValue = base64_encode(Str::random(32)); break; + case 'HEX_32': + $generatedValue = bin2hex(Str::random(32)); + break; + case 'HEX_64': + $generatedValue = bin2hex(Str::random(64)); + break; + case 'HEX_128': + $generatedValue = bin2hex(Str::random(128)); + break; case 'USER': $generatedValue = Str::random(16); break; diff --git a/composer.json b/composer.json index 9e34e8b23..694bad882 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-data": "^4.11", + "spatie/laravel-ray": "^1.37", "spatie/laravel-schemaless-attributes": "^2.4", "spatie/url": "^2.2", "stripe/stripe-php": "^16.2.0", @@ -63,7 +64,6 @@ "phpunit/phpunit": "^11.4", "serversideup/spin": "^2.3", "spatie/laravel-ignition": "^2.1.0", - "spatie/laravel-ray": "^1.37", "symfony/http-client": "^7.1" }, "minimum-stability": "stable", diff --git a/composer.lock b/composer.lock index 63d070405..8ea0d9a5a 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": "c19f791a42f2d1458409e399ff7d28ec", + "content-hash": "f50de759f43a3eefb58ce9ebbb02d33b", "packages": [ { "name": "3sidedcube/laravel-redoc", @@ -979,16 +979,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.325.7", + "version": "3.327.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "55852104253172e66c3358bf4c35281bbd8622b2" + "reference": "3d52ec587989b136e486f94eff3dd316465aeb42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/55852104253172e66c3358bf4c35281bbd8622b2", - "reference": "55852104253172e66c3358bf4c35281bbd8622b2", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3d52ec587989b136e486f94eff3dd316465aeb42", + "reference": "3d52ec587989b136e486f94eff3dd316465aeb42", "shasum": "" }, "require": { @@ -1071,9 +1071,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.325.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.327.1" }, - "time": "2024-11-12T19:27:31+00:00" + "time": "2024-11-15T01:53:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -5473,6 +5473,134 @@ }, "time": "2024-09-04T12:51:01+00:00" }, + { + "name": "php-di/invoker", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2023-09-08T09:24:21+00:00" + }, + { + "name": "php-di/php-di", + "version": "7.0.7", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=8.0", + "php-di/invoker": "^2.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "friendsofphp/proxy-manager-lts": "^1", + "mnapoli/phpunit-easymock": "^1.3", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.6" + }, + "suggest": { + "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2024-07-21T15:55:45+00:00" + }, { "name": "phpdocumentor/reflection", "version": "6.0.0", @@ -5947,6 +6075,64 @@ }, "time": "2024-10-13T11:29:49+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.12.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-11-11T15:37:09+00:00" + }, { "name": "pion/laravel-chunk-upload", "version": "v1.5.4", @@ -6983,6 +7169,65 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "rector/rector", + "version": "1.2.10", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "40f9cf38c05296bd32f444121336a521a293fa61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", + "reference": "40f9cf38c05296bd32f444121336a521a293fa61", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.12.5" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/1.2.10" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2024-11-08T13:59:10+00:00" + }, { "name": "resend/resend-laravel", "version": "v0.15.0", @@ -7484,6 +7729,69 @@ }, "time": "2024-03-15T03:02:10+00:00" }, + { + "name": "spatie/backtrace", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/backtrace.git", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-json": "*", + "laravel/serializable-closure": "^1.3", + "phpunit/phpunit": "^9.3", + "spatie/phpunit-snapshot-assertions": "^4.2", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/backtrace/tree/1.6.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2024-07-22T08:21:24+00:00" + }, { "name": "spatie/laravel-activitylog", "version": "4.9.0", @@ -7719,6 +8027,93 @@ ], "time": "2024-08-27T18:56:10+00:00" }, + { + "name": "spatie/laravel-ray", + "version": "1.37.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ray.git", + "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^7.20|^8.19|^9.0|^10.0|^11.0", + "illuminate/database": "^7.20|^8.19|^9.0|^10.0|^11.0", + "illuminate/queue": "^7.20|^8.19|^9.0|^10.0|^11.0", + "illuminate/support": "^7.20|^8.19|^9.0|^10.0|^11.0", + "php": "^7.4|^8.0", + "rector/rector": "^0.19.2|^1.0", + "spatie/backtrace": "^1.0", + "spatie/ray": "^1.41.1", + "symfony/stopwatch": "4.2|^5.1|^6.0|^7.0", + "zbateson/mail-mime-parser": "^1.3.1|^2.0|^3.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.3", + "laravel/framework": "^7.20|^8.19|^9.0|^10.0|^11.0", + "orchestra/testbench-core": "^5.0|^6.0|^7.0|^8.0|^9.0", + "pestphp/pest": "^1.22|^2.0", + "phpstan/phpstan": "^1.10.57", + "phpunit/phpunit": "^9.3|^10.1", + "spatie/pest-plugin-snapshots": "^1.1|^2.0", + "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\LaravelRay\\RayServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\LaravelRay\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily debug Laravel apps", + "homepage": "https://github.com/spatie/laravel-ray", + "keywords": [ + "laravel-ray", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-ray/issues", + "source": "https://github.com/spatie/laravel-ray/tree/1.37.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2024-07-12T12:35:17+00:00" + }, { "name": "spatie/laravel-schemaless-attributes", "version": "2.5.0", @@ -7925,6 +8320,91 @@ ], "time": "2024-08-29T10:43:45+00:00" }, + { + "name": "spatie/ray", + "version": "1.41.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/ray.git", + "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ray/zipball/c44f8cfbf82c69909b505de61d8d3f2d324e93fc", + "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": "^7.3|^8.0", + "ramsey/uuid": "^3.0|^4.1", + "spatie/backtrace": "^1.1", + "spatie/macroable": "^1.0|^2.0", + "symfony/stopwatch": "^4.0|^5.1|^6.0|^7.0", + "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" + }, + "require-dev": { + "illuminate/support": "6.x|^8.18|^9.0", + "nesbot/carbon": "^2.63", + "pestphp/pest": "^1.22", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.19.2", + "spatie/phpunit-snapshot-assertions": "^4.2", + "spatie/test-time": "^1.2" + }, + "bin": [ + "bin/remove-ray.sh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Ray\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Debug with Ray to fix problems faster", + "homepage": "https://github.com/spatie/ray", + "keywords": [ + "ray", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/ray/issues", + "source": "https://github.com/spatie/ray/tree/1.41.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2024-04-24T14:21:46+00:00" + }, { "name": "spatie/url", "version": "2.4.0", @@ -9141,6 +9621,86 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-iconv": "*" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.31.0", @@ -10006,6 +10566,68 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/stopwatch", + "version": "v7.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05", + "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, { "name": "symfony/string", "version": "v7.1.8", @@ -10930,6 +11552,214 @@ }, "time": "2018-08-08T15:08:14+00:00" }, + { + "name": "zbateson/mail-mime-parser", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/zbateson/mail-mime-parser.git", + "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/e0d4423fe27850c9dd301190767dbc421acc2f19", + "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2.5", + "php": ">=8.0", + "php-di/php-di": "^6.0|^7.0", + "psr/log": "^1|^2|^3", + "zbateson/mb-wrapper": "^2.0", + "zbateson/stream-decorators": "^2.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "monolog/monolog": "^2|^3", + "phpstan/phpstan": "*", + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-iconv": "For best support/performance", + "ext-mbstring": "For best support/performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\MailMimeParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + }, + { + "name": "Contributors", + "homepage": "https://github.com/zbateson/mail-mime-parser/graphs/contributors" + } + ], + "description": "MIME email message parser", + "homepage": "https://mail-mime-parser.org", + "keywords": [ + "MimeMailParser", + "email", + "mail", + "mailparse", + "mime", + "mimeparse", + "parser", + "php-imap" + ], + "support": { + "docs": "https://mail-mime-parser.org/#usage-guide", + "issues": "https://github.com/zbateson/mail-mime-parser/issues", + "source": "https://github.com/zbateson/mail-mime-parser" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2024-08-10T18:44:09+00:00" + }, + { + "name": "zbateson/mb-wrapper", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/zbateson/mb-wrapper.git", + "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/9e4373a153585d12b6c621ac4a6bb143264d4619", + "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "symfony/polyfill-iconv": "^1.9", + "symfony/polyfill-mbstring": "^1.9" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "<10.0" + }, + "suggest": { + "ext-iconv": "For best support/performance", + "ext-mbstring": "For best support/performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\MbWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + } + ], + "description": "Wrapper for mbstring with fallback to iconv for encoding conversion and string manipulation", + "keywords": [ + "charset", + "encoding", + "http", + "iconv", + "mail", + "mb", + "mb_convert_encoding", + "mbstring", + "mime", + "multibyte", + "string" + ], + "support": { + "issues": "https://github.com/zbateson/mb-wrapper/issues", + "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2024-03-20T01:38:07+00:00" + }, + { + "name": "zbateson/stream-decorators", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/zbateson/stream-decorators.git", + "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2.5", + "php": ">=8.0", + "zbateson/mb-wrapper": "^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "^9.6|^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\StreamDecorators\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + } + ], + "description": "PHP psr7 stream decorators for mime message part streams", + "keywords": [ + "base64", + "charset", + "decorators", + "mail", + "mime", + "psr7", + "quoted-printable", + "stream", + "uuencode" + ], + "support": { + "issues": "https://github.com/zbateson/stream-decorators/issues", + "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2024-04-29T21:42:39+00:00" + }, { "name": "zircote/swagger-php", "version": "4.11.1", @@ -11015,16 +11845,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.14.6", + "version": "v3.14.7", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "14e4517bd49130d6119228107eb21ae47ae120ab" + "reference": "f484b8c9124de0b163da39958331098ffcd4a65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/14e4517bd49130d6119228107eb21ae47ae120ab", - "reference": "14e4517bd49130d6119228107eb21ae47ae120ab", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f484b8c9124de0b163da39958331098ffcd4a65e", + "reference": "f484b8c9124de0b163da39958331098ffcd4a65e", "shasum": "" }, "require": { @@ -11083,7 +11913,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.6" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.7" }, "funding": [ { @@ -11095,7 +11925,7 @@ "type": "github" } ], - "time": "2024-10-18T13:15:12+00:00" + "time": "2024-11-14T09:12:35+00:00" }, { "name": "brianium/paratest", @@ -12393,134 +13223,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "php-di/invoker", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2023-09-08T09:24:21+00:00" - }, - { - "name": "php-di/php-di", - "version": "7.0.7", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=8.0", - "php-di/invoker": "^2.0", - "psr/container": "^1.1 || ^2.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3", - "friendsofphp/proxy-manager-lts": "^1", - "mnapoli/phpunit-easymock": "^1.3", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.6" - }, - "suggest": { - "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2024-07-21T15:55:45+00:00" - }, { "name": "php-webdriver/webdriver", "version": "1.15.1", @@ -12587,64 +13289,6 @@ }, "time": "2023-10-20T12:21:20+00:00" }, - { - "name": "phpstan/phpstan", - "version": "1.12.10", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", - "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2024-11-11T15:37:09+00:00" - }, { "name": "phpunit/php-code-coverage", "version": "11.0.7", @@ -13068,65 +13712,6 @@ ], "time": "2024-10-28T13:07:50+00:00" }, - { - "name": "rector/rector", - "version": "1.2.10", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.12.5" - }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" - }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "keywords": [ - "automation", - "dev", - "migration", - "refactoring" - ], - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.10" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2024-11-08T13:59:10+00:00" - }, { "name": "sebastian/cli-parser", "version": "3.0.2", @@ -14095,69 +14680,6 @@ ], "time": "2024-10-15T15:12:28+00:00" }, - { - "name": "spatie/backtrace", - "version": "1.6.2", - "source": { - "type": "git", - "url": "https://github.com/spatie/backtrace.git", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "ext-json": "*", - "laravel/serializable-closure": "^1.3", - "phpunit/phpunit": "^9.3", - "spatie/phpunit-snapshot-assertions": "^4.2", - "symfony/var-dumper": "^5.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Spatie\\Backtrace\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van de Herten", - "email": "freek@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - } - ], - "description": "A better backtrace", - "homepage": "https://github.com/spatie/backtrace", - "keywords": [ - "Backtrace", - "spatie" - ], - "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.2" - }, - "funding": [ - { - "url": "https://github.com/sponsors/spatie", - "type": "github" - }, - { - "url": "https://spatie.be/open-source/support-us", - "type": "other" - } - ], - "time": "2024-07-22T08:21:24+00:00" - }, { "name": "spatie/error-solutions", "version": "1.1.1", @@ -14475,178 +14997,6 @@ ], "time": "2024-06-12T15:01:18+00:00" }, - { - "name": "spatie/laravel-ray", - "version": "1.37.1", - "source": { - "type": "git", - "url": "https://github.com/spatie/laravel-ray.git", - "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", - "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", - "shasum": "" - }, - "require": { - "ext-json": "*", - "illuminate/contracts": "^7.20|^8.19|^9.0|^10.0|^11.0", - "illuminate/database": "^7.20|^8.19|^9.0|^10.0|^11.0", - "illuminate/queue": "^7.20|^8.19|^9.0|^10.0|^11.0", - "illuminate/support": "^7.20|^8.19|^9.0|^10.0|^11.0", - "php": "^7.4|^8.0", - "rector/rector": "^0.19.2|^1.0", - "spatie/backtrace": "^1.0", - "spatie/ray": "^1.41.1", - "symfony/stopwatch": "4.2|^5.1|^6.0|^7.0", - "zbateson/mail-mime-parser": "^1.3.1|^2.0|^3.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^7.3", - "laravel/framework": "^7.20|^8.19|^9.0|^10.0|^11.0", - "orchestra/testbench-core": "^5.0|^6.0|^7.0|^8.0|^9.0", - "pestphp/pest": "^1.22|^2.0", - "phpstan/phpstan": "^1.10.57", - "phpunit/phpunit": "^9.3|^10.1", - "spatie/pest-plugin-snapshots": "^1.1|^2.0", - "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - }, - "laravel": { - "providers": [ - "Spatie\\LaravelRay\\RayServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Spatie\\LaravelRay\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - } - ], - "description": "Easily debug Laravel apps", - "homepage": "https://github.com/spatie/laravel-ray", - "keywords": [ - "laravel-ray", - "spatie" - ], - "support": { - "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.37.1" - }, - "funding": [ - { - "url": "https://github.com/sponsors/spatie", - "type": "github" - }, - { - "url": "https://spatie.be/open-source/support-us", - "type": "other" - } - ], - "time": "2024-07-12T12:35:17+00:00" - }, - { - "name": "spatie/ray", - "version": "1.41.2", - "source": { - "type": "git", - "url": "https://github.com/spatie/ray.git", - "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/ray/zipball/c44f8cfbf82c69909b505de61d8d3f2d324e93fc", - "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "php": "^7.3|^8.0", - "ramsey/uuid": "^3.0|^4.1", - "spatie/backtrace": "^1.1", - "spatie/macroable": "^1.0|^2.0", - "symfony/stopwatch": "^4.0|^5.1|^6.0|^7.0", - "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" - }, - "require-dev": { - "illuminate/support": "6.x|^8.18|^9.0", - "nesbot/carbon": "^2.63", - "pestphp/pest": "^1.22", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.19.2", - "spatie/phpunit-snapshot-assertions": "^4.2", - "spatie/test-time": "^1.2" - }, - "bin": [ - "bin/remove-ray.sh" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Spatie\\Ray\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - } - ], - "description": "Debug with Ray to fix problems faster", - "homepage": "https://github.com/spatie/ray", - "keywords": [ - "ray", - "spatie" - ], - "support": { - "issues": "https://github.com/spatie/ray/issues", - "source": "https://github.com/spatie/ray/tree/1.41.2" - }, - "funding": [ - { - "url": "https://github.com/sponsors/spatie", - "type": "github" - }, - { - "url": "https://spatie.be/open-source/support-us", - "type": "other" - } - ], - "time": "2024-04-24T14:21:46+00:00" - }, { "name": "symfony/http-client", "version": "v7.1.8", @@ -14819,148 +15169,6 @@ ], "time": "2024-04-18T09:32:20+00:00" }, - { - "name": "symfony/polyfill-iconv", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", - "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-iconv": "*" - }, - "suggest": { - "ext-iconv": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Iconv extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "iconv", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v7.1.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05", - "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.1.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, { "name": "ta-tikoma/phpunit-architecture-test", "version": "0.8.4", @@ -15069,214 +15277,6 @@ } ], "time": "2024-03-03T12:36:25+00:00" - }, - { - "name": "zbateson/mail-mime-parser", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/e0d4423fe27850c9dd301190767dbc421acc2f19", - "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19", - "shasum": "" - }, - "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "php-di/php-di": "^6.0|^7.0", - "psr/log": "^1|^2|^3", - "zbateson/mb-wrapper": "^2.0", - "zbateson/stream-decorators": "^2.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "monolog/monolog": "^2|^3", - "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6" - }, - "suggest": { - "ext-iconv": "For best support/performance", - "ext-mbstring": "For best support/performance" - }, - "type": "library", - "autoload": { - "psr-4": { - "ZBateson\\MailMimeParser\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Zaahid Bateson" - }, - { - "name": "Contributors", - "homepage": "https://github.com/zbateson/mail-mime-parser/graphs/contributors" - } - ], - "description": "MIME email message parser", - "homepage": "https://mail-mime-parser.org", - "keywords": [ - "MimeMailParser", - "email", - "mail", - "mailparse", - "mime", - "mimeparse", - "parser", - "php-imap" - ], - "support": { - "docs": "https://mail-mime-parser.org/#usage-guide", - "issues": "https://github.com/zbateson/mail-mime-parser/issues", - "source": "https://github.com/zbateson/mail-mime-parser" - }, - "funding": [ - { - "url": "https://github.com/zbateson", - "type": "github" - } - ], - "time": "2024-08-10T18:44:09+00:00" - }, - { - "name": "zbateson/mb-wrapper", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/zbateson/mb-wrapper.git", - "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/9e4373a153585d12b6c621ac4a6bb143264d4619", - "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "symfony/polyfill-iconv": "^1.9", - "symfony/polyfill-mbstring": "^1.9" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": "<10.0" - }, - "suggest": { - "ext-iconv": "For best support/performance", - "ext-mbstring": "For best support/performance" - }, - "type": "library", - "autoload": { - "psr-4": { - "ZBateson\\MbWrapper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Zaahid Bateson" - } - ], - "description": "Wrapper for mbstring with fallback to iconv for encoding conversion and string manipulation", - "keywords": [ - "charset", - "encoding", - "http", - "iconv", - "mail", - "mb", - "mb_convert_encoding", - "mbstring", - "mime", - "multibyte", - "string" - ], - "support": { - "issues": "https://github.com/zbateson/mb-wrapper/issues", - "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.0" - }, - "funding": [ - { - "url": "https://github.com/zbateson", - "type": "github" - } - ], - "time": "2024-03-20T01:38:07+00:00" - }, - { - "name": "zbateson/stream-decorators", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/zbateson/stream-decorators.git", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", - "shasum": "" - }, - "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "zbateson/mb-wrapper": "^2.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "ZBateson\\StreamDecorators\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Zaahid Bateson" - } - ], - "description": "PHP psr7 stream decorators for mime message part streams", - "keywords": [ - "base64", - "charset", - "decorators", - "mail", - "mime", - "psr7", - "quoted-printable", - "stream", - "uuencode" - ], - "support": { - "issues": "https://github.com/zbateson/stream-decorators/issues", - "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" - }, - "funding": [ - { - "url": "https://github.com/zbateson", - "type": "github" - } - ], - "time": "2024-04-29T21:42:39+00:00" } ], "aliases": [], diff --git a/config/constants.php b/config/constants.php index 532feb56c..c947635be 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,9 +2,9 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.368', + 'version' => '4.0.0-beta.374', 'self_hosted' => env('SELF_HOSTED', true), - 'autoupdate' => env('AUTOUPDATE', false), + 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), diff --git a/config/version.php b/config/version.php deleted file mode 100644 index ddb1cb354..000000000 --- a/config/version.php +++ /dev/null @@ -1,3 +0,0 @@ -getMessage()); + } + } + + public function down() + { + try { + DB::statement('DROP INDEX IF EXISTS idx_activity_type_uuid'); + DB::statement('ALTER TABLE activity_log ALTER COLUMN properties TYPE json USING properties::json'); + } catch (\Exception $e) { + Log::error('Error dropping index from activity_log: '.$e->getMessage()); + } + } +} diff --git a/openapi.json b/openapi.json index cdcd47f40..2ec218438 100644 --- a/openapi.json +++ b/openapi.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.0", + "openapi": "3.1.0", "info": { "title": "Coolify", "version": "0.1" @@ -5391,6 +5391,17 @@ "summary": "Update", "description": "Update Server.", "operationId": "update-server-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { "description": "Server updated.", "required": true, @@ -5451,10 +5462,7 @@ "content": { "application\/json": { "schema": { - "type": "array", - "items": { - "$ref": "#\/components\/schemas\/Server" - } + "$ref": "#\/components\/schemas\/Server" } } } @@ -7441,13 +7449,8 @@ "type": "string", "description": "The swarm cluster configuration." }, - "delete_unused_volumes": { - "type": "boolean", - "description": "The flag to indicate if the unused volumes should be deleted." - }, - "delete_unused_networks": { - "type": "boolean", - "description": "The flag to indicate if the unused networks should be deleted." + "settings": { + "$ref": "#\/components\/schemas\/ServerSetting" } }, "type": "object" @@ -7556,6 +7559,14 @@ }, "updated_at": { "type": "string" + }, + "delete_unused_volumes": { + "type": "boolean", + "description": "The flag to indicate if the unused volumes should be deleted." + }, + "delete_unused_networks": { + "type": "boolean", + "description": "The flag to indicate if the unused networks should be deleted." } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index 2b1ece41c..2a22c730c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3671,6 +3671,14 @@ paths: summary: Update description: 'Update Server.' operationId: update-server-by-uuid + parameters: + - + name: uuid + in: path + description: 'Server UUID' + required: true + schema: + type: string requestBody: description: 'Server updated.' required: true @@ -3713,9 +3721,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Server' + $ref: '#/components/schemas/Server' '401': $ref: '#/components/responses/401' '400': @@ -4967,12 +4973,8 @@ components: swarm_cluster: type: string description: 'The swarm cluster configuration.' - delete_unused_volumes: - type: boolean - description: 'The flag to indicate if the unused volumes should be deleted.' - delete_unused_networks: - type: boolean - description: 'The flag to indicate if the unused networks should be deleted.' + settings: + $ref: '#/components/schemas/ServerSetting' type: object ServerSetting: description: 'Server Settings model' @@ -5045,6 +5047,12 @@ components: type: string updated_at: type: string + delete_unused_volumes: + type: boolean + description: 'The flag to indicate if the unused volumes should be deleted.' + delete_unused_networks: + type: boolean + description: 'The flag to indicate if the unused networks should be deleted.' type: object Service: description: 'Service model' diff --git a/public/svgs/beszel.svg b/public/svgs/beszel.svg new file mode 100644 index 000000000..c6836479c --- /dev/null +++ b/public/svgs/beszel.svg @@ -0,0 +1 @@ + diff --git a/public/svgs/convertx.png b/public/svgs/convertx.png new file mode 100644 index 000000000..7f4c41e2e Binary files /dev/null and b/public/svgs/convertx.png differ diff --git a/public/svgs/default.webp b/public/svgs/default.webp new file mode 100644 index 000000000..e47383469 Binary files /dev/null and b/public/svgs/default.webp differ diff --git a/public/svgs/macos.svg b/public/svgs/macos.svg new file mode 100644 index 000000000..483fa6a17 --- /dev/null +++ b/public/svgs/macos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/maybe.svg b/public/svgs/maybe.svg new file mode 100644 index 000000000..9a8aa75cb --- /dev/null +++ b/public/svgs/maybe.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svgs/mealie.png b/public/svgs/mealie.png new file mode 100644 index 000000000..74a2d7b62 Binary files /dev/null and b/public/svgs/mealie.png differ diff --git a/public/svgs/overseerr.svg b/public/svgs/overseerr.svg new file mode 100644 index 000000000..8116787c2 --- /dev/null +++ b/public/svgs/overseerr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/svgs/privatebin.svg b/public/svgs/privatebin.svg new file mode 100644 index 000000000..d63c65dbd --- /dev/null +++ b/public/svgs/privatebin.svg @@ -0,0 +1 @@ + diff --git a/public/svgs/prowlarr.svg b/public/svgs/prowlarr.svg new file mode 100644 index 000000000..a1a5a8ce3 --- /dev/null +++ b/public/svgs/prowlarr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/pterodactyl.png b/public/svgs/pterodactyl.png new file mode 100644 index 000000000..a5addb87c Binary files /dev/null and b/public/svgs/pterodactyl.png differ diff --git a/public/svgs/radarr.svg b/public/svgs/radarr.svg new file mode 100644 index 000000000..93a4c9232 --- /dev/null +++ b/public/svgs/radarr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/redlib.svg b/public/svgs/redlib.svg new file mode 100644 index 000000000..16f73b5dd --- /dev/null +++ b/public/svgs/redlib.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/svgs/sonarr.svg b/public/svgs/sonarr.svg new file mode 100644 index 000000000..91c04e289 --- /dev/null +++ b/public/svgs/sonarr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/windows.svg b/public/svgs/windows.svg new file mode 100644 index 000000000..2c7392e9c --- /dev/null +++ b/public/svgs/windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index e378d4640..32d476c1a 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -6,7 +6,7 @@ html, body { - @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400; + @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400 w-full; } body { @@ -30,6 +30,14 @@ body { @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300; } +.input-sticky { + @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 dark:focus:ring-coolgray-300 focus:ring-neutral-400 block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset; +} + +.input-sticky-active { + @apply border-2 border-coollabs text-black dark:text-white focus:bg-neutral-200 dark:focus:bg-coolgray-400 focus:border-coollabs; +} + /* Readonly */ .input { @apply dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200; @@ -314,4 +322,4 @@ section { .dz-button { @apply w-full p-4 py-10 my-4 font-bold bg-white border dark:border-coolgray-400 dark:text-white dark:bg-transparent hover:dark:bg-coolgray-400; -} +} \ No newline at end of file diff --git a/resources/js/terminal.js b/resources/js/terminal.js index 59c9a79a8..e70a8cd1a 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -60,7 +60,22 @@ export function initializeTerminalComponent() { }; }, + resetTerminal() { + if (this.term) { + this.$wire.dispatch('error', 'Terminal websocket connection lost.'); + this.term.reset(); + this.term.clear(); + this.pendingWrites = 0; + this.paused = false; + this.commandBuffer = ''; + // Force a refresh + this.$nextTick(() => { + this.resizeTerminal(); + this.term.focus(); + }); + } + }, setupTerminal() { const terminalElement = document.getElementById('terminal'); if (terminalElement) { @@ -69,9 +84,15 @@ export function initializeTerminalComponent() { rows: 30, fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"', cursorBlink: true, + rendererType: 'canvas', + convertEol: true, + disableStdin: false }); this.fitAddon = new FitAddon(); this.term.loadAddon(this.fitAddon); + this.$nextTick(() => { + this.resizeTerminal(); + }); } }, @@ -101,12 +122,19 @@ export function initializeTerminalComponent() { `${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}` this.socket = new WebSocket(url); + this.socket.onopen = () => { + console.log('[Terminal] WebSocket connection established. Cool cool cool cool cool cool.'); + }; + this.socket.onmessage = this.handleSocketMessage.bind(this); this.socket.onerror = (e) => { - console.error('WebSocket error:', e); + console.error('[Terminal] WebSocket error.'); }; this.socket.onclose = () => { - console.log('WebSocket connection closed'); + console.warn('[Terminal] WebSocket connection closed.'); + this.resetTerminal(); + this.message = '(connection closed)'; + this.terminalActive = false; this.reconnect(); }; } @@ -117,19 +145,18 @@ export function initializeTerminalComponent() { clearInterval(this.reconnectInterval); } this.reconnectInterval = setInterval(() => { - console.log('Attempting to reconnect...'); + console.warn('[Terminal] Attempting to reconnect...'); this.initializeWebSocket(); if (this.socket && this.socket.readyState === WebSocket.OPEN) { - console.log('Reconnected successfully'); + console.log('[Terminal] Reconnected successfully'); clearInterval(this.reconnectInterval); this.reconnectInterval = null; - window.location.reload(); + } }, 2000); }, handleSocketMessage(event) { - this.message = '(connection closed)'; if (event.data === 'pty-ready') { if (!this.term._initialized) { this.term.open(document.getElementById('terminal')); @@ -150,8 +177,17 @@ export function initializeTerminalComponent() { this.term.reset(); this.commandBuffer = ''; } else { - this.pendingWrites++; - this.term.write(event.data, this.flowControlCallback.bind(this)); + try { + this.pendingWrites++; + this.term.write(event.data, (err) => { + if (err) { + console.error('[Terminal] Write error:', err); + } + this.flowControlCallback(); + }); + } catch (error) { + console.error('[Terminal] Write operation failed:', error); + } } }, @@ -173,11 +209,15 @@ export function initializeTerminalComponent() { if (!this.term) return; this.term.onData((data) => { - this.socket.send(JSON.stringify({ message: data })); - if (data === '\r') { - this.commandBuffer = ''; + if (this.socket.readyState === WebSocket.OPEN) { + this.socket.send(JSON.stringify({ message: data })); + if (data === '\r') { + this.commandBuffer = ''; + } else { + this.commandBuffer += data; + } } else { - this.commandBuffer += data; + console.warn('[Terminal] WebSocket not ready, data not sent'); } }); diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index ef4d5a7f3..fb244962d 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -5,6 +5,7 @@ 'disabled' => false, 'instantSave' => false, 'value' => null, + 'checked' => false, 'hideLabel' => false, 'fullWidth' => false, ]) @@ -14,7 +15,9 @@ 'w-full' => $fullWidth, ])> @if (!$hideLabel) -